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