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