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