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