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