1 /* 2 // Copyright (c) 2018 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include <boost/container/flat_map.hpp> 19 #include <boost/container/flat_set.hpp> 20 #include <dbus_singleton.hpp> 21 #include <error_messages.hpp> 22 #include <node.hpp> 23 #include <optional> 24 #include <regex> 25 #include <utils/json_utils.hpp> 26 #include <variant> 27 28 namespace redfish 29 { 30 31 /** 32 * DBus types primitives for several generic DBus interfaces 33 * TODO(Pawel) consider move this to separate file into boost::dbus 34 */ 35 using PropertiesMapType = boost::container::flat_map< 36 std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t, 37 int32_t, uint32_t, int64_t, uint64_t, double>>; 38 39 using GetManagedObjects = std::vector<std::pair< 40 sdbusplus::message::object_path, 41 std::vector<std::pair< 42 std::string, 43 boost::container::flat_map< 44 std::string, 45 std::variant<std::string, bool, uint8_t, int16_t, uint16_t, int32_t, 46 uint32_t, int64_t, uint64_t, double, 47 std::vector<std::string>>>>>>>; 48 49 enum class LinkType 50 { 51 Local, 52 Global 53 }; 54 55 /** 56 * Structure for keeping IPv4 data required by Redfish 57 */ 58 struct IPv4AddressData 59 { 60 std::string id; 61 std::string address; 62 std::string domain; 63 std::string gateway; 64 std::string netmask; 65 std::string origin; 66 LinkType linktype; 67 68 bool operator<(const IPv4AddressData &obj) const 69 { 70 return id < obj.id; 71 } 72 }; 73 74 /** 75 * Structure for keeping IPv6 data required by Redfish 76 */ 77 struct IPv6AddressData 78 { 79 std::string id; 80 std::string address; 81 std::string origin; 82 uint8_t prefixLength; 83 84 bool operator<(const IPv6AddressData &obj) const 85 { 86 return id < obj.id; 87 } 88 }; 89 /** 90 * Structure for keeping basic single Ethernet Interface information 91 * available from DBus 92 */ 93 struct EthernetInterfaceData 94 { 95 uint32_t speed; 96 bool auto_neg; 97 bool DNSEnabled; 98 bool NTPEnabled; 99 bool HostNameEnabled; 100 bool SendHostNameEnabled; 101 bool linkUp; 102 bool nicEnabled; 103 std::string DHCPEnabled; 104 std::string operatingMode; 105 std::string hostname; 106 std::string default_gateway; 107 std::string ipv6_default_gateway; 108 std::string mac_address; 109 std::vector<std::uint32_t> vlan_id; 110 std::vector<std::string> nameServers; 111 std::vector<std::string> staticNameServers; 112 std::vector<std::string> domainnames; 113 }; 114 115 struct DHCPParameters 116 { 117 std::optional<bool> dhcpv4Enabled; 118 std::optional<bool> useDNSServers; 119 std::optional<bool> useNTPServers; 120 std::optional<bool> useUseDomainName; 121 std::optional<std::string> dhcpv6OperatingMode; 122 }; 123 124 // Helper function that changes bits netmask notation (i.e. /24) 125 // into full dot notation 126 inline std::string getNetmask(unsigned int bits) 127 { 128 uint32_t value = 0xffffffff << (32 - bits); 129 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 130 std::to_string((value >> 16) & 0xff) + "." + 131 std::to_string((value >> 8) & 0xff) + "." + 132 std::to_string(value & 0xff); 133 return netmask; 134 } 135 136 inline bool translateDHCPEnabledToBool(const std::string &inputDHCP, 137 bool isIPv4) 138 { 139 if (isIPv4) 140 { 141 return ( 142 (inputDHCP == 143 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || 144 (inputDHCP == 145 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 146 } 147 return ((inputDHCP == 148 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || 149 (inputDHCP == 150 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 151 } 152 153 inline std::string GetDHCPEnabledEnumeration(bool isIPv4, bool isIPv6) 154 { 155 if (isIPv4 && isIPv6) 156 { 157 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; 158 } 159 else if (isIPv4) 160 { 161 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; 162 } 163 else if (isIPv6) 164 { 165 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; 166 } 167 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; 168 } 169 170 inline std::string 171 translateAddressOriginDbusToRedfish(const std::string &inputOrigin, 172 bool isIPv4) 173 { 174 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 175 { 176 return "Static"; 177 } 178 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 179 { 180 if (isIPv4) 181 { 182 return "IPv4LinkLocal"; 183 } 184 else 185 { 186 return "LinkLocal"; 187 } 188 } 189 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 190 { 191 if (isIPv4) 192 { 193 return "DHCP"; 194 } 195 else 196 { 197 return "DHCPv6"; 198 } 199 } 200 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 201 { 202 return "SLAAC"; 203 } 204 return ""; 205 } 206 207 inline bool extractEthernetInterfaceData(const std::string ðiface_id, 208 const GetManagedObjects &dbus_data, 209 EthernetInterfaceData ðData) 210 { 211 bool idFound = false; 212 for (const auto &objpath : dbus_data) 213 { 214 for (const auto &ifacePair : objpath.second) 215 { 216 if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id) 217 { 218 idFound = true; 219 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 220 { 221 for (const auto &propertyPair : ifacePair.second) 222 { 223 if (propertyPair.first == "MACAddress") 224 { 225 const std::string *mac = 226 std::get_if<std::string>(&propertyPair.second); 227 if (mac != nullptr) 228 { 229 ethData.mac_address = *mac; 230 } 231 } 232 } 233 } 234 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 235 { 236 for (const auto &propertyPair : ifacePair.second) 237 { 238 if (propertyPair.first == "Id") 239 { 240 const uint32_t *id = 241 std::get_if<uint32_t>(&propertyPair.second); 242 if (id != nullptr) 243 { 244 ethData.vlan_id.push_back(*id); 245 } 246 } 247 } 248 } 249 else if (ifacePair.first == 250 "xyz.openbmc_project.Network.EthernetInterface") 251 { 252 for (const auto &propertyPair : ifacePair.second) 253 { 254 if (propertyPair.first == "AutoNeg") 255 { 256 const bool *auto_neg = 257 std::get_if<bool>(&propertyPair.second); 258 if (auto_neg != nullptr) 259 { 260 ethData.auto_neg = *auto_neg; 261 } 262 } 263 else if (propertyPair.first == "Speed") 264 { 265 const uint32_t *speed = 266 std::get_if<uint32_t>(&propertyPair.second); 267 if (speed != nullptr) 268 { 269 ethData.speed = *speed; 270 } 271 } 272 else if (propertyPair.first == "LinkUp") 273 { 274 const bool *linkUp = 275 std::get_if<bool>(&propertyPair.second); 276 if (linkUp != nullptr) 277 { 278 ethData.linkUp = *linkUp; 279 } 280 } 281 else if (propertyPair.first == "NICEnabled") 282 { 283 const bool *nicEnabled = 284 std::get_if<bool>(&propertyPair.second); 285 if (nicEnabled != nullptr) 286 { 287 ethData.nicEnabled = *nicEnabled; 288 } 289 } 290 else if (propertyPair.first == "Nameservers") 291 { 292 const std::vector<std::string> *nameservers = 293 std::get_if<std::vector<std::string>>( 294 &propertyPair.second); 295 if (nameservers != nullptr) 296 { 297 ethData.nameServers = std::move(*nameservers); 298 } 299 } 300 else if (propertyPair.first == "StaticNameServers") 301 { 302 const std::vector<std::string> *staticNameServers = 303 std::get_if<std::vector<std::string>>( 304 &propertyPair.second); 305 if (staticNameServers != nullptr) 306 { 307 ethData.staticNameServers = 308 std::move(*staticNameServers); 309 } 310 } 311 else if (propertyPair.first == "DHCPEnabled") 312 { 313 const std::string *DHCPEnabled = 314 std::get_if<std::string>(&propertyPair.second); 315 if (DHCPEnabled != nullptr) 316 { 317 ethData.DHCPEnabled = *DHCPEnabled; 318 } 319 } 320 else if (propertyPair.first == "DomainName") 321 { 322 const std::vector<std::string> *domainNames = 323 std::get_if<std::vector<std::string>>( 324 &propertyPair.second); 325 if (domainNames != nullptr) 326 { 327 ethData.domainnames = std::move(*domainNames); 328 } 329 } 330 } 331 } 332 } 333 334 if (objpath.first == "/xyz/openbmc_project/network/config/dhcp") 335 { 336 if (ifacePair.first == 337 "xyz.openbmc_project.Network.DHCPConfiguration") 338 { 339 for (const auto &propertyPair : ifacePair.second) 340 { 341 if (propertyPair.first == "DNSEnabled") 342 { 343 const bool *DNSEnabled = 344 std::get_if<bool>(&propertyPair.second); 345 if (DNSEnabled != nullptr) 346 { 347 ethData.DNSEnabled = *DNSEnabled; 348 } 349 } 350 else if (propertyPair.first == "NTPEnabled") 351 { 352 const bool *NTPEnabled = 353 std::get_if<bool>(&propertyPair.second); 354 if (NTPEnabled != nullptr) 355 { 356 ethData.NTPEnabled = *NTPEnabled; 357 } 358 } 359 else if (propertyPair.first == "HostNameEnabled") 360 { 361 const bool *HostNameEnabled = 362 std::get_if<bool>(&propertyPair.second); 363 if (HostNameEnabled != nullptr) 364 { 365 ethData.HostNameEnabled = *HostNameEnabled; 366 } 367 } 368 else if (propertyPair.first == "SendHostNameEnabled") 369 { 370 const bool *SendHostNameEnabled = 371 std::get_if<bool>(&propertyPair.second); 372 if (SendHostNameEnabled != nullptr) 373 { 374 ethData.SendHostNameEnabled = 375 *SendHostNameEnabled; 376 } 377 } 378 } 379 } 380 } 381 // System configuration shows up in the global namespace, so no need 382 // to check eth number 383 if (ifacePair.first == 384 "xyz.openbmc_project.Network.SystemConfiguration") 385 { 386 for (const auto &propertyPair : ifacePair.second) 387 { 388 if (propertyPair.first == "HostName") 389 { 390 const std::string *hostname = 391 std::get_if<std::string>(&propertyPair.second); 392 if (hostname != nullptr) 393 { 394 ethData.hostname = *hostname; 395 } 396 } 397 else if (propertyPair.first == "DefaultGateway") 398 { 399 const std::string *defaultGateway = 400 std::get_if<std::string>(&propertyPair.second); 401 if (defaultGateway != nullptr) 402 { 403 ethData.default_gateway = *defaultGateway; 404 } 405 } 406 else if (propertyPair.first == "DefaultGateway6") 407 { 408 const std::string *defaultGateway6 = 409 std::get_if<std::string>(&propertyPair.second); 410 if (defaultGateway6 != nullptr) 411 { 412 ethData.ipv6_default_gateway = *defaultGateway6; 413 } 414 } 415 } 416 } 417 } 418 } 419 return idFound; 420 } 421 422 // Helper function that extracts data for single ethernet ipv6 address 423 inline void 424 extractIPV6Data(const std::string ðiface_id, 425 const GetManagedObjects &dbus_data, 426 boost::container::flat_set<IPv6AddressData> &ipv6_config) 427 { 428 const std::string ipv6PathStart = 429 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv6/"; 430 431 // Since there might be several IPv6 configurations aligned with 432 // single ethernet interface, loop over all of them 433 for (const auto &objpath : dbus_data) 434 { 435 // Check if proper pattern for object path appears 436 if (boost::starts_with(objpath.first.str, ipv6PathStart)) 437 { 438 for (auto &interface : objpath.second) 439 { 440 if (interface.first == "xyz.openbmc_project.Network.IP") 441 { 442 // Instance IPv6AddressData structure, and set as 443 // appropriate 444 std::pair< 445 boost::container::flat_set<IPv6AddressData>::iterator, 446 bool> 447 it = ipv6_config.insert(IPv6AddressData{}); 448 IPv6AddressData &ipv6_address = *it.first; 449 ipv6_address.id = 450 objpath.first.str.substr(ipv6PathStart.size()); 451 for (auto &property : interface.second) 452 { 453 if (property.first == "Address") 454 { 455 const std::string *address = 456 std::get_if<std::string>(&property.second); 457 if (address != nullptr) 458 { 459 ipv6_address.address = *address; 460 } 461 } 462 else if (property.first == "Origin") 463 { 464 const std::string *origin = 465 std::get_if<std::string>(&property.second); 466 if (origin != nullptr) 467 { 468 ipv6_address.origin = 469 translateAddressOriginDbusToRedfish(*origin, 470 false); 471 } 472 } 473 else if (property.first == "PrefixLength") 474 { 475 const uint8_t *prefix = 476 std::get_if<uint8_t>(&property.second); 477 if (prefix != nullptr) 478 { 479 ipv6_address.prefixLength = *prefix; 480 } 481 } 482 else 483 { 484 BMCWEB_LOG_ERROR 485 << "Got extra property: " << property.first 486 << " on the " << objpath.first.str << " object"; 487 } 488 } 489 } 490 } 491 } 492 } 493 } 494 495 // Helper function that extracts data for single ethernet ipv4 address 496 inline void 497 extractIPData(const std::string ðiface_id, 498 const GetManagedObjects &dbus_data, 499 boost::container::flat_set<IPv4AddressData> &ipv4_config) 500 { 501 const std::string ipv4PathStart = 502 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/"; 503 504 // Since there might be several IPv4 configurations aligned with 505 // single ethernet interface, loop over all of them 506 for (const auto &objpath : dbus_data) 507 { 508 // Check if proper pattern for object path appears 509 if (boost::starts_with(objpath.first.str, ipv4PathStart)) 510 { 511 for (auto &interface : objpath.second) 512 { 513 if (interface.first == "xyz.openbmc_project.Network.IP") 514 { 515 // Instance IPv4AddressData structure, and set as 516 // appropriate 517 std::pair< 518 boost::container::flat_set<IPv4AddressData>::iterator, 519 bool> 520 it = ipv4_config.insert(IPv4AddressData{}); 521 IPv4AddressData &ipv4_address = *it.first; 522 ipv4_address.id = 523 objpath.first.str.substr(ipv4PathStart.size()); 524 for (auto &property : interface.second) 525 { 526 if (property.first == "Address") 527 { 528 const std::string *address = 529 std::get_if<std::string>(&property.second); 530 if (address != nullptr) 531 { 532 ipv4_address.address = *address; 533 } 534 } 535 else if (property.first == "Gateway") 536 { 537 const std::string *gateway = 538 std::get_if<std::string>(&property.second); 539 if (gateway != nullptr) 540 { 541 ipv4_address.gateway = *gateway; 542 } 543 } 544 else if (property.first == "Origin") 545 { 546 const std::string *origin = 547 std::get_if<std::string>(&property.second); 548 if (origin != nullptr) 549 { 550 ipv4_address.origin = 551 translateAddressOriginDbusToRedfish(*origin, 552 true); 553 } 554 } 555 else if (property.first == "PrefixLength") 556 { 557 const uint8_t *mask = 558 std::get_if<uint8_t>(&property.second); 559 if (mask != nullptr) 560 { 561 // convert it to the string 562 ipv4_address.netmask = getNetmask(*mask); 563 } 564 } 565 else 566 { 567 BMCWEB_LOG_ERROR 568 << "Got extra property: " << property.first 569 << " on the " << objpath.first.str << " object"; 570 } 571 } 572 // Check if given address is local, or global 573 ipv4_address.linktype = 574 boost::starts_with(ipv4_address.address, "169.254.") 575 ? LinkType::Local 576 : LinkType::Global; 577 } 578 } 579 } 580 } 581 } 582 583 /** 584 * @brief Sets given Id on the given VLAN interface through D-Bus 585 * 586 * @param[in] ifaceId Id of VLAN interface that should be modified 587 * @param[in] inputVlanId New ID of the VLAN 588 * @param[in] callback Function that will be called after the operation 589 * 590 * @return None. 591 */ 592 template <typename CallbackFunc> 593 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId, 594 CallbackFunc &&callback) 595 { 596 crow::connections::systemBus->async_method_call( 597 callback, "xyz.openbmc_project.Network", 598 std::string("/xyz/openbmc_project/network/") + ifaceId, 599 "org.freedesktop.DBus.Properties", "Set", 600 "xyz.openbmc_project.Network.VLAN", "Id", 601 std::variant<uint32_t>(inputVlanId)); 602 } 603 604 /** 605 * @brief Helper function that verifies IP address to check if it is in 606 * proper format. If bits pointer is provided, also calculates active 607 * bit count for Subnet Mask. 608 * 609 * @param[in] ip IP that will be verified 610 * @param[out] bits Calculated mask in bits notation 611 * 612 * @return true in case of success, false otherwise 613 */ 614 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip, 615 uint8_t *bits = nullptr) 616 { 617 std::vector<std::string> bytesInMask; 618 619 boost::split(bytesInMask, ip, boost::is_any_of(".")); 620 621 static const constexpr int ipV4AddressSectionsCount = 4; 622 if (bytesInMask.size() != ipV4AddressSectionsCount) 623 { 624 return false; 625 } 626 627 if (bits != nullptr) 628 { 629 *bits = 0; 630 } 631 632 char *endPtr; 633 long previousValue = 255; 634 bool firstZeroInByteHit; 635 for (const std::string &byte : bytesInMask) 636 { 637 if (byte.empty()) 638 { 639 return false; 640 } 641 642 // Use strtol instead of stroi to avoid exceptions 643 long value = std::strtol(byte.c_str(), &endPtr, 10); 644 645 // endPtr should point to the end of the string, otherwise given string 646 // is not 100% number 647 if (*endPtr != '\0') 648 { 649 return false; 650 } 651 652 // Value should be contained in byte 653 if (value < 0 || value > 255) 654 { 655 return false; 656 } 657 658 if (bits != nullptr) 659 { 660 // Mask has to be continuous between bytes 661 if (previousValue != 255 && value != 0) 662 { 663 return false; 664 } 665 666 // Mask has to be continuous inside bytes 667 firstZeroInByteHit = false; 668 669 // Count bits 670 for (int bitIdx = 7; bitIdx >= 0; bitIdx--) 671 { 672 if (value & (1 << bitIdx)) 673 { 674 if (firstZeroInByteHit) 675 { 676 // Continuity not preserved 677 return false; 678 } 679 else 680 { 681 (*bits)++; 682 } 683 } 684 else 685 { 686 firstZeroInByteHit = true; 687 } 688 } 689 } 690 691 previousValue = value; 692 } 693 694 return true; 695 } 696 697 /** 698 * @brief Deletes given IPv4 interface 699 * 700 * @param[in] ifaceId Id of interface whose IP should be deleted 701 * @param[in] ipHash DBus Hash id of IP that should be deleted 702 * @param[io] asyncResp Response object that will be returned to client 703 * 704 * @return None 705 */ 706 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash, 707 const std::shared_ptr<AsyncResp> asyncResp) 708 { 709 crow::connections::systemBus->async_method_call( 710 [asyncResp](const boost::system::error_code ec) { 711 if (ec) 712 { 713 messages::internalError(asyncResp->res); 714 } 715 }, 716 "xyz.openbmc_project.Network", 717 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 718 "xyz.openbmc_project.Object.Delete", "Delete"); 719 } 720 721 /** 722 * @brief Creates a static IPv4 entry 723 * 724 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 725 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 726 * @param[in] gateway IPv4 address of this interfaces gateway 727 * @param[in] address IPv4 address to assign to this interface 728 * @param[io] asyncResp Response object that will be returned to client 729 * 730 * @return None 731 */ 732 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx, 733 uint8_t prefixLength, const std::string &gateway, 734 const std::string &address, 735 std::shared_ptr<AsyncResp> asyncResp) 736 { 737 crow::connections::systemBus->async_method_call( 738 [asyncResp](const boost::system::error_code ec) { 739 if (ec) 740 { 741 messages::internalError(asyncResp->res); 742 } 743 }, 744 "xyz.openbmc_project.Network", 745 "/xyz/openbmc_project/network/" + ifaceId, 746 "xyz.openbmc_project.Network.IP.Create", "IP", 747 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 748 gateway); 749 } 750 751 /** 752 * @brief Deletes the IPv4 entry for this interface and creates a replacement 753 * static IPv4 entry 754 * 755 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 756 * @param[in] id The unique hash entry identifying the DBus entry 757 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 758 * @param[in] gateway IPv4 address of this interfaces gateway 759 * @param[in] address IPv4 address to assign to this interface 760 * @param[io] asyncResp Response object that will be returned to client 761 * 762 * @return None 763 */ 764 inline void deleteAndCreateIPv4(const std::string &ifaceId, 765 const std::string &id, uint8_t prefixLength, 766 const std::string &gateway, 767 const std::string &address, 768 std::shared_ptr<AsyncResp> asyncResp) 769 { 770 crow::connections::systemBus->async_method_call( 771 [asyncResp, ifaceId, address, prefixLength, 772 gateway](const boost::system::error_code ec) { 773 if (ec) 774 { 775 messages::internalError(asyncResp->res); 776 } 777 crow::connections::systemBus->async_method_call( 778 [asyncResp](const boost::system::error_code ec) { 779 if (ec) 780 { 781 messages::internalError(asyncResp->res); 782 } 783 }, 784 "xyz.openbmc_project.Network", 785 "/xyz/openbmc_project/network/" + ifaceId, 786 "xyz.openbmc_project.Network.IP.Create", "IP", 787 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 788 prefixLength, gateway); 789 }, 790 "xyz.openbmc_project.Network", 791 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 792 "xyz.openbmc_project.Object.Delete", "Delete"); 793 } 794 795 /** 796 * @brief Deletes given IPv6 797 * 798 * @param[in] ifaceId Id of interface whose IP should be deleted 799 * @param[in] ipHash DBus Hash id of IP that should be deleted 800 * @param[io] asyncResp Response object that will be returned to client 801 * 802 * @return None 803 */ 804 inline void deleteIPv6(const std::string &ifaceId, const std::string &ipHash, 805 const std::shared_ptr<AsyncResp> asyncResp) 806 { 807 crow::connections::systemBus->async_method_call( 808 [asyncResp](const boost::system::error_code ec) { 809 if (ec) 810 { 811 messages::internalError(asyncResp->res); 812 } 813 }, 814 "xyz.openbmc_project.Network", 815 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 816 "xyz.openbmc_project.Object.Delete", "Delete"); 817 } 818 819 /** 820 * @brief Deletes the IPv6 entry for this interface and creates a replacement 821 * static IPv6 entry 822 * 823 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 824 * @param[in] id The unique hash entry identifying the DBus entry 825 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 826 * @param[in] address IPv6 address to assign to this interface 827 * @param[io] asyncResp Response object that will be returned to client 828 * 829 * @return None 830 */ 831 inline void deleteAndCreateIPv6(const std::string &ifaceId, 832 const std::string &id, uint8_t prefixLength, 833 const std::string &address, 834 std::shared_ptr<AsyncResp> asyncResp) 835 { 836 crow::connections::systemBus->async_method_call( 837 [asyncResp, ifaceId, address, 838 prefixLength](const boost::system::error_code ec) { 839 if (ec) 840 { 841 messages::internalError(asyncResp->res); 842 } 843 crow::connections::systemBus->async_method_call( 844 [asyncResp](const boost::system::error_code ec) { 845 if (ec) 846 { 847 messages::internalError(asyncResp->res); 848 } 849 }, 850 "xyz.openbmc_project.Network", 851 "/xyz/openbmc_project/network/" + ifaceId, 852 "xyz.openbmc_project.Network.IP.Create", "IP", 853 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 854 prefixLength, ""); 855 }, 856 "xyz.openbmc_project.Network", 857 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 858 "xyz.openbmc_project.Object.Delete", "Delete"); 859 } 860 861 /** 862 * @brief Creates IPv6 with given data 863 * 864 * @param[in] ifaceId Id of interface whose IP should be added 865 * @param[in] prefixLength Prefix length that needs to be added 866 * @param[in] address IP address that needs to be added 867 * @param[io] asyncResp Response object that will be returned to client 868 * 869 * @return None 870 */ 871 inline void createIPv6(const std::string &ifaceId, uint8_t prefixLength, 872 const std::string &address, 873 std::shared_ptr<AsyncResp> asyncResp) 874 { 875 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 876 if (ec) 877 { 878 messages::internalError(asyncResp->res); 879 } 880 }; 881 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 882 // does not have assosiated gateway property 883 crow::connections::systemBus->async_method_call( 884 std::move(createIpHandler), "xyz.openbmc_project.Network", 885 "/xyz/openbmc_project/network/" + ifaceId, 886 "xyz.openbmc_project.Network.IP.Create", "IP", 887 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 888 ""); 889 } 890 891 /** 892 * Function that retrieves all properties for given Ethernet Interface 893 * Object 894 * from EntityManager Network Manager 895 * @param ethiface_id a eth interface id to query on DBus 896 * @param callback a function that shall be called to convert Dbus output 897 * into JSON 898 */ 899 template <typename CallbackFunc> 900 void getEthernetIfaceData(const std::string ðiface_id, 901 CallbackFunc &&callback) 902 { 903 crow::connections::systemBus->async_method_call( 904 [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}]( 905 const boost::system::error_code error_code, 906 const GetManagedObjects &resp) { 907 EthernetInterfaceData ethData{}; 908 boost::container::flat_set<IPv4AddressData> ipv4Data; 909 boost::container::flat_set<IPv6AddressData> ipv6Data; 910 911 if (error_code) 912 { 913 callback(false, ethData, ipv4Data, ipv6Data); 914 return; 915 } 916 917 bool found = 918 extractEthernetInterfaceData(ethiface_id, resp, ethData); 919 if (!found) 920 { 921 callback(false, ethData, ipv4Data, ipv6Data); 922 return; 923 } 924 925 extractIPData(ethiface_id, resp, ipv4Data); 926 // Fix global GW 927 for (IPv4AddressData &ipv4 : ipv4Data) 928 { 929 if (((ipv4.linktype == LinkType::Global) && 930 (ipv4.gateway == "0.0.0.0")) || 931 (ipv4.origin == "DHCP")) 932 { 933 ipv4.gateway = ethData.default_gateway; 934 } 935 } 936 937 extractIPV6Data(ethiface_id, resp, ipv6Data); 938 // Finally make a callback with usefull data 939 callback(true, ethData, ipv4Data, ipv6Data); 940 }, 941 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 942 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 943 } 944 945 /** 946 * Function that retrieves all Ethernet Interfaces available through Network 947 * Manager 948 * @param callback a function that shall be called to convert Dbus output 949 * into JSON. 950 */ 951 template <typename CallbackFunc> 952 void getEthernetIfaceList(CallbackFunc &&callback) 953 { 954 crow::connections::systemBus->async_method_call( 955 [callback{std::move(callback)}]( 956 const boost::system::error_code error_code, 957 GetManagedObjects &resp) { 958 // Callback requires vector<string> to retrieve all available 959 // ethernet interfaces 960 boost::container::flat_set<std::string> iface_list; 961 iface_list.reserve(resp.size()); 962 if (error_code) 963 { 964 callback(false, iface_list); 965 return; 966 } 967 968 // Iterate over all retrieved ObjectPaths. 969 for (const auto &objpath : resp) 970 { 971 // And all interfaces available for certain ObjectPath. 972 for (const auto &interface : objpath.second) 973 { 974 // If interface is 975 // xyz.openbmc_project.Network.EthernetInterface, this is 976 // what we're looking for. 977 if (interface.first == 978 "xyz.openbmc_project.Network.EthernetInterface") 979 { 980 // Cut out everyting until last "/", ... 981 const std::string &iface_id = objpath.first.str; 982 std::size_t last_pos = iface_id.rfind("/"); 983 if (last_pos != std::string::npos) 984 { 985 // and put it into output vector. 986 iface_list.emplace(iface_id.substr(last_pos + 1)); 987 } 988 } 989 } 990 } 991 // Finally make a callback with useful data 992 callback(true, iface_list); 993 }, 994 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 995 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 996 } 997 998 /** 999 * EthernetCollection derived class for delivering Ethernet Collection Schema 1000 */ 1001 class EthernetCollection : public Node 1002 { 1003 public: 1004 template <typename CrowApp> 1005 EthernetCollection(CrowApp &app) : 1006 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1007 { 1008 entityPrivileges = { 1009 {boost::beast::http::verb::get, {{"Login"}}}, 1010 {boost::beast::http::verb::head, {{"Login"}}}, 1011 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1012 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1013 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1014 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1015 } 1016 1017 private: 1018 /** 1019 * Functions triggers appropriate requests on DBus 1020 */ 1021 void doGet(crow::Response &res, const crow::Request &req, 1022 const std::vector<std::string> ¶ms) override 1023 { 1024 res.jsonValue["@odata.type"] = 1025 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1026 res.jsonValue["@odata.id"] = 1027 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1028 res.jsonValue["Name"] = "Ethernet Network Interface Collection"; 1029 res.jsonValue["Description"] = 1030 "Collection of EthernetInterfaces for this Manager"; 1031 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1032 // Get eth interface list, and call the below callback for JSON 1033 // preparation 1034 getEthernetIfaceList( 1035 [asyncResp]( 1036 const bool &success, 1037 const boost::container::flat_set<std::string> &iface_list) { 1038 if (!success) 1039 { 1040 messages::internalError(asyncResp->res); 1041 return; 1042 } 1043 1044 nlohmann::json &iface_array = 1045 asyncResp->res.jsonValue["Members"]; 1046 iface_array = nlohmann::json::array(); 1047 std::string tag = "_"; 1048 for (const std::string &iface_item : iface_list) 1049 { 1050 std::size_t found = iface_item.find(tag); 1051 if (found == std::string::npos) 1052 { 1053 iface_array.push_back( 1054 {{"@odata.id", 1055 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1056 iface_item}}); 1057 } 1058 } 1059 1060 asyncResp->res.jsonValue["Members@odata.count"] = 1061 iface_array.size(); 1062 asyncResp->res.jsonValue["@odata.id"] = 1063 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1064 }); 1065 } 1066 }; 1067 1068 /** 1069 * EthernetInterface derived class for delivering Ethernet Schema 1070 */ 1071 class EthernetInterface : public Node 1072 { 1073 public: 1074 /* 1075 * Default Constructor 1076 */ 1077 template <typename CrowApp> 1078 EthernetInterface(CrowApp &app) : 1079 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/", 1080 std::string()) 1081 { 1082 entityPrivileges = { 1083 {boost::beast::http::verb::get, {{"Login"}}}, 1084 {boost::beast::http::verb::head, {{"Login"}}}, 1085 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1086 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1087 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1088 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1089 } 1090 1091 private: 1092 void handleHostnamePatch(const std::string &hostname, 1093 const std::shared_ptr<AsyncResp> asyncResp) 1094 { 1095 // SHOULD handle host names of up to 255 characters(RFC 1123) 1096 if (hostname.length() > 255) 1097 { 1098 messages::propertyValueFormatError(asyncResp->res, hostname, 1099 "HostName"); 1100 return; 1101 } 1102 crow::connections::systemBus->async_method_call( 1103 [asyncResp](const boost::system::error_code ec) { 1104 if (ec) 1105 { 1106 messages::internalError(asyncResp->res); 1107 } 1108 }, 1109 "xyz.openbmc_project.Network", 1110 "/xyz/openbmc_project/network/config", 1111 "org.freedesktop.DBus.Properties", "Set", 1112 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1113 std::variant<std::string>(hostname)); 1114 } 1115 1116 void handleDomainnamePatch(const std::string &ifaceId, 1117 const std::string &domainname, 1118 const std::shared_ptr<AsyncResp> asyncResp) 1119 { 1120 std::vector<std::string> vectorDomainname = {domainname}; 1121 crow::connections::systemBus->async_method_call( 1122 [asyncResp](const boost::system::error_code ec) { 1123 if (ec) 1124 { 1125 messages::internalError(asyncResp->res); 1126 } 1127 }, 1128 "xyz.openbmc_project.Network", 1129 "/xyz/openbmc_project/network/" + ifaceId, 1130 "org.freedesktop.DBus.Properties", "Set", 1131 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1132 std::variant<std::vector<std::string>>(vectorDomainname)); 1133 } 1134 1135 void handleFqdnPatch(const std::string &ifaceId, const std::string &fqdn, 1136 const std::shared_ptr<AsyncResp> asyncResp) 1137 { 1138 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1139 if (fqdn.length() > 255) 1140 { 1141 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1142 return; 1143 } 1144 1145 size_t pos = fqdn.find('.'); 1146 if (pos == std::string::npos) 1147 { 1148 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1149 return; 1150 } 1151 1152 std::string hostname; 1153 std::string domainname; 1154 domainname = (fqdn).substr(pos + 1); 1155 hostname = (fqdn).substr(0, pos); 1156 1157 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1158 { 1159 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1160 return; 1161 } 1162 1163 handleHostnamePatch(hostname, asyncResp); 1164 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1165 } 1166 1167 bool isHostnameValid(const std::string &hostname) 1168 { 1169 // A valid host name can never have the dotted-decimal form (RFC 1123) 1170 if (std::all_of(hostname.begin(), hostname.end(), ::isdigit)) 1171 { 1172 return false; 1173 } 1174 // Each label(hostname/subdomains) within a valid FQDN 1175 // MUST handle host names of up to 63 characters (RFC 1123) 1176 // labels cannot start or end with hyphens (RFC 952) 1177 // labels can start with numbers (RFC 1123) 1178 const std::regex pattern( 1179 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1180 1181 return std::regex_match(hostname, pattern); 1182 } 1183 1184 bool isDomainnameValid(const std::string &domainname) 1185 { 1186 // Can have multiple subdomains 1187 // Top Level Domain's min length is 2 character 1188 const std::regex pattern("^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]" 1189 "{1,30}\\.)*[a-zA-Z]{2,}$"); 1190 1191 return std::regex_match(domainname, pattern); 1192 } 1193 1194 void handleMACAddressPatch(const std::string &ifaceId, 1195 const std::string &macAddress, 1196 const std::shared_ptr<AsyncResp> &asyncResp) 1197 { 1198 crow::connections::systemBus->async_method_call( 1199 [asyncResp, macAddress](const boost::system::error_code ec) { 1200 if (ec) 1201 { 1202 messages::internalError(asyncResp->res); 1203 return; 1204 } 1205 }, 1206 "xyz.openbmc_project.Network", 1207 "/xyz/openbmc_project/network/" + ifaceId, 1208 "org.freedesktop.DBus.Properties", "Set", 1209 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1210 std::variant<std::string>(macAddress)); 1211 } 1212 1213 void setDHCPEnabled(const std::string &ifaceId, 1214 const std::string &propertyName, const bool v4Value, 1215 const bool v6Value, 1216 const std::shared_ptr<AsyncResp> asyncResp) 1217 { 1218 const std::string dhcp = GetDHCPEnabledEnumeration(v4Value, v6Value); 1219 crow::connections::systemBus->async_method_call( 1220 [asyncResp](const boost::system::error_code ec) { 1221 if (ec) 1222 { 1223 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1224 messages::internalError(asyncResp->res); 1225 return; 1226 } 1227 }, 1228 "xyz.openbmc_project.Network", 1229 "/xyz/openbmc_project/network/" + ifaceId, 1230 "org.freedesktop.DBus.Properties", "Set", 1231 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1232 std::variant<std::string>{dhcp}); 1233 } 1234 1235 void setEthernetInterfaceBoolProperty( 1236 const std::string &ifaceId, const std::string &propertyName, 1237 const bool &value, const std::shared_ptr<AsyncResp> asyncResp) 1238 { 1239 crow::connections::systemBus->async_method_call( 1240 [asyncResp](const boost::system::error_code ec) { 1241 if (ec) 1242 { 1243 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1244 messages::internalError(asyncResp->res); 1245 return; 1246 } 1247 }, 1248 "xyz.openbmc_project.Network", 1249 "/xyz/openbmc_project/network/" + ifaceId, 1250 "org.freedesktop.DBus.Properties", "Set", 1251 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1252 std::variant<bool>{value}); 1253 } 1254 1255 void setDHCPv4Config(const std::string &propertyName, const bool &value, 1256 const std::shared_ptr<AsyncResp> asyncResp) 1257 { 1258 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1259 crow::connections::systemBus->async_method_call( 1260 [asyncResp](const boost::system::error_code ec) { 1261 if (ec) 1262 { 1263 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1264 messages::internalError(asyncResp->res); 1265 return; 1266 } 1267 }, 1268 "xyz.openbmc_project.Network", 1269 "/xyz/openbmc_project/network/config/dhcp", 1270 "org.freedesktop.DBus.Properties", "Set", 1271 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1272 std::variant<bool>{value}); 1273 } 1274 1275 void handleDHCPPatch(const std::string &ifaceId, 1276 const EthernetInterfaceData ðData, 1277 DHCPParameters v4dhcpParms, DHCPParameters v6dhcpParms, 1278 const std::shared_ptr<AsyncResp> asyncResp) 1279 { 1280 bool ipv4Active = translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1281 bool ipv6Active = 1282 translateDHCPEnabledToBool(ethData.DHCPEnabled, false); 1283 1284 bool nextv4DHCPState = 1285 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1286 1287 bool nextv6DHCPState{}; 1288 if (v6dhcpParms.dhcpv6OperatingMode) 1289 { 1290 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1291 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1292 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1293 { 1294 messages::propertyValueFormatError( 1295 asyncResp->res, *v6dhcpParms.dhcpv6OperatingMode, 1296 "OperatingMode"); 1297 return; 1298 } 1299 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1300 } 1301 else 1302 { 1303 nextv6DHCPState = ipv6Active; 1304 } 1305 1306 bool nextDNS{}; 1307 if (v4dhcpParms.useDNSServers && v6dhcpParms.useDNSServers) 1308 { 1309 if (*v4dhcpParms.useDNSServers != *v6dhcpParms.useDNSServers) 1310 { 1311 messages::generalError(asyncResp->res); 1312 return; 1313 } 1314 nextDNS = *v4dhcpParms.useDNSServers; 1315 } 1316 else if (v4dhcpParms.useDNSServers) 1317 { 1318 nextDNS = *v4dhcpParms.useDNSServers; 1319 } 1320 else if (v6dhcpParms.useDNSServers) 1321 { 1322 nextDNS = *v6dhcpParms.useDNSServers; 1323 } 1324 else 1325 { 1326 nextDNS = ethData.DNSEnabled; 1327 } 1328 1329 bool nextNTP{}; 1330 if (v4dhcpParms.useNTPServers && v6dhcpParms.useNTPServers) 1331 { 1332 if (*v4dhcpParms.useNTPServers != *v6dhcpParms.useNTPServers) 1333 { 1334 messages::generalError(asyncResp->res); 1335 return; 1336 } 1337 nextNTP = *v4dhcpParms.useNTPServers; 1338 } 1339 else if (v4dhcpParms.useNTPServers) 1340 { 1341 nextNTP = *v4dhcpParms.useNTPServers; 1342 } 1343 else if (v6dhcpParms.useNTPServers) 1344 { 1345 nextNTP = *v6dhcpParms.useNTPServers; 1346 } 1347 else 1348 { 1349 nextNTP = ethData.NTPEnabled; 1350 } 1351 1352 bool nextUseDomain{}; 1353 if (v4dhcpParms.useUseDomainName && v6dhcpParms.useUseDomainName) 1354 { 1355 if (*v4dhcpParms.useUseDomainName != *v6dhcpParms.useUseDomainName) 1356 { 1357 messages::generalError(asyncResp->res); 1358 return; 1359 } 1360 nextUseDomain = *v4dhcpParms.useUseDomainName; 1361 } 1362 else if (v4dhcpParms.useUseDomainName) 1363 { 1364 nextUseDomain = *v4dhcpParms.useUseDomainName; 1365 } 1366 else if (v6dhcpParms.useUseDomainName) 1367 { 1368 nextUseDomain = *v6dhcpParms.useUseDomainName; 1369 } 1370 else 1371 { 1372 nextUseDomain = ethData.HostNameEnabled; 1373 } 1374 1375 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1376 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1377 asyncResp); 1378 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1379 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1380 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1381 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1382 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1383 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1384 } 1385 1386 boost::container::flat_set<IPv4AddressData>::const_iterator 1387 GetNextStaticIPEntry( 1388 boost::container::flat_set<IPv4AddressData>::const_iterator head, 1389 boost::container::flat_set<IPv4AddressData>::const_iterator end) 1390 { 1391 for (; head != end; head++) 1392 { 1393 if (head->origin == "Static") 1394 { 1395 return head; 1396 } 1397 } 1398 return end; 1399 } 1400 1401 boost::container::flat_set<IPv6AddressData>::const_iterator 1402 GetNextStaticIPEntry( 1403 boost::container::flat_set<IPv6AddressData>::const_iterator head, 1404 boost::container::flat_set<IPv6AddressData>::const_iterator end) 1405 { 1406 for (; head != end; head++) 1407 { 1408 if (head->origin == "Static") 1409 { 1410 return head; 1411 } 1412 } 1413 return end; 1414 } 1415 1416 void handleIPv4StaticPatch( 1417 const std::string &ifaceId, nlohmann::json &input, 1418 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1419 const std::shared_ptr<AsyncResp> asyncResp) 1420 { 1421 if ((!input.is_array()) || input.empty()) 1422 { 1423 messages::propertyValueTypeError(asyncResp->res, input.dump(), 1424 "IPv4StaticAddresses"); 1425 return; 1426 } 1427 1428 unsigned entryIdx = 1; 1429 // Find the first static IP address currently active on the NIC and 1430 // match it to the first JSON element in the IPv4StaticAddresses array. 1431 // Match each subsequent JSON element to the next static IP programmed 1432 // into the NIC. 1433 boost::container::flat_set<IPv4AddressData>::const_iterator NICIPentry = 1434 GetNextStaticIPEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1435 1436 for (nlohmann::json &thisJson : input) 1437 { 1438 std::string pathString = 1439 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1440 1441 if (!thisJson.is_null() && !thisJson.empty()) 1442 { 1443 std::optional<std::string> address; 1444 std::optional<std::string> subnetMask; 1445 std::optional<std::string> gateway; 1446 1447 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1448 address, "SubnetMask", subnetMask, 1449 "Gateway", gateway)) 1450 { 1451 messages::propertyValueFormatError( 1452 asyncResp->res, thisJson.dump(), pathString); 1453 return; 1454 } 1455 1456 // Find the address/subnet/gateway values. Any values that are 1457 // not explicitly provided are assumed to be unmodified from the 1458 // current state of the interface. Merge existing state into the 1459 // current request. 1460 const std::string *addr = nullptr; 1461 const std::string *gw = nullptr; 1462 uint8_t prefixLength = 0; 1463 bool errorInEntry = false; 1464 if (address) 1465 { 1466 if (ipv4VerifyIpAndGetBitcount(*address)) 1467 { 1468 addr = &(*address); 1469 } 1470 else 1471 { 1472 messages::propertyValueFormatError( 1473 asyncResp->res, *address, pathString + "/Address"); 1474 errorInEntry = true; 1475 } 1476 } 1477 else if (NICIPentry != ipv4Data.cend()) 1478 { 1479 addr = &(NICIPentry->address); 1480 } 1481 else 1482 { 1483 messages::propertyMissing(asyncResp->res, 1484 pathString + "/Address"); 1485 errorInEntry = true; 1486 } 1487 1488 if (subnetMask) 1489 { 1490 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 1491 { 1492 messages::propertyValueFormatError( 1493 asyncResp->res, *subnetMask, 1494 pathString + "/SubnetMask"); 1495 errorInEntry = true; 1496 } 1497 } 1498 else if (NICIPentry != ipv4Data.cend()) 1499 { 1500 if (!ipv4VerifyIpAndGetBitcount(NICIPentry->netmask, 1501 &prefixLength)) 1502 { 1503 messages::propertyValueFormatError( 1504 asyncResp->res, NICIPentry->netmask, 1505 pathString + "/SubnetMask"); 1506 errorInEntry = true; 1507 } 1508 } 1509 else 1510 { 1511 messages::propertyMissing(asyncResp->res, 1512 pathString + "/SubnetMask"); 1513 errorInEntry = true; 1514 } 1515 1516 if (gateway) 1517 { 1518 if (ipv4VerifyIpAndGetBitcount(*gateway)) 1519 { 1520 gw = &(*gateway); 1521 } 1522 else 1523 { 1524 messages::propertyValueFormatError( 1525 asyncResp->res, *gateway, pathString + "/Gateway"); 1526 errorInEntry = true; 1527 } 1528 } 1529 else if (NICIPentry != ipv4Data.cend()) 1530 { 1531 gw = &NICIPentry->gateway; 1532 } 1533 else 1534 { 1535 messages::propertyMissing(asyncResp->res, 1536 pathString + "/Gateway"); 1537 errorInEntry = true; 1538 } 1539 1540 if (errorInEntry) 1541 { 1542 return; 1543 } 1544 1545 if (NICIPentry != ipv4Data.cend()) 1546 { 1547 deleteAndCreateIPv4(ifaceId, NICIPentry->id, prefixLength, 1548 *gw, *addr, asyncResp); 1549 NICIPentry = 1550 GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend()); 1551 } 1552 else 1553 { 1554 createIPv4(ifaceId, entryIdx, prefixLength, *gateway, 1555 *address, asyncResp); 1556 } 1557 entryIdx++; 1558 } 1559 else 1560 { 1561 if (NICIPentry == ipv4Data.cend()) 1562 { 1563 // Requesting a DELETE/DO NOT MODIFY action for an item 1564 // that isn't present on the eth(n) interface. Input JSON is 1565 // in error, so bail out. 1566 if (thisJson.is_null()) 1567 { 1568 messages::resourceCannotBeDeleted(asyncResp->res); 1569 return; 1570 } 1571 else 1572 { 1573 messages::propertyValueFormatError( 1574 asyncResp->res, thisJson.dump(), pathString); 1575 return; 1576 } 1577 } 1578 1579 if (thisJson.is_null()) 1580 { 1581 deleteIPv4(ifaceId, NICIPentry->id, asyncResp); 1582 } 1583 if (NICIPentry != ipv4Data.cend()) 1584 { 1585 NICIPentry = 1586 GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend()); 1587 } 1588 entryIdx++; 1589 } 1590 } 1591 } 1592 1593 void handleStaticNameServersPatch( 1594 const std::string &ifaceId, 1595 const std::vector<std::string> &updatedStaticNameServers, 1596 const std::shared_ptr<AsyncResp> &asyncResp) 1597 { 1598 crow::connections::systemBus->async_method_call( 1599 [asyncResp](const boost::system::error_code ec) { 1600 if (ec) 1601 { 1602 messages::internalError(asyncResp->res); 1603 return; 1604 } 1605 }, 1606 "xyz.openbmc_project.Network", 1607 "/xyz/openbmc_project/network/" + ifaceId, 1608 "org.freedesktop.DBus.Properties", "Set", 1609 "xyz.openbmc_project.Network.EthernetInterface", 1610 "StaticNameServers", 1611 std::variant<std::vector<std::string>>{updatedStaticNameServers}); 1612 } 1613 1614 void handleIPv6StaticAddressesPatch( 1615 const std::string &ifaceId, nlohmann::json &input, 1616 const boost::container::flat_set<IPv6AddressData> &ipv6Data, 1617 const std::shared_ptr<AsyncResp> asyncResp) 1618 { 1619 if (!input.is_array() || input.empty()) 1620 { 1621 messages::propertyValueTypeError(asyncResp->res, input.dump(), 1622 "IPv6StaticAddresses"); 1623 return; 1624 } 1625 size_t entryIdx = 1; 1626 boost::container::flat_set<IPv6AddressData>::const_iterator NICIPentry = 1627 GetNextStaticIPEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1628 for (nlohmann::json &thisJson : input) 1629 { 1630 std::string pathString = 1631 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1632 1633 if (!thisJson.is_null() && !thisJson.empty()) 1634 { 1635 std::optional<std::string> address; 1636 std::optional<uint8_t> prefixLength; 1637 1638 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1639 address, "PrefixLength", prefixLength)) 1640 { 1641 messages::propertyValueFormatError( 1642 asyncResp->res, thisJson.dump(), pathString); 1643 return; 1644 } 1645 1646 const std::string *addr; 1647 uint8_t prefix; 1648 1649 // Find the address and prefixLength values. Any values that are 1650 // not explicitly provided are assumed to be unmodified from the 1651 // current state of the interface. Merge existing state into the 1652 // current request. 1653 if (address) 1654 { 1655 addr = &(*address); 1656 } 1657 else if (NICIPentry != ipv6Data.end()) 1658 { 1659 addr = &(NICIPentry->address); 1660 } 1661 else 1662 { 1663 messages::propertyMissing(asyncResp->res, 1664 pathString + "/Address"); 1665 return; 1666 } 1667 1668 if (prefixLength) 1669 { 1670 prefix = *prefixLength; 1671 } 1672 else if (NICIPentry != ipv6Data.end()) 1673 { 1674 prefix = NICIPentry->prefixLength; 1675 } 1676 else 1677 { 1678 messages::propertyMissing(asyncResp->res, 1679 pathString + "/PrefixLength"); 1680 return; 1681 } 1682 1683 if (NICIPentry != ipv6Data.end()) 1684 { 1685 deleteAndCreateIPv6(ifaceId, NICIPentry->id, prefix, *addr, 1686 asyncResp); 1687 NICIPentry = 1688 GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend()); 1689 } 1690 else 1691 { 1692 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1693 } 1694 entryIdx++; 1695 } 1696 else 1697 { 1698 if (NICIPentry == ipv6Data.end()) 1699 { 1700 // Requesting a DELETE/DO NOT MODIFY action for an item 1701 // that isn't present on the eth(n) interface. Input JSON is 1702 // in error, so bail out. 1703 if (thisJson.is_null()) 1704 { 1705 messages::resourceCannotBeDeleted(asyncResp->res); 1706 return; 1707 } 1708 else 1709 { 1710 messages::propertyValueFormatError( 1711 asyncResp->res, thisJson.dump(), pathString); 1712 return; 1713 } 1714 } 1715 1716 if (thisJson.is_null()) 1717 { 1718 deleteIPv6(ifaceId, NICIPentry->id, asyncResp); 1719 } 1720 if (NICIPentry != ipv6Data.cend()) 1721 { 1722 NICIPentry = 1723 GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend()); 1724 } 1725 entryIdx++; 1726 } 1727 } 1728 } 1729 1730 void parseInterfaceData( 1731 std::shared_ptr<AsyncResp> asyncResp, const std::string &iface_id, 1732 const EthernetInterfaceData ðData, 1733 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1734 const boost::container::flat_set<IPv6AddressData> &ipv6Data) 1735 { 1736 constexpr const std::array<const char *, 1> inventoryForEthernet = { 1737 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1738 1739 nlohmann::json &json_response = asyncResp->res.jsonValue; 1740 json_response["Id"] = iface_id; 1741 json_response["@odata.id"] = 1742 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id; 1743 json_response["InterfaceEnabled"] = ethData.nicEnabled; 1744 1745 auto health = std::make_shared<HealthPopulate>(asyncResp); 1746 1747 crow::connections::systemBus->async_method_call( 1748 [health](const boost::system::error_code ec, 1749 std::vector<std::string> &resp) { 1750 if (ec) 1751 { 1752 return; 1753 } 1754 1755 health->inventory = std::move(resp); 1756 }, 1757 "xyz.openbmc_project.ObjectMapper", 1758 "/xyz/openbmc_project/object_mapper", 1759 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", 1760 int32_t(0), inventoryForEthernet); 1761 1762 health->populate(); 1763 1764 if (ethData.nicEnabled) 1765 { 1766 json_response["LinkStatus"] = "LinkUp"; 1767 json_response["Status"]["State"] = "Enabled"; 1768 } 1769 else 1770 { 1771 json_response["LinkStatus"] = "NoLink"; 1772 json_response["Status"]["State"] = "Disabled"; 1773 } 1774 1775 json_response["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1776 json_response["SpeedMbps"] = ethData.speed; 1777 json_response["MACAddress"] = ethData.mac_address; 1778 json_response["DHCPv4"]["DHCPEnabled"] = 1779 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1780 json_response["DHCPv4"]["UseNTPServers"] = ethData.NTPEnabled; 1781 json_response["DHCPv4"]["UseDNSServers"] = ethData.DNSEnabled; 1782 json_response["DHCPv4"]["UseDomainName"] = ethData.HostNameEnabled; 1783 1784 json_response["DHCPv6"]["OperatingMode"] = 1785 translateDHCPEnabledToBool(ethData.DHCPEnabled, false) ? "Stateful" 1786 : "Disabled"; 1787 json_response["DHCPv6"]["UseNTPServers"] = ethData.NTPEnabled; 1788 json_response["DHCPv6"]["UseDNSServers"] = ethData.DNSEnabled; 1789 json_response["DHCPv6"]["UseDomainName"] = ethData.HostNameEnabled; 1790 1791 if (!ethData.hostname.empty()) 1792 { 1793 json_response["HostName"] = ethData.hostname; 1794 1795 // When domain name is empty then it means, that it is a network 1796 // without domain names, and the host name itself must be treated as 1797 // FQDN 1798 std::string FQDN = std::move(ethData.hostname); 1799 if (!ethData.domainnames.empty()) 1800 { 1801 FQDN += "." + ethData.domainnames[0]; 1802 } 1803 json_response["FQDN"] = FQDN; 1804 } 1805 1806 json_response["VLANs"] = { 1807 {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1808 iface_id + "/VLANs"}}; 1809 1810 json_response["NameServers"] = ethData.nameServers; 1811 json_response["StaticNameServers"] = ethData.staticNameServers; 1812 1813 nlohmann::json &ipv4_array = json_response["IPv4Addresses"]; 1814 nlohmann::json &ipv4_static_array = 1815 json_response["IPv4StaticAddresses"]; 1816 ipv4_array = nlohmann::json::array(); 1817 ipv4_static_array = nlohmann::json::array(); 1818 for (auto &ipv4_config : ipv4Data) 1819 { 1820 1821 std::string gatewayStr = ipv4_config.gateway; 1822 if (gatewayStr.empty()) 1823 { 1824 gatewayStr = "0.0.0.0"; 1825 } 1826 1827 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin}, 1828 {"SubnetMask", ipv4_config.netmask}, 1829 {"Address", ipv4_config.address}, 1830 {"Gateway", gatewayStr}}); 1831 if (ipv4_config.origin == "Static") 1832 { 1833 ipv4_static_array.push_back( 1834 {{"AddressOrigin", ipv4_config.origin}, 1835 {"SubnetMask", ipv4_config.netmask}, 1836 {"Address", ipv4_config.address}, 1837 {"Gateway", gatewayStr}}); 1838 } 1839 } 1840 1841 json_response["IPv6DefaultGateway"] = ethData.ipv6_default_gateway; 1842 1843 nlohmann::json &ipv6_array = json_response["IPv6Addresses"]; 1844 nlohmann::json &ipv6_static_array = 1845 json_response["IPv6StaticAddresses"]; 1846 ipv6_array = nlohmann::json::array(); 1847 ipv6_static_array = nlohmann::json::array(); 1848 nlohmann::json &ipv6AddrPolicyTable = 1849 json_response["IPv6AddressPolicyTable"]; 1850 ipv6AddrPolicyTable = nlohmann::json::array(); 1851 for (auto &ipv6_config : ipv6Data) 1852 { 1853 ipv6_array.push_back({{"Address", ipv6_config.address}, 1854 {"PrefixLength", ipv6_config.prefixLength}, 1855 {"AddressOrigin", ipv6_config.origin}, 1856 {"AddressState", nullptr}}); 1857 if (ipv6_config.origin == "Static") 1858 { 1859 ipv6_static_array.push_back( 1860 {{"Address", ipv6_config.address}, 1861 {"PrefixLength", ipv6_config.prefixLength}, 1862 {"AddressOrigin", ipv6_config.origin}, 1863 {"AddressState", nullptr}}); 1864 } 1865 } 1866 } 1867 1868 /** 1869 * Functions triggers appropriate requests on DBus 1870 */ 1871 void doGet(crow::Response &res, const crow::Request &req, 1872 const std::vector<std::string> ¶ms) override 1873 { 1874 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1875 if (params.size() != 1) 1876 { 1877 messages::internalError(asyncResp->res); 1878 return; 1879 } 1880 1881 getEthernetIfaceData( 1882 params[0], 1883 [this, asyncResp, iface_id{std::string(params[0])}]( 1884 const bool &success, const EthernetInterfaceData ðData, 1885 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1886 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 1887 if (!success) 1888 { 1889 // TODO(Pawel)consider distinguish between non existing 1890 // object, and other errors 1891 messages::resourceNotFound(asyncResp->res, 1892 "EthernetInterface", iface_id); 1893 return; 1894 } 1895 1896 asyncResp->res.jsonValue["@odata.type"] = 1897 "#EthernetInterface.v1_4_1.EthernetInterface"; 1898 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1899 asyncResp->res.jsonValue["Description"] = 1900 "Management Network Interface"; 1901 1902 parseInterfaceData(asyncResp, iface_id, ethData, ipv4Data, 1903 ipv6Data); 1904 }); 1905 } 1906 1907 void doPatch(crow::Response &res, const crow::Request &req, 1908 const std::vector<std::string> ¶ms) override 1909 { 1910 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1911 if (params.size() != 1) 1912 { 1913 messages::internalError(asyncResp->res); 1914 return; 1915 } 1916 1917 const std::string &iface_id = params[0]; 1918 1919 std::optional<std::string> hostname; 1920 std::optional<std::string> fqdn; 1921 std::optional<std::string> macAddress; 1922 std::optional<std::string> ipv6DefaultGateway; 1923 std::optional<nlohmann::json> ipv4StaticAddresses; 1924 std::optional<nlohmann::json> ipv6StaticAddresses; 1925 std::optional<std::vector<std::string>> staticNameServers; 1926 std::optional<nlohmann::json> dhcpv4; 1927 std::optional<nlohmann::json> dhcpv6; 1928 std::optional<bool> interfaceEnabled; 1929 DHCPParameters v4dhcpParms; 1930 DHCPParameters v6dhcpParms; 1931 1932 if (!json_util::readJson( 1933 req, res, "HostName", hostname, "FQDN", fqdn, 1934 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1935 macAddress, "StaticNameServers", staticNameServers, 1936 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1937 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1938 "InterfaceEnabled", interfaceEnabled)) 1939 { 1940 return; 1941 } 1942 if (dhcpv4) 1943 { 1944 if (!json_util::readJson(*dhcpv4, res, "DHCPEnabled", 1945 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1946 v4dhcpParms.useDNSServers, "UseNTPServers", 1947 v4dhcpParms.useNTPServers, "UseDomainName", 1948 v4dhcpParms.useUseDomainName)) 1949 { 1950 return; 1951 } 1952 } 1953 1954 if (dhcpv6) 1955 { 1956 if (!json_util::readJson(*dhcpv6, res, "OperatingMode", 1957 v6dhcpParms.dhcpv6OperatingMode, 1958 "UseDNSServers", v6dhcpParms.useDNSServers, 1959 "UseNTPServers", v6dhcpParms.useNTPServers, 1960 "UseDomainName", 1961 v6dhcpParms.useUseDomainName)) 1962 { 1963 return; 1964 } 1965 } 1966 1967 // Get single eth interface data, and call the below callback for 1968 // JSON preparation 1969 getEthernetIfaceData( 1970 iface_id, 1971 [this, asyncResp, iface_id, hostname = std::move(hostname), 1972 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1973 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1974 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1975 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1976 staticNameServers = std::move(staticNameServers), 1977 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), 1978 v4dhcpParms = std::move(v4dhcpParms), 1979 v6dhcpParms = std::move(v6dhcpParms), 1980 interfaceEnabled = std::move(interfaceEnabled)]( 1981 const bool &success, const EthernetInterfaceData ðData, 1982 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 1983 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 1984 if (!success) 1985 { 1986 // ... otherwise return error 1987 // TODO(Pawel)consider distinguish between non existing 1988 // object, and other errors 1989 messages::resourceNotFound(asyncResp->res, 1990 "Ethernet Interface", iface_id); 1991 return; 1992 } 1993 1994 if (dhcpv4 || dhcpv6) 1995 { 1996 handleDHCPPatch(iface_id, ethData, std::move(v4dhcpParms), 1997 std::move(v6dhcpParms), asyncResp); 1998 } 1999 2000 if (hostname) 2001 { 2002 handleHostnamePatch(*hostname, asyncResp); 2003 } 2004 2005 if (fqdn) 2006 { 2007 handleFqdnPatch(iface_id, *fqdn, asyncResp); 2008 } 2009 2010 if (macAddress) 2011 { 2012 handleMACAddressPatch(iface_id, *macAddress, asyncResp); 2013 } 2014 2015 if (ipv4StaticAddresses) 2016 { 2017 // TODO(ed) for some reason the capture of ipv4Addresses 2018 // above is returning a const value, not a non-const 2019 // value. This doesn't really work for us, as we need to 2020 // be able to efficiently move out the intermedia 2021 // nlohmann::json objects. This makes a copy of the 2022 // structure, and operates on that, but could be done 2023 // more efficiently 2024 nlohmann::json ipv4Static = std::move(*ipv4StaticAddresses); 2025 handleIPv4StaticPatch(iface_id, ipv4Static, ipv4Data, 2026 asyncResp); 2027 } 2028 2029 if (staticNameServers) 2030 { 2031 handleStaticNameServersPatch(iface_id, *staticNameServers, 2032 asyncResp); 2033 } 2034 2035 if (ipv6DefaultGateway) 2036 { 2037 messages::propertyNotWritable(asyncResp->res, 2038 "IPv6DefaultGateway"); 2039 } 2040 2041 if (ipv6StaticAddresses) 2042 { 2043 nlohmann::json ipv6Static = std::move(*ipv6StaticAddresses); 2044 handleIPv6StaticAddressesPatch(iface_id, ipv6Static, 2045 ipv6Data, asyncResp); 2046 } 2047 2048 if (interfaceEnabled) 2049 { 2050 setEthernetInterfaceBoolProperty( 2051 iface_id, "NICEnabled", *interfaceEnabled, asyncResp); 2052 } 2053 }); 2054 } 2055 }; 2056 2057 /** 2058 * VlanNetworkInterface derived class for delivering VLANNetworkInterface 2059 * Schema 2060 */ 2061 class VlanNetworkInterface : public Node 2062 { 2063 public: 2064 /* 2065 * Default Constructor 2066 */ 2067 template <typename CrowApp> 2068 VlanNetworkInterface(CrowApp &app) : 2069 Node(app, 2070 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/", 2071 std::string(), std::string()) 2072 { 2073 entityPrivileges = { 2074 {boost::beast::http::verb::get, {{"Login"}}}, 2075 {boost::beast::http::verb::head, {{"Login"}}}, 2076 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2077 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2078 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2079 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2080 } 2081 2082 private: 2083 void parseInterfaceData( 2084 nlohmann::json &json_response, const std::string &parent_iface_id, 2085 const std::string &iface_id, const EthernetInterfaceData ðData, 2086 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2087 const boost::container::flat_set<IPv6AddressData> &ipv6Data) 2088 { 2089 // Fill out obvious data... 2090 json_response["Id"] = iface_id; 2091 json_response["@odata.id"] = 2092 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id + 2093 "/VLANs/" + iface_id; 2094 2095 json_response["VLANEnable"] = true; 2096 if (!ethData.vlan_id.empty()) 2097 { 2098 json_response["VLANId"] = ethData.vlan_id.back(); 2099 } 2100 } 2101 2102 bool verifyNames(const std::string &parent, const std::string &iface) 2103 { 2104 if (!boost::starts_with(iface, parent + "_")) 2105 { 2106 return false; 2107 } 2108 else 2109 { 2110 return true; 2111 } 2112 } 2113 2114 /** 2115 * Functions triggers appropriate requests on DBus 2116 */ 2117 void doGet(crow::Response &res, const crow::Request &req, 2118 const std::vector<std::string> ¶ms) override 2119 { 2120 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2121 // TODO(Pawel) this shall be parameterized call (two params) to get 2122 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 2123 // Check if there is required param, truly entering this shall be 2124 // impossible. 2125 if (params.size() != 2) 2126 { 2127 messages::internalError(res); 2128 res.end(); 2129 return; 2130 } 2131 2132 const std::string &parent_iface_id = params[0]; 2133 const std::string &iface_id = params[1]; 2134 res.jsonValue["@odata.type"] = 2135 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2136 res.jsonValue["Name"] = "VLAN Network Interface"; 2137 2138 if (!verifyNames(parent_iface_id, iface_id)) 2139 { 2140 return; 2141 } 2142 2143 // Get single eth interface data, and call the below callback for 2144 // JSON preparation 2145 getEthernetIfaceData( 2146 params[1], 2147 [this, asyncResp, parent_iface_id{std::string(params[0])}, 2148 iface_id{std::string(params[1])}]( 2149 const bool &success, const EthernetInterfaceData ðData, 2150 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2151 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2152 if (success && ethData.vlan_id.size() != 0) 2153 { 2154 parseInterfaceData(asyncResp->res.jsonValue, 2155 parent_iface_id, iface_id, ethData, 2156 ipv4Data, ipv6Data); 2157 } 2158 else 2159 { 2160 // ... otherwise return error 2161 // TODO(Pawel)consider distinguish between non existing 2162 // object, and other errors 2163 messages::resourceNotFound( 2164 asyncResp->res, "VLAN Network Interface", iface_id); 2165 } 2166 }); 2167 } 2168 2169 void doPatch(crow::Response &res, const crow::Request &req, 2170 const std::vector<std::string> ¶ms) override 2171 { 2172 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2173 if (params.size() != 2) 2174 { 2175 messages::internalError(asyncResp->res); 2176 return; 2177 } 2178 2179 const std::string &parentIfaceId = params[0]; 2180 const std::string &ifaceId = params[1]; 2181 2182 if (!verifyNames(parentIfaceId, ifaceId)) 2183 { 2184 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2185 ifaceId); 2186 return; 2187 } 2188 2189 bool vlanEnable = false; 2190 uint32_t vlanId = 0; 2191 2192 if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId", 2193 vlanId)) 2194 { 2195 return; 2196 } 2197 2198 // Get single eth interface data, and call the below callback for 2199 // JSON preparation 2200 getEthernetIfaceData( 2201 params[1], 2202 [asyncResp, parentIfaceId{std::string(params[0])}, 2203 ifaceId{std::string(params[1])}, &vlanEnable, &vlanId]( 2204 const bool &success, const EthernetInterfaceData ðData, 2205 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2206 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2207 if (success && !ethData.vlan_id.empty()) 2208 { 2209 auto callback = 2210 [asyncResp](const boost::system::error_code ec) { 2211 if (ec) 2212 { 2213 messages::internalError(asyncResp->res); 2214 } 2215 }; 2216 2217 if (vlanEnable == true) 2218 { 2219 crow::connections::systemBus->async_method_call( 2220 std::move(callback), "xyz.openbmc_project.Network", 2221 "/xyz/openbmc_project/network/" + ifaceId, 2222 "org.freedesktop.DBus.Properties", "Set", 2223 "xyz.openbmc_project.Network.VLAN", "Id", 2224 std::variant<uint32_t>(vlanId)); 2225 } 2226 else 2227 { 2228 BMCWEB_LOG_DEBUG << "vlanEnable is false. Deleting the " 2229 "vlan interface"; 2230 crow::connections::systemBus->async_method_call( 2231 std::move(callback), "xyz.openbmc_project.Network", 2232 std::string("/xyz/openbmc_project/network/") + 2233 ifaceId, 2234 "xyz.openbmc_project.Object.Delete", "Delete"); 2235 } 2236 } 2237 else 2238 { 2239 // TODO(Pawel)consider distinguish between non existing 2240 // object, and other errors 2241 messages::resourceNotFound( 2242 asyncResp->res, "VLAN Network Interface", ifaceId); 2243 return; 2244 } 2245 }); 2246 } 2247 2248 void doDelete(crow::Response &res, const crow::Request &req, 2249 const std::vector<std::string> ¶ms) override 2250 { 2251 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2252 if (params.size() != 2) 2253 { 2254 messages::internalError(asyncResp->res); 2255 return; 2256 } 2257 2258 const std::string &parentIfaceId = params[0]; 2259 const std::string &ifaceId = params[1]; 2260 2261 if (!verifyNames(parentIfaceId, ifaceId)) 2262 { 2263 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2264 ifaceId); 2265 return; 2266 } 2267 2268 // Get single eth interface data, and call the below callback for 2269 // JSON preparation 2270 getEthernetIfaceData( 2271 params[1], 2272 [asyncResp, parentIfaceId{std::string(params[0])}, 2273 ifaceId{std::string(params[1])}]( 2274 const bool &success, const EthernetInterfaceData ðData, 2275 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 2276 const boost::container::flat_set<IPv6AddressData> &ipv6Data) { 2277 if (success && !ethData.vlan_id.empty()) 2278 { 2279 auto callback = 2280 [asyncResp](const boost::system::error_code ec) { 2281 if (ec) 2282 { 2283 messages::internalError(asyncResp->res); 2284 } 2285 }; 2286 crow::connections::systemBus->async_method_call( 2287 std::move(callback), "xyz.openbmc_project.Network", 2288 std::string("/xyz/openbmc_project/network/") + ifaceId, 2289 "xyz.openbmc_project.Object.Delete", "Delete"); 2290 } 2291 else 2292 { 2293 // ... otherwise return error 2294 // TODO(Pawel)consider distinguish between non existing 2295 // object, and other errors 2296 messages::resourceNotFound( 2297 asyncResp->res, "VLAN Network Interface", ifaceId); 2298 } 2299 }); 2300 } 2301 }; 2302 2303 /** 2304 * VlanNetworkInterfaceCollection derived class for delivering 2305 * VLANNetworkInterface Collection Schema 2306 */ 2307 class VlanNetworkInterfaceCollection : public Node 2308 { 2309 public: 2310 template <typename CrowApp> 2311 VlanNetworkInterfaceCollection(CrowApp &app) : 2312 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/", 2313 std::string()) 2314 { 2315 entityPrivileges = { 2316 {boost::beast::http::verb::get, {{"Login"}}}, 2317 {boost::beast::http::verb::head, {{"Login"}}}, 2318 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 2319 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 2320 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 2321 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 2322 } 2323 2324 private: 2325 /** 2326 * Functions triggers appropriate requests on DBus 2327 */ 2328 void doGet(crow::Response &res, const crow::Request &req, 2329 const std::vector<std::string> ¶ms) override 2330 { 2331 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2332 if (params.size() != 1) 2333 { 2334 // This means there is a problem with the router 2335 messages::internalError(asyncResp->res); 2336 return; 2337 } 2338 2339 const std::string &rootInterfaceName = params[0]; 2340 2341 // Get eth interface list, and call the below callback for JSON 2342 // preparation 2343 getEthernetIfaceList( 2344 [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}]( 2345 const bool &success, 2346 const boost::container::flat_set<std::string> &iface_list) { 2347 if (!success) 2348 { 2349 messages::internalError(asyncResp->res); 2350 return; 2351 } 2352 2353 if (iface_list.find(rootInterfaceName) == iface_list.end()) 2354 { 2355 messages::resourceNotFound(asyncResp->res, 2356 "VLanNetworkInterfaceCollection", 2357 rootInterfaceName); 2358 return; 2359 } 2360 2361 asyncResp->res.jsonValue["@odata.type"] = 2362 "#VLanNetworkInterfaceCollection." 2363 "VLanNetworkInterfaceCollection"; 2364 asyncResp->res.jsonValue["Name"] = 2365 "VLAN Network Interface Collection"; 2366 2367 nlohmann::json iface_array = nlohmann::json::array(); 2368 2369 for (const std::string &iface_item : iface_list) 2370 { 2371 if (boost::starts_with(iface_item, rootInterfaceName + "_")) 2372 { 2373 iface_array.push_back( 2374 {{"@odata.id", 2375 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2376 rootInterfaceName + "/VLANs/" + iface_item}}); 2377 } 2378 } 2379 2380 asyncResp->res.jsonValue["Members@odata.count"] = 2381 iface_array.size(); 2382 asyncResp->res.jsonValue["Members"] = std::move(iface_array); 2383 asyncResp->res.jsonValue["@odata.id"] = 2384 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2385 rootInterfaceName + "/VLANs"; 2386 }); 2387 } 2388 2389 void doPost(crow::Response &res, const crow::Request &req, 2390 const std::vector<std::string> ¶ms) override 2391 { 2392 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 2393 if (params.size() != 1) 2394 { 2395 messages::internalError(asyncResp->res); 2396 return; 2397 } 2398 bool vlanEnable = false; 2399 uint32_t vlanId = 0; 2400 if (!json_util::readJson(req, res, "VLANId", vlanId, "VLANEnable", 2401 vlanEnable)) 2402 { 2403 return; 2404 } 2405 // Need both vlanId and vlanEnable to service this request 2406 if (!vlanId) 2407 { 2408 messages::propertyMissing(asyncResp->res, "VLANId"); 2409 } 2410 if (!vlanEnable) 2411 { 2412 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2413 } 2414 if (static_cast<bool>(vlanId) ^ vlanEnable) 2415 { 2416 return; 2417 } 2418 2419 const std::string &rootInterfaceName = params[0]; 2420 auto callback = [asyncResp](const boost::system::error_code ec) { 2421 if (ec) 2422 { 2423 // TODO(ed) make more consistent error messages based on 2424 // phosphor-network responses 2425 messages::internalError(asyncResp->res); 2426 return; 2427 } 2428 messages::created(asyncResp->res); 2429 }; 2430 crow::connections::systemBus->async_method_call( 2431 std::move(callback), "xyz.openbmc_project.Network", 2432 "/xyz/openbmc_project/network", 2433 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2434 rootInterfaceName, vlanId); 2435 } 2436 }; 2437 } // namespace redfish 2438