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/algorithm/string/classification.hpp> 20 #include <boost/algorithm/string/split.hpp> 21 #include <boost/container/flat_set.hpp> 22 #include <dbus_singleton.hpp> 23 #include <dbus_utility.hpp> 24 #include <error_messages.hpp> 25 #include <query.hpp> 26 #include <registries/privilege_registry.hpp> 27 #include <utils/json_utils.hpp> 28 29 #include <optional> 30 #include <regex> 31 32 namespace redfish 33 { 34 35 enum class LinkType 36 { 37 Local, 38 Global 39 }; 40 41 /** 42 * Structure for keeping IPv4 data required by Redfish 43 */ 44 struct IPv4AddressData 45 { 46 std::string id; 47 std::string address; 48 std::string domain; 49 std::string gateway; 50 std::string netmask; 51 std::string origin; 52 LinkType linktype; 53 bool isActive; 54 55 bool operator<(const IPv4AddressData& obj) const 56 { 57 return id < obj.id; 58 } 59 }; 60 61 /** 62 * Structure for keeping IPv6 data required by Redfish 63 */ 64 struct IPv6AddressData 65 { 66 std::string id; 67 std::string address; 68 std::string origin; 69 uint8_t prefixLength; 70 71 bool operator<(const IPv6AddressData& obj) const 72 { 73 return id < obj.id; 74 } 75 }; 76 /** 77 * Structure for keeping basic single Ethernet Interface information 78 * available from DBus 79 */ 80 struct EthernetInterfaceData 81 { 82 uint32_t speed; 83 size_t mtuSize; 84 bool autoNeg; 85 bool dnsEnabled; 86 bool ntpEnabled; 87 bool hostNameEnabled; 88 bool linkUp; 89 bool nicEnabled; 90 std::string dhcpEnabled; 91 std::string operatingMode; 92 std::string hostName; 93 std::string defaultGateway; 94 std::string ipv6DefaultGateway; 95 std::string macAddress; 96 std::optional<uint32_t> vlanId; 97 std::vector<std::string> nameServers; 98 std::vector<std::string> staticNameServers; 99 std::vector<std::string> domainnames; 100 }; 101 102 struct DHCPParameters 103 { 104 std::optional<bool> dhcpv4Enabled; 105 std::optional<bool> useDnsServers; 106 std::optional<bool> useNtpServers; 107 std::optional<bool> useDomainName; 108 std::optional<std::string> dhcpv6OperatingMode; 109 }; 110 111 // Helper function that changes bits netmask notation (i.e. /24) 112 // into full dot notation 113 inline std::string getNetmask(unsigned int bits) 114 { 115 uint32_t value = 0xffffffff << (32 - bits); 116 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 117 std::to_string((value >> 16) & 0xff) + "." + 118 std::to_string((value >> 8) & 0xff) + "." + 119 std::to_string(value & 0xff); 120 return netmask; 121 } 122 123 inline bool translateDhcpEnabledToBool(const std::string& inputDHCP, 124 bool isIPv4) 125 { 126 if (isIPv4) 127 { 128 return ( 129 (inputDHCP == 130 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || 131 (inputDHCP == 132 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 133 } 134 return ((inputDHCP == 135 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || 136 (inputDHCP == 137 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 138 } 139 140 inline std::string getDhcpEnabledEnumeration(bool isIPv4, bool isIPv6) 141 { 142 if (isIPv4 && isIPv6) 143 { 144 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; 145 } 146 if (isIPv4) 147 { 148 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; 149 } 150 if (isIPv6) 151 { 152 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; 153 } 154 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; 155 } 156 157 inline std::string 158 translateAddressOriginDbusToRedfish(const std::string& inputOrigin, 159 bool isIPv4) 160 { 161 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 162 { 163 return "Static"; 164 } 165 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 166 { 167 if (isIPv4) 168 { 169 return "IPv4LinkLocal"; 170 } 171 return "LinkLocal"; 172 } 173 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 174 { 175 if (isIPv4) 176 { 177 return "DHCP"; 178 } 179 return "DHCPv6"; 180 } 181 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 182 { 183 return "SLAAC"; 184 } 185 return ""; 186 } 187 188 inline bool extractEthernetInterfaceData( 189 const std::string& ethifaceId, 190 const dbus::utility::ManagedObjectType& dbusData, 191 EthernetInterfaceData& ethData) 192 { 193 bool idFound = false; 194 for (const auto& objpath : dbusData) 195 { 196 for (const auto& ifacePair : objpath.second) 197 { 198 if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId) 199 { 200 idFound = true; 201 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 202 { 203 for (const auto& propertyPair : ifacePair.second) 204 { 205 if (propertyPair.first == "MACAddress") 206 { 207 const std::string* mac = 208 std::get_if<std::string>(&propertyPair.second); 209 if (mac != nullptr) 210 { 211 ethData.macAddress = *mac; 212 } 213 } 214 } 215 } 216 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 217 { 218 for (const auto& propertyPair : ifacePair.second) 219 { 220 if (propertyPair.first == "Id") 221 { 222 const uint32_t* id = 223 std::get_if<uint32_t>(&propertyPair.second); 224 if (id != nullptr) 225 { 226 ethData.vlanId = *id; 227 } 228 } 229 } 230 } 231 else if (ifacePair.first == 232 "xyz.openbmc_project.Network.EthernetInterface") 233 { 234 for (const auto& propertyPair : ifacePair.second) 235 { 236 if (propertyPair.first == "AutoNeg") 237 { 238 const bool* autoNeg = 239 std::get_if<bool>(&propertyPair.second); 240 if (autoNeg != nullptr) 241 { 242 ethData.autoNeg = *autoNeg; 243 } 244 } 245 else if (propertyPair.first == "Speed") 246 { 247 const uint32_t* speed = 248 std::get_if<uint32_t>(&propertyPair.second); 249 if (speed != nullptr) 250 { 251 ethData.speed = *speed; 252 } 253 } 254 else if (propertyPair.first == "MTU") 255 { 256 const uint32_t* mtuSize = 257 std::get_if<uint32_t>(&propertyPair.second); 258 if (mtuSize != nullptr) 259 { 260 ethData.mtuSize = *mtuSize; 261 } 262 } 263 else if (propertyPair.first == "LinkUp") 264 { 265 const bool* linkUp = 266 std::get_if<bool>(&propertyPair.second); 267 if (linkUp != nullptr) 268 { 269 ethData.linkUp = *linkUp; 270 } 271 } 272 else if (propertyPair.first == "NICEnabled") 273 { 274 const bool* nicEnabled = 275 std::get_if<bool>(&propertyPair.second); 276 if (nicEnabled != nullptr) 277 { 278 ethData.nicEnabled = *nicEnabled; 279 } 280 } 281 else if (propertyPair.first == "Nameservers") 282 { 283 const std::vector<std::string>* nameservers = 284 std::get_if<std::vector<std::string>>( 285 &propertyPair.second); 286 if (nameservers != nullptr) 287 { 288 ethData.nameServers = *nameservers; 289 } 290 } 291 else if (propertyPair.first == "StaticNameServers") 292 { 293 const std::vector<std::string>* staticNameServers = 294 std::get_if<std::vector<std::string>>( 295 &propertyPair.second); 296 if (staticNameServers != nullptr) 297 { 298 ethData.staticNameServers = *staticNameServers; 299 } 300 } 301 else if (propertyPair.first == "DHCPEnabled") 302 { 303 const std::string* dhcpEnabled = 304 std::get_if<std::string>(&propertyPair.second); 305 if (dhcpEnabled != nullptr) 306 { 307 ethData.dhcpEnabled = *dhcpEnabled; 308 } 309 } 310 else if (propertyPair.first == "DomainName") 311 { 312 const std::vector<std::string>* domainNames = 313 std::get_if<std::vector<std::string>>( 314 &propertyPair.second); 315 if (domainNames != nullptr) 316 { 317 ethData.domainnames = *domainNames; 318 } 319 } 320 else if (propertyPair.first == "DefaultGateway") 321 { 322 const std::string* defaultGateway = 323 std::get_if<std::string>(&propertyPair.second); 324 if (defaultGateway != nullptr) 325 { 326 std::string defaultGatewayStr = *defaultGateway; 327 if (defaultGatewayStr.empty()) 328 { 329 ethData.defaultGateway = "0.0.0.0"; 330 } 331 else 332 { 333 ethData.defaultGateway = defaultGatewayStr; 334 } 335 } 336 } 337 else if (propertyPair.first == "DefaultGateway6") 338 { 339 const std::string* defaultGateway6 = 340 std::get_if<std::string>(&propertyPair.second); 341 if (defaultGateway6 != nullptr) 342 { 343 std::string defaultGateway6Str = 344 *defaultGateway6; 345 if (defaultGateway6Str.empty()) 346 { 347 ethData.ipv6DefaultGateway = 348 "0:0:0:0:0:0:0:0"; 349 } 350 else 351 { 352 ethData.ipv6DefaultGateway = 353 defaultGateway6Str; 354 } 355 } 356 } 357 } 358 } 359 } 360 361 if (objpath.first == "/xyz/openbmc_project/network/config/dhcp") 362 { 363 if (ifacePair.first == 364 "xyz.openbmc_project.Network.DHCPConfiguration") 365 { 366 for (const auto& propertyPair : ifacePair.second) 367 { 368 if (propertyPair.first == "DNSEnabled") 369 { 370 const bool* dnsEnabled = 371 std::get_if<bool>(&propertyPair.second); 372 if (dnsEnabled != nullptr) 373 { 374 ethData.dnsEnabled = *dnsEnabled; 375 } 376 } 377 else if (propertyPair.first == "NTPEnabled") 378 { 379 const bool* ntpEnabled = 380 std::get_if<bool>(&propertyPair.second); 381 if (ntpEnabled != nullptr) 382 { 383 ethData.ntpEnabled = *ntpEnabled; 384 } 385 } 386 else if (propertyPair.first == "HostNameEnabled") 387 { 388 const bool* hostNameEnabled = 389 std::get_if<bool>(&propertyPair.second); 390 if (hostNameEnabled != nullptr) 391 { 392 ethData.hostNameEnabled = *hostNameEnabled; 393 } 394 } 395 } 396 } 397 } 398 // System configuration shows up in the global namespace, so no need 399 // to check eth number 400 if (ifacePair.first == 401 "xyz.openbmc_project.Network.SystemConfiguration") 402 { 403 for (const auto& propertyPair : ifacePair.second) 404 { 405 if (propertyPair.first == "HostName") 406 { 407 const std::string* hostname = 408 std::get_if<std::string>(&propertyPair.second); 409 if (hostname != nullptr) 410 { 411 ethData.hostName = *hostname; 412 } 413 } 414 } 415 } 416 } 417 } 418 return idFound; 419 } 420 421 // Helper function that extracts data for single ethernet ipv6 address 422 inline void 423 extractIPV6Data(const std::string& ethifaceId, 424 const dbus::utility::ManagedObjectType& dbusData, 425 boost::container::flat_set<IPv6AddressData>& ipv6Config) 426 { 427 const std::string ipv6PathStart = 428 "/xyz/openbmc_project/network/" + ethifaceId + "/ipv6/"; 429 430 // Since there might be several IPv6 configurations aligned with 431 // single ethernet interface, loop over all of them 432 for (const auto& objpath : dbusData) 433 { 434 // Check if proper pattern for object path appears 435 if (objpath.first.str.starts_with(ipv6PathStart)) 436 { 437 for (const auto& interface : objpath.second) 438 { 439 if (interface.first == "xyz.openbmc_project.Network.IP") 440 { 441 // Instance IPv6AddressData structure, and set as 442 // appropriate 443 std::pair< 444 boost::container::flat_set<IPv6AddressData>::iterator, 445 bool> 446 it = ipv6Config.insert(IPv6AddressData{}); 447 IPv6AddressData& ipv6Address = *it.first; 448 ipv6Address.id = 449 objpath.first.str.substr(ipv6PathStart.size()); 450 for (const auto& property : interface.second) 451 { 452 if (property.first == "Address") 453 { 454 const std::string* address = 455 std::get_if<std::string>(&property.second); 456 if (address != nullptr) 457 { 458 ipv6Address.address = *address; 459 } 460 } 461 else if (property.first == "Origin") 462 { 463 const std::string* origin = 464 std::get_if<std::string>(&property.second); 465 if (origin != nullptr) 466 { 467 ipv6Address.origin = 468 translateAddressOriginDbusToRedfish(*origin, 469 false); 470 } 471 } 472 else if (property.first == "PrefixLength") 473 { 474 const uint8_t* prefix = 475 std::get_if<uint8_t>(&property.second); 476 if (prefix != nullptr) 477 { 478 ipv6Address.prefixLength = *prefix; 479 } 480 } 481 else if (property.first == "Type" || 482 property.first == "Gateway") 483 { 484 // Type & Gateway is not used 485 } 486 else 487 { 488 BMCWEB_LOG_ERROR 489 << "Got extra property: " << property.first 490 << " on the " << objpath.first.str << " object"; 491 } 492 } 493 } 494 } 495 } 496 } 497 } 498 499 // Helper function that extracts data for single ethernet ipv4 address 500 inline void 501 extractIPData(const std::string& ethifaceId, 502 const dbus::utility::ManagedObjectType& dbusData, 503 boost::container::flat_set<IPv4AddressData>& ipv4Config) 504 { 505 const std::string ipv4PathStart = 506 "/xyz/openbmc_project/network/" + ethifaceId + "/ipv4/"; 507 508 // Since there might be several IPv4 configurations aligned with 509 // single ethernet interface, loop over all of them 510 for (const auto& objpath : dbusData) 511 { 512 // Check if proper pattern for object path appears 513 if (objpath.first.str.starts_with(ipv4PathStart)) 514 { 515 for (const auto& interface : objpath.second) 516 { 517 if (interface.first == "xyz.openbmc_project.Network.IP") 518 { 519 // Instance IPv4AddressData structure, and set as 520 // appropriate 521 std::pair< 522 boost::container::flat_set<IPv4AddressData>::iterator, 523 bool> 524 it = ipv4Config.insert(IPv4AddressData{}); 525 IPv4AddressData& ipv4Address = *it.first; 526 ipv4Address.id = 527 objpath.first.str.substr(ipv4PathStart.size()); 528 for (const auto& property : interface.second) 529 { 530 if (property.first == "Address") 531 { 532 const std::string* address = 533 std::get_if<std::string>(&property.second); 534 if (address != nullptr) 535 { 536 ipv4Address.address = *address; 537 } 538 } 539 else if (property.first == "Origin") 540 { 541 const std::string* origin = 542 std::get_if<std::string>(&property.second); 543 if (origin != nullptr) 544 { 545 ipv4Address.origin = 546 translateAddressOriginDbusToRedfish(*origin, 547 true); 548 } 549 } 550 else if (property.first == "PrefixLength") 551 { 552 const uint8_t* mask = 553 std::get_if<uint8_t>(&property.second); 554 if (mask != nullptr) 555 { 556 // convert it to the string 557 ipv4Address.netmask = getNetmask(*mask); 558 } 559 } 560 else if (property.first == "Type" || 561 property.first == "Gateway") 562 { 563 // Type & Gateway is not used 564 } 565 else 566 { 567 BMCWEB_LOG_ERROR 568 << "Got extra property: " << property.first 569 << " on the " << objpath.first.str << " object"; 570 } 571 } 572 // Check if given address is local, or global 573 ipv4Address.linktype = 574 ipv4Address.address.starts_with("169.254.") 575 ? LinkType::Local 576 : LinkType::Global; 577 } 578 } 579 } 580 } 581 } 582 583 /** 584 * @brief Helper function that verifies IP address to check if it is in 585 * proper format. If bits pointer is provided, also calculates active 586 * bit count for Subnet Mask. 587 * 588 * @param[in] ip IP that will be verified 589 * @param[out] bits Calculated mask in bits notation 590 * 591 * @return true in case of success, false otherwise 592 */ 593 inline bool ipv4VerifyIpAndGetBitcount(const std::string& ip, 594 uint8_t* bits = nullptr) 595 { 596 std::vector<std::string> bytesInMask; 597 598 boost::split(bytesInMask, ip, boost::is_any_of(".")); 599 600 static const constexpr int ipV4AddressSectionsCount = 4; 601 if (bytesInMask.size() != ipV4AddressSectionsCount) 602 { 603 return false; 604 } 605 606 if (bits != nullptr) 607 { 608 *bits = 0; 609 } 610 611 char* endPtr = nullptr; 612 long previousValue = 255; 613 bool firstZeroInByteHit = false; 614 for (const std::string& byte : bytesInMask) 615 { 616 if (byte.empty()) 617 { 618 return false; 619 } 620 621 // Use strtol instead of stroi to avoid exceptions 622 long value = std::strtol(byte.c_str(), &endPtr, 10); 623 624 // endPtr should point to the end of the string, otherwise given string 625 // is not 100% number 626 if (*endPtr != '\0') 627 { 628 return false; 629 } 630 631 // Value should be contained in byte 632 if (value < 0 || value > 255) 633 { 634 return false; 635 } 636 637 if (bits != nullptr) 638 { 639 // Mask has to be continuous between bytes 640 if (previousValue != 255 && value != 0) 641 { 642 return false; 643 } 644 645 // Mask has to be continuous inside bytes 646 firstZeroInByteHit = false; 647 648 // Count bits 649 for (long bitIdx = 7; bitIdx >= 0; bitIdx--) 650 { 651 if ((value & (1L << bitIdx)) != 0) 652 { 653 if (firstZeroInByteHit) 654 { 655 // Continuity not preserved 656 return false; 657 } 658 (*bits)++; 659 } 660 else 661 { 662 firstZeroInByteHit = true; 663 } 664 } 665 } 666 667 previousValue = value; 668 } 669 670 return true; 671 } 672 673 /** 674 * @brief Deletes given IPv4 interface 675 * 676 * @param[in] ifaceId Id of interface whose IP should be deleted 677 * @param[in] ipHash DBus Hash id of IP that should be deleted 678 * @param[io] asyncResp Response object that will be returned to client 679 * 680 * @return None 681 */ 682 inline void deleteIPv4(const std::string& ifaceId, const std::string& ipHash, 683 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 684 { 685 crow::connections::systemBus->async_method_call( 686 [asyncResp](const boost::system::error_code ec) { 687 if (ec) 688 { 689 messages::internalError(asyncResp->res); 690 } 691 }, 692 "xyz.openbmc_project.Network", 693 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 694 "xyz.openbmc_project.Object.Delete", "Delete"); 695 } 696 697 inline void updateIPv4DefaultGateway( 698 const std::string& ifaceId, const std::string& gateway, 699 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 700 { 701 crow::connections::systemBus->async_method_call( 702 [asyncResp](const boost::system::error_code ec) { 703 if (ec) 704 { 705 messages::internalError(asyncResp->res); 706 return; 707 } 708 asyncResp->res.result(boost::beast::http::status::no_content); 709 }, 710 "xyz.openbmc_project.Network", 711 "/xyz/openbmc_project/network/" + ifaceId, 712 "org.freedesktop.DBus.Properties", "Set", 713 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 714 dbus::utility::DbusVariantType(gateway)); 715 } 716 /** 717 * @brief Creates a static IPv4 entry 718 * 719 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 720 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 721 * @param[in] gateway IPv4 address of this interfaces gateway 722 * @param[in] address IPv4 address to assign to this interface 723 * @param[io] asyncResp Response object that will be returned to client 724 * 725 * @return None 726 */ 727 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 728 const std::string& gateway, const std::string& address, 729 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 730 { 731 auto createIpHandler = 732 [asyncResp, ifaceId, gateway](const boost::system::error_code ec) { 733 if (ec) 734 { 735 messages::internalError(asyncResp->res); 736 return; 737 } 738 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 739 }; 740 741 crow::connections::systemBus->async_method_call( 742 std::move(createIpHandler), "xyz.openbmc_project.Network", 743 "/xyz/openbmc_project/network/" + ifaceId, 744 "xyz.openbmc_project.Network.IP.Create", "IP", 745 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 746 gateway); 747 } 748 749 /** 750 * @brief Deletes the IPv4 entry for this interface and creates a replacement 751 * static IPv4 entry 752 * 753 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 754 * @param[in] id The unique hash entry identifying the DBus entry 755 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 756 * @param[in] gateway IPv4 address of this interfaces gateway 757 * @param[in] address IPv4 address to assign to this interface 758 * @param[io] asyncResp Response object that will be returned to client 759 * 760 * @return None 761 */ 762 inline void 763 deleteAndCreateIPv4(const std::string& ifaceId, const std::string& id, 764 uint8_t prefixLength, const std::string& gateway, 765 const std::string& address, 766 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 767 { 768 crow::connections::systemBus->async_method_call( 769 [asyncResp, ifaceId, address, prefixLength, 770 gateway](const boost::system::error_code ec) { 771 if (ec) 772 { 773 messages::internalError(asyncResp->res); 774 return; 775 } 776 777 crow::connections::systemBus->async_method_call( 778 [asyncResp, ifaceId, gateway](const boost::system::error_code ec2) { 779 if (ec2) 780 { 781 messages::internalError(asyncResp->res); 782 return; 783 } 784 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 785 }, 786 "xyz.openbmc_project.Network", 787 "/xyz/openbmc_project/network/" + ifaceId, 788 "xyz.openbmc_project.Network.IP.Create", "IP", 789 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 790 prefixLength, gateway); 791 }, 792 "xyz.openbmc_project.Network", 793 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 794 "xyz.openbmc_project.Object.Delete", "Delete"); 795 } 796 797 /** 798 * @brief Deletes given IPv6 799 * 800 * @param[in] ifaceId Id of interface whose IP should be deleted 801 * @param[in] ipHash DBus Hash id of IP that should be deleted 802 * @param[io] asyncResp Response object that will be returned to client 803 * 804 * @return None 805 */ 806 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash, 807 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 808 { 809 crow::connections::systemBus->async_method_call( 810 [asyncResp](const boost::system::error_code ec) { 811 if (ec) 812 { 813 messages::internalError(asyncResp->res); 814 } 815 }, 816 "xyz.openbmc_project.Network", 817 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 818 "xyz.openbmc_project.Object.Delete", "Delete"); 819 } 820 821 /** 822 * @brief Deletes the IPv6 entry for this interface and creates a replacement 823 * static IPv6 entry 824 * 825 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 826 * @param[in] id The unique hash entry identifying the DBus entry 827 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 828 * @param[in] address IPv6 address to assign to this interface 829 * @param[io] asyncResp Response object that will be returned to client 830 * 831 * @return None 832 */ 833 inline void 834 deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id, 835 uint8_t prefixLength, const std::string& address, 836 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 837 { 838 crow::connections::systemBus->async_method_call( 839 [asyncResp, ifaceId, address, 840 prefixLength](const boost::system::error_code ec) { 841 if (ec) 842 { 843 messages::internalError(asyncResp->res); 844 } 845 crow::connections::systemBus->async_method_call( 846 [asyncResp](const boost::system::error_code ec2) { 847 if (ec2) 848 { 849 messages::internalError(asyncResp->res); 850 } 851 }, 852 "xyz.openbmc_project.Network", 853 "/xyz/openbmc_project/network/" + ifaceId, 854 "xyz.openbmc_project.Network.IP.Create", "IP", 855 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 856 prefixLength, ""); 857 }, 858 "xyz.openbmc_project.Network", 859 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 860 "xyz.openbmc_project.Object.Delete", "Delete"); 861 } 862 863 /** 864 * @brief Creates IPv6 with given data 865 * 866 * @param[in] ifaceId Id of interface whose IP should be added 867 * @param[in] prefixLength Prefix length that needs to be added 868 * @param[in] address IP address that needs to be added 869 * @param[io] asyncResp Response object that will be returned to client 870 * 871 * @return None 872 */ 873 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 874 const std::string& address, 875 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 876 { 877 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 878 if (ec) 879 { 880 messages::internalError(asyncResp->res); 881 } 882 }; 883 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 884 // does not have associated gateway property 885 crow::connections::systemBus->async_method_call( 886 std::move(createIpHandler), "xyz.openbmc_project.Network", 887 "/xyz/openbmc_project/network/" + ifaceId, 888 "xyz.openbmc_project.Network.IP.Create", "IP", 889 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 890 ""); 891 } 892 893 /** 894 * Function that retrieves all properties for given Ethernet Interface 895 * Object 896 * from EntityManager Network Manager 897 * @param ethiface_id a eth interface id to query on DBus 898 * @param callback a function that shall be called to convert Dbus output 899 * into JSON 900 */ 901 template <typename CallbackFunc> 902 void getEthernetIfaceData(const std::string& ethifaceId, 903 CallbackFunc&& callback) 904 { 905 crow::connections::systemBus->async_method_call( 906 [ethifaceId{std::string{ethifaceId}}, 907 callback{std::forward<CallbackFunc>(callback)}]( 908 const boost::system::error_code errorCode, 909 const dbus::utility::ManagedObjectType& resp) { 910 EthernetInterfaceData ethData{}; 911 boost::container::flat_set<IPv4AddressData> ipv4Data; 912 boost::container::flat_set<IPv6AddressData> ipv6Data; 913 914 if (errorCode) 915 { 916 callback(false, ethData, ipv4Data, ipv6Data); 917 return; 918 } 919 920 bool found = 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 iface.starts_with(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)) 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( 1847 [asyncResp]( 1848 const bool& success, 1849 const boost::container::flat_set<std::string>& ifaceList) { 1850 if (!success) 1851 { 1852 messages::internalError(asyncResp->res); 1853 return; 1854 } 1855 1856 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 1857 ifaceArray = nlohmann::json::array(); 1858 std::string tag = "_"; 1859 for (const std::string& ifaceItem : ifaceList) 1860 { 1861 std::size_t found = ifaceItem.find(tag); 1862 if (found == std::string::npos) 1863 { 1864 nlohmann::json::object_t iface; 1865 iface["@odata.id"] = 1866 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1867 ifaceItem; 1868 ifaceArray.push_back(std::move(iface)); 1869 } 1870 } 1871 1872 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 1873 asyncResp->res.jsonValue["@odata.id"] = 1874 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1875 }); 1876 }); 1877 1878 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1879 .privileges(redfish::privileges::getEthernetInterface) 1880 .methods(boost::beast::http::verb::get)( 1881 [&app](const crow::Request& req, 1882 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1883 const std::string& ifaceId) { 1884 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1885 { 1886 return; 1887 } 1888 getEthernetIfaceData( 1889 ifaceId, 1890 [asyncResp, ifaceId]( 1891 const bool& success, const EthernetInterfaceData& ethData, 1892 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1893 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1894 if (!success) 1895 { 1896 // TODO(Pawel)consider distinguish between non 1897 // existing object, and other errors 1898 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1899 ifaceId); 1900 return; 1901 } 1902 1903 asyncResp->res.jsonValue["@odata.type"] = 1904 "#EthernetInterface.v1_4_1.EthernetInterface"; 1905 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1906 asyncResp->res.jsonValue["Description"] = 1907 "Management Network Interface"; 1908 1909 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data); 1910 }); 1911 }); 1912 1913 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1914 .privileges(redfish::privileges::patchEthernetInterface) 1915 .methods(boost::beast::http::verb::patch)( 1916 [&app](const crow::Request& req, 1917 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1918 const std::string& ifaceId) { 1919 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1920 { 1921 return; 1922 } 1923 std::optional<std::string> hostname; 1924 std::optional<std::string> fqdn; 1925 std::optional<std::string> macAddress; 1926 std::optional<std::string> ipv6DefaultGateway; 1927 std::optional<nlohmann::json> ipv4StaticAddresses; 1928 std::optional<nlohmann::json> ipv6StaticAddresses; 1929 std::optional<std::vector<std::string>> staticNameServers; 1930 std::optional<nlohmann::json> dhcpv4; 1931 std::optional<nlohmann::json> dhcpv6; 1932 std::optional<bool> interfaceEnabled; 1933 std::optional<size_t> mtuSize; 1934 DHCPParameters v4dhcpParms; 1935 DHCPParameters v6dhcpParms; 1936 1937 if (!json_util::readJsonPatch( 1938 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1939 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1940 macAddress, "StaticNameServers", staticNameServers, 1941 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1942 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1943 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled)) 1944 { 1945 return; 1946 } 1947 if (dhcpv4) 1948 { 1949 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 1950 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1951 v4dhcpParms.useDnsServers, "UseNTPServers", 1952 v4dhcpParms.useNtpServers, "UseDomainName", 1953 v4dhcpParms.useDomainName)) 1954 { 1955 return; 1956 } 1957 } 1958 1959 if (dhcpv6) 1960 { 1961 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 1962 v6dhcpParms.dhcpv6OperatingMode, 1963 "UseDNSServers", v6dhcpParms.useDnsServers, 1964 "UseNTPServers", v6dhcpParms.useNtpServers, 1965 "UseDomainName", 1966 v6dhcpParms.useDomainName)) 1967 { 1968 return; 1969 } 1970 } 1971 1972 // Get single eth interface data, and call the below callback 1973 // for JSON preparation 1974 getEthernetIfaceData( 1975 ifaceId, 1976 [asyncResp, ifaceId, hostname = std::move(hostname), 1977 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1978 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1979 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1980 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1981 staticNameServers = std::move(staticNameServers), 1982 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 1983 v4dhcpParms = std::move(v4dhcpParms), 1984 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1985 const bool& success, const EthernetInterfaceData& ethData, 1986 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1987 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1988 if (!success) 1989 { 1990 // ... otherwise return error 1991 // TODO(Pawel)consider distinguish between non 1992 // existing object, and other errors 1993 messages::resourceNotFound(asyncResp->res, "Ethernet Interface", 1994 ifaceId); 1995 return; 1996 } 1997 1998 if (dhcpv4 || dhcpv6) 1999 { 2000 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 2001 asyncResp); 2002 } 2003 2004 if (hostname) 2005 { 2006 handleHostnamePatch(*hostname, asyncResp); 2007 } 2008 2009 if (fqdn) 2010 { 2011 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2012 } 2013 2014 if (macAddress) 2015 { 2016 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 2017 } 2018 2019 if (ipv4StaticAddresses) 2020 { 2021 // TODO(ed) for some reason the capture of 2022 // ipv4Addresses above is returning a const value, 2023 // not a non-const value. This doesn't really work 2024 // for us, as we need to be able to efficiently move 2025 // out the intermedia nlohmann::json objects. This 2026 // makes a copy of the structure, and operates on 2027 // that, but could be done more efficiently 2028 nlohmann::json ipv4Static = *ipv4StaticAddresses; 2029 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 2030 } 2031 2032 if (staticNameServers) 2033 { 2034 handleStaticNameServersPatch(ifaceId, *staticNameServers, 2035 asyncResp); 2036 } 2037 2038 if (ipv6DefaultGateway) 2039 { 2040 messages::propertyNotWritable(asyncResp->res, 2041 "IPv6DefaultGateway"); 2042 } 2043 2044 if (ipv6StaticAddresses) 2045 { 2046 const nlohmann::json& ipv6Static = *ipv6StaticAddresses; 2047 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data, 2048 asyncResp); 2049 } 2050 2051 if (interfaceEnabled) 2052 { 2053 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 2054 *interfaceEnabled, asyncResp); 2055 } 2056 2057 if (mtuSize) 2058 { 2059 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2060 } 2061 }); 2062 }); 2063 2064 BMCWEB_ROUTE( 2065 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2066 .privileges(redfish::privileges::getVLanNetworkInterface) 2067 .methods(boost::beast::http::verb::get)( 2068 [&app](const crow::Request& req, 2069 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2070 const std::string& parentIfaceId, 2071 const std::string& ifaceId) { 2072 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2073 { 2074 return; 2075 } 2076 asyncResp->res.jsonValue["@odata.type"] = 2077 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2078 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 2079 2080 if (!verifyNames(parentIfaceId, ifaceId)) 2081 { 2082 return; 2083 } 2084 2085 // Get single eth interface data, and call the below callback 2086 // for JSON preparation 2087 getEthernetIfaceData( 2088 ifaceId, 2089 [asyncResp, parentIfaceId, 2090 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2091 const boost::container::flat_set<IPv4AddressData>&, 2092 const boost::container::flat_set<IPv6AddressData>&) { 2093 if (success && ethData.vlanId) 2094 { 2095 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 2096 ifaceId, ethData); 2097 } 2098 else 2099 { 2100 // ... otherwise return error 2101 // TODO(Pawel)consider distinguish between non 2102 // existing object, and other errors 2103 messages::resourceNotFound(asyncResp->res, 2104 "VLAN Network Interface", ifaceId); 2105 } 2106 }); 2107 }); 2108 2109 BMCWEB_ROUTE( 2110 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2111 .privileges(redfish::privileges::patchVLanNetworkInterface) 2112 .methods(boost::beast::http::verb::patch)( 2113 [&app](const crow::Request& req, 2114 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2115 const std::string& parentIfaceId, 2116 const std::string& ifaceId) { 2117 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2118 { 2119 return; 2120 } 2121 if (!verifyNames(parentIfaceId, ifaceId)) 2122 { 2123 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2124 ifaceId); 2125 return; 2126 } 2127 2128 std::optional<bool> vlanEnable; 2129 std::optional<uint32_t> vlanId; 2130 2131 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable", 2132 vlanEnable, "VLANId", vlanId)) 2133 { 2134 return; 2135 } 2136 2137 if (vlanId) 2138 { 2139 messages::propertyNotWritable(asyncResp->res, "VLANId"); 2140 return; 2141 } 2142 2143 // Get single eth interface data, and call the below callback 2144 // for JSON preparation 2145 getEthernetIfaceData( 2146 ifaceId, 2147 [asyncResp, parentIfaceId, ifaceId, vlanEnable]( 2148 const bool& success, const EthernetInterfaceData& ethData, 2149 const boost::container::flat_set<IPv4AddressData>&, 2150 const boost::container::flat_set<IPv6AddressData>&) { 2151 if (success && ethData.vlanId) 2152 { 2153 auto callback = 2154 [asyncResp](const boost::system::error_code ec) { 2155 if (ec) 2156 { 2157 messages::internalError(asyncResp->res); 2158 return; 2159 } 2160 }; 2161 2162 if (vlanEnable && !(*vlanEnable)) 2163 { 2164 BMCWEB_LOG_DEBUG << "vlanEnable is false. Deleting the " 2165 "vlan interface"; 2166 crow::connections::systemBus->async_method_call( 2167 std::move(callback), "xyz.openbmc_project.Network", 2168 std::string("/xyz/openbmc_project/network/") + ifaceId, 2169 "xyz.openbmc_project.Object.Delete", "Delete"); 2170 } 2171 } 2172 else 2173 { 2174 // TODO(Pawel)consider distinguish between non 2175 // existing object, and other errors 2176 messages::resourceNotFound(asyncResp->res, 2177 "VLAN Network Interface", ifaceId); 2178 return; 2179 } 2180 }); 2181 }); 2182 2183 BMCWEB_ROUTE( 2184 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2185 .privileges(redfish::privileges::deleteVLanNetworkInterface) 2186 .methods(boost::beast::http::verb::delete_)( 2187 [&app](const crow::Request& req, 2188 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2189 const std::string& parentIfaceId, 2190 const std::string& ifaceId) { 2191 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2192 { 2193 return; 2194 } 2195 if (!verifyNames(parentIfaceId, ifaceId)) 2196 { 2197 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 2198 ifaceId); 2199 return; 2200 } 2201 2202 // Get single eth interface data, and call the below callback 2203 // for JSON preparation 2204 getEthernetIfaceData( 2205 ifaceId, 2206 [asyncResp, parentIfaceId, 2207 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2208 const boost::container::flat_set<IPv4AddressData>&, 2209 const boost::container::flat_set<IPv6AddressData>&) { 2210 if (success && ethData.vlanId) 2211 { 2212 auto callback = 2213 [asyncResp](const boost::system::error_code ec) { 2214 if (ec) 2215 { 2216 messages::internalError(asyncResp->res); 2217 } 2218 }; 2219 crow::connections::systemBus->async_method_call( 2220 std::move(callback), "xyz.openbmc_project.Network", 2221 std::string("/xyz/openbmc_project/network/") + ifaceId, 2222 "xyz.openbmc_project.Object.Delete", "Delete"); 2223 } 2224 else 2225 { 2226 // ... otherwise return error 2227 // TODO(Pawel)consider distinguish between non 2228 // existing object, and other errors 2229 messages::resourceNotFound(asyncResp->res, 2230 "VLAN Network Interface", ifaceId); 2231 } 2232 }); 2233 }); 2234 2235 BMCWEB_ROUTE(app, 2236 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2237 2238 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2239 .methods(boost::beast::http::verb::get)( 2240 [&app](const crow::Request& req, 2241 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2242 const std::string& rootInterfaceName) { 2243 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2244 { 2245 return; 2246 } 2247 // Get eth interface list, and call the below callback for JSON 2248 // preparation 2249 getEthernetIfaceList( 2250 [asyncResp, rootInterfaceName]( 2251 const bool& success, 2252 const boost::container::flat_set<std::string>& ifaceList) { 2253 if (!success) 2254 { 2255 messages::internalError(asyncResp->res); 2256 return; 2257 } 2258 2259 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2260 { 2261 messages::resourceNotFound(asyncResp->res, 2262 "VLanNetworkInterfaceCollection", 2263 rootInterfaceName); 2264 return; 2265 } 2266 2267 asyncResp->res.jsonValue["@odata.type"] = 2268 "#VLanNetworkInterfaceCollection." 2269 "VLanNetworkInterfaceCollection"; 2270 asyncResp->res.jsonValue["Name"] = 2271 "VLAN Network Interface Collection"; 2272 2273 nlohmann::json ifaceArray = nlohmann::json::array(); 2274 2275 for (const std::string& ifaceItem : ifaceList) 2276 { 2277 if (ifaceItem.starts_with(rootInterfaceName + "_")) 2278 { 2279 std::string path = 2280 "/redfish/v1/Managers/bmc/EthernetInterfaces/"; 2281 path += rootInterfaceName; 2282 path += "/VLANs/"; 2283 path += ifaceItem; 2284 nlohmann::json::object_t iface; 2285 iface["@odata.id"] = std::move(path); 2286 ifaceArray.push_back(std::move(iface)); 2287 } 2288 } 2289 2290 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2291 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2292 asyncResp->res.jsonValue["@odata.id"] = 2293 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2294 rootInterfaceName + "/VLANs"; 2295 }); 2296 }); 2297 2298 BMCWEB_ROUTE(app, 2299 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2300 .privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2301 .methods(boost::beast::http::verb::post)( 2302 [&app](const crow::Request& req, 2303 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2304 const std::string& rootInterfaceName) { 2305 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2306 { 2307 return; 2308 } 2309 bool vlanEnable = false; 2310 uint32_t vlanId = 0; 2311 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId, 2312 "VLANEnable", vlanEnable)) 2313 { 2314 return; 2315 } 2316 // Need both vlanId and vlanEnable to service this request 2317 if (vlanId == 0U) 2318 { 2319 messages::propertyMissing(asyncResp->res, "VLANId"); 2320 } 2321 if (!vlanEnable) 2322 { 2323 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2324 } 2325 if (static_cast<bool>(vlanId) ^ vlanEnable) 2326 { 2327 return; 2328 } 2329 2330 auto callback = [asyncResp](const boost::system::error_code ec) { 2331 if (ec) 2332 { 2333 // TODO(ed) make more consistent error messages 2334 // based on phosphor-network responses 2335 messages::internalError(asyncResp->res); 2336 return; 2337 } 2338 messages::created(asyncResp->res); 2339 }; 2340 crow::connections::systemBus->async_method_call( 2341 std::move(callback), "xyz.openbmc_project.Network", 2342 "/xyz/openbmc_project/network", 2343 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2344 rootInterfaceName, vlanId); 2345 }); 2346 } 2347 2348 } // namespace redfish 2349