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