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( // 1553 *obj, asyncResp->res, // 1554 "Address", address, // 1555 "Gateway", gateway, // 1556 "SubnetMask", subnetMask // 1557 )) 1558 { 1559 messages::propertyValueFormatError(asyncResp->res, *obj, 1560 pathString); 1561 return; 1562 } 1563 1564 // Find the address/subnet/gateway values. Any values that are 1565 // not explicitly provided are assumed to be unmodified from the 1566 // current state of the interface. Merge existing state into the 1567 // current request. 1568 if (address) 1569 { 1570 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1571 { 1572 messages::propertyValueFormatError(asyncResp->res, *address, 1573 pathString + "/Address"); 1574 return; 1575 } 1576 } 1577 else if (nicIpEntry != ipv4Data.cend()) 1578 { 1579 address = (nicIpEntry->address); 1580 } 1581 else 1582 { 1583 messages::propertyMissing(asyncResp->res, 1584 pathString + "/Address"); 1585 return; 1586 } 1587 1588 uint8_t prefixLength = 0; 1589 if (subnetMask) 1590 { 1591 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1592 &prefixLength)) 1593 { 1594 messages::propertyValueFormatError( 1595 asyncResp->res, *subnetMask, 1596 pathString + "/SubnetMask"); 1597 return; 1598 } 1599 } 1600 else if (nicIpEntry != ipv4Data.cend()) 1601 { 1602 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1603 &prefixLength)) 1604 { 1605 messages::propertyValueFormatError( 1606 asyncResp->res, nicIpEntry->netmask, 1607 pathString + "/SubnetMask"); 1608 return; 1609 } 1610 } 1611 else 1612 { 1613 messages::propertyMissing(asyncResp->res, 1614 pathString + "/SubnetMask"); 1615 return; 1616 } 1617 1618 if (gateway) 1619 { 1620 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1621 { 1622 messages::propertyValueFormatError(asyncResp->res, *gateway, 1623 pathString + "/Gateway"); 1624 return; 1625 } 1626 } 1627 else if (nicIpEntry != ipv4Data.cend()) 1628 { 1629 gateway = nicIpEntry->gateway; 1630 } 1631 else 1632 { 1633 messages::propertyMissing(asyncResp->res, 1634 pathString + "/Gateway"); 1635 return; 1636 } 1637 1638 if (gatewayValueAssigned) 1639 { 1640 if (activeGateway != gateway) 1641 { 1642 // A NIC can only have a single active gateway value. 1643 // If any gateway in the array of static addresses 1644 // mismatch the PATCH is in error. 1645 std::string arg1 = pathString + "/Gateway"; 1646 std::string arg2 = activePath + "/Gateway"; 1647 messages::propertyValueConflict(asyncResp->res, arg1, arg2); 1648 return; 1649 } 1650 } 1651 else 1652 { 1653 // Capture the very first gateway value from the incoming 1654 // JSON record and use it at the default gateway. 1655 updateIPv4DefaultGateway(ifaceId, *gateway, asyncResp); 1656 activeGateway = *gateway; 1657 activePath = pathString; 1658 gatewayValueAssigned = true; 1659 } 1660 1661 if (nicIpEntry != ipv4Data.cend()) 1662 { 1663 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId, 1664 nicIpEntry->id, prefixLength, *address, 1665 *gateway, asyncResp); 1666 nicIpEntry = 1667 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1668 preserveGateway = true; 1669 } 1670 else 1671 { 1672 createIPv4(ifaceId, prefixLength, *gateway, *address, 1673 asyncResp); 1674 preserveGateway = true; 1675 } 1676 entryIdx++; 1677 } 1678 else 1679 { 1680 // Received {}, do not modify this address 1681 if (nicIpEntry != ipv4Data.cend()) 1682 { 1683 nicIpEntry = 1684 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1685 preserveGateway = true; 1686 entryIdx++; 1687 } 1688 else 1689 { 1690 // Requested a DO NOT MODIFY action on an entry not assigned 1691 // to the NIC 1692 messages::propertyValueFormatError(asyncResp->res, *obj, 1693 pathString); 1694 return; 1695 } 1696 } 1697 } 1698 } 1699 1700 inline void handleStaticNameServersPatch( 1701 const std::string& ifaceId, 1702 const std::vector<std::string>& updatedStaticNameServers, 1703 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1704 { 1705 setDbusProperty( 1706 asyncResp, "StaticNameServers", "xyz.openbmc_project.Network", 1707 sdbusplus::message::object_path("/xyz/openbmc_project/network") / 1708 ifaceId, 1709 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1710 updatedStaticNameServers); 1711 } 1712 1713 inline void handleIPv6StaticAddressesPatch( 1714 const std::string& ifaceId, 1715 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input, 1716 const std::vector<IPv6AddressData>& ipv6Data, 1717 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1718 { 1719 size_t entryIdx = 1; 1720 std::vector<IPv6AddressData>::const_iterator nicIpEntry = 1721 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1722 for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson : 1723 input) 1724 { 1725 std::string pathString = 1726 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1727 nlohmann::json::object_t* obj = 1728 std::get_if<nlohmann::json::object_t>(&thisJson); 1729 if (obj != nullptr && !obj->empty()) 1730 { 1731 std::optional<std::string> address; 1732 std::optional<uint8_t> prefixLength; 1733 nlohmann::json::object_t thisJsonCopy = *obj; 1734 if (!json_util::readJsonObject( // 1735 thisJsonCopy, asyncResp->res, // 1736 "Address", address, // 1737 "PrefixLength", prefixLength // 1738 )) 1739 { 1740 messages::propertyValueFormatError(asyncResp->res, thisJsonCopy, 1741 pathString); 1742 return; 1743 } 1744 1745 // Find the address and prefixLength values. Any values that are 1746 // not explicitly provided are assumed to be unmodified from the 1747 // current state of the interface. Merge existing state into the 1748 // current request. 1749 if (!address) 1750 { 1751 if (nicIpEntry == ipv6Data.end()) 1752 { 1753 messages::propertyMissing(asyncResp->res, 1754 pathString + "/Address"); 1755 return; 1756 } 1757 address = nicIpEntry->address; 1758 } 1759 1760 if (!prefixLength) 1761 { 1762 if (nicIpEntry == ipv6Data.end()) 1763 { 1764 messages::propertyMissing(asyncResp->res, 1765 pathString + "/PrefixLength"); 1766 return; 1767 } 1768 prefixLength = nicIpEntry->prefixLength; 1769 } 1770 1771 if (nicIpEntry != ipv6Data.end()) 1772 { 1773 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId, 1774 nicIpEntry->id, *prefixLength, 1775 *address, "", asyncResp); 1776 nicIpEntry = 1777 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1778 } 1779 else 1780 { 1781 createIPv6(ifaceId, *prefixLength, *address, asyncResp); 1782 } 1783 entryIdx++; 1784 } 1785 else 1786 { 1787 if (nicIpEntry == ipv6Data.end()) 1788 { 1789 // Requesting a DELETE/DO NOT MODIFY action for an item 1790 // that isn't present on the eth(n) interface. Input JSON is 1791 // in error, so bail out. 1792 if (obj == nullptr) 1793 { 1794 messages::resourceCannotBeDeleted(asyncResp->res); 1795 return; 1796 } 1797 messages::propertyValueFormatError(asyncResp->res, *obj, 1798 pathString); 1799 return; 1800 } 1801 1802 if (obj == nullptr) 1803 { 1804 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp); 1805 } 1806 if (nicIpEntry != ipv6Data.cend()) 1807 { 1808 nicIpEntry = 1809 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1810 } 1811 entryIdx++; 1812 } 1813 } 1814 } 1815 1816 inline std::string extractParentInterfaceName(const std::string& ifaceId) 1817 { 1818 std::size_t pos = ifaceId.find('_'); 1819 return ifaceId.substr(0, pos); 1820 } 1821 1822 inline void parseInterfaceData( 1823 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1824 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1825 const std::vector<IPv4AddressData>& ipv4Data, 1826 const std::vector<IPv6AddressData>& ipv6Data, 1827 const std::vector<StaticGatewayData>& ipv6GatewayData) 1828 { 1829 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1830 jsonResponse["Id"] = ifaceId; 1831 jsonResponse["@odata.id"] = 1832 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", 1833 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceId); 1834 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1835 1836 if (ethData.nicEnabled) 1837 { 1838 jsonResponse["LinkStatus"] = 1839 ethData.linkUp ? ethernet_interface::LinkStatus::LinkUp 1840 : ethernet_interface::LinkStatus::LinkDown; 1841 jsonResponse["Status"]["State"] = resource::State::Enabled; 1842 } 1843 else 1844 { 1845 jsonResponse["LinkStatus"] = ethernet_interface::LinkStatus::NoLink; 1846 jsonResponse["Status"]["State"] = resource::State::Disabled; 1847 } 1848 1849 jsonResponse["SpeedMbps"] = ethData.speed; 1850 jsonResponse["MTUSize"] = ethData.mtuSize; 1851 if (ethData.macAddress) 1852 { 1853 jsonResponse["MACAddress"] = *ethData.macAddress; 1854 } 1855 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1856 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1857 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled; 1858 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled; 1859 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.domainv4Enabled; 1860 jsonResponse["DHCPv6"]["OperatingMode"] = 1861 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) 1862 ? "Enabled" 1863 : "Disabled"; 1864 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled; 1865 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled; 1866 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.domainv6Enabled; 1867 jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] = 1868 ethData.ipv6AcceptRa; 1869 1870 if (!ethData.hostName.empty()) 1871 { 1872 jsonResponse["HostName"] = ethData.hostName; 1873 1874 // When domain name is empty then it means, that it is a network 1875 // without domain names, and the host name itself must be treated as 1876 // FQDN 1877 std::string fqdn = ethData.hostName; 1878 if (!ethData.domainnames.empty()) 1879 { 1880 fqdn += "." + ethData.domainnames[0]; 1881 } 1882 jsonResponse["FQDN"] = fqdn; 1883 } 1884 1885 if (ethData.vlanId) 1886 { 1887 jsonResponse["EthernetInterfaceType"] = 1888 ethernet_interface::EthernetDeviceType::Virtual; 1889 jsonResponse["VLAN"]["VLANEnable"] = true; 1890 jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId; 1891 jsonResponse["VLAN"]["Tagged"] = true; 1892 1893 nlohmann::json::array_t relatedInterfaces; 1894 nlohmann::json& parentInterface = relatedInterfaces.emplace_back(); 1895 parentInterface["@odata.id"] = 1896 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces", 1897 BMCWEB_REDFISH_MANAGER_URI_NAME, 1898 extractParentInterfaceName(ifaceId)); 1899 jsonResponse["Links"]["RelatedInterfaces"] = 1900 std::move(relatedInterfaces); 1901 } 1902 else 1903 { 1904 jsonResponse["EthernetInterfaceType"] = 1905 ethernet_interface::EthernetDeviceType::Physical; 1906 } 1907 1908 jsonResponse["NameServers"] = ethData.nameServers; 1909 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1910 1911 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1912 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1913 ipv4Array = nlohmann::json::array(); 1914 ipv4StaticArray = nlohmann::json::array(); 1915 for (const auto& ipv4Config : ipv4Data) 1916 { 1917 std::string gatewayStr = ipv4Config.gateway; 1918 if (gatewayStr.empty()) 1919 { 1920 gatewayStr = "0.0.0.0"; 1921 } 1922 nlohmann::json::object_t ipv4; 1923 ipv4["AddressOrigin"] = ipv4Config.origin; 1924 ipv4["SubnetMask"] = ipv4Config.netmask; 1925 ipv4["Address"] = ipv4Config.address; 1926 ipv4["Gateway"] = gatewayStr; 1927 1928 if (ipv4Config.origin == "Static") 1929 { 1930 ipv4StaticArray.push_back(ipv4); 1931 } 1932 1933 ipv4Array.emplace_back(std::move(ipv4)); 1934 } 1935 1936 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1937 if (ipv6GatewayStr.empty()) 1938 { 1939 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1940 } 1941 1942 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1943 1944 nlohmann::json::array_t ipv6StaticGatewayArray; 1945 for (const auto& ipv6GatewayConfig : ipv6GatewayData) 1946 { 1947 nlohmann::json::object_t ipv6Gateway; 1948 ipv6Gateway["Address"] = ipv6GatewayConfig.gateway; 1949 ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway)); 1950 } 1951 jsonResponse["IPv6StaticDefaultGateways"] = 1952 std::move(ipv6StaticGatewayArray); 1953 1954 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1955 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1956 ipv6Array = nlohmann::json::array(); 1957 ipv6StaticArray = nlohmann::json::array(); 1958 nlohmann::json& ipv6AddrPolicyTable = 1959 jsonResponse["IPv6AddressPolicyTable"]; 1960 ipv6AddrPolicyTable = nlohmann::json::array(); 1961 for (const auto& ipv6Config : ipv6Data) 1962 { 1963 nlohmann::json::object_t ipv6; 1964 ipv6["Address"] = ipv6Config.address; 1965 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1966 ipv6["AddressOrigin"] = ipv6Config.origin; 1967 1968 ipv6Array.emplace_back(std::move(ipv6)); 1969 if (ipv6Config.origin == "Static") 1970 { 1971 nlohmann::json::object_t ipv6Static; 1972 ipv6Static["Address"] = ipv6Config.address; 1973 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1974 ipv6StaticArray.emplace_back(std::move(ipv6Static)); 1975 } 1976 } 1977 } 1978 1979 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1980 const std::string& ifaceId, 1981 const boost::system::error_code& ec, 1982 const sdbusplus::message_t& m) 1983 { 1984 if (!ec) 1985 { 1986 return; 1987 } 1988 const sd_bus_error* dbusError = m.get_error(); 1989 if (dbusError == nullptr) 1990 { 1991 messages::internalError(asyncResp->res); 1992 return; 1993 } 1994 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 1995 1996 if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") == 1997 dbusError->name) 1998 { 1999 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 2000 ifaceId); 2001 return; 2002 } 2003 if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") == 2004 dbusError->name) 2005 { 2006 messages::resourceCannotBeDeleted(asyncResp->res); 2007 return; 2008 } 2009 messages::internalError(asyncResp->res); 2010 } 2011 2012 inline void afterVlanCreate( 2013 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2014 const std::string& parentInterfaceUri, const std::string& vlanInterface, 2015 const boost::system::error_code& ec, const sdbusplus::message_t& m 2016 2017 ) 2018 { 2019 if (ec) 2020 { 2021 const sd_bus_error* dbusError = m.get_error(); 2022 if (dbusError == nullptr) 2023 { 2024 messages::internalError(asyncResp->res); 2025 return; 2026 } 2027 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name); 2028 2029 if (std::string_view( 2030 "xyz.openbmc_project.Common.Error.ResourceNotFound") == 2031 dbusError->name) 2032 { 2033 messages::propertyValueNotInList( 2034 asyncResp->res, parentInterfaceUri, 2035 "Links/RelatedInterfaces/0/@odata.id"); 2036 return; 2037 } 2038 if (std::string_view( 2039 "xyz.openbmc_project.Common.Error.InvalidArgument") == 2040 dbusError->name) 2041 { 2042 messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface", 2043 "Id", vlanInterface); 2044 return; 2045 } 2046 messages::internalError(asyncResp->res); 2047 return; 2048 } 2049 2050 const boost::urls::url vlanInterfaceUri = 2051 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}", 2052 BMCWEB_REDFISH_MANAGER_URI_NAME, vlanInterface); 2053 asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer()); 2054 } 2055 2056 inline void requestEthernetInterfacesRoutes(App& app) 2057 { 2058 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/") 2059 .privileges(redfish::privileges::getEthernetInterfaceCollection) 2060 .methods(boost::beast::http::verb::get)( 2061 [&app](const crow::Request& req, 2062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2063 const std::string& managerId) { 2064 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2065 { 2066 return; 2067 } 2068 2069 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2070 { 2071 messages::resourceNotFound(asyncResp->res, "Manager", 2072 managerId); 2073 return; 2074 } 2075 2076 asyncResp->res.jsonValue["@odata.type"] = 2077 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 2078 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 2079 "/redfish/v1/Managers/{}/EthernetInterfaces", 2080 BMCWEB_REDFISH_MANAGER_URI_NAME); 2081 asyncResp->res.jsonValue["Name"] = 2082 "Ethernet Network Interface Collection"; 2083 asyncResp->res.jsonValue["Description"] = 2084 "Collection of EthernetInterfaces for this Manager"; 2085 2086 // Get eth interface list, and call the below callback for JSON 2087 // preparation 2088 getEthernetIfaceList( 2089 [asyncResp](const bool& success, 2090 const std::vector<std::string>& ifaceList) { 2091 if (!success) 2092 { 2093 messages::internalError(asyncResp->res); 2094 return; 2095 } 2096 2097 nlohmann::json& ifaceArray = 2098 asyncResp->res.jsonValue["Members"]; 2099 ifaceArray = nlohmann::json::array(); 2100 for (const std::string& ifaceItem : ifaceList) 2101 { 2102 nlohmann::json::object_t iface; 2103 iface["@odata.id"] = boost::urls::format( 2104 "/redfish/v1/Managers/{}/EthernetInterfaces/{}", 2105 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceItem); 2106 ifaceArray.push_back(std::move(iface)); 2107 } 2108 2109 asyncResp->res.jsonValue["Members@odata.count"] = 2110 ifaceArray.size(); 2111 asyncResp->res.jsonValue["@odata.id"] = 2112 boost::urls::format( 2113 "/redfish/v1/Managers/{}/EthernetInterfaces", 2114 BMCWEB_REDFISH_MANAGER_URI_NAME); 2115 }); 2116 }); 2117 2118 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/") 2119 .privileges(redfish::privileges::postEthernetInterfaceCollection) 2120 .methods(boost::beast::http::verb::post)( 2121 [&app](const crow::Request& req, 2122 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2123 const std::string& managerId) { 2124 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2125 { 2126 return; 2127 } 2128 2129 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2130 { 2131 messages::resourceNotFound(asyncResp->res, "Manager", 2132 managerId); 2133 return; 2134 } 2135 2136 bool vlanEnable = false; 2137 uint32_t vlanId = 0; 2138 std::vector<nlohmann::json::object_t> relatedInterfaces; 2139 2140 if (!json_util::readJsonPatch( // 2141 req, asyncResp->res, // 2142 "Links/RelatedInterfaces", relatedInterfaces, // 2143 "VLAN/VLANEnable", vlanEnable, // 2144 "VLAN/VLANId", vlanId // 2145 )) 2146 { 2147 return; 2148 } 2149 2150 if (relatedInterfaces.size() != 1) 2151 { 2152 messages::arraySizeTooLong(asyncResp->res, 2153 "Links/RelatedInterfaces", 2154 relatedInterfaces.size()); 2155 return; 2156 } 2157 2158 std::string parentInterfaceUri; 2159 if (!json_util::readJsonObject(relatedInterfaces[0], 2160 asyncResp->res, "@odata.id", 2161 parentInterfaceUri)) 2162 { 2163 messages::propertyMissing( 2164 asyncResp->res, "Links/RelatedInterfaces/0/@odata.id"); 2165 return; 2166 } 2167 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri); 2168 2169 boost::system::result<boost::urls::url_view> parsedUri = 2170 boost::urls::parse_relative_ref(parentInterfaceUri); 2171 if (!parsedUri) 2172 { 2173 messages::propertyValueFormatError( 2174 asyncResp->res, parentInterfaceUri, 2175 "Links/RelatedInterfaces/0/@odata.id"); 2176 return; 2177 } 2178 2179 std::string parentInterface; 2180 if (!crow::utility::readUrlSegments( 2181 *parsedUri, "redfish", "v1", "Managers", "bmc", 2182 "EthernetInterfaces", std::ref(parentInterface))) 2183 { 2184 messages::propertyValueNotInList( 2185 asyncResp->res, parentInterfaceUri, 2186 "Links/RelatedInterfaces/0/@odata.id"); 2187 return; 2188 } 2189 2190 if (!vlanEnable) 2191 { 2192 // In OpenBMC implementation, VLANEnable cannot be false on 2193 // create 2194 messages::propertyValueIncorrect( 2195 asyncResp->res, "VLAN/VLANEnable", "false"); 2196 return; 2197 } 2198 2199 std::string vlanInterface = 2200 parentInterface + "_" + std::to_string(vlanId); 2201 crow::connections::systemBus->async_method_call( 2202 [asyncResp, parentInterfaceUri, 2203 vlanInterface](const boost::system::error_code& ec, 2204 const sdbusplus::message_t& m) { 2205 afterVlanCreate(asyncResp, parentInterfaceUri, 2206 vlanInterface, ec, m); 2207 }, 2208 "xyz.openbmc_project.Network", 2209 "/xyz/openbmc_project/network", 2210 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2211 parentInterface, vlanId); 2212 }); 2213 2214 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2215 .privileges(redfish::privileges::getEthernetInterface) 2216 .methods(boost::beast::http::verb::get)( 2217 [&app](const crow::Request& req, 2218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2219 const std::string& managerId, const std::string& ifaceId) { 2220 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2221 { 2222 return; 2223 } 2224 2225 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2226 { 2227 messages::resourceNotFound(asyncResp->res, "Manager", 2228 managerId); 2229 return; 2230 } 2231 2232 getEthernetIfaceData( 2233 ifaceId, 2234 [asyncResp, ifaceId]( 2235 const bool& success, 2236 const EthernetInterfaceData& ethData, 2237 const std::vector<IPv4AddressData>& ipv4Data, 2238 const std::vector<IPv6AddressData>& ipv6Data, 2239 const std::vector<StaticGatewayData>& ipv6GatewayData) { 2240 if (!success) 2241 { 2242 // TODO(Pawel)consider distinguish between non 2243 // existing object, and other errors 2244 messages::resourceNotFound( 2245 asyncResp->res, "EthernetInterface", ifaceId); 2246 return; 2247 } 2248 2249 asyncResp->res.jsonValue["@odata.type"] = 2250 "#EthernetInterface.v1_9_0.EthernetInterface"; 2251 asyncResp->res.jsonValue["Name"] = 2252 "Manager Ethernet Interface"; 2253 asyncResp->res.jsonValue["Description"] = 2254 "Management Network Interface"; 2255 2256 parseInterfaceData(asyncResp, ifaceId, ethData, 2257 ipv4Data, ipv6Data, ipv6GatewayData); 2258 }); 2259 }); 2260 2261 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2262 .privileges(redfish::privileges::patchEthernetInterface) 2263 .methods(boost::beast::http::verb::patch)( 2264 [&app](const crow::Request& req, 2265 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2266 const std::string& managerId, const std::string& ifaceId) { 2267 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2268 { 2269 return; 2270 } 2271 2272 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2273 { 2274 messages::resourceNotFound(asyncResp->res, "Manager", 2275 managerId); 2276 return; 2277 } 2278 2279 std::optional<std::string> hostname; 2280 std::optional<std::string> fqdn; 2281 std::optional<std::string> macAddress; 2282 std::optional<std::string> ipv6DefaultGateway; 2283 std::optional<std::vector< 2284 std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2285 ipv4StaticAddresses; 2286 std::optional<std::vector< 2287 std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2288 ipv6StaticAddresses; 2289 std::optional<std::vector< 2290 std::variant<nlohmann::json::object_t, std::nullptr_t>>> 2291 ipv6StaticDefaultGateways; 2292 std::optional<std::vector<std::string>> staticNameServers; 2293 std::optional<bool> ipv6AutoConfigEnabled; 2294 std::optional<bool> interfaceEnabled; 2295 std::optional<size_t> mtuSize; 2296 DHCPParameters v4dhcpParms; 2297 DHCPParameters v6dhcpParms; 2298 2299 if (!json_util::readJsonPatch( // 2300 req, asyncResp->res, // 2301 "DHCPv4/DHCPEnabled", v4dhcpParms.dhcpv4Enabled, // 2302 "DHCPv4/UseDNSServers", v4dhcpParms.useDnsServers, // 2303 "DHCPv4/UseDomainName", v4dhcpParms.useDomainName, // 2304 "DHCPv4/UseNTPServers", v4dhcpParms.useNtpServers, // 2305 "DHCPv6/OperatingMode", 2306 v6dhcpParms.dhcpv6OperatingMode, // 2307 "DHCPv6/UseDNSServers", v6dhcpParms.useDnsServers, // 2308 "DHCPv6/UseDomainName", v6dhcpParms.useDomainName, // 2309 "DHCPv6/UseNTPServers", v6dhcpParms.useNtpServers, // 2310 "FQDN", fqdn, // 2311 "HostName", hostname, // 2312 "InterfaceEnabled", interfaceEnabled, // 2313 "IPv4StaticAddresses", ipv4StaticAddresses, // 2314 "IPv6DefaultGateway", ipv6DefaultGateway, // 2315 "IPv6StaticAddresses", ipv6StaticAddresses, // 2316 "IPv6StaticDefaultGateways", 2317 ipv6StaticDefaultGateways, // 2318 "InterfaceEnabled", interfaceEnabled, // 2319 "MACAddress", macAddress, // 2320 "MTUSize", mtuSize, // 2321 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled", 2322 ipv6AutoConfigEnabled, // 2323 "StaticNameServers", staticNameServers // 2324 )) 2325 { 2326 return; 2327 } 2328 2329 // Get single eth interface data, and call the below callback 2330 // for JSON preparation 2331 getEthernetIfaceData( 2332 ifaceId, 2333 [asyncResp, ifaceId, hostname = std::move(hostname), 2334 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 2335 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 2336 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 2337 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 2338 ipv6StaticDefaultGateway = 2339 std::move(ipv6StaticDefaultGateways), 2340 staticNameServers = std::move(staticNameServers), mtuSize, 2341 ipv6AutoConfigEnabled, 2342 v4dhcpParms = std::move(v4dhcpParms), 2343 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 2344 const bool success, 2345 const EthernetInterfaceData& ethData, 2346 const std::vector<IPv4AddressData>& ipv4Data, 2347 const std::vector<IPv6AddressData>& ipv6Data, 2348 const std::vector<StaticGatewayData>& 2349 ipv6GatewayData) mutable { 2350 if (!success) 2351 { 2352 // ... otherwise return error 2353 // TODO(Pawel)consider distinguish between non 2354 // existing object, and other errors 2355 messages::resourceNotFound( 2356 asyncResp->res, "EthernetInterface", ifaceId); 2357 return; 2358 } 2359 2360 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, 2361 v6dhcpParms, asyncResp); 2362 2363 if (hostname) 2364 { 2365 handleHostnamePatch(*hostname, asyncResp); 2366 } 2367 2368 if (ipv6AutoConfigEnabled) 2369 { 2370 handleSLAACAutoConfigPatch( 2371 ifaceId, *ipv6AutoConfigEnabled, asyncResp); 2372 } 2373 2374 if (fqdn) 2375 { 2376 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2377 } 2378 2379 if (macAddress) 2380 { 2381 handleMACAddressPatch(ifaceId, *macAddress, 2382 asyncResp); 2383 } 2384 2385 if (ipv4StaticAddresses) 2386 { 2387 handleIPv4StaticPatch(ifaceId, *ipv4StaticAddresses, 2388 ethData, ipv4Data, asyncResp); 2389 } 2390 2391 if (staticNameServers) 2392 { 2393 handleStaticNameServersPatch( 2394 ifaceId, *staticNameServers, asyncResp); 2395 } 2396 2397 if (ipv6DefaultGateway) 2398 { 2399 messages::propertyNotWritable(asyncResp->res, 2400 "IPv6DefaultGateway"); 2401 } 2402 2403 if (ipv6StaticAddresses) 2404 { 2405 handleIPv6StaticAddressesPatch(ifaceId, 2406 *ipv6StaticAddresses, 2407 ipv6Data, asyncResp); 2408 } 2409 2410 if (ipv6StaticDefaultGateway) 2411 { 2412 handleIPv6DefaultGateway( 2413 ifaceId, *ipv6StaticDefaultGateway, 2414 ipv6GatewayData, asyncResp); 2415 } 2416 2417 if (interfaceEnabled) 2418 { 2419 setDbusProperty( 2420 asyncResp, "InterfaceEnabled", 2421 "xyz.openbmc_project.Network", 2422 sdbusplus::message::object_path( 2423 "/xyz/openbmc_project/network") / 2424 ifaceId, 2425 "xyz.openbmc_project.Network.EthernetInterface", 2426 "NICEnabled", *interfaceEnabled); 2427 } 2428 2429 if (mtuSize) 2430 { 2431 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2432 } 2433 }); 2434 }); 2435 2436 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/") 2437 .privileges(redfish::privileges::deleteEthernetInterface) 2438 .methods(boost::beast::http::verb::delete_)( 2439 [&app](const crow::Request& req, 2440 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2441 const std::string& managerId, const std::string& ifaceId) { 2442 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2443 { 2444 return; 2445 } 2446 2447 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 2448 { 2449 messages::resourceNotFound(asyncResp->res, "Manager", 2450 managerId); 2451 return; 2452 } 2453 2454 crow::connections::systemBus->async_method_call( 2455 [asyncResp, ifaceId](const boost::system::error_code& ec, 2456 const sdbusplus::message_t& m) { 2457 afterDelete(asyncResp, ifaceId, ec, m); 2458 }, 2459 "xyz.openbmc_project.Network", 2460 std::string("/xyz/openbmc_project/network/") + ifaceId, 2461 "xyz.openbmc_project.Object.Delete", "Delete"); 2462 }); 2463 } 2464 2465 } // namespace redfish 2466