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