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