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