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