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 = nullptr; 639 long previousValue = 255; 640 bool firstZeroInByteHit = false; 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}}, 935 callback{std::forward<CallbackFunc>(callback)}]( 936 const boost::system::error_code errorCode, 937 dbus::utility::ManagedObjectType& resp) { 938 EthernetInterfaceData ethData{}; 939 boost::container::flat_set<IPv4AddressData> ipv4Data; 940 boost::container::flat_set<IPv6AddressData> ipv6Data; 941 942 if (errorCode) 943 { 944 callback(false, ethData, ipv4Data, ipv6Data); 945 return; 946 } 947 948 bool found = 949 extractEthernetInterfaceData(ethifaceId, resp, ethData); 950 if (!found) 951 { 952 callback(false, ethData, ipv4Data, ipv6Data); 953 return; 954 } 955 956 extractIPData(ethifaceId, resp, ipv4Data); 957 // Fix global GW 958 for (IPv4AddressData& ipv4 : ipv4Data) 959 { 960 if (((ipv4.linktype == LinkType::Global) && 961 (ipv4.gateway == "0.0.0.0")) || 962 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 963 { 964 ipv4.gateway = ethData.default_gateway; 965 } 966 } 967 968 extractIPV6Data(ethifaceId, resp, ipv6Data); 969 // Finally make a callback with useful data 970 callback(true, ethData, ipv4Data, ipv6Data); 971 }, 972 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 973 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 974 } 975 976 /** 977 * Function that retrieves all Ethernet Interfaces available through Network 978 * Manager 979 * @param callback a function that shall be called to convert Dbus output 980 * into JSON. 981 */ 982 template <typename CallbackFunc> 983 void getEthernetIfaceList(CallbackFunc&& callback) 984 { 985 crow::connections::systemBus->async_method_call( 986 [callback{std::forward<CallbackFunc>(callback)}]( 987 const boost::system::error_code errorCode, 988 dbus::utility::ManagedObjectType& resp) { 989 // Callback requires vector<string> to retrieve all available 990 // ethernet interfaces 991 boost::container::flat_set<std::string> ifaceList; 992 ifaceList.reserve(resp.size()); 993 if (errorCode) 994 { 995 callback(false, ifaceList); 996 return; 997 } 998 999 // Iterate over all retrieved ObjectPaths. 1000 for (const auto& objpath : resp) 1001 { 1002 // And all interfaces available for certain ObjectPath. 1003 for (const auto& interface : objpath.second) 1004 { 1005 // If interface is 1006 // xyz.openbmc_project.Network.EthernetInterface, this is 1007 // what we're looking for. 1008 if (interface.first == 1009 "xyz.openbmc_project.Network.EthernetInterface") 1010 { 1011 std::string ifaceId = objpath.first.filename(); 1012 if (ifaceId.empty()) 1013 { 1014 continue; 1015 } 1016 // and put it into output vector. 1017 ifaceList.emplace(ifaceId); 1018 } 1019 } 1020 } 1021 // Finally make a callback with useful data 1022 callback(true, ifaceList); 1023 }, 1024 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 1025 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1026 } 1027 1028 inline void 1029 handleHostnamePatch(const std::string& hostname, 1030 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1031 { 1032 // SHOULD handle host names of up to 255 characters(RFC 1123) 1033 if (hostname.length() > 255) 1034 { 1035 messages::propertyValueFormatError(asyncResp->res, hostname, 1036 "HostName"); 1037 return; 1038 } 1039 crow::connections::systemBus->async_method_call( 1040 [asyncResp](const boost::system::error_code ec) { 1041 if (ec) 1042 { 1043 messages::internalError(asyncResp->res); 1044 } 1045 }, 1046 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 1047 "org.freedesktop.DBus.Properties", "Set", 1048 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 1049 dbus::utility::DbusVariantType(hostname)); 1050 } 1051 1052 inline void 1053 handleDomainnamePatch(const std::string& ifaceId, 1054 const std::string& domainname, 1055 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1056 { 1057 std::vector<std::string> vectorDomainname = {domainname}; 1058 crow::connections::systemBus->async_method_call( 1059 [asyncResp](const boost::system::error_code ec) { 1060 if (ec) 1061 { 1062 messages::internalError(asyncResp->res); 1063 } 1064 }, 1065 "xyz.openbmc_project.Network", 1066 "/xyz/openbmc_project/network/" + ifaceId, 1067 "org.freedesktop.DBus.Properties", "Set", 1068 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1069 dbus::utility::DbusVariantType(vectorDomainname)); 1070 } 1071 1072 inline bool isHostnameValid(const std::string& hostname) 1073 { 1074 // A valid host name can never have the dotted-decimal form (RFC 1123) 1075 if (std::all_of(hostname.begin(), hostname.end(), ::isdigit)) 1076 { 1077 return false; 1078 } 1079 // Each label(hostname/subdomains) within a valid FQDN 1080 // MUST handle host names of up to 63 characters (RFC 1123) 1081 // labels cannot start or end with hyphens (RFC 952) 1082 // labels can start with numbers (RFC 1123) 1083 const std::regex pattern( 1084 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1085 1086 return std::regex_match(hostname, pattern); 1087 } 1088 1089 inline bool isDomainnameValid(const std::string& domainname) 1090 { 1091 // Can have multiple subdomains 1092 // Top Level Domain's min length is 2 character 1093 const std::regex pattern( 1094 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1095 1096 return std::regex_match(domainname, pattern); 1097 } 1098 1099 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1100 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1101 { 1102 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1103 if (fqdn.length() > 255) 1104 { 1105 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1106 return; 1107 } 1108 1109 size_t pos = fqdn.find('.'); 1110 if (pos == std::string::npos) 1111 { 1112 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1113 return; 1114 } 1115 1116 std::string hostname; 1117 std::string domainname; 1118 domainname = (fqdn).substr(pos + 1); 1119 hostname = (fqdn).substr(0, pos); 1120 1121 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1122 { 1123 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1124 return; 1125 } 1126 1127 handleHostnamePatch(hostname, asyncResp); 1128 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1129 } 1130 1131 inline void 1132 handleMACAddressPatch(const std::string& ifaceId, 1133 const std::string& macAddress, 1134 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1135 { 1136 crow::connections::systemBus->async_method_call( 1137 [asyncResp, macAddress](const boost::system::error_code ec) { 1138 if (ec) 1139 { 1140 messages::internalError(asyncResp->res); 1141 return; 1142 } 1143 }, 1144 "xyz.openbmc_project.Network", 1145 "/xyz/openbmc_project/network/" + ifaceId, 1146 "org.freedesktop.DBus.Properties", "Set", 1147 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1148 dbus::utility::DbusVariantType(macAddress)); 1149 } 1150 1151 inline void setDHCPEnabled(const std::string& ifaceId, 1152 const std::string& propertyName, const bool v4Value, 1153 const bool v6Value, 1154 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1155 { 1156 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1157 crow::connections::systemBus->async_method_call( 1158 [asyncResp](const boost::system::error_code ec) { 1159 if (ec) 1160 { 1161 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1162 messages::internalError(asyncResp->res); 1163 return; 1164 } 1165 messages::success(asyncResp->res); 1166 }, 1167 "xyz.openbmc_project.Network", 1168 "/xyz/openbmc_project/network/" + ifaceId, 1169 "org.freedesktop.DBus.Properties", "Set", 1170 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1171 dbus::utility::DbusVariantType{dhcp}); 1172 } 1173 1174 inline void setEthernetInterfaceBoolProperty( 1175 const std::string& ifaceId, const std::string& propertyName, 1176 const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1177 { 1178 crow::connections::systemBus->async_method_call( 1179 [asyncResp](const boost::system::error_code ec) { 1180 if (ec) 1181 { 1182 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1183 messages::internalError(asyncResp->res); 1184 return; 1185 } 1186 }, 1187 "xyz.openbmc_project.Network", 1188 "/xyz/openbmc_project/network/" + ifaceId, 1189 "org.freedesktop.DBus.Properties", "Set", 1190 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1191 dbus::utility::DbusVariantType{value}); 1192 } 1193 1194 inline void setDHCPv4Config(const std::string& propertyName, const bool& value, 1195 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1196 { 1197 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1198 crow::connections::systemBus->async_method_call( 1199 [asyncResp](const boost::system::error_code ec) { 1200 if (ec) 1201 { 1202 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1203 messages::internalError(asyncResp->res); 1204 return; 1205 } 1206 }, 1207 "xyz.openbmc_project.Network", 1208 "/xyz/openbmc_project/network/config/dhcp", 1209 "org.freedesktop.DBus.Properties", "Set", 1210 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1211 dbus::utility::DbusVariantType{value}); 1212 } 1213 1214 inline void handleDHCPPatch(const std::string& ifaceId, 1215 const EthernetInterfaceData& ethData, 1216 const DHCPParameters& v4dhcpParms, 1217 const DHCPParameters& v6dhcpParms, 1218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1219 { 1220 bool ipv4Active = translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1221 bool ipv6Active = translateDHCPEnabledToBool(ethData.DHCPEnabled, false); 1222 1223 bool nextv4DHCPState = 1224 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1225 1226 bool nextv6DHCPState{}; 1227 if (v6dhcpParms.dhcpv6OperatingMode) 1228 { 1229 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1230 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1231 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1232 { 1233 messages::propertyValueFormatError(asyncResp->res, 1234 *v6dhcpParms.dhcpv6OperatingMode, 1235 "OperatingMode"); 1236 return; 1237 } 1238 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1239 } 1240 else 1241 { 1242 nextv6DHCPState = ipv6Active; 1243 } 1244 1245 bool nextDNS{}; 1246 if (v4dhcpParms.useDNSServers && v6dhcpParms.useDNSServers) 1247 { 1248 if (*v4dhcpParms.useDNSServers != *v6dhcpParms.useDNSServers) 1249 { 1250 messages::generalError(asyncResp->res); 1251 return; 1252 } 1253 nextDNS = *v4dhcpParms.useDNSServers; 1254 } 1255 else if (v4dhcpParms.useDNSServers) 1256 { 1257 nextDNS = *v4dhcpParms.useDNSServers; 1258 } 1259 else if (v6dhcpParms.useDNSServers) 1260 { 1261 nextDNS = *v6dhcpParms.useDNSServers; 1262 } 1263 else 1264 { 1265 nextDNS = ethData.DNSEnabled; 1266 } 1267 1268 bool nextNTP{}; 1269 if (v4dhcpParms.useNTPServers && v6dhcpParms.useNTPServers) 1270 { 1271 if (*v4dhcpParms.useNTPServers != *v6dhcpParms.useNTPServers) 1272 { 1273 messages::generalError(asyncResp->res); 1274 return; 1275 } 1276 nextNTP = *v4dhcpParms.useNTPServers; 1277 } 1278 else if (v4dhcpParms.useNTPServers) 1279 { 1280 nextNTP = *v4dhcpParms.useNTPServers; 1281 } 1282 else if (v6dhcpParms.useNTPServers) 1283 { 1284 nextNTP = *v6dhcpParms.useNTPServers; 1285 } 1286 else 1287 { 1288 nextNTP = ethData.NTPEnabled; 1289 } 1290 1291 bool nextUseDomain{}; 1292 if (v4dhcpParms.useUseDomainName && v6dhcpParms.useUseDomainName) 1293 { 1294 if (*v4dhcpParms.useUseDomainName != *v6dhcpParms.useUseDomainName) 1295 { 1296 messages::generalError(asyncResp->res); 1297 return; 1298 } 1299 nextUseDomain = *v4dhcpParms.useUseDomainName; 1300 } 1301 else if (v4dhcpParms.useUseDomainName) 1302 { 1303 nextUseDomain = *v4dhcpParms.useUseDomainName; 1304 } 1305 else if (v6dhcpParms.useUseDomainName) 1306 { 1307 nextUseDomain = *v6dhcpParms.useUseDomainName; 1308 } 1309 else 1310 { 1311 nextUseDomain = ethData.HostNameEnabled; 1312 } 1313 1314 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1315 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1316 asyncResp); 1317 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1318 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1319 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1320 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1321 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1322 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1323 } 1324 1325 inline boost::container::flat_set<IPv4AddressData>::const_iterator 1326 getNextStaticIpEntry( 1327 const boost::container::flat_set<IPv4AddressData>::const_iterator& head, 1328 const boost::container::flat_set<IPv4AddressData>::const_iterator& end) 1329 { 1330 return std::find_if(head, end, [](const IPv4AddressData& value) { 1331 return value.origin == "Static"; 1332 }); 1333 } 1334 1335 inline boost::container::flat_set<IPv6AddressData>::const_iterator 1336 getNextStaticIpEntry( 1337 const boost::container::flat_set<IPv6AddressData>::const_iterator& head, 1338 const boost::container::flat_set<IPv6AddressData>::const_iterator& end) 1339 { 1340 return std::find_if(head, end, [](const IPv6AddressData& value) { 1341 return value.origin == "Static"; 1342 }); 1343 } 1344 1345 inline void handleIPv4StaticPatch( 1346 const std::string& ifaceId, nlohmann::json& input, 1347 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1348 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1349 { 1350 if ((!input.is_array()) || input.empty()) 1351 { 1352 messages::propertyValueTypeError( 1353 asyncResp->res, 1354 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1355 "IPv4StaticAddresses"); 1356 return; 1357 } 1358 1359 unsigned entryIdx = 1; 1360 // Find the first static IP address currently active on the NIC and 1361 // match it to the first JSON element in the IPv4StaticAddresses array. 1362 // Match each subsequent JSON element to the next static IP programmed 1363 // into the NIC. 1364 boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry = 1365 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1366 1367 for (nlohmann::json& thisJson : input) 1368 { 1369 std::string pathString = 1370 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1371 1372 if (!thisJson.is_null() && !thisJson.empty()) 1373 { 1374 std::optional<std::string> address; 1375 std::optional<std::string> subnetMask; 1376 std::optional<std::string> gateway; 1377 1378 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1379 address, "SubnetMask", subnetMask, 1380 "Gateway", gateway)) 1381 { 1382 messages::propertyValueFormatError( 1383 asyncResp->res, 1384 thisJson.dump(2, ' ', true, 1385 nlohmann::json::error_handler_t::replace), 1386 pathString); 1387 return; 1388 } 1389 1390 // Find the address/subnet/gateway values. Any values that are 1391 // not explicitly provided are assumed to be unmodified from the 1392 // current state of the interface. Merge existing state into the 1393 // current request. 1394 const std::string* addr = nullptr; 1395 const std::string* gw = nullptr; 1396 uint8_t prefixLength = 0; 1397 bool errorInEntry = false; 1398 if (address) 1399 { 1400 if (ipv4VerifyIpAndGetBitcount(*address)) 1401 { 1402 addr = &(*address); 1403 } 1404 else 1405 { 1406 messages::propertyValueFormatError(asyncResp->res, *address, 1407 pathString + "/Address"); 1408 errorInEntry = true; 1409 } 1410 } 1411 else if (nicIpEntry != ipv4Data.cend()) 1412 { 1413 addr = &(nicIpEntry->address); 1414 } 1415 else 1416 { 1417 messages::propertyMissing(asyncResp->res, 1418 pathString + "/Address"); 1419 errorInEntry = true; 1420 } 1421 1422 if (subnetMask) 1423 { 1424 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 1425 { 1426 messages::propertyValueFormatError( 1427 asyncResp->res, *subnetMask, 1428 pathString + "/SubnetMask"); 1429 errorInEntry = true; 1430 } 1431 } 1432 else if (nicIpEntry != ipv4Data.cend()) 1433 { 1434 if (!ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1435 &prefixLength)) 1436 { 1437 messages::propertyValueFormatError( 1438 asyncResp->res, nicIpEntry->netmask, 1439 pathString + "/SubnetMask"); 1440 errorInEntry = true; 1441 } 1442 } 1443 else 1444 { 1445 messages::propertyMissing(asyncResp->res, 1446 pathString + "/SubnetMask"); 1447 errorInEntry = true; 1448 } 1449 1450 if (gateway) 1451 { 1452 if (ipv4VerifyIpAndGetBitcount(*gateway)) 1453 { 1454 gw = &(*gateway); 1455 } 1456 else 1457 { 1458 messages::propertyValueFormatError(asyncResp->res, *gateway, 1459 pathString + "/Gateway"); 1460 errorInEntry = true; 1461 } 1462 } 1463 else if (nicIpEntry != ipv4Data.cend()) 1464 { 1465 gw = &nicIpEntry->gateway; 1466 } 1467 else 1468 { 1469 messages::propertyMissing(asyncResp->res, 1470 pathString + "/Gateway"); 1471 errorInEntry = true; 1472 } 1473 1474 if (errorInEntry) 1475 { 1476 return; 1477 } 1478 1479 if (nicIpEntry != ipv4Data.cend()) 1480 { 1481 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw, 1482 *addr, asyncResp); 1483 nicIpEntry = 1484 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1485 } 1486 else 1487 { 1488 createIPv4(ifaceId, prefixLength, *gateway, *address, 1489 asyncResp); 1490 } 1491 entryIdx++; 1492 } 1493 else 1494 { 1495 if (nicIpEntry == ipv4Data.cend()) 1496 { 1497 // Requesting a DELETE/DO NOT MODIFY action for an item 1498 // that isn't present on the eth(n) interface. Input JSON is 1499 // in error, so bail out. 1500 if (thisJson.is_null()) 1501 { 1502 messages::resourceCannotBeDeleted(asyncResp->res); 1503 return; 1504 } 1505 messages::propertyValueFormatError( 1506 asyncResp->res, 1507 thisJson.dump(2, ' ', true, 1508 nlohmann::json::error_handler_t::replace), 1509 pathString); 1510 return; 1511 } 1512 1513 if (thisJson.is_null()) 1514 { 1515 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp); 1516 } 1517 if (nicIpEntry != ipv4Data.cend()) 1518 { 1519 nicIpEntry = 1520 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1521 } 1522 entryIdx++; 1523 } 1524 } 1525 } 1526 1527 inline void handleStaticNameServersPatch( 1528 const std::string& ifaceId, 1529 const std::vector<std::string>& updatedStaticNameServers, 1530 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1531 { 1532 crow::connections::systemBus->async_method_call( 1533 [asyncResp](const boost::system::error_code ec) { 1534 if (ec) 1535 { 1536 messages::internalError(asyncResp->res); 1537 return; 1538 } 1539 }, 1540 "xyz.openbmc_project.Network", 1541 "/xyz/openbmc_project/network/" + ifaceId, 1542 "org.freedesktop.DBus.Properties", "Set", 1543 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1544 dbus::utility::DbusVariantType{updatedStaticNameServers}); 1545 } 1546 1547 inline void handleIPv6StaticAddressesPatch( 1548 const std::string& ifaceId, const nlohmann::json& input, 1549 const boost::container::flat_set<IPv6AddressData>& ipv6Data, 1550 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1551 { 1552 if (!input.is_array() || input.empty()) 1553 { 1554 messages::propertyValueTypeError( 1555 asyncResp->res, 1556 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1557 "IPv6StaticAddresses"); 1558 return; 1559 } 1560 size_t entryIdx = 1; 1561 boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry = 1562 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1563 for (const nlohmann::json& thisJson : input) 1564 { 1565 std::string pathString = 1566 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1567 1568 if (!thisJson.is_null() && !thisJson.empty()) 1569 { 1570 std::optional<std::string> address; 1571 std::optional<uint8_t> prefixLength; 1572 nlohmann::json thisJsonCopy = thisJson; 1573 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1574 address, "PrefixLength", prefixLength)) 1575 { 1576 messages::propertyValueFormatError( 1577 asyncResp->res, 1578 thisJson.dump(2, ' ', true, 1579 nlohmann::json::error_handler_t::replace), 1580 pathString); 1581 return; 1582 } 1583 1584 const std::string* addr = nullptr; 1585 uint8_t prefix = 0; 1586 1587 // Find the address and prefixLength values. Any values that are 1588 // not explicitly provided are assumed to be unmodified from the 1589 // current state of the interface. Merge existing state into the 1590 // current request. 1591 if (address) 1592 { 1593 addr = &(*address); 1594 } 1595 else if (nicIpEntry != ipv6Data.end()) 1596 { 1597 addr = &(nicIpEntry->address); 1598 } 1599 else 1600 { 1601 messages::propertyMissing(asyncResp->res, 1602 pathString + "/Address"); 1603 return; 1604 } 1605 1606 if (prefixLength) 1607 { 1608 prefix = *prefixLength; 1609 } 1610 else if (nicIpEntry != ipv6Data.end()) 1611 { 1612 prefix = nicIpEntry->prefixLength; 1613 } 1614 else 1615 { 1616 messages::propertyMissing(asyncResp->res, 1617 pathString + "/PrefixLength"); 1618 return; 1619 } 1620 1621 if (nicIpEntry != ipv6Data.end()) 1622 { 1623 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr, 1624 asyncResp); 1625 nicIpEntry = 1626 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1627 } 1628 else 1629 { 1630 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1631 } 1632 entryIdx++; 1633 } 1634 else 1635 { 1636 if (nicIpEntry == ipv6Data.end()) 1637 { 1638 // Requesting a DELETE/DO NOT MODIFY action for an item 1639 // that isn't present on the eth(n) interface. Input JSON is 1640 // in error, so bail out. 1641 if (thisJson.is_null()) 1642 { 1643 messages::resourceCannotBeDeleted(asyncResp->res); 1644 return; 1645 } 1646 messages::propertyValueFormatError( 1647 asyncResp->res, 1648 thisJson.dump(2, ' ', true, 1649 nlohmann::json::error_handler_t::replace), 1650 pathString); 1651 return; 1652 } 1653 1654 if (thisJson.is_null()) 1655 { 1656 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp); 1657 } 1658 if (nicIpEntry != ipv6Data.cend()) 1659 { 1660 nicIpEntry = 1661 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1662 } 1663 entryIdx++; 1664 } 1665 } 1666 } 1667 1668 inline void parseInterfaceData( 1669 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1670 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1671 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1672 const boost::container::flat_set<IPv6AddressData>& ipv6Data) 1673 { 1674 constexpr const std::array<const char*, 1> inventoryForEthernet = { 1675 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1676 1677 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1678 jsonResponse["Id"] = ifaceId; 1679 jsonResponse["@odata.id"] = 1680 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId; 1681 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1682 1683 auto health = std::make_shared<HealthPopulate>(asyncResp); 1684 1685 crow::connections::systemBus->async_method_call( 1686 [health](const boost::system::error_code ec, 1687 const std::vector<std::string>& resp) { 1688 if (ec) 1689 { 1690 return; 1691 } 1692 1693 health->inventory = resp; 1694 }, 1695 "xyz.openbmc_project.ObjectMapper", 1696 "/xyz/openbmc_project/object_mapper", 1697 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0), 1698 inventoryForEthernet); 1699 1700 health->populate(); 1701 1702 if (ethData.nicEnabled) 1703 { 1704 jsonResponse["LinkStatus"] = "LinkUp"; 1705 jsonResponse["Status"]["State"] = "Enabled"; 1706 } 1707 else 1708 { 1709 jsonResponse["LinkStatus"] = "NoLink"; 1710 jsonResponse["Status"]["State"] = "Disabled"; 1711 } 1712 1713 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1714 jsonResponse["SpeedMbps"] = ethData.speed; 1715 jsonResponse["MACAddress"] = ethData.mac_address; 1716 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1717 translateDHCPEnabledToBool(ethData.DHCPEnabled, true); 1718 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.NTPEnabled; 1719 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.DNSEnabled; 1720 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.HostNameEnabled; 1721 1722 jsonResponse["DHCPv6"]["OperatingMode"] = 1723 translateDHCPEnabledToBool(ethData.DHCPEnabled, false) ? "Stateful" 1724 : "Disabled"; 1725 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.NTPEnabled; 1726 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.DNSEnabled; 1727 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.HostNameEnabled; 1728 1729 if (!ethData.hostname.empty()) 1730 { 1731 jsonResponse["HostName"] = ethData.hostname; 1732 1733 // When domain name is empty then it means, that it is a network 1734 // without domain names, and the host name itself must be treated as 1735 // FQDN 1736 std::string fqdn = ethData.hostname; 1737 if (!ethData.domainnames.empty()) 1738 { 1739 fqdn += "." + ethData.domainnames[0]; 1740 } 1741 jsonResponse["FQDN"] = fqdn; 1742 } 1743 1744 jsonResponse["VLANs"] = { 1745 {"@odata.id", 1746 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId + "/VLANs"}}; 1747 1748 jsonResponse["NameServers"] = ethData.nameServers; 1749 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1750 1751 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1752 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1753 ipv4Array = nlohmann::json::array(); 1754 ipv4StaticArray = nlohmann::json::array(); 1755 for (auto& ipv4Config : ipv4Data) 1756 { 1757 1758 std::string gatewayStr = ipv4Config.gateway; 1759 if (gatewayStr.empty()) 1760 { 1761 gatewayStr = "0.0.0.0"; 1762 } 1763 1764 ipv4Array.push_back({{"AddressOrigin", ipv4Config.origin}, 1765 {"SubnetMask", ipv4Config.netmask}, 1766 {"Address", ipv4Config.address}, 1767 {"Gateway", gatewayStr}}); 1768 if (ipv4Config.origin == "Static") 1769 { 1770 ipv4StaticArray.push_back({{"AddressOrigin", ipv4Config.origin}, 1771 {"SubnetMask", ipv4Config.netmask}, 1772 {"Address", ipv4Config.address}, 1773 {"Gateway", gatewayStr}}); 1774 } 1775 } 1776 1777 std::string ipv6GatewayStr = ethData.ipv6_default_gateway; 1778 if (ipv6GatewayStr.empty()) 1779 { 1780 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1781 } 1782 1783 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1784 1785 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1786 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1787 ipv6Array = nlohmann::json::array(); 1788 ipv6StaticArray = nlohmann::json::array(); 1789 nlohmann::json& ipv6AddrPolicyTable = 1790 jsonResponse["IPv6AddressPolicyTable"]; 1791 ipv6AddrPolicyTable = nlohmann::json::array(); 1792 for (auto& ipv6Config : ipv6Data) 1793 { 1794 ipv6Array.push_back({{"Address", ipv6Config.address}, 1795 {"PrefixLength", ipv6Config.prefixLength}, 1796 {"AddressOrigin", ipv6Config.origin}, 1797 {"AddressState", nullptr}}); 1798 if (ipv6Config.origin == "Static") 1799 { 1800 ipv6StaticArray.push_back( 1801 {{"Address", ipv6Config.address}, 1802 {"PrefixLength", ipv6Config.prefixLength}}); 1803 } 1804 } 1805 } 1806 1807 inline void parseInterfaceData(nlohmann::json& jsonResponse, 1808 const std::string& parentIfaceId, 1809 const std::string& ifaceId, 1810 const EthernetInterfaceData& ethData) 1811 { 1812 // Fill out obvious data... 1813 jsonResponse["Id"] = ifaceId; 1814 jsonResponse["@odata.id"] = "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1815 parentIfaceId + "/VLANs/" + ifaceId; 1816 1817 jsonResponse["VLANEnable"] = true; 1818 if (!ethData.vlan_id.empty()) 1819 { 1820 jsonResponse["VLANId"] = ethData.vlan_id.back(); 1821 } 1822 } 1823 1824 inline bool verifyNames(const std::string& parent, const std::string& iface) 1825 { 1826 if (!boost::starts_with(iface, parent + "_")) 1827 { 1828 return false; 1829 } 1830 return true; 1831 } 1832 1833 inline void requestEthernetInterfacesRoutes(App& app) 1834 { 1835 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1836 .privileges(redfish::privileges::getEthernetInterfaceCollection) 1837 .methods( 1838 boost::beast::http::verb:: 1839 get)([](const crow::Request&, 1840 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1841 asyncResp->res.jsonValue["@odata.type"] = 1842 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1843 asyncResp->res.jsonValue["@odata.id"] = 1844 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1845 asyncResp->res.jsonValue["Name"] = 1846 "Ethernet Network Interface Collection"; 1847 asyncResp->res.jsonValue["Description"] = 1848 "Collection of EthernetInterfaces for this Manager"; 1849 1850 // Get eth interface list, and call the below callback for JSON 1851 // preparation 1852 getEthernetIfaceList([asyncResp](const bool& success, 1853 const boost::container::flat_set< 1854 std::string>& ifaceList) { 1855 if (!success) 1856 { 1857 messages::internalError(asyncResp->res); 1858 return; 1859 } 1860 1861 nlohmann::json& ifaceArray = 1862 asyncResp->res.jsonValue["Members"]; 1863 ifaceArray = nlohmann::json::array(); 1864 std::string tag = "_"; 1865 for (const std::string& ifaceItem : ifaceList) 1866 { 1867 std::size_t found = ifaceItem.find(tag); 1868 if (found == std::string::npos) 1869 { 1870 ifaceArray.push_back( 1871 {{"@odata.id", 1872 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1873 ifaceItem}}); 1874 } 1875 } 1876 1877 asyncResp->res.jsonValue["Members@odata.count"] = 1878 ifaceArray.size(); 1879 asyncResp->res.jsonValue["@odata.id"] = 1880 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1881 }); 1882 }); 1883 1884 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1885 .privileges(redfish::privileges::getEthernetInterface) 1886 .methods(boost::beast::http::verb::get)( 1887 [](const crow::Request&, 1888 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1889 const std::string& ifaceId) { 1890 getEthernetIfaceData( 1891 ifaceId, 1892 [asyncResp, 1893 ifaceId](const bool& success, 1894 const EthernetInterfaceData& ethData, 1895 const boost::container::flat_set<IPv4AddressData>& 1896 ipv4Data, 1897 const boost::container::flat_set<IPv6AddressData>& 1898 ipv6Data) { 1899 if (!success) 1900 { 1901 // TODO(Pawel)consider distinguish between non 1902 // existing object, and other errors 1903 messages::resourceNotFound( 1904 asyncResp->res, "EthernetInterface", ifaceId); 1905 return; 1906 } 1907 1908 asyncResp->res.jsonValue["@odata.type"] = 1909 "#EthernetInterface.v1_4_1.EthernetInterface"; 1910 asyncResp->res.jsonValue["Name"] = 1911 "Manager Ethernet Interface"; 1912 asyncResp->res.jsonValue["Description"] = 1913 "Management Network Interface"; 1914 1915 parseInterfaceData(asyncResp, ifaceId, ethData, 1916 ipv4Data, ipv6Data); 1917 }); 1918 }); 1919 1920 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1921 .privileges(redfish::privileges::patchEthernetInterface) 1922 1923 .methods(boost::beast::http::verb::patch)( 1924 [](const crow::Request& req, 1925 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1926 const std::string& ifaceId) { 1927 std::optional<std::string> hostname; 1928 std::optional<std::string> fqdn; 1929 std::optional<std::string> macAddress; 1930 std::optional<std::string> ipv6DefaultGateway; 1931 std::optional<nlohmann::json> ipv4StaticAddresses; 1932 std::optional<nlohmann::json> ipv6StaticAddresses; 1933 std::optional<std::vector<std::string>> staticNameServers; 1934 std::optional<nlohmann::json> dhcpv4; 1935 std::optional<nlohmann::json> dhcpv6; 1936 std::optional<bool> interfaceEnabled; 1937 DHCPParameters v4dhcpParms; 1938 DHCPParameters v6dhcpParms; 1939 1940 if (!json_util::readJson( 1941 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1942 "IPv4StaticAddresses", ipv4StaticAddresses, 1943 "MACAddress", macAddress, "StaticNameServers", 1944 staticNameServers, "IPv6DefaultGateway", 1945 ipv6DefaultGateway, "IPv6StaticAddresses", 1946 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1947 "InterfaceEnabled", interfaceEnabled)) 1948 { 1949 return; 1950 } 1951 if (dhcpv4) 1952 { 1953 if (!json_util::readJson( 1954 *dhcpv4, asyncResp->res, "DHCPEnabled", 1955 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1956 v4dhcpParms.useDNSServers, "UseNTPServers", 1957 v4dhcpParms.useNTPServers, "UseDomainName", 1958 v4dhcpParms.useUseDomainName)) 1959 { 1960 return; 1961 } 1962 } 1963 1964 if (dhcpv6) 1965 { 1966 if (!json_util::readJson( 1967 *dhcpv6, asyncResp->res, "OperatingMode", 1968 v6dhcpParms.dhcpv6OperatingMode, "UseDNSServers", 1969 v6dhcpParms.useDNSServers, "UseNTPServers", 1970 v6dhcpParms.useNTPServers, "UseDomainName", 1971 v6dhcpParms.useUseDomainName)) 1972 { 1973 return; 1974 } 1975 } 1976 1977 // Get single eth interface data, and call the below callback 1978 // for JSON preparation 1979 getEthernetIfaceData( 1980 ifaceId, 1981 [asyncResp, ifaceId, hostname = std::move(hostname), 1982 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1983 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1984 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1985 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1986 staticNameServers = std::move(staticNameServers), 1987 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), 1988 v4dhcpParms = std::move(v4dhcpParms), 1989 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1990 const bool& success, 1991 const EthernetInterfaceData& ethData, 1992 const boost::container::flat_set<IPv4AddressData>& 1993 ipv4Data, 1994 const boost::container::flat_set<IPv6AddressData>& 1995 ipv6Data) { 1996 if (!success) 1997 { 1998 // ... otherwise return error 1999 // TODO(Pawel)consider distinguish between non 2000 // existing object, and other errors 2001 messages::resourceNotFound( 2002 asyncResp->res, "Ethernet Interface", ifaceId); 2003 return; 2004 } 2005 2006 if (dhcpv4 || dhcpv6) 2007 { 2008 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, 2009 v6dhcpParms, asyncResp); 2010 } 2011 2012 if (hostname) 2013 { 2014 handleHostnamePatch(*hostname, asyncResp); 2015 } 2016 2017 if (fqdn) 2018 { 2019 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 2020 } 2021 2022 if (macAddress) 2023 { 2024 handleMACAddressPatch(ifaceId, *macAddress, 2025 asyncResp); 2026 } 2027 2028 if (ipv4StaticAddresses) 2029 { 2030 // TODO(ed) for some reason the capture of 2031 // ipv4Addresses above is returning a const value, 2032 // not a non-const value. This doesn't really work 2033 // for us, as we need to be able to efficiently move 2034 // out the intermedia nlohmann::json objects. This 2035 // makes a copy of the structure, and operates on 2036 // that, but could be done more efficiently 2037 nlohmann::json ipv4Static = *ipv4StaticAddresses; 2038 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, 2039 asyncResp); 2040 } 2041 2042 if (staticNameServers) 2043 { 2044 handleStaticNameServersPatch( 2045 ifaceId, *staticNameServers, asyncResp); 2046 } 2047 2048 if (ipv6DefaultGateway) 2049 { 2050 messages::propertyNotWritable(asyncResp->res, 2051 "IPv6DefaultGateway"); 2052 } 2053 2054 if (ipv6StaticAddresses) 2055 { 2056 nlohmann::json ipv6Static = *ipv6StaticAddresses; 2057 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, 2058 ipv6Data, asyncResp); 2059 } 2060 2061 if (interfaceEnabled) 2062 { 2063 setEthernetInterfaceBoolProperty( 2064 ifaceId, "NICEnabled", *interfaceEnabled, 2065 asyncResp); 2066 } 2067 }); 2068 }); 2069 2070 BMCWEB_ROUTE( 2071 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2072 .privileges(redfish::privileges::getVLanNetworkInterface) 2073 2074 .methods(boost::beast::http::verb::get)( 2075 [](const crow::Request& /* req */, 2076 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2077 const std::string& parentIfaceId, const std::string& ifaceId) { 2078 asyncResp->res.jsonValue["@odata.type"] = 2079 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2080 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 2081 2082 if (!verifyNames(parentIfaceId, ifaceId)) 2083 { 2084 return; 2085 } 2086 2087 // Get single eth interface data, and call the below callback 2088 // for JSON preparation 2089 getEthernetIfaceData( 2090 ifaceId, 2091 [asyncResp, parentIfaceId, ifaceId]( 2092 const bool& success, 2093 const EthernetInterfaceData& ethData, 2094 const boost::container::flat_set<IPv4AddressData>&, 2095 const boost::container::flat_set<IPv6AddressData>&) { 2096 if (success && ethData.vlan_id.size() != 0) 2097 { 2098 parseInterfaceData(asyncResp->res.jsonValue, 2099 parentIfaceId, ifaceId, ethData); 2100 } 2101 else 2102 { 2103 // ... otherwise return error 2104 // TODO(Pawel)consider distinguish between non 2105 // existing object, and other errors 2106 messages::resourceNotFound(asyncResp->res, 2107 "VLAN Network Interface", 2108 ifaceId); 2109 } 2110 }); 2111 }); 2112 2113 BMCWEB_ROUTE( 2114 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2115 // This privilege is incorrect, it should be ConfigureManager 2116 //.privileges(redfish::privileges::patchVLanNetworkInterface) 2117 .privileges({{"ConfigureComponents"}}) 2118 .methods(boost::beast::http::verb::patch)( 2119 [](const crow::Request& req, 2120 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2121 const std::string& parentIfaceId, const std::string& ifaceId) { 2122 if (!verifyNames(parentIfaceId, ifaceId)) 2123 { 2124 messages::resourceNotFound( 2125 asyncResp->res, "VLAN Network Interface", ifaceId); 2126 return; 2127 } 2128 2129 bool vlanEnable = false; 2130 uint32_t vlanId = 0; 2131 2132 if (!json_util::readJson(req, asyncResp->res, "VLANEnable", 2133 vlanEnable, "VLANId", vlanId)) 2134 { 2135 return; 2136 } 2137 2138 // Get single eth interface data, and call the below callback 2139 // for JSON preparation 2140 getEthernetIfaceData( 2141 ifaceId, 2142 [asyncResp, parentIfaceId, ifaceId, &vlanEnable, &vlanId]( 2143 const bool& success, 2144 const EthernetInterfaceData& ethData, 2145 const boost::container::flat_set<IPv4AddressData>&, 2146 const boost::container::flat_set<IPv6AddressData>&) { 2147 if (success && !ethData.vlan_id.empty()) 2148 { 2149 auto callback = 2150 [asyncResp]( 2151 const boost::system::error_code ec) { 2152 if (ec) 2153 { 2154 messages::internalError(asyncResp->res); 2155 } 2156 }; 2157 2158 if (vlanEnable == true) 2159 { 2160 crow::connections::systemBus->async_method_call( 2161 std::move(callback), 2162 "xyz.openbmc_project.Network", 2163 "/xyz/openbmc_project/network/" + ifaceId, 2164 "org.freedesktop.DBus.Properties", "Set", 2165 "xyz.openbmc_project.Network.VLAN", "Id", 2166 dbus::utility::DbusVariantType(vlanId)); 2167 } 2168 else 2169 { 2170 BMCWEB_LOG_DEBUG 2171 << "vlanEnable is false. Deleting the " 2172 "vlan interface"; 2173 crow::connections::systemBus->async_method_call( 2174 std::move(callback), 2175 "xyz.openbmc_project.Network", 2176 std::string( 2177 "/xyz/openbmc_project/network/") + 2178 ifaceId, 2179 "xyz.openbmc_project.Object.Delete", 2180 "Delete"); 2181 } 2182 } 2183 else 2184 { 2185 // TODO(Pawel)consider distinguish between non 2186 // existing object, and other errors 2187 messages::resourceNotFound(asyncResp->res, 2188 "VLAN Network Interface", 2189 ifaceId); 2190 return; 2191 } 2192 }); 2193 }); 2194 2195 BMCWEB_ROUTE( 2196 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2197 // This privilege is incorrect, it should be ConfigureManager 2198 //.privileges(redfish::privileges::deleteVLanNetworkInterface) 2199 .privileges({{"ConfigureComponents"}}) 2200 .methods(boost::beast::http::verb::delete_)( 2201 [](const crow::Request& /* req */, 2202 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2203 const std::string& parentIfaceId, const std::string& ifaceId) { 2204 if (!verifyNames(parentIfaceId, ifaceId)) 2205 { 2206 messages::resourceNotFound( 2207 asyncResp->res, "VLAN Network Interface", ifaceId); 2208 return; 2209 } 2210 2211 // Get single eth interface data, and call the below callback 2212 // for JSON preparation 2213 getEthernetIfaceData( 2214 ifaceId, 2215 [asyncResp, parentIfaceId, ifaceId]( 2216 const bool& success, 2217 const EthernetInterfaceData& ethData, 2218 const boost::container::flat_set<IPv4AddressData>&, 2219 const boost::container::flat_set<IPv6AddressData>&) { 2220 if (success && !ethData.vlan_id.empty()) 2221 { 2222 auto callback = 2223 [asyncResp]( 2224 const boost::system::error_code ec) { 2225 if (ec) 2226 { 2227 messages::internalError(asyncResp->res); 2228 } 2229 }; 2230 crow::connections::systemBus->async_method_call( 2231 std::move(callback), 2232 "xyz.openbmc_project.Network", 2233 std::string("/xyz/openbmc_project/network/") + 2234 ifaceId, 2235 "xyz.openbmc_project.Object.Delete", "Delete"); 2236 } 2237 else 2238 { 2239 // ... otherwise return error 2240 // TODO(Pawel)consider distinguish between non 2241 // existing object, and other errors 2242 messages::resourceNotFound(asyncResp->res, 2243 "VLAN Network Interface", 2244 ifaceId); 2245 } 2246 }); 2247 }); 2248 2249 BMCWEB_ROUTE(app, 2250 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2251 2252 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2253 .methods( 2254 boost::beast::http::verb:: 2255 get)([](const crow::Request& /* req */, 2256 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2257 const std::string& rootInterfaceName) { 2258 // Get eth interface list, and call the below callback for JSON 2259 // preparation 2260 getEthernetIfaceList([asyncResp, rootInterfaceName]( 2261 const bool& success, 2262 const boost::container::flat_set< 2263 std::string>& ifaceList) { 2264 if (!success) 2265 { 2266 messages::internalError(asyncResp->res); 2267 return; 2268 } 2269 2270 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2271 { 2272 messages::resourceNotFound(asyncResp->res, 2273 "VLanNetworkInterfaceCollection", 2274 rootInterfaceName); 2275 return; 2276 } 2277 2278 asyncResp->res.jsonValue["@odata.type"] = 2279 "#VLanNetworkInterfaceCollection." 2280 "VLanNetworkInterfaceCollection"; 2281 asyncResp->res.jsonValue["Name"] = 2282 "VLAN Network Interface Collection"; 2283 2284 nlohmann::json ifaceArray = nlohmann::json::array(); 2285 2286 for (const std::string& ifaceItem : ifaceList) 2287 { 2288 if (boost::starts_with(ifaceItem, rootInterfaceName + "_")) 2289 { 2290 std::string path = 2291 "/redfish/v1/Managers/bmc/EthernetInterfaces/"; 2292 path += rootInterfaceName; 2293 path += "/VLANs/"; 2294 path += ifaceItem; 2295 ifaceArray.push_back({{"@odata.id", std::move(path)}}); 2296 } 2297 } 2298 2299 asyncResp->res.jsonValue["Members@odata.count"] = 2300 ifaceArray.size(); 2301 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2302 asyncResp->res.jsonValue["@odata.id"] = 2303 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2304 rootInterfaceName + "/VLANs"; 2305 }); 2306 }); 2307 2308 BMCWEB_ROUTE(app, 2309 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2310 // This privilege is wrong, it should be ConfigureManager 2311 //.privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2312 .privileges({{"ConfigureComponents"}}) 2313 .methods(boost::beast::http::verb::post)( 2314 [](const crow::Request& req, 2315 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2316 const std::string& rootInterfaceName) { 2317 bool vlanEnable = false; 2318 uint32_t vlanId = 0; 2319 if (!json_util::readJson(req, asyncResp->res, "VLANId", vlanId, 2320 "VLANEnable", vlanEnable)) 2321 { 2322 return; 2323 } 2324 // Need both vlanId and vlanEnable to service this request 2325 if (!vlanId) 2326 { 2327 messages::propertyMissing(asyncResp->res, "VLANId"); 2328 } 2329 if (!vlanEnable) 2330 { 2331 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2332 } 2333 if (static_cast<bool>(vlanId) ^ vlanEnable) 2334 { 2335 return; 2336 } 2337 2338 auto callback = 2339 [asyncResp](const boost::system::error_code ec) { 2340 if (ec) 2341 { 2342 // TODO(ed) make more consistent error messages 2343 // based on phosphor-network responses 2344 messages::internalError(asyncResp->res); 2345 return; 2346 } 2347 messages::created(asyncResp->res); 2348 }; 2349 crow::connections::systemBus->async_method_call( 2350 std::move(callback), "xyz.openbmc_project.Network", 2351 "/xyz/openbmc_project/network", 2352 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2353 rootInterfaceName, vlanId); 2354 }); 2355 } 2356 2357 } // namespace redfish 2358