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 = 730 [asyncResp, ifaceId, 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, gateway](const boost::system::error_code ec2) { 777 if (ec2) 778 { 779 messages::internalError(asyncResp->res); 780 return; 781 } 782 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 783 }, 784 "xyz.openbmc_project.Network", 785 "/xyz/openbmc_project/network/" + ifaceId, 786 "xyz.openbmc_project.Network.IP.Create", "IP", 787 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 788 prefixLength, gateway); 789 }, 790 "xyz.openbmc_project.Network", 791 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 792 "xyz.openbmc_project.Object.Delete", "Delete"); 793 } 794 795 /** 796 * @brief Deletes given IPv6 797 * 798 * @param[in] ifaceId Id of interface whose IP should be deleted 799 * @param[in] ipHash DBus Hash id of IP that should be deleted 800 * @param[io] asyncResp Response object that will be returned to client 801 * 802 * @return None 803 */ 804 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash, 805 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 806 { 807 crow::connections::systemBus->async_method_call( 808 [asyncResp](const boost::system::error_code ec) { 809 if (ec) 810 { 811 messages::internalError(asyncResp->res); 812 } 813 }, 814 "xyz.openbmc_project.Network", 815 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 816 "xyz.openbmc_project.Object.Delete", "Delete"); 817 } 818 819 /** 820 * @brief Deletes the IPv6 entry for this interface and creates a replacement 821 * static IPv6 entry 822 * 823 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 824 * @param[in] id The unique hash entry identifying the DBus entry 825 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 826 * @param[in] address IPv6 address to assign to this interface 827 * @param[io] asyncResp Response object that will be returned to client 828 * 829 * @return None 830 */ 831 inline void 832 deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id, 833 uint8_t prefixLength, const std::string& address, 834 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 835 { 836 crow::connections::systemBus->async_method_call( 837 [asyncResp, ifaceId, address, 838 prefixLength](const boost::system::error_code ec) { 839 if (ec) 840 { 841 messages::internalError(asyncResp->res); 842 } 843 crow::connections::systemBus->async_method_call( 844 [asyncResp](const boost::system::error_code ec2) { 845 if (ec2) 846 { 847 messages::internalError(asyncResp->res); 848 } 849 }, 850 "xyz.openbmc_project.Network", 851 "/xyz/openbmc_project/network/" + ifaceId, 852 "xyz.openbmc_project.Network.IP.Create", "IP", 853 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 854 prefixLength, ""); 855 }, 856 "xyz.openbmc_project.Network", 857 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 858 "xyz.openbmc_project.Object.Delete", "Delete"); 859 } 860 861 /** 862 * @brief Creates IPv6 with given data 863 * 864 * @param[in] ifaceId Id of interface whose IP should be added 865 * @param[in] prefixLength Prefix length that needs to be added 866 * @param[in] address IP address that needs to be added 867 * @param[io] asyncResp Response object that will be returned to client 868 * 869 * @return None 870 */ 871 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 872 const std::string& address, 873 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 874 { 875 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 876 if (ec) 877 { 878 messages::internalError(asyncResp->res); 879 } 880 }; 881 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 882 // does not have associated gateway property 883 crow::connections::systemBus->async_method_call( 884 std::move(createIpHandler), "xyz.openbmc_project.Network", 885 "/xyz/openbmc_project/network/" + ifaceId, 886 "xyz.openbmc_project.Network.IP.Create", "IP", 887 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 888 ""); 889 } 890 891 /** 892 * Function that retrieves all properties for given Ethernet Interface 893 * Object 894 * from EntityManager Network Manager 895 * @param ethiface_id a eth interface id to query on DBus 896 * @param callback a function that shall be called to convert Dbus output 897 * into JSON 898 */ 899 template <typename CallbackFunc> 900 void getEthernetIfaceData(const std::string& ethifaceId, 901 CallbackFunc&& callback) 902 { 903 crow::connections::systemBus->async_method_call( 904 [ethifaceId{std::string{ethifaceId}}, 905 callback{std::forward<CallbackFunc>(callback)}]( 906 const boost::system::error_code errorCode, 907 dbus::utility::ManagedObjectType& resp) { 908 EthernetInterfaceData ethData{}; 909 boost::container::flat_set<IPv4AddressData> ipv4Data; 910 boost::container::flat_set<IPv6AddressData> ipv6Data; 911 912 if (errorCode) 913 { 914 callback(false, ethData, ipv4Data, ipv6Data); 915 return; 916 } 917 918 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 919 if (!found) 920 { 921 callback(false, ethData, ipv4Data, ipv6Data); 922 return; 923 } 924 925 extractIPData(ethifaceId, resp, ipv4Data); 926 // Fix global GW 927 for (IPv4AddressData& ipv4 : ipv4Data) 928 { 929 if (((ipv4.linktype == LinkType::Global) && 930 (ipv4.gateway == "0.0.0.0")) || 931 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 932 { 933 ipv4.gateway = ethData.defaultGateway; 934 } 935 } 936 937 extractIPV6Data(ethifaceId, resp, ipv6Data); 938 // Finally make a callback with useful data 939 callback(true, ethData, ipv4Data, ipv6Data); 940 }, 941 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 942 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 943 } 944 945 /** 946 * Function that retrieves all Ethernet Interfaces available through Network 947 * Manager 948 * @param callback a function that shall be called to convert Dbus output 949 * into JSON. 950 */ 951 template <typename CallbackFunc> 952 void getEthernetIfaceList(CallbackFunc&& callback) 953 { 954 crow::connections::systemBus->async_method_call( 955 [callback{std::forward<CallbackFunc>(callback)}]( 956 const boost::system::error_code errorCode, 957 dbus::utility::ManagedObjectType& resp) { 958 // Callback requires vector<string> to retrieve all available 959 // ethernet interfaces 960 boost::container::flat_set<std::string> ifaceList; 961 ifaceList.reserve(resp.size()); 962 if (errorCode) 963 { 964 callback(false, ifaceList); 965 return; 966 } 967 968 // Iterate over all retrieved ObjectPaths. 969 for (const auto& objpath : resp) 970 { 971 // And all interfaces available for certain ObjectPath. 972 for (const auto& interface : objpath.second) 973 { 974 // If interface is 975 // xyz.openbmc_project.Network.EthernetInterface, this is 976 // what we're looking for. 977 if (interface.first == 978 "xyz.openbmc_project.Network.EthernetInterface") 979 { 980 std::string ifaceId = objpath.first.filename(); 981 if (ifaceId.empty()) 982 { 983 continue; 984 } 985 // and put it into output vector. 986 ifaceList.emplace(ifaceId); 987 } 988 } 989 } 990 // Finally make a callback with useful data 991 callback(true, ifaceList); 992 }, 993 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 994 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 995 } 996 997 inline void 998 handleHostnamePatch(const std::string& hostname, 999 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1000 { 1001 // SHOULD handle host names of up to 255 characters(RFC 1123) 1002 if (hostname.length() > 255) 1003 { 1004 messages::propertyValueFormatError(asyncResp->res, hostname, 1005 "HostName"); 1006 return; 1007 } 1008 crow::connections::systemBus->async_method_call( 1009 [asyncResp](const boost::system::error_code ec) { 1010 if (ec) 1011 { 1012 messages::internalError(asyncResp->res); 1013 } 1014 }, 1015 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 1016 "org.freedesktop.DBus.Properties", "Set", 1017 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1018 dbus::utility::DbusVariantType(hostname)); 1019 } 1020 1021 inline void 1022 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 1023 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1024 { 1025 sdbusplus::message::object_path objPath = 1026 "/xyz/openbmc_project/network/" + ifaceId; 1027 crow::connections::systemBus->async_method_call( 1028 [asyncResp](const boost::system::error_code ec) { 1029 if (ec) 1030 { 1031 messages::internalError(asyncResp->res); 1032 } 1033 }, 1034 "xyz.openbmc_project.Network", objPath, 1035 "org.freedesktop.DBus.Properties", "Set", 1036 "xyz.openbmc_project.Network.EthernetInterface", "MTU", 1037 std::variant<size_t>(mtuSize)); 1038 } 1039 1040 inline void 1041 handleDomainnamePatch(const std::string& ifaceId, 1042 const std::string& domainname, 1043 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1044 { 1045 std::vector<std::string> vectorDomainname = {domainname}; 1046 crow::connections::systemBus->async_method_call( 1047 [asyncResp](const boost::system::error_code ec) { 1048 if (ec) 1049 { 1050 messages::internalError(asyncResp->res); 1051 } 1052 }, 1053 "xyz.openbmc_project.Network", 1054 "/xyz/openbmc_project/network/" + ifaceId, 1055 "org.freedesktop.DBus.Properties", "Set", 1056 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1057 dbus::utility::DbusVariantType(vectorDomainname)); 1058 } 1059 1060 inline bool isHostnameValid(const std::string& hostname) 1061 { 1062 // A valid host name can never have the dotted-decimal form (RFC 1123) 1063 if (std::all_of(hostname.begin(), hostname.end(), ::isdigit)) 1064 { 1065 return false; 1066 } 1067 // Each label(hostname/subdomains) within a valid FQDN 1068 // MUST handle host names of up to 63 characters (RFC 1123) 1069 // labels cannot start or end with hyphens (RFC 952) 1070 // labels can start with numbers (RFC 1123) 1071 const std::regex pattern( 1072 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1073 1074 return std::regex_match(hostname, pattern); 1075 } 1076 1077 inline bool isDomainnameValid(const std::string& domainname) 1078 { 1079 // Can have multiple subdomains 1080 // Top Level Domain's min length is 2 character 1081 const std::regex pattern( 1082 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1083 1084 return std::regex_match(domainname, pattern); 1085 } 1086 1087 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1088 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1089 { 1090 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1091 if (fqdn.length() > 255) 1092 { 1093 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1094 return; 1095 } 1096 1097 size_t pos = fqdn.find('.'); 1098 if (pos == std::string::npos) 1099 { 1100 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1101 return; 1102 } 1103 1104 std::string hostname; 1105 std::string domainname; 1106 domainname = (fqdn).substr(pos + 1); 1107 hostname = (fqdn).substr(0, pos); 1108 1109 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1110 { 1111 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1112 return; 1113 } 1114 1115 handleHostnamePatch(hostname, asyncResp); 1116 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1117 } 1118 1119 inline void 1120 handleMACAddressPatch(const std::string& ifaceId, 1121 const std::string& macAddress, 1122 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1123 { 1124 crow::connections::systemBus->async_method_call( 1125 [asyncResp, macAddress](const boost::system::error_code ec) { 1126 if (ec) 1127 { 1128 messages::internalError(asyncResp->res); 1129 return; 1130 } 1131 }, 1132 "xyz.openbmc_project.Network", 1133 "/xyz/openbmc_project/network/" + ifaceId, 1134 "org.freedesktop.DBus.Properties", "Set", 1135 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1136 dbus::utility::DbusVariantType(macAddress)); 1137 } 1138 1139 inline void setDHCPEnabled(const std::string& ifaceId, 1140 const std::string& propertyName, const bool v4Value, 1141 const bool v6Value, 1142 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1143 { 1144 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1145 crow::connections::systemBus->async_method_call( 1146 [asyncResp](const boost::system::error_code ec) { 1147 if (ec) 1148 { 1149 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1150 messages::internalError(asyncResp->res); 1151 return; 1152 } 1153 messages::success(asyncResp->res); 1154 }, 1155 "xyz.openbmc_project.Network", 1156 "/xyz/openbmc_project/network/" + ifaceId, 1157 "org.freedesktop.DBus.Properties", "Set", 1158 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1159 dbus::utility::DbusVariantType{dhcp}); 1160 } 1161 1162 inline void setEthernetInterfaceBoolProperty( 1163 const std::string& ifaceId, const std::string& propertyName, 1164 const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1165 { 1166 crow::connections::systemBus->async_method_call( 1167 [asyncResp](const boost::system::error_code ec) { 1168 if (ec) 1169 { 1170 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1171 messages::internalError(asyncResp->res); 1172 return; 1173 } 1174 }, 1175 "xyz.openbmc_project.Network", 1176 "/xyz/openbmc_project/network/" + ifaceId, 1177 "org.freedesktop.DBus.Properties", "Set", 1178 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1179 dbus::utility::DbusVariantType{value}); 1180 } 1181 1182 inline void setDHCPv4Config(const std::string& propertyName, const bool& value, 1183 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1184 { 1185 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1186 crow::connections::systemBus->async_method_call( 1187 [asyncResp](const boost::system::error_code ec) { 1188 if (ec) 1189 { 1190 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1191 messages::internalError(asyncResp->res); 1192 return; 1193 } 1194 }, 1195 "xyz.openbmc_project.Network", 1196 "/xyz/openbmc_project/network/config/dhcp", 1197 "org.freedesktop.DBus.Properties", "Set", 1198 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1199 dbus::utility::DbusVariantType{value}); 1200 } 1201 1202 inline void handleDHCPPatch(const std::string& ifaceId, 1203 const EthernetInterfaceData& ethData, 1204 const DHCPParameters& v4dhcpParms, 1205 const DHCPParameters& v6dhcpParms, 1206 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1207 { 1208 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1209 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1210 1211 bool nextv4DHCPState = 1212 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1213 1214 bool nextv6DHCPState{}; 1215 if (v6dhcpParms.dhcpv6OperatingMode) 1216 { 1217 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1218 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1219 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1220 { 1221 messages::propertyValueFormatError(asyncResp->res, 1222 *v6dhcpParms.dhcpv6OperatingMode, 1223 "OperatingMode"); 1224 return; 1225 } 1226 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1227 } 1228 else 1229 { 1230 nextv6DHCPState = ipv6Active; 1231 } 1232 1233 bool nextDNS{}; 1234 if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers) 1235 { 1236 if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers) 1237 { 1238 messages::generalError(asyncResp->res); 1239 return; 1240 } 1241 nextDNS = *v4dhcpParms.useDnsServers; 1242 } 1243 else if (v4dhcpParms.useDnsServers) 1244 { 1245 nextDNS = *v4dhcpParms.useDnsServers; 1246 } 1247 else if (v6dhcpParms.useDnsServers) 1248 { 1249 nextDNS = *v6dhcpParms.useDnsServers; 1250 } 1251 else 1252 { 1253 nextDNS = ethData.dnsEnabled; 1254 } 1255 1256 bool nextNTP{}; 1257 if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers) 1258 { 1259 if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers) 1260 { 1261 messages::generalError(asyncResp->res); 1262 return; 1263 } 1264 nextNTP = *v4dhcpParms.useNtpServers; 1265 } 1266 else if (v4dhcpParms.useNtpServers) 1267 { 1268 nextNTP = *v4dhcpParms.useNtpServers; 1269 } 1270 else if (v6dhcpParms.useNtpServers) 1271 { 1272 nextNTP = *v6dhcpParms.useNtpServers; 1273 } 1274 else 1275 { 1276 nextNTP = ethData.ntpEnabled; 1277 } 1278 1279 bool nextUseDomain{}; 1280 if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName) 1281 { 1282 if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName) 1283 { 1284 messages::generalError(asyncResp->res); 1285 return; 1286 } 1287 nextUseDomain = *v4dhcpParms.useDomainName; 1288 } 1289 else if (v4dhcpParms.useDomainName) 1290 { 1291 nextUseDomain = *v4dhcpParms.useDomainName; 1292 } 1293 else if (v6dhcpParms.useDomainName) 1294 { 1295 nextUseDomain = *v6dhcpParms.useDomainName; 1296 } 1297 else 1298 { 1299 nextUseDomain = ethData.hostNameEnabled; 1300 } 1301 1302 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1303 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1304 asyncResp); 1305 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1306 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1307 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1308 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1309 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1310 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1311 } 1312 1313 inline boost::container::flat_set<IPv4AddressData>::const_iterator 1314 getNextStaticIpEntry( 1315 const boost::container::flat_set<IPv4AddressData>::const_iterator& head, 1316 const boost::container::flat_set<IPv4AddressData>::const_iterator& end) 1317 { 1318 return std::find_if(head, end, [](const IPv4AddressData& value) { 1319 return value.origin == "Static"; 1320 }); 1321 } 1322 1323 inline boost::container::flat_set<IPv6AddressData>::const_iterator 1324 getNextStaticIpEntry( 1325 const boost::container::flat_set<IPv6AddressData>::const_iterator& head, 1326 const boost::container::flat_set<IPv6AddressData>::const_iterator& end) 1327 { 1328 return std::find_if(head, end, [](const IPv6AddressData& value) { 1329 return value.origin == "Static"; 1330 }); 1331 } 1332 1333 inline void handleIPv4StaticPatch( 1334 const std::string& ifaceId, nlohmann::json& input, 1335 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1336 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1337 { 1338 if ((!input.is_array()) || input.empty()) 1339 { 1340 messages::propertyValueTypeError( 1341 asyncResp->res, 1342 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1343 "IPv4StaticAddresses"); 1344 return; 1345 } 1346 1347 unsigned entryIdx = 1; 1348 // Find the first static IP address currently active on the NIC and 1349 // match it to the first JSON element in the IPv4StaticAddresses array. 1350 // Match each subsequent JSON element to the next static IP programmed 1351 // into the NIC. 1352 boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry = 1353 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1354 1355 for (nlohmann::json& thisJson : input) 1356 { 1357 std::string pathString = 1358 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1359 1360 if (!thisJson.is_null() && !thisJson.empty()) 1361 { 1362 std::optional<std::string> address; 1363 std::optional<std::string> subnetMask; 1364 std::optional<std::string> gateway; 1365 1366 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1367 address, "SubnetMask", subnetMask, 1368 "Gateway", gateway)) 1369 { 1370 messages::propertyValueFormatError( 1371 asyncResp->res, 1372 thisJson.dump(2, ' ', true, 1373 nlohmann::json::error_handler_t::replace), 1374 pathString); 1375 return; 1376 } 1377 1378 // Find the address/subnet/gateway values. Any values that are 1379 // not explicitly provided are assumed to be unmodified from the 1380 // current state of the interface. Merge existing state into the 1381 // current request. 1382 const std::string* addr = nullptr; 1383 const std::string* gw = nullptr; 1384 uint8_t prefixLength = 0; 1385 bool errorInEntry = false; 1386 if (address) 1387 { 1388 if (ipv4VerifyIpAndGetBitcount(*address)) 1389 { 1390 addr = &(*address); 1391 } 1392 else 1393 { 1394 messages::propertyValueFormatError(asyncResp->res, *address, 1395 pathString + "/Address"); 1396 errorInEntry = true; 1397 } 1398 } 1399 else if (nicIpEntry != ipv4Data.cend()) 1400 { 1401 addr = &(nicIpEntry->address); 1402 } 1403 else 1404 { 1405 messages::propertyMissing(asyncResp->res, 1406 pathString + "/Address"); 1407 errorInEntry = true; 1408 } 1409 1410 if (subnetMask) 1411 { 1412 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 1413 { 1414 messages::propertyValueFormatError( 1415 asyncResp->res, *subnetMask, 1416 pathString + "/SubnetMask"); 1417 errorInEntry = true; 1418 } 1419 } 1420 else if (nicIpEntry != ipv4Data.cend()) 1421 { 1422 if (!ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1423 &prefixLength)) 1424 { 1425 messages::propertyValueFormatError( 1426 asyncResp->res, nicIpEntry->netmask, 1427 pathString + "/SubnetMask"); 1428 errorInEntry = true; 1429 } 1430 } 1431 else 1432 { 1433 messages::propertyMissing(asyncResp->res, 1434 pathString + "/SubnetMask"); 1435 errorInEntry = true; 1436 } 1437 1438 if (gateway) 1439 { 1440 if (ipv4VerifyIpAndGetBitcount(*gateway)) 1441 { 1442 gw = &(*gateway); 1443 } 1444 else 1445 { 1446 messages::propertyValueFormatError(asyncResp->res, *gateway, 1447 pathString + "/Gateway"); 1448 errorInEntry = true; 1449 } 1450 } 1451 else if (nicIpEntry != ipv4Data.cend()) 1452 { 1453 gw = &nicIpEntry->gateway; 1454 } 1455 else 1456 { 1457 messages::propertyMissing(asyncResp->res, 1458 pathString + "/Gateway"); 1459 errorInEntry = true; 1460 } 1461 1462 if (errorInEntry) 1463 { 1464 return; 1465 } 1466 1467 if (nicIpEntry != ipv4Data.cend()) 1468 { 1469 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw, 1470 *addr, asyncResp); 1471 nicIpEntry = 1472 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1473 } 1474 else 1475 { 1476 createIPv4(ifaceId, prefixLength, *gateway, *address, 1477 asyncResp); 1478 } 1479 entryIdx++; 1480 } 1481 else 1482 { 1483 if (nicIpEntry == ipv4Data.cend()) 1484 { 1485 // Requesting a DELETE/DO NOT MODIFY action for an item 1486 // that isn't present on the eth(n) interface. Input JSON is 1487 // in error, so bail out. 1488 if (thisJson.is_null()) 1489 { 1490 messages::resourceCannotBeDeleted(asyncResp->res); 1491 return; 1492 } 1493 messages::propertyValueFormatError( 1494 asyncResp->res, 1495 thisJson.dump(2, ' ', true, 1496 nlohmann::json::error_handler_t::replace), 1497 pathString); 1498 return; 1499 } 1500 1501 if (thisJson.is_null()) 1502 { 1503 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp); 1504 } 1505 if (nicIpEntry != ipv4Data.cend()) 1506 { 1507 nicIpEntry = 1508 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1509 } 1510 entryIdx++; 1511 } 1512 } 1513 } 1514 1515 inline void handleStaticNameServersPatch( 1516 const std::string& ifaceId, 1517 const std::vector<std::string>& updatedStaticNameServers, 1518 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1519 { 1520 crow::connections::systemBus->async_method_call( 1521 [asyncResp](const boost::system::error_code ec) { 1522 if (ec) 1523 { 1524 messages::internalError(asyncResp->res); 1525 return; 1526 } 1527 }, 1528 "xyz.openbmc_project.Network", 1529 "/xyz/openbmc_project/network/" + ifaceId, 1530 "org.freedesktop.DBus.Properties", "Set", 1531 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1532 dbus::utility::DbusVariantType{updatedStaticNameServers}); 1533 } 1534 1535 inline void handleIPv6StaticAddressesPatch( 1536 const std::string& ifaceId, const nlohmann::json& input, 1537 const boost::container::flat_set<IPv6AddressData>& ipv6Data, 1538 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1539 { 1540 if (!input.is_array() || input.empty()) 1541 { 1542 messages::propertyValueTypeError( 1543 asyncResp->res, 1544 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1545 "IPv6StaticAddresses"); 1546 return; 1547 } 1548 size_t entryIdx = 1; 1549 boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry = 1550 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1551 for (const nlohmann::json& thisJson : input) 1552 { 1553 std::string pathString = 1554 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1555 1556 if (!thisJson.is_null() && !thisJson.empty()) 1557 { 1558 std::optional<std::string> address; 1559 std::optional<uint8_t> prefixLength; 1560 nlohmann::json thisJsonCopy = thisJson; 1561 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1562 address, "PrefixLength", prefixLength)) 1563 { 1564 messages::propertyValueFormatError( 1565 asyncResp->res, 1566 thisJson.dump(2, ' ', true, 1567 nlohmann::json::error_handler_t::replace), 1568 pathString); 1569 return; 1570 } 1571 1572 const std::string* addr = nullptr; 1573 uint8_t prefix = 0; 1574 1575 // Find the address and prefixLength values. Any values that are 1576 // not explicitly provided are assumed to be unmodified from the 1577 // current state of the interface. Merge existing state into the 1578 // current request. 1579 if (address) 1580 { 1581 addr = &(*address); 1582 } 1583 else if (nicIpEntry != ipv6Data.end()) 1584 { 1585 addr = &(nicIpEntry->address); 1586 } 1587 else 1588 { 1589 messages::propertyMissing(asyncResp->res, 1590 pathString + "/Address"); 1591 return; 1592 } 1593 1594 if (prefixLength) 1595 { 1596 prefix = *prefixLength; 1597 } 1598 else if (nicIpEntry != ipv6Data.end()) 1599 { 1600 prefix = nicIpEntry->prefixLength; 1601 } 1602 else 1603 { 1604 messages::propertyMissing(asyncResp->res, 1605 pathString + "/PrefixLength"); 1606 return; 1607 } 1608 1609 if (nicIpEntry != ipv6Data.end()) 1610 { 1611 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr, 1612 asyncResp); 1613 nicIpEntry = 1614 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1615 } 1616 else 1617 { 1618 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1619 } 1620 entryIdx++; 1621 } 1622 else 1623 { 1624 if (nicIpEntry == ipv6Data.end()) 1625 { 1626 // Requesting a DELETE/DO NOT MODIFY action for an item 1627 // that isn't present on the eth(n) interface. Input JSON is 1628 // in error, so bail out. 1629 if (thisJson.is_null()) 1630 { 1631 messages::resourceCannotBeDeleted(asyncResp->res); 1632 return; 1633 } 1634 messages::propertyValueFormatError( 1635 asyncResp->res, 1636 thisJson.dump(2, ' ', true, 1637 nlohmann::json::error_handler_t::replace), 1638 pathString); 1639 return; 1640 } 1641 1642 if (thisJson.is_null()) 1643 { 1644 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp); 1645 } 1646 if (nicIpEntry != ipv6Data.cend()) 1647 { 1648 nicIpEntry = 1649 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1650 } 1651 entryIdx++; 1652 } 1653 } 1654 } 1655 1656 inline void parseInterfaceData( 1657 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1658 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1659 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1660 const boost::container::flat_set<IPv6AddressData>& ipv6Data) 1661 { 1662 constexpr const std::array<const char*, 1> inventoryForEthernet = { 1663 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1664 1665 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1666 jsonResponse["Id"] = ifaceId; 1667 jsonResponse["@odata.id"] = 1668 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId; 1669 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1670 1671 auto health = std::make_shared<HealthPopulate>(asyncResp); 1672 1673 crow::connections::systemBus->async_method_call( 1674 [health](const boost::system::error_code ec, 1675 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1676 if (ec) 1677 { 1678 return; 1679 } 1680 1681 health->inventory = resp; 1682 }, 1683 "xyz.openbmc_project.ObjectMapper", 1684 "/xyz/openbmc_project/object_mapper", 1685 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0), 1686 inventoryForEthernet); 1687 1688 health->populate(); 1689 1690 if (ethData.nicEnabled) 1691 { 1692 jsonResponse["LinkStatus"] = "LinkUp"; 1693 jsonResponse["Status"]["State"] = "Enabled"; 1694 } 1695 else 1696 { 1697 jsonResponse["LinkStatus"] = "NoLink"; 1698 jsonResponse["Status"]["State"] = "Disabled"; 1699 } 1700 1701 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1702 jsonResponse["SpeedMbps"] = ethData.speed; 1703 jsonResponse["MTUSize"] = ethData.mtuSize; 1704 jsonResponse["MACAddress"] = ethData.macAddress; 1705 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1706 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1707 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled; 1708 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled; 1709 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled; 1710 1711 jsonResponse["DHCPv6"]["OperatingMode"] = 1712 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful" 1713 : "Disabled"; 1714 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled; 1715 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled; 1716 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled; 1717 1718 if (!ethData.hostName.empty()) 1719 { 1720 jsonResponse["HostName"] = ethData.hostName; 1721 1722 // When domain name is empty then it means, that it is a network 1723 // without domain names, and the host name itself must be treated as 1724 // FQDN 1725 std::string fqdn = ethData.hostName; 1726 if (!ethData.domainnames.empty()) 1727 { 1728 fqdn += "." + ethData.domainnames[0]; 1729 } 1730 jsonResponse["FQDN"] = fqdn; 1731 } 1732 1733 jsonResponse["VLANs"] = { 1734 {"@odata.id", 1735 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId + "/VLANs"}}; 1736 1737 jsonResponse["NameServers"] = ethData.nameServers; 1738 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1739 1740 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1741 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1742 ipv4Array = nlohmann::json::array(); 1743 ipv4StaticArray = nlohmann::json::array(); 1744 for (const auto& ipv4Config : ipv4Data) 1745 { 1746 1747 std::string gatewayStr = ipv4Config.gateway; 1748 if (gatewayStr.empty()) 1749 { 1750 gatewayStr = "0.0.0.0"; 1751 } 1752 nlohmann::json::object_t ipv4; 1753 ipv4["AddressOrigin"] = ipv4Config.origin; 1754 ipv4["SubnetMask"] = ipv4Config.netmask; 1755 ipv4["Address"] = ipv4Config.address; 1756 ipv4["Gateway"] = gatewayStr; 1757 1758 if (ipv4Config.origin == "Static") 1759 { 1760 ipv4StaticArray.push_back(ipv4); 1761 } 1762 1763 ipv4Array.push_back(std::move(ipv4)); 1764 } 1765 1766 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1767 if (ipv6GatewayStr.empty()) 1768 { 1769 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1770 } 1771 1772 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1773 1774 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1775 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1776 ipv6Array = nlohmann::json::array(); 1777 ipv6StaticArray = nlohmann::json::array(); 1778 nlohmann::json& ipv6AddrPolicyTable = 1779 jsonResponse["IPv6AddressPolicyTable"]; 1780 ipv6AddrPolicyTable = nlohmann::json::array(); 1781 for (const auto& ipv6Config : ipv6Data) 1782 { 1783 nlohmann::json::object_t ipv6; 1784 ipv6["Address"] = ipv6Config.address; 1785 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1786 ipv6["AddressOrigin"] = ipv6Config.origin; 1787 ipv6["AddressState"] = nullptr; 1788 ipv6Array.push_back(std::move(ipv6)); 1789 if (ipv6Config.origin == "Static") 1790 { 1791 nlohmann::json::object_t ipv6Static; 1792 ipv6Static["Address"] = ipv6Config.address; 1793 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1794 ipv6StaticArray.push_back(std::move(ipv6Static)); 1795 } 1796 } 1797 } 1798 1799 inline void parseInterfaceData(nlohmann::json& jsonResponse, 1800 const std::string& parentIfaceId, 1801 const std::string& ifaceId, 1802 const EthernetInterfaceData& ethData) 1803 { 1804 // Fill out obvious data... 1805 jsonResponse["Id"] = ifaceId; 1806 jsonResponse["@odata.id"] = "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1807 parentIfaceId + "/VLANs/" + ifaceId; 1808 1809 jsonResponse["VLANEnable"] = true; 1810 if (ethData.vlanId) 1811 { 1812 jsonResponse["VLANId"] = *ethData.vlanId; 1813 } 1814 } 1815 1816 inline bool verifyNames(const std::string& parent, const std::string& iface) 1817 { 1818 return boost::starts_with(iface, parent + "_"); 1819 } 1820 1821 inline void requestEthernetInterfacesRoutes(App& app) 1822 { 1823 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1824 .privileges(redfish::privileges::getEthernetInterfaceCollection) 1825 .methods(boost::beast::http::verb::get)( 1826 [&app](const crow::Request& req, 1827 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1828 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1829 { 1830 return; 1831 } 1832 1833 asyncResp->res.jsonValue["@odata.type"] = 1834 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1835 asyncResp->res.jsonValue["@odata.id"] = 1836 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1837 asyncResp->res.jsonValue["Name"] = 1838 "Ethernet Network Interface Collection"; 1839 asyncResp->res.jsonValue["Description"] = 1840 "Collection of EthernetInterfaces for this Manager"; 1841 1842 // Get eth interface list, and call the below callback for JSON 1843 // preparation 1844 getEthernetIfaceList( 1845 [asyncResp]( 1846 const bool& success, 1847 const boost::container::flat_set<std::string>& ifaceList) { 1848 if (!success) 1849 { 1850 messages::internalError(asyncResp->res); 1851 return; 1852 } 1853 1854 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 1855 ifaceArray = nlohmann::json::array(); 1856 std::string tag = "_"; 1857 for (const std::string& ifaceItem : ifaceList) 1858 { 1859 std::size_t found = ifaceItem.find(tag); 1860 if (found == std::string::npos) 1861 { 1862 nlohmann::json::object_t iface; 1863 iface["@odata.id"] = 1864 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1865 ifaceItem; 1866 ifaceArray.push_back(std::move(iface)); 1867 } 1868 } 1869 1870 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 1871 asyncResp->res.jsonValue["@odata.id"] = 1872 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1873 }); 1874 }); 1875 1876 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1877 .privileges(redfish::privileges::getEthernetInterface) 1878 .methods(boost::beast::http::verb::get)( 1879 [&app](const crow::Request& req, 1880 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1881 const std::string& ifaceId) { 1882 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1883 { 1884 return; 1885 } 1886 getEthernetIfaceData( 1887 ifaceId, 1888 [asyncResp, ifaceId]( 1889 const bool& success, const EthernetInterfaceData& ethData, 1890 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1891 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1892 if (!success) 1893 { 1894 // TODO(Pawel)consider distinguish between non 1895 // existing object, and other errors 1896 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1897 ifaceId); 1898 return; 1899 } 1900 1901 asyncResp->res.jsonValue["@odata.type"] = 1902 "#EthernetInterface.v1_4_1.EthernetInterface"; 1903 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1904 asyncResp->res.jsonValue["Description"] = 1905 "Management Network Interface"; 1906 1907 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data); 1908 }); 1909 }); 1910 1911 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1912 .privileges(redfish::privileges::patchEthernetInterface) 1913 .methods(boost::beast::http::verb::patch)( 1914 [&app](const crow::Request& req, 1915 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1916 const std::string& ifaceId) { 1917 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 1918 { 1919 return; 1920 } 1921 std::optional<std::string> hostname; 1922 std::optional<std::string> fqdn; 1923 std::optional<std::string> macAddress; 1924 std::optional<std::string> ipv6DefaultGateway; 1925 std::optional<nlohmann::json> ipv4StaticAddresses; 1926 std::optional<nlohmann::json> ipv6StaticAddresses; 1927 std::optional<std::vector<std::string>> staticNameServers; 1928 std::optional<nlohmann::json> dhcpv4; 1929 std::optional<nlohmann::json> dhcpv6; 1930 std::optional<bool> interfaceEnabled; 1931 std::optional<size_t> mtuSize; 1932 DHCPParameters v4dhcpParms; 1933 DHCPParameters v6dhcpParms; 1934 1935 if (!json_util::readJsonPatch( 1936 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1937 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1938 macAddress, "StaticNameServers", staticNameServers, 1939 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1940 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1941 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled)) 1942 { 1943 return; 1944 } 1945 if (dhcpv4) 1946 { 1947 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 1948 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1949 v4dhcpParms.useDnsServers, "UseNTPServers", 1950 v4dhcpParms.useNtpServers, "UseDomainName", 1951 v4dhcpParms.useDomainName)) 1952 { 1953 return; 1954 } 1955 } 1956 1957 if (dhcpv6) 1958 { 1959 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 1960 v6dhcpParms.dhcpv6OperatingMode, 1961 "UseDNSServers", v6dhcpParms.useDnsServers, 1962 "UseNTPServers", v6dhcpParms.useNtpServers, 1963 "UseDomainName", 1964 v6dhcpParms.useDomainName)) 1965 { 1966 return; 1967 } 1968 } 1969 1970 // Get single eth interface data, and call the below callback 1971 // for JSON preparation 1972 getEthernetIfaceData( 1973 ifaceId, 1974 [asyncResp, ifaceId, hostname = std::move(hostname), 1975 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1976 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1977 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1978 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1979 staticNameServers = std::move(staticNameServers), 1980 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), 1981 mtuSize = mtuSize, v4dhcpParms = std::move(v4dhcpParms), 1982 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1983 const bool& success, const EthernetInterfaceData& ethData, 1984 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1985 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1986 if (!success) 1987 { 1988 // ... otherwise return error 1989 // TODO(Pawel)consider distinguish between non 1990 // existing object, and other errors 1991 messages::resourceNotFound(asyncResp->res, "Ethernet Interface", 1992 ifaceId); 1993 return; 1994 } 1995 1996 if (dhcpv4 || dhcpv6) 1997 { 1998 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 1999 asyncResp); 2000 } 2001 2002 if (hostname) 2003 { 2004 handleHostnamePatch(*hostname, asyncResp); 2005 } 2006 2007 if (fqdn) 2008 { 2009 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2010 } 2011 2012 if (macAddress) 2013 { 2014 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 2015 } 2016 2017 if (ipv4StaticAddresses) 2018 { 2019 // TODO(ed) for some reason the capture of 2020 // ipv4Addresses above is returning a const value, 2021 // not a non-const value. This doesn't really work 2022 // for us, as we need to be able to efficiently move 2023 // out the intermedia nlohmann::json objects. This 2024 // makes a copy of the structure, and operates on 2025 // that, but could be done more efficiently 2026 nlohmann::json ipv4Static = *ipv4StaticAddresses; 2027 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 2028 } 2029 2030 if (staticNameServers) 2031 { 2032 handleStaticNameServersPatch(ifaceId, *staticNameServers, 2033 asyncResp); 2034 } 2035 2036 if (ipv6DefaultGateway) 2037 { 2038 messages::propertyNotWritable(asyncResp->res, 2039 "IPv6DefaultGateway"); 2040 } 2041 2042 if (ipv6StaticAddresses) 2043 { 2044 const nlohmann::json& ipv6Static = *ipv6StaticAddresses; 2045 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data, 2046 asyncResp); 2047 } 2048 2049 if (interfaceEnabled) 2050 { 2051 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 2052 *interfaceEnabled, asyncResp); 2053 } 2054 2055 if (mtuSize) 2056 { 2057 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2058 } 2059 }); 2060 }); 2061 2062 BMCWEB_ROUTE( 2063 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2064 .privileges(redfish::privileges::getVLanNetworkInterface) 2065 .methods(boost::beast::http::verb::get)( 2066 [&app](const crow::Request& req, 2067 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2068 const std::string& parentIfaceId, 2069 const std::string& ifaceId) { 2070 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 2071 { 2072 return; 2073 } 2074 asyncResp->res.jsonValue["@odata.type"] = 2075 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2076 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 2077 2078 if (!verifyNames(parentIfaceId, ifaceId)) 2079 { 2080 return; 2081 } 2082 2083 // Get single eth interface data, and call the below callback 2084 // for JSON preparation 2085 getEthernetIfaceData( 2086 ifaceId, 2087 [asyncResp, parentIfaceId, 2088 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2089 const boost::container::flat_set<IPv4AddressData>&, 2090 const boost::container::flat_set<IPv6AddressData>&) { 2091 if (success && ethData.vlanId) 2092 { 2093 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 2094 ifaceId, ethData); 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 "VLAN Network Interface", ifaceId); 2103 } 2104 }); 2105 }); 2106 2107 BMCWEB_ROUTE( 2108 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2109 .privileges(redfish::privileges::patchVLanNetworkInterface) 2110 .methods(boost::beast::http::verb::patch)( 2111 [&app](const crow::Request& req, 2112 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2113 const std::string& parentIfaceId, 2114 const std::string& ifaceId) { 2115 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 2116 { 2117 return; 2118 } 2119 if (!verifyNames(parentIfaceId, ifaceId)) 2120 { 2121 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2122 ifaceId); 2123 return; 2124 } 2125 2126 std::optional<bool> vlanEnable; 2127 std::optional<uint32_t> vlanId; 2128 2129 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable", 2130 vlanEnable, "VLANId", vlanId)) 2131 { 2132 return; 2133 } 2134 2135 if (vlanId) 2136 { 2137 messages::propertyNotWritable(asyncResp->res, "VLANId"); 2138 return; 2139 } 2140 2141 // Get single eth interface data, and call the below callback 2142 // for JSON preparation 2143 getEthernetIfaceData( 2144 ifaceId, 2145 [asyncResp, parentIfaceId, ifaceId, vlanEnable]( 2146 const bool& success, const EthernetInterfaceData& ethData, 2147 const boost::container::flat_set<IPv4AddressData>&, 2148 const boost::container::flat_set<IPv6AddressData>&) { 2149 if (success && ethData.vlanId) 2150 { 2151 auto callback = 2152 [asyncResp](const boost::system::error_code ec) { 2153 if (ec) 2154 { 2155 messages::internalError(asyncResp->res); 2156 return; 2157 } 2158 }; 2159 2160 if (vlanEnable && !(*vlanEnable)) 2161 { 2162 BMCWEB_LOG_DEBUG << "vlanEnable is false. Deleting the " 2163 "vlan interface"; 2164 crow::connections::systemBus->async_method_call( 2165 std::move(callback), "xyz.openbmc_project.Network", 2166 std::string("/xyz/openbmc_project/network/") + ifaceId, 2167 "xyz.openbmc_project.Object.Delete", "Delete"); 2168 } 2169 } 2170 else 2171 { 2172 // TODO(Pawel)consider distinguish between non 2173 // existing object, and other errors 2174 messages::resourceNotFound(asyncResp->res, 2175 "VLAN Network Interface", ifaceId); 2176 return; 2177 } 2178 }); 2179 }); 2180 2181 BMCWEB_ROUTE( 2182 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2183 .privileges(redfish::privileges::deleteVLanNetworkInterface) 2184 .methods(boost::beast::http::verb::delete_)( 2185 [&app](const crow::Request& req, 2186 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2187 const std::string& parentIfaceId, 2188 const std::string& ifaceId) { 2189 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 2190 { 2191 return; 2192 } 2193 if (!verifyNames(parentIfaceId, ifaceId)) 2194 { 2195 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2196 ifaceId); 2197 return; 2198 } 2199 2200 // Get single eth interface data, and call the below callback 2201 // for JSON preparation 2202 getEthernetIfaceData( 2203 ifaceId, 2204 [asyncResp, parentIfaceId, 2205 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2206 const boost::container::flat_set<IPv4AddressData>&, 2207 const boost::container::flat_set<IPv6AddressData>&) { 2208 if (success && ethData.vlanId) 2209 { 2210 auto callback = 2211 [asyncResp](const boost::system::error_code ec) { 2212 if (ec) 2213 { 2214 messages::internalError(asyncResp->res); 2215 } 2216 }; 2217 crow::connections::systemBus->async_method_call( 2218 std::move(callback), "xyz.openbmc_project.Network", 2219 std::string("/xyz/openbmc_project/network/") + ifaceId, 2220 "xyz.openbmc_project.Object.Delete", "Delete"); 2221 } 2222 else 2223 { 2224 // ... otherwise return error 2225 // TODO(Pawel)consider distinguish between non 2226 // existing object, and other errors 2227 messages::resourceNotFound(asyncResp->res, 2228 "VLAN Network Interface", ifaceId); 2229 } 2230 }); 2231 }); 2232 2233 BMCWEB_ROUTE(app, 2234 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2235 2236 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2237 .methods(boost::beast::http::verb::get)( 2238 [&app](const crow::Request& req, 2239 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2240 const std::string& rootInterfaceName) { 2241 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 2242 { 2243 return; 2244 } 2245 // Get eth interface list, and call the below callback for JSON 2246 // preparation 2247 getEthernetIfaceList( 2248 [asyncResp, rootInterfaceName]( 2249 const bool& success, 2250 const boost::container::flat_set<std::string>& ifaceList) { 2251 if (!success) 2252 { 2253 messages::internalError(asyncResp->res); 2254 return; 2255 } 2256 2257 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2258 { 2259 messages::resourceNotFound(asyncResp->res, 2260 "VLanNetworkInterfaceCollection", 2261 rootInterfaceName); 2262 return; 2263 } 2264 2265 asyncResp->res.jsonValue["@odata.type"] = 2266 "#VLanNetworkInterfaceCollection." 2267 "VLanNetworkInterfaceCollection"; 2268 asyncResp->res.jsonValue["Name"] = 2269 "VLAN Network Interface Collection"; 2270 2271 nlohmann::json ifaceArray = nlohmann::json::array(); 2272 2273 for (const std::string& ifaceItem : ifaceList) 2274 { 2275 if (boost::starts_with(ifaceItem, rootInterfaceName + "_")) 2276 { 2277 std::string path = 2278 "/redfish/v1/Managers/bmc/EthernetInterfaces/"; 2279 path += rootInterfaceName; 2280 path += "/VLANs/"; 2281 path += ifaceItem; 2282 nlohmann::json::object_t iface; 2283 iface["@odata.id"] = std::move(path); 2284 ifaceArray.push_back(std::move(iface)); 2285 } 2286 } 2287 2288 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2289 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2290 asyncResp->res.jsonValue["@odata.id"] = 2291 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2292 rootInterfaceName + "/VLANs"; 2293 }); 2294 }); 2295 2296 BMCWEB_ROUTE(app, 2297 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2298 .privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2299 .methods(boost::beast::http::verb::post)( 2300 [&app](const crow::Request& req, 2301 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2302 const std::string& rootInterfaceName) { 2303 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 2304 { 2305 return; 2306 } 2307 bool vlanEnable = false; 2308 uint32_t vlanId = 0; 2309 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId, 2310 "VLANEnable", vlanEnable)) 2311 { 2312 return; 2313 } 2314 // Need both vlanId and vlanEnable to service this request 2315 if (vlanId == 0U) 2316 { 2317 messages::propertyMissing(asyncResp->res, "VLANId"); 2318 } 2319 if (!vlanEnable) 2320 { 2321 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2322 } 2323 if (static_cast<bool>(vlanId) ^ vlanEnable) 2324 { 2325 return; 2326 } 2327 2328 auto callback = [asyncResp](const boost::system::error_code ec) { 2329 if (ec) 2330 { 2331 // TODO(ed) make more consistent error messages 2332 // based on phosphor-network responses 2333 messages::internalError(asyncResp->res); 2334 return; 2335 } 2336 messages::created(asyncResp->res); 2337 }; 2338 crow::connections::systemBus->async_method_call( 2339 std::move(callback), "xyz.openbmc_project.Network", 2340 "/xyz/openbmc_project/network", 2341 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2342 rootInterfaceName, vlanId); 2343 }); 2344 } 2345 2346 } // namespace redfish 2347