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