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