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