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