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