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