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