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