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