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