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