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