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