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