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