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 basic single Ethernet Interface information 75 * available from DBus 76 */ 77 struct EthernetInterfaceData 78 { 79 uint32_t speed; 80 bool auto_neg; 81 bool DHCPEnabled; 82 std::string hostname; 83 std::string default_gateway; 84 std::string mac_address; 85 std::optional<uint32_t> vlan_id; 86 std::vector<std::string> nameservers; 87 }; 88 89 // Helper function that changes bits netmask notation (i.e. /24) 90 // into full dot notation 91 inline std::string getNetmask(unsigned int bits) 92 { 93 uint32_t value = 0xffffffff << (32 - bits); 94 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 95 std::to_string((value >> 16) & 0xff) + "." + 96 std::to_string((value >> 8) & 0xff) + "." + 97 std::to_string(value & 0xff); 98 return netmask; 99 } 100 101 inline std::string 102 translateAddressOriginDbusToRedfish(const std::string &inputOrigin, 103 bool isIPv4) 104 { 105 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 106 { 107 return "Static"; 108 } 109 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 110 { 111 if (isIPv4) 112 { 113 return "IPv4LinkLocal"; 114 } 115 else 116 { 117 return "LinkLocal"; 118 } 119 } 120 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 121 { 122 if (isIPv4) 123 { 124 return "DHCP"; 125 } 126 else 127 { 128 return "DHCPv6"; 129 } 130 } 131 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 132 { 133 return "SLAAC"; 134 } 135 return ""; 136 } 137 138 inline std::string 139 translateAddressOriginRedfishToDbus(const std::string &inputOrigin) 140 { 141 if (inputOrigin == "Static") 142 { 143 return "xyz.openbmc_project.Network.IP.AddressOrigin.Static"; 144 } 145 if (inputOrigin == "DHCP" || inputOrigin == "DHCPv6") 146 { 147 return "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP"; 148 } 149 if (inputOrigin == "IPv4LinkLocal" || inputOrigin == "LinkLocal") 150 { 151 return "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal"; 152 } 153 if (inputOrigin == "SLAAC") 154 { 155 return "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC"; 156 } 157 return ""; 158 } 159 160 inline void extractEthernetInterfaceData(const std::string ðiface_id, 161 const GetManagedObjects &dbus_data, 162 EthernetInterfaceData ðData) 163 { 164 for (const auto &objpath : dbus_data) 165 { 166 for (const auto &ifacePair : objpath.second) 167 { 168 if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id) 169 { 170 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 171 { 172 for (const auto &propertyPair : ifacePair.second) 173 { 174 if (propertyPair.first == "MACAddress") 175 { 176 const std::string *mac = 177 std::get_if<std::string>(&propertyPair.second); 178 if (mac != nullptr) 179 { 180 ethData.mac_address = *mac; 181 } 182 } 183 } 184 } 185 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 186 { 187 for (const auto &propertyPair : ifacePair.second) 188 { 189 if (propertyPair.first == "Id") 190 { 191 const uint32_t *id = 192 std::get_if<uint32_t>(&propertyPair.second); 193 if (id != nullptr) 194 { 195 ethData.vlan_id = *id; 196 } 197 } 198 } 199 } 200 else if (ifacePair.first == 201 "xyz.openbmc_project.Network.EthernetInterface") 202 { 203 for (const auto &propertyPair : ifacePair.second) 204 { 205 if (propertyPair.first == "AutoNeg") 206 { 207 const bool *auto_neg = 208 std::get_if<bool>(&propertyPair.second); 209 if (auto_neg != nullptr) 210 { 211 ethData.auto_neg = *auto_neg; 212 } 213 } 214 else if (propertyPair.first == "Speed") 215 { 216 const uint32_t *speed = 217 std::get_if<uint32_t>(&propertyPair.second); 218 if (speed != nullptr) 219 { 220 ethData.speed = *speed; 221 } 222 } 223 else if (propertyPair.first == "NameServers") 224 { 225 const std::vector<std::string> *nameservers = 226 sdbusplus::message::variant_ns::get_if< 227 std::vector<std::string>>( 228 &propertyPair.second); 229 if (nameservers != nullptr) 230 { 231 ethData.nameservers = std::move(*nameservers); 232 } 233 } 234 else if (propertyPair.first == "DHCPEnabled") 235 { 236 const bool *DHCPEnabled = 237 std::get_if<bool>(&propertyPair.second); 238 if (DHCPEnabled != nullptr) 239 { 240 ethData.DHCPEnabled = *DHCPEnabled; 241 } 242 } 243 } 244 } 245 } 246 // System configuration shows up in the global namespace, so no need 247 // to check eth number 248 if (ifacePair.first == 249 "xyz.openbmc_project.Network.SystemConfiguration") 250 { 251 for (const auto &propertyPair : ifacePair.second) 252 { 253 if (propertyPair.first == "HostName") 254 { 255 const std::string *hostname = 256 sdbusplus::message::variant_ns::get_if<std::string>( 257 &propertyPair.second); 258 if (hostname != nullptr) 259 { 260 ethData.hostname = *hostname; 261 } 262 } 263 else if (propertyPair.first == "DefaultGateway") 264 { 265 const std::string *defaultGateway = 266 sdbusplus::message::variant_ns::get_if<std::string>( 267 &propertyPair.second); 268 if (defaultGateway != nullptr) 269 { 270 ethData.default_gateway = *defaultGateway; 271 } 272 } 273 } 274 } 275 } 276 } 277 } 278 279 // Helper function that extracts data for single ethernet ipv4 address 280 inline void 281 extractIPData(const std::string ðiface_id, 282 const GetManagedObjects &dbus_data, 283 boost::container::flat_set<IPv4AddressData> &ipv4_config) 284 { 285 const std::string ipv4PathStart = 286 "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/"; 287 288 // Since there might be several IPv4 configurations aligned with 289 // single ethernet interface, loop over all of them 290 for (const auto &objpath : dbus_data) 291 { 292 // Check if proper pattern for object path appears 293 if (boost::starts_with(objpath.first.str, ipv4PathStart)) 294 { 295 for (auto &interface : objpath.second) 296 { 297 if (interface.first == "xyz.openbmc_project.Network.IP") 298 { 299 // Instance IPv4AddressData structure, and set as 300 // appropriate 301 std::pair< 302 boost::container::flat_set<IPv4AddressData>::iterator, 303 bool> 304 it = ipv4_config.insert( 305 {objpath.first.str.substr(ipv4PathStart.size())}); 306 IPv4AddressData &ipv4_address = *it.first; 307 for (auto &property : interface.second) 308 { 309 if (property.first == "Address") 310 { 311 const std::string *address = 312 std::get_if<std::string>(&property.second); 313 if (address != nullptr) 314 { 315 ipv4_address.address = *address; 316 } 317 } 318 else if (property.first == "Gateway") 319 { 320 const std::string *gateway = 321 std::get_if<std::string>(&property.second); 322 if (gateway != nullptr) 323 { 324 ipv4_address.gateway = *gateway; 325 } 326 } 327 else if (property.first == "Origin") 328 { 329 const std::string *origin = 330 std::get_if<std::string>(&property.second); 331 if (origin != nullptr) 332 { 333 ipv4_address.origin = 334 translateAddressOriginDbusToRedfish(*origin, 335 true); 336 } 337 } 338 else if (property.first == "PrefixLength") 339 { 340 const uint8_t *mask = 341 std::get_if<uint8_t>(&property.second); 342 if (mask != nullptr) 343 { 344 // convert it to the string 345 ipv4_address.netmask = getNetmask(*mask); 346 } 347 } 348 else 349 { 350 BMCWEB_LOG_ERROR 351 << "Got extra property: " << property.first 352 << " on the " << objpath.first.str << " object"; 353 } 354 } 355 // Check if given address is local, or global 356 ipv4_address.linktype = 357 boost::starts_with(ipv4_address.address, "169.254.") 358 ? LinkType::Global 359 : LinkType::Local; 360 } 361 } 362 } 363 } 364 } 365 366 /** 367 * @brief Sets given Id on the given VLAN interface through D-Bus 368 * 369 * @param[in] ifaceId Id of VLAN interface that should be modified 370 * @param[in] inputVlanId New ID of the VLAN 371 * @param[in] callback Function that will be called after the operation 372 * 373 * @return None. 374 */ 375 template <typename CallbackFunc> 376 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId, 377 CallbackFunc &&callback) 378 { 379 crow::connections::systemBus->async_method_call( 380 callback, "xyz.openbmc_project.Network", 381 std::string("/xyz/openbmc_project/network/") + ifaceId, 382 "org.freedesktop.DBus.Properties", "Set", 383 "xyz.openbmc_project.Network.VLAN", "Id", 384 std::variant<uint32_t>(inputVlanId)); 385 } 386 387 /** 388 * @brief Helper function that verifies IP address to check if it is in 389 * proper format. If bits pointer is provided, also calculates active 390 * bit count for Subnet Mask. 391 * 392 * @param[in] ip IP that will be verified 393 * @param[out] bits Calculated mask in bits notation 394 * 395 * @return true in case of success, false otherwise 396 */ 397 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip, 398 uint8_t *bits = nullptr) 399 { 400 std::vector<std::string> bytesInMask; 401 402 boost::split(bytesInMask, ip, boost::is_any_of(".")); 403 404 static const constexpr int ipV4AddressSectionsCount = 4; 405 if (bytesInMask.size() != ipV4AddressSectionsCount) 406 { 407 return false; 408 } 409 410 if (bits != nullptr) 411 { 412 *bits = 0; 413 } 414 415 char *endPtr; 416 long previousValue = 255; 417 bool firstZeroInByteHit; 418 for (const std::string &byte : bytesInMask) 419 { 420 if (byte.empty()) 421 { 422 return false; 423 } 424 425 // Use strtol instead of stroi to avoid exceptions 426 long value = std::strtol(byte.c_str(), &endPtr, 10); 427 428 // endPtr should point to the end of the string, otherwise given string 429 // is not 100% number 430 if (*endPtr != '\0') 431 { 432 return false; 433 } 434 435 // Value should be contained in byte 436 if (value < 0 || value > 255) 437 { 438 return false; 439 } 440 441 if (bits != nullptr) 442 { 443 // Mask has to be continuous between bytes 444 if (previousValue != 255 && value != 0) 445 { 446 return false; 447 } 448 449 // Mask has to be continuous inside bytes 450 firstZeroInByteHit = false; 451 452 // Count bits 453 for (int bitIdx = 7; bitIdx >= 0; bitIdx--) 454 { 455 if (value & (1 << bitIdx)) 456 { 457 if (firstZeroInByteHit) 458 { 459 // Continuity not preserved 460 return false; 461 } 462 else 463 { 464 (*bits)++; 465 } 466 } 467 else 468 { 469 firstZeroInByteHit = true; 470 } 471 } 472 } 473 474 previousValue = value; 475 } 476 477 return true; 478 } 479 480 /** 481 * @brief Changes IPv4 address type property (Address, Gateway) 482 * 483 * @param[in] ifaceId Id of interface whose IP should be modified 484 * @param[in] ipIdx Index of IP in input array that should be modified 485 * @param[in] ipHash DBus Hash id of modified IP 486 * @param[in] name Name of field in JSON representation 487 * @param[in] newValue New value that should be written 488 * @param[io] asyncResp Response object that will be returned to client 489 * 490 * @return true if give IP is valid and has been sent do D-Bus, false 491 * otherwise 492 */ 493 inline void changeIPv4AddressProperty( 494 const std::string &ifaceId, int ipIdx, const std::string &ipHash, 495 const std::string &name, const std::string &newValue, 496 const std::shared_ptr<AsyncResp> asyncResp) 497 { 498 auto callback = [asyncResp, ipIdx, name{std::string(name)}, 499 newValue{std::move(newValue)}]( 500 const boost::system::error_code ec) { 501 if (ec) 502 { 503 messages::internalError(asyncResp->res); 504 } 505 else 506 { 507 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx][name] = newValue; 508 } 509 }; 510 511 crow::connections::systemBus->async_method_call( 512 std::move(callback), "xyz.openbmc_project.Network", 513 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 514 "org.freedesktop.DBus.Properties", "Set", 515 "xyz.openbmc_project.Network.IP", name, 516 std::variant<std::string>(newValue)); 517 } 518 519 /** 520 * @brief Changes IPv4 address origin property 521 * 522 * @param[in] ifaceId Id of interface whose IP should be modified 523 * @param[in] ipIdx Index of IP in input array that should be 524 * modified 525 * @param[in] ipHash DBus Hash id of modified IP 526 * @param[in] newValue New value in Redfish format 527 * @param[in] newValueDbus New value in D-Bus format 528 * @param[io] asyncResp Response object that will be returned to client 529 * 530 * @return true if give IP is valid and has been sent do D-Bus, false 531 * otherwise 532 */ 533 inline void changeIPv4Origin(const std::string &ifaceId, int ipIdx, 534 const std::string &ipHash, 535 const std::string &newValue, 536 const std::string &newValueDbus, 537 const std::shared_ptr<AsyncResp> asyncResp) 538 { 539 auto callback = [asyncResp, ipIdx, newValue{std::move(newValue)}]( 540 const boost::system::error_code ec) { 541 if (ec) 542 { 543 messages::internalError(asyncResp->res); 544 } 545 else 546 { 547 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["AddressOrigin"] = 548 newValue; 549 } 550 }; 551 552 crow::connections::systemBus->async_method_call( 553 std::move(callback), "xyz.openbmc_project.Network", 554 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 555 "org.freedesktop.DBus.Properties", "Set", 556 "xyz.openbmc_project.Network.IP", "Origin", 557 std::variant<std::string>(newValueDbus)); 558 } 559 560 /** 561 * @brief Modifies SubnetMask for given IP 562 * 563 * @param[in] ifaceId Id of interface whose IP should be modified 564 * @param[in] ipIdx Index of IP in input array that should be 565 * modified 566 * @param[in] ipHash DBus Hash id of modified IP 567 * @param[in] newValueStr Mask in dot notation as string 568 * @param[in] newValue Mask as PrefixLength in bitcount 569 * @param[io] asyncResp Response object that will be returned to client 570 * 571 * @return None 572 */ 573 inline void changeIPv4SubnetMaskProperty(const std::string &ifaceId, int ipIdx, 574 const std::string &ipHash, 575 const std::string &newValueStr, 576 uint8_t &newValue, 577 std::shared_ptr<AsyncResp> asyncResp) 578 { 579 auto callback = [asyncResp, ipIdx, newValueStr{std::move(newValueStr)}]( 580 const boost::system::error_code ec) { 581 if (ec) 582 { 583 messages::internalError(asyncResp->res); 584 } 585 else 586 { 587 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["SubnetMask"] = 588 newValueStr; 589 } 590 }; 591 592 crow::connections::systemBus->async_method_call( 593 std::move(callback), "xyz.openbmc_project.Network", 594 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 595 "org.freedesktop.DBus.Properties", "Set", 596 "xyz.openbmc_project.Network.IP", "PrefixLength", 597 std::variant<uint8_t>(newValue)); 598 } 599 600 /** 601 * @brief Deletes given IPv4 602 * 603 * @param[in] ifaceId Id of interface whose IP should be deleted 604 * @param[in] ipIdx Index of IP in input array that should be deleted 605 * @param[in] ipHash DBus Hash id of IP that should be deleted 606 * @param[io] asyncResp Response object that will be returned to client 607 * 608 * @return None 609 */ 610 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash, 611 unsigned int ipIdx, 612 const std::shared_ptr<AsyncResp> asyncResp) 613 { 614 crow::connections::systemBus->async_method_call( 615 [ipIdx, asyncResp](const boost::system::error_code ec) { 616 if (ec) 617 { 618 messages::internalError(asyncResp->res); 619 } 620 else 621 { 622 asyncResp->res.jsonValue["IPv4Addresses"][ipIdx] = nullptr; 623 } 624 }, 625 "xyz.openbmc_project.Network", 626 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 627 "xyz.openbmc_project.Object.Delete", "Delete"); 628 } 629 630 /** 631 * @brief Creates IPv4 with given data 632 * 633 * @param[in] ifaceId Id of interface whose IP should be deleted 634 * @param[in] ipIdx Index of IP in input array that should be deleted 635 * @param[in] ipHash DBus Hash id of IP that should be deleted 636 * @param[io] asyncResp Response object that will be returned to client 637 * 638 * @return None 639 */ 640 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx, 641 uint8_t subnetMask, const std::string &gateway, 642 const std::string &address, 643 std::shared_ptr<AsyncResp> asyncResp) 644 { 645 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 646 if (ec) 647 { 648 messages::internalError(asyncResp->res); 649 } 650 }; 651 652 crow::connections::systemBus->async_method_call( 653 std::move(createIpHandler), "xyz.openbmc_project.Network", 654 "/xyz/openbmc_project/network/" + ifaceId, 655 "xyz.openbmc_project.Network.IP.Create", "IP", 656 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask, 657 gateway); 658 } 659 using GetAllPropertiesType = 660 boost::container::flat_map<std::string, sdbusplus::message::variant<bool>>; 661 662 inline void getDHCPConfigData(const std::shared_ptr<AsyncResp> asyncResp) 663 { 664 auto getConfig = [asyncResp](const boost::system::error_code error_code, 665 const GetAllPropertiesType &dbus_data) { 666 if (error_code) 667 { 668 BMCWEB_LOG_ERROR << "D-Bus response error: " << error_code; 669 messages::internalError(asyncResp->res); 670 return; 671 } 672 nlohmann::json &DHCPConfigTypeJson = 673 asyncResp->res.jsonValue["DHCPv4Configuration"]; 674 for (const auto &property : dbus_data) 675 { 676 auto value = 677 sdbusplus::message::variant_ns::get_if<bool>(&property.second); 678 679 if (value == nullptr) 680 { 681 continue; 682 } 683 if (property.first == "DNSEnabled") 684 { 685 DHCPConfigTypeJson["UseDNSServers"] = *value; 686 } 687 else if (property.first == "HostNameEnabled") 688 { 689 DHCPConfigTypeJson["UseDomainName"] = *value; 690 } 691 else if (property.first == "NTPEnabled") 692 { 693 DHCPConfigTypeJson["UseNTPServers"] = *value; 694 } 695 } 696 }; 697 crow::connections::systemBus->async_method_call( 698 std::move(getConfig), "xyz.openbmc_project.Network", 699 "/xyz/openbmc_project/network/config/dhcp", 700 "org.freedesktop.DBus.Properties", "GetAll", 701 "xyz.openbmc_project.Network.DHCPConfiguration"); 702 } 703 704 /** 705 * Function that retrieves all properties for given Ethernet Interface 706 * Object 707 * from EntityManager Network Manager 708 * @param ethiface_id a eth interface id to query on DBus 709 * @param callback a function that shall be called to convert Dbus output 710 * into JSON 711 */ 712 template <typename CallbackFunc> 713 void getEthernetIfaceData(const std::string ðiface_id, 714 CallbackFunc &&callback) 715 { 716 crow::connections::systemBus->async_method_call( 717 [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}]( 718 const boost::system::error_code error_code, 719 const GetManagedObjects &resp) { 720 EthernetInterfaceData ethData{}; 721 boost::container::flat_set<IPv4AddressData> ipv4Data; 722 723 if (error_code) 724 { 725 callback(false, ethData, ipv4Data); 726 return; 727 } 728 729 extractEthernetInterfaceData(ethiface_id, resp, ethData); 730 extractIPData(ethiface_id, resp, ipv4Data); 731 732 // Fix global GW 733 for (IPv4AddressData &ipv4 : ipv4Data) 734 { 735 if ((ipv4.linktype == LinkType::Global) && 736 (ipv4.gateway == "0.0.0.0")) 737 { 738 ipv4.gateway = ethData.default_gateway; 739 } 740 } 741 742 // Finally make a callback with usefull data 743 callback(true, ethData, ipv4Data); 744 }, 745 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 746 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 747 }; 748 749 /** 750 * Function that retrieves all Ethernet Interfaces available through Network 751 * Manager 752 * @param callback a function that shall be called to convert Dbus output 753 * into JSON. 754 */ 755 template <typename CallbackFunc> 756 void getEthernetIfaceList(CallbackFunc &&callback) 757 { 758 crow::connections::systemBus->async_method_call( 759 [callback{std::move(callback)}]( 760 const boost::system::error_code error_code, 761 GetManagedObjects &resp) { 762 // Callback requires vector<string> to retrieve all available 763 // ethernet interfaces 764 std::vector<std::string> iface_list; 765 iface_list.reserve(resp.size()); 766 if (error_code) 767 { 768 callback(false, iface_list); 769 return; 770 } 771 772 // Iterate over all retrieved ObjectPaths. 773 for (const auto &objpath : resp) 774 { 775 // And all interfaces available for certain ObjectPath. 776 for (const auto &interface : objpath.second) 777 { 778 // If interface is 779 // xyz.openbmc_project.Network.EthernetInterface, this is 780 // what we're looking for. 781 if (interface.first == 782 "xyz.openbmc_project.Network.EthernetInterface") 783 { 784 // Cut out everyting until last "/", ... 785 const std::string &iface_id = objpath.first.str; 786 std::size_t last_pos = iface_id.rfind("/"); 787 if (last_pos != std::string::npos) 788 { 789 // and put it into output vector. 790 iface_list.emplace_back( 791 iface_id.substr(last_pos + 1)); 792 } 793 } 794 } 795 } 796 // Finally make a callback with useful data 797 callback(true, iface_list); 798 }, 799 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 800 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 801 }; 802 803 /** 804 * EthernetCollection derived class for delivering Ethernet Collection Schema 805 */ 806 class EthernetCollection : public Node 807 { 808 public: 809 template <typename CrowApp> 810 EthernetCollection(CrowApp &app) : 811 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 812 { 813 entityPrivileges = { 814 {boost::beast::http::verb::get, {{"Login"}}}, 815 {boost::beast::http::verb::head, {{"Login"}}}, 816 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 817 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 818 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 819 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 820 } 821 822 private: 823 /** 824 * Functions triggers appropriate requests on DBus 825 */ 826 void doGet(crow::Response &res, const crow::Request &req, 827 const std::vector<std::string> ¶ms) override 828 { 829 res.jsonValue["@odata.type"] = 830 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 831 res.jsonValue["@odata.context"] = 832 "/redfish/v1/" 833 "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection"; 834 res.jsonValue["@odata.id"] = 835 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 836 res.jsonValue["Name"] = "Ethernet Network Interface Collection"; 837 res.jsonValue["Description"] = 838 "Collection of EthernetInterfaces for this Manager"; 839 840 // Get eth interface list, and call the below callback for JSON 841 // preparation 842 getEthernetIfaceList( 843 [&res](const bool &success, 844 const std::vector<std::string> &iface_list) { 845 if (!success) 846 { 847 messages::internalError(res); 848 res.end(); 849 return; 850 } 851 852 nlohmann::json &iface_array = res.jsonValue["Members"]; 853 iface_array = nlohmann::json::array(); 854 for (const std::string &iface_item : iface_list) 855 { 856 iface_array.push_back( 857 {{"@odata.id", 858 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 859 iface_item}}); 860 } 861 862 res.jsonValue["Members@odata.count"] = iface_array.size(); 863 res.jsonValue["@odata.id"] = 864 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 865 res.end(); 866 }); 867 } 868 }; 869 870 /** 871 * EthernetInterface derived class for delivering Ethernet Schema 872 */ 873 class EthernetInterface : public Node 874 { 875 public: 876 /* 877 * Default Constructor 878 */ 879 template <typename CrowApp> 880 EthernetInterface(CrowApp &app) : 881 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/", 882 std::string()) 883 { 884 entityPrivileges = { 885 {boost::beast::http::verb::get, {{"Login"}}}, 886 {boost::beast::http::verb::head, {{"Login"}}}, 887 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 888 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 889 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 890 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 891 } 892 893 // TODO(kkowalsk) Find a suitable class/namespace for this 894 static void handleVlanPatch(const std::string &ifaceId, bool vlanEnable, 895 uint64_t vlanId, 896 const EthernetInterfaceData ðData, 897 const std::shared_ptr<AsyncResp> asyncResp) 898 { 899 if (!ethData.vlan_id) 900 { 901 // This interface is not a VLAN. Cannot do anything with it 902 // TODO(kkowalsk) Change this message 903 messages::propertyNotWritable(asyncResp->res, "VLANEnable"); 904 905 return; 906 } 907 908 // VLAN is configured on the interface 909 if (vlanEnable == true) 910 { 911 // Change VLAN Id 912 asyncResp->res.jsonValue["VLANId"] = vlanId; 913 auto callback = [asyncResp](const boost::system::error_code ec) { 914 if (ec) 915 { 916 messages::internalError(asyncResp->res); 917 } 918 else 919 { 920 asyncResp->res.jsonValue["VLANEnable"] = true; 921 } 922 }; 923 crow::connections::systemBus->async_method_call( 924 std::move(callback), "xyz.openbmc_project.Network", 925 "/xyz/openbmc_project/network/" + ifaceId, 926 "org.freedesktop.DBus.Properties", "Set", 927 "xyz.openbmc_project.Network.VLAN", "Id", 928 std::variant<uint32_t>(vlanId)); 929 } 930 else 931 { 932 auto callback = [asyncResp](const boost::system::error_code ec) { 933 if (ec) 934 { 935 messages::internalError(asyncResp->res); 936 return; 937 } 938 asyncResp->res.jsonValue["VLANEnable"] = false; 939 }; 940 941 crow::connections::systemBus->async_method_call( 942 std::move(callback), "xyz.openbmc_project.Network", 943 "/xyz/openbmc_project/network/" + ifaceId, 944 "xyz.openbmc_project.Object.Delete", "Delete"); 945 } 946 } 947 948 private: 949 void handleHostnamePatch(const std::string &hostname, 950 const std::shared_ptr<AsyncResp> asyncResp) 951 { 952 asyncResp->res.jsonValue["HostName"] = hostname; 953 crow::connections::systemBus->async_method_call( 954 [asyncResp](const boost::system::error_code ec) { 955 if (ec) 956 { 957 messages::internalError(asyncResp->res); 958 } 959 }, 960 "xyz.openbmc_project.Network", 961 "/xyz/openbmc_project/network/config", 962 "org.freedesktop.DBus.Properties", "Set", 963 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 964 std::variant<std::string>(hostname)); 965 } 966 967 void handleMACAddressPatch(const std::string &ifaceId, 968 const std::string &macAddress, 969 const std::shared_ptr<AsyncResp> &asyncResp) 970 { 971 crow::connections::systemBus->async_method_call( 972 [asyncResp, macAddress](const boost::system::error_code ec) { 973 if (ec) 974 { 975 messages::internalError(asyncResp->res); 976 return; 977 } 978 asyncResp->res.jsonValue["MACAddress"] = std::move(macAddress); 979 }, 980 "xyz.openbmc_project.Network", 981 "/xyz/openbmc_project/network/" + ifaceId, 982 "org.freedesktop.DBus.Properties", "Set", 983 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 984 std::variant<std::string>(macAddress)); 985 } 986 987 void handleIPv4Patch( 988 const std::string &ifaceId, nlohmann::json &input, 989 const boost::container::flat_set<IPv4AddressData> &ipv4Data, 990 const std::shared_ptr<AsyncResp> asyncResp) 991 { 992 if (!input.is_array()) 993 { 994 messages::propertyValueTypeError(asyncResp->res, input.dump(), 995 "IPv4Addresses"); 996 return; 997 } 998 999 int entryIdx = 0; 1000 boost::container::flat_set<IPv4AddressData>::const_iterator thisData = 1001 ipv4Data.begin(); 1002 for (nlohmann::json &thisJson : input) 1003 { 1004 std::string pathString = 1005 "IPv4Addresses/" + std::to_string(entryIdx); 1006 1007 if (thisJson.is_null()) 1008 { 1009 if (thisData != ipv4Data.end()) 1010 { 1011 deleteIPv4(ifaceId, thisData->id, entryIdx, asyncResp); 1012 thisData++; 1013 } 1014 else 1015 { 1016 messages::propertyValueFormatError( 1017 asyncResp->res, input.dump(), pathString); 1018 return; 1019 // TODO(ratagupt) Not sure about the property where value is 1020 // list and if unable to update one of the 1021 // list value then should we proceed further or 1022 // break there, would ask in the redfish forum 1023 // till then we stop processing the next list item. 1024 } 1025 entryIdx++; 1026 continue; // not an error as per the redfish spec. 1027 } 1028 1029 if (thisJson.empty()) 1030 { 1031 if (thisData != ipv4Data.end()) 1032 { 1033 thisData++; 1034 } 1035 else 1036 { 1037 messages::propertyMissing(asyncResp->res, 1038 pathString + "/Address"); 1039 return; 1040 // TODO(ratagupt) Not sure about the property where value is 1041 // list and if unable to update one of the 1042 // list value then should we proceed further or 1043 // break there, would ask in the redfish forum 1044 // till then we stop processing the next list item. 1045 } 1046 entryIdx++; 1047 continue; // not an error as per the redfish spec. 1048 } 1049 1050 std::optional<std::string> address; 1051 std::optional<std::string> addressOrigin; 1052 std::optional<std::string> subnetMask; 1053 std::optional<std::string> gateway; 1054 1055 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1056 address, "AddressOrigin", addressOrigin, 1057 "SubnetMask", subnetMask, "Gateway", 1058 gateway)) 1059 { 1060 return; 1061 } 1062 1063 if (address) 1064 { 1065 if (!ipv4VerifyIpAndGetBitcount(*address)) 1066 { 1067 messages::propertyValueFormatError(asyncResp->res, *address, 1068 pathString + "/Address"); 1069 return; 1070 } 1071 } 1072 1073 uint8_t prefixLength = 0; 1074 if (subnetMask) 1075 { 1076 if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength)) 1077 { 1078 messages::propertyValueFormatError( 1079 asyncResp->res, *subnetMask, 1080 pathString + "/SubnetMask"); 1081 return; 1082 } 1083 } 1084 std::string addressOriginInDBusFormat; 1085 if (addressOrigin) 1086 { 1087 // Get Address origin in proper format 1088 addressOriginInDBusFormat = 1089 translateAddressOriginRedfishToDbus(*addressOrigin); 1090 if (addressOriginInDBusFormat.empty()) 1091 { 1092 messages::propertyValueNotInList( 1093 asyncResp->res, *addressOrigin, 1094 pathString + "/AddressOrigin"); 1095 return; 1096 } 1097 } 1098 1099 if (gateway) 1100 { 1101 if (!ipv4VerifyIpAndGetBitcount(*gateway)) 1102 { 1103 messages::propertyValueFormatError(asyncResp->res, *gateway, 1104 pathString + "/Gateway"); 1105 return; 1106 } 1107 } 1108 1109 // if IP address exist then modify it. 1110 if (thisData != ipv4Data.end()) 1111 { 1112 // Apply changes 1113 if (address) 1114 { 1115 auto callback = [asyncResp, entryIdx, 1116 address{std::string(*address)}]( 1117 const boost::system::error_code ec) { 1118 if (ec) 1119 { 1120 messages::internalError(asyncResp->res); 1121 return; 1122 } 1123 asyncResp->res 1124 .jsonValue["IPv4Addresses"][entryIdx]["Address"] = 1125 std::move(address); 1126 }; 1127 1128 crow::connections::systemBus->async_method_call( 1129 std::move(callback), "xyz.openbmc_project.Network", 1130 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + 1131 thisData->id, 1132 "org.freedesktop.DBus.Properties", "Set", 1133 "xyz.openbmc_project.Network.IP", "Address", 1134 std::variant<std::string>(*address)); 1135 } 1136 1137 if (subnetMask) 1138 { 1139 changeIPv4SubnetMaskProperty(ifaceId, entryIdx, 1140 thisData->id, *subnetMask, 1141 prefixLength, asyncResp); 1142 } 1143 1144 if (addressOrigin) 1145 { 1146 changeIPv4Origin(ifaceId, entryIdx, thisData->id, 1147 *addressOrigin, addressOriginInDBusFormat, 1148 asyncResp); 1149 } 1150 1151 if (gateway) 1152 { 1153 auto callback = [asyncResp, entryIdx, 1154 gateway{std::string(*gateway)}]( 1155 const boost::system::error_code ec) { 1156 if (ec) 1157 { 1158 messages::internalError(asyncResp->res); 1159 return; 1160 } 1161 asyncResp->res 1162 .jsonValue["IPv4Addresses"][entryIdx]["Gateway"] = 1163 std::move(gateway); 1164 }; 1165 1166 crow::connections::systemBus->async_method_call( 1167 std::move(callback), "xyz.openbmc_project.Network", 1168 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + 1169 thisData->id, 1170 "org.freedesktop.DBus.Properties", "Set", 1171 "xyz.openbmc_project.Network.IP", "Gateway", 1172 std::variant<std::string>(*gateway)); 1173 } 1174 1175 thisData++; 1176 } 1177 else 1178 { 1179 // Create IPv4 with provided data 1180 if (!gateway) 1181 { 1182 messages::propertyMissing(asyncResp->res, 1183 pathString + "/Gateway"); 1184 continue; 1185 } 1186 1187 if (!address) 1188 { 1189 messages::propertyMissing(asyncResp->res, 1190 pathString + "/Address"); 1191 continue; 1192 } 1193 1194 if (!subnetMask) 1195 { 1196 messages::propertyMissing(asyncResp->res, 1197 pathString + "/SubnetMask"); 1198 continue; 1199 } 1200 1201 createIPv4(ifaceId, entryIdx, prefixLength, *gateway, *address, 1202 asyncResp); 1203 1204 nlohmann::json &ipv4AddressJson = 1205 asyncResp->res.jsonValue["IPv4Addresses"][entryIdx]; 1206 ipv4AddressJson["Address"] = *address; 1207 ipv4AddressJson["SubnetMask"] = *subnetMask; 1208 ipv4AddressJson["Gateway"] = *gateway; 1209 } 1210 entryIdx++; 1211 } 1212 } 1213 1214 void parseInterfaceData( 1215 nlohmann::json &json_response, const std::string &iface_id, 1216 const EthernetInterfaceData ðData, 1217 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1218 { 1219 json_response["Id"] = iface_id; 1220 json_response["@odata.id"] = 1221 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id; 1222 json_response["InterfaceEnabled"] = true; 1223 if (ethData.speed == 0) 1224 { 1225 json_response["LinkStatus"] = "NoLink"; 1226 json_response["Status"] = { 1227 {"Health", "OK"}, 1228 {"State", "Disabled"}, 1229 }; 1230 } 1231 else 1232 { 1233 json_response["LinkStatus"] = "LinkUp"; 1234 json_response["Status"] = { 1235 {"Health", "OK"}, 1236 {"State", "Enabled"}, 1237 }; 1238 } 1239 json_response["SpeedMbps"] = ethData.speed; 1240 json_response["MACAddress"] = ethData.mac_address; 1241 json_response["DHCPv4Configuration"]["DHCPEnabled"] = 1242 ethData.DHCPEnabled; 1243 1244 if (!ethData.hostname.empty()) 1245 { 1246 json_response["HostName"] = ethData.hostname; 1247 } 1248 1249 nlohmann::json &vlanObj = json_response["VLAN"]; 1250 if (ethData.vlan_id) 1251 { 1252 vlanObj["VLANEnable"] = true; 1253 vlanObj["VLANId"] = *ethData.vlan_id; 1254 } 1255 else 1256 { 1257 vlanObj["VLANEnable"] = false; 1258 vlanObj["VLANId"] = 0; 1259 } 1260 json_response["NameServers"] = ethData.nameservers; 1261 1262 if (ipv4Data.size() > 0) 1263 { 1264 nlohmann::json &ipv4_array = json_response["IPv4Addresses"]; 1265 ipv4_array = nlohmann::json::array(); 1266 for (auto &ipv4_config : ipv4Data) 1267 { 1268 ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin}, 1269 {"SubnetMask", ipv4_config.netmask}, 1270 {"Address", ipv4_config.address}, 1271 {"Gateway", ipv4_config.gateway}}); 1272 } 1273 } 1274 } 1275 1276 /** 1277 * Functions triggers appropriate requests on DBus 1278 */ 1279 void doGet(crow::Response &res, const crow::Request &req, 1280 const std::vector<std::string> ¶ms) override 1281 { 1282 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1283 if (params.size() != 1) 1284 { 1285 messages::internalError(asyncResp->res); 1286 return; 1287 } 1288 1289 getEthernetIfaceData( 1290 params[0], 1291 [this, asyncResp, iface_id{std::string(params[0])}]( 1292 const bool &success, const EthernetInterfaceData ðData, 1293 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1294 if (!success) 1295 { 1296 // TODO(Pawel)consider distinguish between non existing 1297 // object, and other errors 1298 messages::resourceNotFound(asyncResp->res, 1299 "EthernetInterface", iface_id); 1300 return; 1301 } 1302 asyncResp->res.jsonValue["@odata.type"] = 1303 "#EthernetInterface.v1_2_0.EthernetInterface"; 1304 asyncResp->res.jsonValue["@odata.context"] = 1305 "/redfish/v1/$metadata#EthernetInterface.EthernetInterface"; 1306 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1307 asyncResp->res.jsonValue["Description"] = 1308 "Management Network Interface"; 1309 1310 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData, 1311 ipv4Data); 1312 }); 1313 getDHCPConfigData(asyncResp); 1314 } 1315 1316 void doPatch(crow::Response &res, const crow::Request &req, 1317 const std::vector<std::string> ¶ms) override 1318 { 1319 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1320 if (params.size() != 1) 1321 { 1322 messages::internalError(asyncResp->res); 1323 return; 1324 } 1325 1326 const std::string &iface_id = params[0]; 1327 1328 std::optional<nlohmann::json> vlan; 1329 std::optional<std::string> hostname; 1330 std::optional<std::string> macAddress; 1331 std::optional<nlohmann::json> ipv4Addresses; 1332 std::optional<nlohmann::json> ipv6Addresses; 1333 1334 if (!json_util::readJson(req, res, "VLAN", vlan, "HostName", hostname, 1335 "IPv4Addresses", ipv4Addresses, 1336 "IPv6Addresses", ipv6Addresses, "MACAddress", 1337 macAddress)) 1338 { 1339 return; 1340 } 1341 1342 std::optional<uint64_t> vlanId; 1343 std::optional<bool> vlanEnable; 1344 1345 if (vlan) 1346 { 1347 if (!json_util::readJson(*vlan, res, "VLANEnable", vlanEnable, 1348 "VLANId", vlanId)) 1349 { 1350 return; 1351 } 1352 // Need both vlanId and vlanEnable to service this request 1353 if (static_cast<bool>(vlanId) ^ static_cast<bool>(vlanEnable)) 1354 { 1355 if (vlanId) 1356 { 1357 messages::propertyMissing(asyncResp->res, "VLANEnable"); 1358 } 1359 else 1360 { 1361 messages::propertyMissing(asyncResp->res, "VLANId"); 1362 } 1363 1364 return; 1365 } 1366 } 1367 1368 // Get single eth interface data, and call the below callback for JSON 1369 // preparation 1370 getEthernetIfaceData( 1371 iface_id, 1372 [this, asyncResp, iface_id, vlanId, vlanEnable, 1373 hostname = std::move(hostname), macAddress = std::move(macAddress), 1374 ipv4Addresses = std::move(ipv4Addresses), 1375 ipv6Addresses = std::move(ipv6Addresses)]( 1376 const bool &success, const EthernetInterfaceData ðData, 1377 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1378 if (!success) 1379 { 1380 // ... otherwise return error 1381 // TODO(Pawel)consider distinguish between non existing 1382 // object, and other errors 1383 messages::resourceNotFound( 1384 asyncResp->res, "VLAN Network Interface", iface_id); 1385 return; 1386 } 1387 1388 parseInterfaceData(asyncResp->res.jsonValue, iface_id, ethData, 1389 ipv4Data); 1390 1391 if (vlanId && vlanEnable) 1392 { 1393 handleVlanPatch(iface_id, *vlanId, *vlanEnable, ethData, 1394 asyncResp); 1395 } 1396 1397 if (hostname) 1398 { 1399 handleHostnamePatch(*hostname, asyncResp); 1400 } 1401 1402 if (macAddress) 1403 { 1404 handleMACAddressPatch(iface_id, *macAddress, asyncResp); 1405 } 1406 1407 if (ipv4Addresses) 1408 { 1409 // TODO(ed) for some reason the capture of ipv4Addresses 1410 // above is returning a const value, not a non-const value. 1411 // This doesn't really work for us, as we need to be able to 1412 // efficiently move out the intermedia nlohmann::json 1413 // objects. This makes a copy of the structure, and operates 1414 // on that, but could be done more efficiently 1415 nlohmann::json ipv4 = std::move(*ipv4Addresses); 1416 handleIPv4Patch(iface_id, ipv4, ipv4Data, asyncResp); 1417 } 1418 1419 if (ipv6Addresses) 1420 { 1421 // TODO(kkowalsk) IPv6 Not supported on D-Bus yet 1422 messages::propertyNotWritable(asyncResp->res, 1423 "IPv6Addresses"); 1424 } 1425 }); 1426 } 1427 }; 1428 1429 /** 1430 * VlanNetworkInterface derived class for delivering VLANNetworkInterface 1431 * Schema 1432 */ 1433 class VlanNetworkInterface : public Node 1434 { 1435 public: 1436 /* 1437 * Default Constructor 1438 */ 1439 template <typename CrowApp> 1440 VlanNetworkInterface(CrowApp &app) : 1441 Node(app, 1442 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>", 1443 std::string(), std::string()) 1444 { 1445 entityPrivileges = { 1446 {boost::beast::http::verb::get, {{"Login"}}}, 1447 {boost::beast::http::verb::head, {{"Login"}}}, 1448 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1449 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1450 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1451 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1452 } 1453 1454 private: 1455 void parseInterfaceData( 1456 nlohmann::json &json_response, const std::string &parent_iface_id, 1457 const std::string &iface_id, const EthernetInterfaceData ðData, 1458 const boost::container::flat_set<IPv4AddressData> &ipv4Data) 1459 { 1460 // Fill out obvious data... 1461 json_response["Id"] = iface_id; 1462 json_response["@odata.id"] = 1463 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id + 1464 "/VLANs/" + iface_id; 1465 1466 json_response["VLANEnable"] = true; 1467 if (ethData.vlan_id) 1468 { 1469 json_response["VLANId"] = *ethData.vlan_id; 1470 } 1471 } 1472 1473 bool verifyNames(crow::Response &res, const std::string &parent, 1474 const std::string &iface) 1475 { 1476 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1477 if (!boost::starts_with(iface, parent + "_")) 1478 { 1479 messages::resourceNotFound(asyncResp->res, "VLAN Network Interface", 1480 iface); 1481 return false; 1482 } 1483 else 1484 { 1485 return true; 1486 } 1487 } 1488 1489 /** 1490 * Functions triggers appropriate requests on DBus 1491 */ 1492 void doGet(crow::Response &res, const crow::Request &req, 1493 const std::vector<std::string> ¶ms) override 1494 { 1495 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1496 // TODO(Pawel) this shall be parameterized call (two params) to get 1497 // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'. 1498 // Check if there is required param, truly entering this shall be 1499 // impossible. 1500 if (params.size() != 2) 1501 { 1502 messages::internalError(res); 1503 res.end(); 1504 return; 1505 } 1506 1507 const std::string &parent_iface_id = params[0]; 1508 const std::string &iface_id = params[1]; 1509 res.jsonValue["@odata.type"] = 1510 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1511 res.jsonValue["@odata.context"] = 1512 "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface"; 1513 res.jsonValue["Name"] = "VLAN Network Interface"; 1514 1515 if (!verifyNames(res, parent_iface_id, iface_id)) 1516 { 1517 return; 1518 } 1519 1520 // Get single eth interface data, and call the below callback for JSON 1521 // preparation 1522 getEthernetIfaceData( 1523 iface_id, 1524 [this, asyncResp, parent_iface_id, iface_id]( 1525 const bool &success, const EthernetInterfaceData ðData, 1526 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1527 if (success && ethData.vlan_id) 1528 { 1529 parseInterfaceData(asyncResp->res.jsonValue, 1530 parent_iface_id, iface_id, ethData, 1531 ipv4Data); 1532 } 1533 else 1534 { 1535 // ... otherwise return error 1536 // TODO(Pawel)consider distinguish between non existing 1537 // object, and other errors 1538 messages::resourceNotFound( 1539 asyncResp->res, "VLAN Network Interface", iface_id); 1540 } 1541 }); 1542 } 1543 1544 void doPatch(crow::Response &res, const crow::Request &req, 1545 const std::vector<std::string> ¶ms) override 1546 { 1547 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1548 if (params.size() != 2) 1549 { 1550 messages::internalError(asyncResp->res); 1551 return; 1552 } 1553 1554 const std::string &parentIfaceId = params[0]; 1555 const std::string &ifaceId = params[1]; 1556 1557 if (!verifyNames(res, parentIfaceId, ifaceId)) 1558 { 1559 return; 1560 } 1561 1562 bool vlanEnable = false; 1563 uint64_t vlanId = 0; 1564 1565 if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId", 1566 vlanId)) 1567 { 1568 return; 1569 } 1570 1571 // Get single eth interface data, and call the below callback for JSON 1572 // preparation 1573 getEthernetIfaceData( 1574 ifaceId, 1575 [this, asyncResp, parentIfaceId, ifaceId, vlanEnable, vlanId]( 1576 const bool &success, const EthernetInterfaceData ðData, 1577 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1578 if (!success) 1579 { 1580 // TODO(Pawel)consider distinguish between non existing 1581 // object, and other errors 1582 messages::resourceNotFound( 1583 asyncResp->res, "VLAN Network Interface", ifaceId); 1584 1585 return; 1586 } 1587 1588 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 1589 ifaceId, ethData, ipv4Data); 1590 1591 EthernetInterface::handleVlanPatch(ifaceId, vlanId, vlanEnable, 1592 ethData, asyncResp); 1593 }); 1594 } 1595 1596 void doDelete(crow::Response &res, const crow::Request &req, 1597 const std::vector<std::string> ¶ms) override 1598 { 1599 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1600 if (params.size() != 2) 1601 { 1602 messages::internalError(asyncResp->res); 1603 return; 1604 } 1605 1606 const std::string &parentIfaceId = params[0]; 1607 const std::string &ifaceId = params[1]; 1608 1609 if (!verifyNames(asyncResp->res, parentIfaceId, ifaceId)) 1610 { 1611 return; 1612 } 1613 1614 // Get single eth interface data, and call the below callback for JSON 1615 // preparation 1616 getEthernetIfaceData( 1617 ifaceId, 1618 [this, asyncResp, parentIfaceId{std::string(parentIfaceId)}, 1619 ifaceId{std::string(ifaceId)}]( 1620 const bool &success, const EthernetInterfaceData ðData, 1621 const boost::container::flat_set<IPv4AddressData> &ipv4Data) { 1622 if (success && ethData.vlan_id) 1623 { 1624 parseInterfaceData(asyncResp->res.jsonValue, parentIfaceId, 1625 ifaceId, ethData, ipv4Data); 1626 1627 auto callback = 1628 [asyncResp](const boost::system::error_code ec) { 1629 if (ec) 1630 { 1631 messages::internalError(asyncResp->res); 1632 } 1633 }; 1634 crow::connections::systemBus->async_method_call( 1635 std::move(callback), "xyz.openbmc_project.Network", 1636 std::string("/xyz/openbmc_project/network/") + ifaceId, 1637 "xyz.openbmc_project.Object.Delete", "Delete"); 1638 } 1639 else 1640 { 1641 // ... otherwise return error 1642 // TODO(Pawel)consider distinguish between non existing 1643 // object, and other errors 1644 messages::resourceNotFound( 1645 asyncResp->res, "VLAN Network Interface", ifaceId); 1646 } 1647 }); 1648 } 1649 }; 1650 1651 /** 1652 * VlanNetworkInterfaceCollection derived class for delivering 1653 * VLANNetworkInterface Collection Schema 1654 */ 1655 class VlanNetworkInterfaceCollection : public Node 1656 { 1657 public: 1658 template <typename CrowApp> 1659 VlanNetworkInterfaceCollection(CrowApp &app) : 1660 Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/", 1661 std::string()) 1662 { 1663 entityPrivileges = { 1664 {boost::beast::http::verb::get, {{"Login"}}}, 1665 {boost::beast::http::verb::head, {{"Login"}}}, 1666 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1667 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1668 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1669 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1670 } 1671 1672 private: 1673 /** 1674 * Functions triggers appropriate requests on DBus 1675 */ 1676 void doGet(crow::Response &res, const crow::Request &req, 1677 const std::vector<std::string> ¶ms) override 1678 { 1679 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1680 if (params.size() != 1) 1681 { 1682 // This means there is a problem with the router 1683 messages::internalError(asyncResp->res); 1684 return; 1685 } 1686 1687 const std::string &rootInterfaceName = params[0]; 1688 1689 // Get eth interface list, and call the below callback for JSON 1690 // preparation 1691 getEthernetIfaceList( 1692 [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}]( 1693 const bool &success, 1694 const std::vector<std::string> &iface_list) { 1695 if (!success) 1696 { 1697 messages::internalError(asyncResp->res); 1698 return; 1699 } 1700 asyncResp->res.jsonValue["@odata.type"] = 1701 "#VLanNetworkInterfaceCollection." 1702 "VLanNetworkInterfaceCollection"; 1703 asyncResp->res.jsonValue["@odata.context"] = 1704 "/redfish/v1/$metadata" 1705 "#VLanNetworkInterfaceCollection." 1706 "VLanNetworkInterfaceCollection"; 1707 asyncResp->res.jsonValue["Name"] = 1708 "VLAN Network Interface Collection"; 1709 1710 nlohmann::json iface_array = nlohmann::json::array(); 1711 1712 for (const std::string &iface_item : iface_list) 1713 { 1714 if (boost::starts_with(iface_item, rootInterfaceName + "_")) 1715 { 1716 iface_array.push_back( 1717 {{"@odata.id", 1718 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1719 rootInterfaceName + "/VLANs/" + iface_item}}); 1720 } 1721 } 1722 1723 if (iface_array.empty()) 1724 { 1725 messages::resourceNotFound( 1726 asyncResp->res, "EthernetInterface", rootInterfaceName); 1727 return; 1728 } 1729 asyncResp->res.jsonValue["Members@odata.count"] = 1730 iface_array.size(); 1731 asyncResp->res.jsonValue["Members"] = std::move(iface_array); 1732 asyncResp->res.jsonValue["@odata.id"] = 1733 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1734 rootInterfaceName + "/VLANs"; 1735 }); 1736 } 1737 1738 void doPost(crow::Response &res, const crow::Request &req, 1739 const std::vector<std::string> ¶ms) override 1740 { 1741 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 1742 if (params.size() != 1) 1743 { 1744 messages::internalError(asyncResp->res); 1745 return; 1746 } 1747 1748 uint32_t vlanId = 0; 1749 if (!json_util::readJson(req, res, "VLANId", vlanId)) 1750 { 1751 return; 1752 } 1753 const std::string &rootInterfaceName = params[0]; 1754 auto callback = [asyncResp](const boost::system::error_code ec) { 1755 if (ec) 1756 { 1757 // TODO(ed) make more consistent error messages based on 1758 // phosphor-network responses 1759 messages::internalError(asyncResp->res); 1760 return; 1761 } 1762 messages::created(asyncResp->res); 1763 }; 1764 crow::connections::systemBus->async_method_call( 1765 std::move(callback), "xyz.openbmc_project.Network", 1766 "/xyz/openbmc_project/network", 1767 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 1768 rootInterfaceName, vlanId); 1769 } 1770 }; 1771 } // namespace redfish 1772