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