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 "app.hpp" 19 #include "dbus_singleton.hpp" 20 #include "dbus_utility.hpp" 21 #include "error_messages.hpp" 22 #include "health.hpp" 23 #include "query.hpp" 24 #include "registries/privilege_registry.hpp" 25 #include "utils/ip_utils.hpp" 26 #include "utils/json_utils.hpp" 27 28 #include <boost/algorithm/string/classification.hpp> 29 #include <boost/algorithm/string/split.hpp> 30 #include <boost/container/flat_set.hpp> 31 32 #include <array> 33 #include <optional> 34 #include <regex> 35 #include <string_view> 36 37 namespace redfish 38 { 39 40 enum class LinkType 41 { 42 Local, 43 Global 44 }; 45 46 /** 47 * Structure for keeping IPv4 data required by Redfish 48 */ 49 struct IPv4AddressData 50 { 51 std::string id; 52 std::string address; 53 std::string domain; 54 std::string gateway; 55 std::string netmask; 56 std::string origin; 57 LinkType linktype; 58 bool isActive; 59 60 bool operator<(const IPv4AddressData& obj) const 61 { 62 return id < obj.id; 63 } 64 }; 65 66 /** 67 * Structure for keeping IPv6 data required by Redfish 68 */ 69 struct IPv6AddressData 70 { 71 std::string id; 72 std::string address; 73 std::string origin; 74 uint8_t prefixLength; 75 76 bool operator<(const IPv6AddressData& obj) const 77 { 78 return id < obj.id; 79 } 80 }; 81 /** 82 * Structure for keeping basic single Ethernet Interface information 83 * available from DBus 84 */ 85 struct EthernetInterfaceData 86 { 87 uint32_t speed; 88 size_t mtuSize; 89 bool autoNeg; 90 bool dnsEnabled; 91 bool ntpEnabled; 92 bool hostNameEnabled; 93 bool linkUp; 94 bool nicEnabled; 95 std::string dhcpEnabled; 96 std::string operatingMode; 97 std::string hostName; 98 std::string defaultGateway; 99 std::string ipv6DefaultGateway; 100 std::string macAddress; 101 std::optional<uint32_t> vlanId; 102 std::vector<std::string> nameServers; 103 std::vector<std::string> staticNameServers; 104 std::vector<std::string> domainnames; 105 }; 106 107 struct DHCPParameters 108 { 109 std::optional<bool> dhcpv4Enabled; 110 std::optional<bool> useDnsServers; 111 std::optional<bool> useNtpServers; 112 std::optional<bool> useDomainName; 113 std::optional<std::string> dhcpv6OperatingMode; 114 }; 115 116 // Helper function that changes bits netmask notation (i.e. /24) 117 // into full dot notation 118 inline std::string getNetmask(unsigned int bits) 119 { 120 uint32_t value = 0xffffffff << (32 - bits); 121 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 122 std::to_string((value >> 16) & 0xff) + "." + 123 std::to_string((value >> 8) & 0xff) + "." + 124 std::to_string(value & 0xff); 125 return netmask; 126 } 127 128 inline bool translateDhcpEnabledToBool(const std::string& inputDHCP, 129 bool isIPv4) 130 { 131 if (isIPv4) 132 { 133 return ( 134 (inputDHCP == 135 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || 136 (inputDHCP == 137 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 138 } 139 return ((inputDHCP == 140 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || 141 (inputDHCP == 142 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 143 } 144 145 inline std::string getDhcpEnabledEnumeration(bool isIPv4, bool isIPv6) 146 { 147 if (isIPv4 && isIPv6) 148 { 149 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; 150 } 151 if (isIPv4) 152 { 153 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; 154 } 155 if (isIPv6) 156 { 157 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; 158 } 159 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; 160 } 161 162 inline std::string 163 translateAddressOriginDbusToRedfish(const std::string& inputOrigin, 164 bool isIPv4) 165 { 166 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 167 { 168 return "Static"; 169 } 170 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 171 { 172 if (isIPv4) 173 { 174 return "IPv4LinkLocal"; 175 } 176 return "LinkLocal"; 177 } 178 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 179 { 180 if (isIPv4) 181 { 182 return "DHCP"; 183 } 184 return "DHCPv6"; 185 } 186 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 187 { 188 return "SLAAC"; 189 } 190 return ""; 191 } 192 193 inline bool extractEthernetInterfaceData( 194 const std::string& ethifaceId, 195 const dbus::utility::ManagedObjectType& dbusData, 196 EthernetInterfaceData& ethData) 197 { 198 bool idFound = false; 199 for (const auto& objpath : dbusData) 200 { 201 for (const auto& ifacePair : objpath.second) 202 { 203 if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId) 204 { 205 idFound = true; 206 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 207 { 208 for (const auto& propertyPair : ifacePair.second) 209 { 210 if (propertyPair.first == "MACAddress") 211 { 212 const std::string* mac = 213 std::get_if<std::string>(&propertyPair.second); 214 if (mac != nullptr) 215 { 216 ethData.macAddress = *mac; 217 } 218 } 219 } 220 } 221 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 222 { 223 for (const auto& propertyPair : ifacePair.second) 224 { 225 if (propertyPair.first == "Id") 226 { 227 const uint32_t* id = 228 std::get_if<uint32_t>(&propertyPair.second); 229 if (id != nullptr) 230 { 231 ethData.vlanId = *id; 232 } 233 } 234 } 235 } 236 else if (ifacePair.first == 237 "xyz.openbmc_project.Network.EthernetInterface") 238 { 239 for (const auto& propertyPair : ifacePair.second) 240 { 241 if (propertyPair.first == "AutoNeg") 242 { 243 const bool* autoNeg = 244 std::get_if<bool>(&propertyPair.second); 245 if (autoNeg != nullptr) 246 { 247 ethData.autoNeg = *autoNeg; 248 } 249 } 250 else if (propertyPair.first == "Speed") 251 { 252 const uint32_t* speed = 253 std::get_if<uint32_t>(&propertyPair.second); 254 if (speed != nullptr) 255 { 256 ethData.speed = *speed; 257 } 258 } 259 else if (propertyPair.first == "MTU") 260 { 261 const uint32_t* mtuSize = 262 std::get_if<uint32_t>(&propertyPair.second); 263 if (mtuSize != nullptr) 264 { 265 ethData.mtuSize = *mtuSize; 266 } 267 } 268 else if (propertyPair.first == "LinkUp") 269 { 270 const bool* linkUp = 271 std::get_if<bool>(&propertyPair.second); 272 if (linkUp != nullptr) 273 { 274 ethData.linkUp = *linkUp; 275 } 276 } 277 else if (propertyPair.first == "NICEnabled") 278 { 279 const bool* nicEnabled = 280 std::get_if<bool>(&propertyPair.second); 281 if (nicEnabled != nullptr) 282 { 283 ethData.nicEnabled = *nicEnabled; 284 } 285 } 286 else if (propertyPair.first == "Nameservers") 287 { 288 const std::vector<std::string>* nameservers = 289 std::get_if<std::vector<std::string>>( 290 &propertyPair.second); 291 if (nameservers != nullptr) 292 { 293 ethData.nameServers = *nameservers; 294 } 295 } 296 else if (propertyPair.first == "StaticNameServers") 297 { 298 const std::vector<std::string>* staticNameServers = 299 std::get_if<std::vector<std::string>>( 300 &propertyPair.second); 301 if (staticNameServers != nullptr) 302 { 303 ethData.staticNameServers = *staticNameServers; 304 } 305 } 306 else if (propertyPair.first == "DHCPEnabled") 307 { 308 const std::string* dhcpEnabled = 309 std::get_if<std::string>(&propertyPair.second); 310 if (dhcpEnabled != nullptr) 311 { 312 ethData.dhcpEnabled = *dhcpEnabled; 313 } 314 } 315 else if (propertyPair.first == "DomainName") 316 { 317 const std::vector<std::string>* domainNames = 318 std::get_if<std::vector<std::string>>( 319 &propertyPair.second); 320 if (domainNames != nullptr) 321 { 322 ethData.domainnames = *domainNames; 323 } 324 } 325 else if (propertyPair.first == "DefaultGateway") 326 { 327 const std::string* defaultGateway = 328 std::get_if<std::string>(&propertyPair.second); 329 if (defaultGateway != nullptr) 330 { 331 std::string defaultGatewayStr = *defaultGateway; 332 if (defaultGatewayStr.empty()) 333 { 334 ethData.defaultGateway = "0.0.0.0"; 335 } 336 else 337 { 338 ethData.defaultGateway = defaultGatewayStr; 339 } 340 } 341 } 342 else if (propertyPair.first == "DefaultGateway6") 343 { 344 const std::string* defaultGateway6 = 345 std::get_if<std::string>(&propertyPair.second); 346 if (defaultGateway6 != nullptr) 347 { 348 std::string defaultGateway6Str = 349 *defaultGateway6; 350 if (defaultGateway6Str.empty()) 351 { 352 ethData.ipv6DefaultGateway = 353 "0:0:0:0:0:0:0:0"; 354 } 355 else 356 { 357 ethData.ipv6DefaultGateway = 358 defaultGateway6Str; 359 } 360 } 361 } 362 } 363 } 364 } 365 366 if (objpath.first == "/xyz/openbmc_project/network/dhcp") 367 { 368 if (ifacePair.first == 369 "xyz.openbmc_project.Network.DHCPConfiguration") 370 { 371 for (const auto& propertyPair : ifacePair.second) 372 { 373 if (propertyPair.first == "DNSEnabled") 374 { 375 const bool* dnsEnabled = 376 std::get_if<bool>(&propertyPair.second); 377 if (dnsEnabled != nullptr) 378 { 379 ethData.dnsEnabled = *dnsEnabled; 380 } 381 } 382 else if (propertyPair.first == "NTPEnabled") 383 { 384 const bool* ntpEnabled = 385 std::get_if<bool>(&propertyPair.second); 386 if (ntpEnabled != nullptr) 387 { 388 ethData.ntpEnabled = *ntpEnabled; 389 } 390 } 391 else if (propertyPair.first == "HostNameEnabled") 392 { 393 const bool* hostNameEnabled = 394 std::get_if<bool>(&propertyPair.second); 395 if (hostNameEnabled != nullptr) 396 { 397 ethData.hostNameEnabled = *hostNameEnabled; 398 } 399 } 400 } 401 } 402 } 403 // System configuration shows up in the global namespace, so no need 404 // to check eth number 405 if (ifacePair.first == 406 "xyz.openbmc_project.Network.SystemConfiguration") 407 { 408 for (const auto& propertyPair : ifacePair.second) 409 { 410 if (propertyPair.first == "HostName") 411 { 412 const std::string* hostname = 413 std::get_if<std::string>(&propertyPair.second); 414 if (hostname != nullptr) 415 { 416 ethData.hostName = *hostname; 417 } 418 } 419 } 420 } 421 } 422 } 423 return idFound; 424 } 425 426 // Helper function that extracts data for single ethernet ipv6 address 427 inline void 428 extractIPV6Data(const std::string& ethifaceId, 429 const dbus::utility::ManagedObjectType& dbusData, 430 boost::container::flat_set<IPv6AddressData>& ipv6Config) 431 { 432 const std::string ipPathStart = 433 "/xyz/openbmc_project/network/" + ethifaceId; 434 435 // Since there might be several IPv6 configurations aligned with 436 // single ethernet interface, loop over all of them 437 for (const auto& objpath : dbusData) 438 { 439 // Check if proper pattern for object path appears 440 if (objpath.first.str.starts_with(ipPathStart + "/")) 441 { 442 for (const auto& interface : objpath.second) 443 { 444 if (interface.first == "xyz.openbmc_project.Network.IP") 445 { 446 auto type = std::find_if(interface.second.begin(), 447 interface.second.end(), 448 [](const auto& property) { 449 return property.first == "Type"; 450 }); 451 if (type == interface.second.end()) 452 { 453 continue; 454 } 455 456 const std::string* typeStr = 457 std::get_if<std::string>(&type->second); 458 459 if (typeStr == nullptr || 460 (*typeStr != 461 "xyz.openbmc_project.Network.IP.Protocol.IPv6")) 462 { 463 continue; 464 } 465 466 // Instance IPv6AddressData structure, and set as 467 // appropriate 468 std::pair< 469 boost::container::flat_set<IPv6AddressData>::iterator, 470 bool> 471 it = ipv6Config.insert(IPv6AddressData{}); 472 IPv6AddressData& ipv6Address = *it.first; 473 ipv6Address.id = 474 objpath.first.str.substr(ipPathStart.size()); 475 for (const auto& property : interface.second) 476 { 477 if (property.first == "Address") 478 { 479 const std::string* address = 480 std::get_if<std::string>(&property.second); 481 if (address != nullptr) 482 { 483 ipv6Address.address = *address; 484 } 485 } 486 else if (property.first == "Origin") 487 { 488 const std::string* origin = 489 std::get_if<std::string>(&property.second); 490 if (origin != nullptr) 491 { 492 ipv6Address.origin = 493 translateAddressOriginDbusToRedfish(*origin, 494 false); 495 } 496 } 497 else if (property.first == "PrefixLength") 498 { 499 const uint8_t* prefix = 500 std::get_if<uint8_t>(&property.second); 501 if (prefix != nullptr) 502 { 503 ipv6Address.prefixLength = *prefix; 504 } 505 } 506 else if (property.first == "Type" || 507 property.first == "Gateway") 508 { 509 // Type & Gateway is not used 510 } 511 else 512 { 513 BMCWEB_LOG_ERROR 514 << "Got extra property: " << property.first 515 << " on the " << objpath.first.str << " object"; 516 } 517 } 518 } 519 } 520 } 521 } 522 } 523 524 // Helper function that extracts data for single ethernet ipv4 address 525 inline void 526 extractIPData(const std::string& ethifaceId, 527 const dbus::utility::ManagedObjectType& dbusData, 528 boost::container::flat_set<IPv4AddressData>& ipv4Config) 529 { 530 const std::string ipPathStart = 531 "/xyz/openbmc_project/network/" + ethifaceId; 532 533 // Since there might be several IPv4 configurations aligned with 534 // single ethernet interface, loop over all of them 535 for (const auto& objpath : dbusData) 536 { 537 // Check if proper pattern for object path appears 538 if (objpath.first.str.starts_with(ipPathStart + "/")) 539 { 540 for (const auto& interface : objpath.second) 541 { 542 if (interface.first == "xyz.openbmc_project.Network.IP") 543 { 544 auto type = std::find_if(interface.second.begin(), 545 interface.second.end(), 546 [](const auto& property) { 547 return property.first == "Type"; 548 }); 549 if (type == interface.second.end()) 550 { 551 continue; 552 } 553 554 const std::string* typeStr = 555 std::get_if<std::string>(&type->second); 556 557 if (typeStr == nullptr || 558 (*typeStr != 559 "xyz.openbmc_project.Network.IP.Protocol.IPv4")) 560 { 561 continue; 562 } 563 564 // Instance IPv4AddressData structure, and set as 565 // appropriate 566 std::pair< 567 boost::container::flat_set<IPv4AddressData>::iterator, 568 bool> 569 it = ipv4Config.insert(IPv4AddressData{}); 570 IPv4AddressData& ipv4Address = *it.first; 571 ipv4Address.id = 572 objpath.first.str.substr(ipPathStart.size()); 573 for (const auto& property : interface.second) 574 { 575 if (property.first == "Address") 576 { 577 const std::string* address = 578 std::get_if<std::string>(&property.second); 579 if (address != nullptr) 580 { 581 ipv4Address.address = *address; 582 } 583 } 584 else if (property.first == "Origin") 585 { 586 const std::string* origin = 587 std::get_if<std::string>(&property.second); 588 if (origin != nullptr) 589 { 590 ipv4Address.origin = 591 translateAddressOriginDbusToRedfish(*origin, 592 true); 593 } 594 } 595 else if (property.first == "PrefixLength") 596 { 597 const uint8_t* mask = 598 std::get_if<uint8_t>(&property.second); 599 if (mask != nullptr) 600 { 601 // convert it to the string 602 ipv4Address.netmask = getNetmask(*mask); 603 } 604 } 605 else if (property.first == "Type" || 606 property.first == "Gateway") 607 { 608 // Type & Gateway is not used 609 } 610 else 611 { 612 BMCWEB_LOG_ERROR 613 << "Got extra property: " << property.first 614 << " on the " << objpath.first.str << " object"; 615 } 616 } 617 // Check if given address is local, or global 618 ipv4Address.linktype = 619 ipv4Address.address.starts_with("169.254.") 620 ? LinkType::Local 621 : LinkType::Global; 622 } 623 } 624 } 625 } 626 } 627 628 /** 629 * @brief Deletes given IPv4 interface 630 * 631 * @param[in] ifaceId Id of interface whose IP should be deleted 632 * @param[in] ipHash DBus Hash id of IP that should be deleted 633 * @param[io] asyncResp Response object that will be returned to client 634 * 635 * @return None 636 */ 637 inline void deleteIPv4(const std::string& ifaceId, const std::string& ipHash, 638 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 639 { 640 crow::connections::systemBus->async_method_call( 641 [asyncResp](const boost::system::error_code& ec) { 642 if (ec) 643 { 644 messages::internalError(asyncResp->res); 645 } 646 }, 647 "xyz.openbmc_project.Network", 648 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 649 "xyz.openbmc_project.Object.Delete", "Delete"); 650 } 651 652 inline void updateIPv4DefaultGateway( 653 const std::string& ifaceId, const std::string& gateway, 654 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 655 { 656 crow::connections::systemBus->async_method_call( 657 [asyncResp](const boost::system::error_code& ec) { 658 if (ec) 659 { 660 messages::internalError(asyncResp->res); 661 return; 662 } 663 asyncResp->res.result(boost::beast::http::status::no_content); 664 }, 665 "xyz.openbmc_project.Network", 666 "/xyz/openbmc_project/network/" + ifaceId, 667 "org.freedesktop.DBus.Properties", "Set", 668 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 669 dbus::utility::DbusVariantType(gateway)); 670 } 671 /** 672 * @brief Creates a static IPv4 entry 673 * 674 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 675 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 676 * @param[in] gateway IPv4 address of this interfaces gateway 677 * @param[in] address IPv4 address to assign to this interface 678 * @param[io] asyncResp Response object that will be returned to client 679 * 680 * @return None 681 */ 682 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 683 const std::string& gateway, const std::string& address, 684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 685 { 686 auto createIpHandler = 687 [asyncResp, ifaceId, gateway](const boost::system::error_code& ec) { 688 if (ec) 689 { 690 messages::internalError(asyncResp->res); 691 return; 692 } 693 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 694 }; 695 696 crow::connections::systemBus->async_method_call( 697 std::move(createIpHandler), "xyz.openbmc_project.Network", 698 "/xyz/openbmc_project/network/" + ifaceId, 699 "xyz.openbmc_project.Network.IP.Create", "IP", 700 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 701 gateway); 702 } 703 704 /** 705 * @brief Deletes the IPv4 entry for this interface and creates a replacement 706 * static IPv4 entry 707 * 708 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 709 * @param[in] id The unique hash entry identifying the DBus entry 710 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 711 * @param[in] gateway IPv4 address of this interfaces gateway 712 * @param[in] address IPv4 address to assign to this interface 713 * @param[io] asyncResp Response object that will be returned to client 714 * 715 * @return None 716 */ 717 inline void 718 deleteAndCreateIPv4(const std::string& ifaceId, const std::string& id, 719 uint8_t prefixLength, const std::string& gateway, 720 const std::string& address, 721 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 722 { 723 crow::connections::systemBus->async_method_call( 724 [asyncResp, ifaceId, address, prefixLength, 725 gateway](const boost::system::error_code& ec) { 726 if (ec) 727 { 728 messages::internalError(asyncResp->res); 729 return; 730 } 731 732 crow::connections::systemBus->async_method_call( 733 [asyncResp, ifaceId, 734 gateway](const boost::system::error_code& ec2) { 735 if (ec2) 736 { 737 messages::internalError(asyncResp->res); 738 return; 739 } 740 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 741 }, 742 "xyz.openbmc_project.Network", 743 "/xyz/openbmc_project/network/" + ifaceId, 744 "xyz.openbmc_project.Network.IP.Create", "IP", 745 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 746 prefixLength, gateway); 747 }, 748 "xyz.openbmc_project.Network", 749 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 750 "xyz.openbmc_project.Object.Delete", "Delete"); 751 } 752 753 /** 754 * @brief Deletes given IPv6 755 * 756 * @param[in] ifaceId Id of interface whose IP should be deleted 757 * @param[in] ipHash DBus Hash id of IP that should be deleted 758 * @param[io] asyncResp Response object that will be returned to client 759 * 760 * @return None 761 */ 762 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash, 763 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 764 { 765 crow::connections::systemBus->async_method_call( 766 [asyncResp](const boost::system::error_code& ec) { 767 if (ec) 768 { 769 messages::internalError(asyncResp->res); 770 } 771 }, 772 "xyz.openbmc_project.Network", 773 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 774 "xyz.openbmc_project.Object.Delete", "Delete"); 775 } 776 777 /** 778 * @brief Deletes the IPv6 entry for this interface and creates a replacement 779 * static IPv6 entry 780 * 781 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 782 * @param[in] id The unique hash entry identifying the DBus entry 783 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 784 * @param[in] address IPv6 address to assign to this interface 785 * @param[io] asyncResp Response object that will be returned to client 786 * 787 * @return None 788 */ 789 inline void 790 deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id, 791 uint8_t prefixLength, const std::string& address, 792 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 793 { 794 crow::connections::systemBus->async_method_call( 795 [asyncResp, ifaceId, address, 796 prefixLength](const boost::system::error_code& ec) { 797 if (ec) 798 { 799 messages::internalError(asyncResp->res); 800 } 801 crow::connections::systemBus->async_method_call( 802 [asyncResp](const boost::system::error_code& ec2) { 803 if (ec2) 804 { 805 messages::internalError(asyncResp->res); 806 } 807 }, 808 "xyz.openbmc_project.Network", 809 "/xyz/openbmc_project/network/" + ifaceId, 810 "xyz.openbmc_project.Network.IP.Create", "IP", 811 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 812 prefixLength, ""); 813 }, 814 "xyz.openbmc_project.Network", 815 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 816 "xyz.openbmc_project.Object.Delete", "Delete"); 817 } 818 819 /** 820 * @brief Creates IPv6 with given data 821 * 822 * @param[in] ifaceId Id of interface whose IP should be added 823 * @param[in] prefixLength Prefix length that needs to be added 824 * @param[in] address IP address that needs to be added 825 * @param[io] asyncResp Response object that will be returned to client 826 * 827 * @return None 828 */ 829 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 830 const std::string& address, 831 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 832 { 833 auto createIpHandler = [asyncResp](const boost::system::error_code& ec) { 834 if (ec) 835 { 836 messages::internalError(asyncResp->res); 837 } 838 }; 839 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 840 // does not have associated gateway property 841 crow::connections::systemBus->async_method_call( 842 std::move(createIpHandler), "xyz.openbmc_project.Network", 843 "/xyz/openbmc_project/network/" + ifaceId, 844 "xyz.openbmc_project.Network.IP.Create", "IP", 845 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 846 ""); 847 } 848 849 /** 850 * Function that retrieves all properties for given Ethernet Interface 851 * Object 852 * from EntityManager Network Manager 853 * @param ethiface_id a eth interface id to query on DBus 854 * @param callback a function that shall be called to convert Dbus output 855 * into JSON 856 */ 857 template <typename CallbackFunc> 858 void getEthernetIfaceData(const std::string& ethifaceId, 859 CallbackFunc&& callback) 860 { 861 crow::connections::systemBus->async_method_call( 862 [ethifaceId{std::string{ethifaceId}}, 863 callback{std::forward<CallbackFunc>(callback)}]( 864 const boost::system::error_code& errorCode, 865 const dbus::utility::ManagedObjectType& resp) { 866 EthernetInterfaceData ethData{}; 867 boost::container::flat_set<IPv4AddressData> ipv4Data; 868 boost::container::flat_set<IPv6AddressData> ipv6Data; 869 870 if (errorCode) 871 { 872 callback(false, ethData, ipv4Data, ipv6Data); 873 return; 874 } 875 876 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 877 if (!found) 878 { 879 callback(false, ethData, ipv4Data, ipv6Data); 880 return; 881 } 882 883 extractIPData(ethifaceId, resp, ipv4Data); 884 // Fix global GW 885 for (IPv4AddressData& ipv4 : ipv4Data) 886 { 887 if (((ipv4.linktype == LinkType::Global) && 888 (ipv4.gateway == "0.0.0.0")) || 889 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 890 { 891 ipv4.gateway = ethData.defaultGateway; 892 } 893 } 894 895 extractIPV6Data(ethifaceId, resp, ipv6Data); 896 // Finally make a callback with useful data 897 callback(true, ethData, ipv4Data, ipv6Data); 898 }, 899 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 900 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 901 } 902 903 /** 904 * Function that retrieves all Ethernet Interfaces available through Network 905 * Manager 906 * @param callback a function that shall be called to convert Dbus output 907 * into JSON. 908 */ 909 template <typename CallbackFunc> 910 void getEthernetIfaceList(CallbackFunc&& callback) 911 { 912 crow::connections::systemBus->async_method_call( 913 [callback{std::forward<CallbackFunc>(callback)}]( 914 const boost::system::error_code& errorCode, 915 dbus::utility::ManagedObjectType& resp) { 916 // Callback requires vector<string> to retrieve all available 917 // ethernet interfaces 918 boost::container::flat_set<std::string> ifaceList; 919 ifaceList.reserve(resp.size()); 920 if (errorCode) 921 { 922 callback(false, ifaceList); 923 return; 924 } 925 926 // Iterate over all retrieved ObjectPaths. 927 for (const auto& objpath : resp) 928 { 929 // And all interfaces available for certain ObjectPath. 930 for (const auto& interface : objpath.second) 931 { 932 // If interface is 933 // xyz.openbmc_project.Network.EthernetInterface, this is 934 // what we're looking for. 935 if (interface.first == 936 "xyz.openbmc_project.Network.EthernetInterface") 937 { 938 std::string ifaceId = objpath.first.filename(); 939 if (ifaceId.empty()) 940 { 941 continue; 942 } 943 // and put it into output vector. 944 ifaceList.emplace(ifaceId); 945 } 946 } 947 } 948 // Finally make a callback with useful data 949 callback(true, ifaceList); 950 }, 951 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 952 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 953 } 954 955 inline void 956 handleHostnamePatch(const std::string& hostname, 957 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 958 { 959 // SHOULD handle host names of up to 255 characters(RFC 1123) 960 if (hostname.length() > 255) 961 { 962 messages::propertyValueFormatError(asyncResp->res, hostname, 963 "HostName"); 964 return; 965 } 966 crow::connections::systemBus->async_method_call( 967 [asyncResp](const boost::system::error_code& ec) { 968 if (ec) 969 { 970 messages::internalError(asyncResp->res); 971 } 972 }, 973 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 974 "org.freedesktop.DBus.Properties", "Set", 975 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 976 dbus::utility::DbusVariantType(hostname)); 977 } 978 979 inline void 980 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 981 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 982 { 983 sdbusplus::message::object_path objPath = 984 "/xyz/openbmc_project/network/" + ifaceId; 985 crow::connections::systemBus->async_method_call( 986 [asyncResp](const boost::system::error_code& ec) { 987 if (ec) 988 { 989 messages::internalError(asyncResp->res); 990 } 991 }, 992 "xyz.openbmc_project.Network", objPath, 993 "org.freedesktop.DBus.Properties", "Set", 994 "xyz.openbmc_project.Network.EthernetInterface", "MTU", 995 std::variant<size_t>(mtuSize)); 996 } 997 998 inline void 999 handleDomainnamePatch(const std::string& ifaceId, 1000 const std::string& domainname, 1001 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1002 { 1003 std::vector<std::string> vectorDomainname = {domainname}; 1004 crow::connections::systemBus->async_method_call( 1005 [asyncResp](const boost::system::error_code& ec) { 1006 if (ec) 1007 { 1008 messages::internalError(asyncResp->res); 1009 } 1010 }, 1011 "xyz.openbmc_project.Network", 1012 "/xyz/openbmc_project/network/" + ifaceId, 1013 "org.freedesktop.DBus.Properties", "Set", 1014 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 1015 dbus::utility::DbusVariantType(vectorDomainname)); 1016 } 1017 1018 inline bool isHostnameValid(const std::string& hostname) 1019 { 1020 // A valid host name can never have the dotted-decimal form (RFC 1123) 1021 if (std::all_of(hostname.begin(), hostname.end(), ::isdigit)) 1022 { 1023 return false; 1024 } 1025 // Each label(hostname/subdomains) within a valid FQDN 1026 // MUST handle host names of up to 63 characters (RFC 1123) 1027 // labels cannot start or end with hyphens (RFC 952) 1028 // labels can start with numbers (RFC 1123) 1029 const std::regex pattern( 1030 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 1031 1032 return std::regex_match(hostname, pattern); 1033 } 1034 1035 inline bool isDomainnameValid(const std::string& domainname) 1036 { 1037 // Can have multiple subdomains 1038 // Top Level Domain's min length is 2 character 1039 const std::regex pattern( 1040 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 1041 1042 return std::regex_match(domainname, pattern); 1043 } 1044 1045 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1046 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1047 { 1048 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1049 if (fqdn.length() > 255) 1050 { 1051 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1052 return; 1053 } 1054 1055 size_t pos = fqdn.find('.'); 1056 if (pos == std::string::npos) 1057 { 1058 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1059 return; 1060 } 1061 1062 std::string hostname; 1063 std::string domainname; 1064 domainname = (fqdn).substr(pos + 1); 1065 hostname = (fqdn).substr(0, pos); 1066 1067 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1068 { 1069 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1070 return; 1071 } 1072 1073 handleHostnamePatch(hostname, asyncResp); 1074 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1075 } 1076 1077 inline void 1078 handleMACAddressPatch(const std::string& ifaceId, 1079 const std::string& macAddress, 1080 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1081 { 1082 static constexpr std::string_view dbusNotAllowedError = 1083 "xyz.openbmc_project.Common.Error.NotAllowed"; 1084 1085 crow::connections::systemBus->async_method_call( 1086 [asyncResp, macAddress](const boost::system::error_code& ec, 1087 const sdbusplus::message_t& msg) { 1088 if (ec) 1089 { 1090 const sd_bus_error* err = msg.get_error(); 1091 if (err == nullptr) 1092 { 1093 messages::internalError(asyncResp->res); 1094 return; 1095 } 1096 if (err->name == dbusNotAllowedError) 1097 { 1098 messages::propertyNotWritable(asyncResp->res, "MACAddress"); 1099 return; 1100 } 1101 messages::internalError(asyncResp->res); 1102 return; 1103 } 1104 }, 1105 "xyz.openbmc_project.Network", 1106 "/xyz/openbmc_project/network/" + ifaceId, 1107 "org.freedesktop.DBus.Properties", "Set", 1108 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1109 dbus::utility::DbusVariantType(macAddress)); 1110 } 1111 1112 inline void setDHCPEnabled(const std::string& ifaceId, 1113 const std::string& propertyName, const bool v4Value, 1114 const bool v6Value, 1115 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1116 { 1117 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1118 crow::connections::systemBus->async_method_call( 1119 [asyncResp](const boost::system::error_code& ec) { 1120 if (ec) 1121 { 1122 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1123 messages::internalError(asyncResp->res); 1124 return; 1125 } 1126 messages::success(asyncResp->res); 1127 }, 1128 "xyz.openbmc_project.Network", 1129 "/xyz/openbmc_project/network/" + ifaceId, 1130 "org.freedesktop.DBus.Properties", "Set", 1131 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1132 dbus::utility::DbusVariantType{dhcp}); 1133 } 1134 1135 inline void setEthernetInterfaceBoolProperty( 1136 const std::string& ifaceId, const std::string& propertyName, 1137 const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1138 { 1139 crow::connections::systemBus->async_method_call( 1140 [asyncResp](const boost::system::error_code& ec) { 1141 if (ec) 1142 { 1143 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1144 messages::internalError(asyncResp->res); 1145 return; 1146 } 1147 }, 1148 "xyz.openbmc_project.Network", 1149 "/xyz/openbmc_project/network/" + ifaceId, 1150 "org.freedesktop.DBus.Properties", "Set", 1151 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1152 dbus::utility::DbusVariantType{value}); 1153 } 1154 1155 inline void setDHCPv4Config(const std::string& propertyName, const bool& value, 1156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1157 { 1158 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1159 crow::connections::systemBus->async_method_call( 1160 [asyncResp](const boost::system::error_code& ec) { 1161 if (ec) 1162 { 1163 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1164 messages::internalError(asyncResp->res); 1165 return; 1166 } 1167 }, 1168 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/dhcp", 1169 "org.freedesktop.DBus.Properties", "Set", 1170 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1171 dbus::utility::DbusVariantType{value}); 1172 } 1173 1174 inline void handleDHCPPatch(const std::string& ifaceId, 1175 const EthernetInterfaceData& ethData, 1176 const DHCPParameters& v4dhcpParms, 1177 const DHCPParameters& v6dhcpParms, 1178 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1179 { 1180 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1181 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1182 1183 bool nextv4DHCPState = 1184 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1185 1186 bool nextv6DHCPState{}; 1187 if (v6dhcpParms.dhcpv6OperatingMode) 1188 { 1189 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1190 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1191 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1192 { 1193 messages::propertyValueFormatError(asyncResp->res, 1194 *v6dhcpParms.dhcpv6OperatingMode, 1195 "OperatingMode"); 1196 return; 1197 } 1198 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1199 } 1200 else 1201 { 1202 nextv6DHCPState = ipv6Active; 1203 } 1204 1205 bool nextDNS{}; 1206 if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers) 1207 { 1208 if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers) 1209 { 1210 messages::generalError(asyncResp->res); 1211 return; 1212 } 1213 nextDNS = *v4dhcpParms.useDnsServers; 1214 } 1215 else if (v4dhcpParms.useDnsServers) 1216 { 1217 nextDNS = *v4dhcpParms.useDnsServers; 1218 } 1219 else if (v6dhcpParms.useDnsServers) 1220 { 1221 nextDNS = *v6dhcpParms.useDnsServers; 1222 } 1223 else 1224 { 1225 nextDNS = ethData.dnsEnabled; 1226 } 1227 1228 bool nextNTP{}; 1229 if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers) 1230 { 1231 if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers) 1232 { 1233 messages::generalError(asyncResp->res); 1234 return; 1235 } 1236 nextNTP = *v4dhcpParms.useNtpServers; 1237 } 1238 else if (v4dhcpParms.useNtpServers) 1239 { 1240 nextNTP = *v4dhcpParms.useNtpServers; 1241 } 1242 else if (v6dhcpParms.useNtpServers) 1243 { 1244 nextNTP = *v6dhcpParms.useNtpServers; 1245 } 1246 else 1247 { 1248 nextNTP = ethData.ntpEnabled; 1249 } 1250 1251 bool nextUseDomain{}; 1252 if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName) 1253 { 1254 if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName) 1255 { 1256 messages::generalError(asyncResp->res); 1257 return; 1258 } 1259 nextUseDomain = *v4dhcpParms.useDomainName; 1260 } 1261 else if (v4dhcpParms.useDomainName) 1262 { 1263 nextUseDomain = *v4dhcpParms.useDomainName; 1264 } 1265 else if (v6dhcpParms.useDomainName) 1266 { 1267 nextUseDomain = *v6dhcpParms.useDomainName; 1268 } 1269 else 1270 { 1271 nextUseDomain = ethData.hostNameEnabled; 1272 } 1273 1274 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1275 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1276 asyncResp); 1277 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1278 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1279 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1280 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1281 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1282 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1283 } 1284 1285 inline boost::container::flat_set<IPv4AddressData>::const_iterator 1286 getNextStaticIpEntry( 1287 const boost::container::flat_set<IPv4AddressData>::const_iterator& head, 1288 const boost::container::flat_set<IPv4AddressData>::const_iterator& end) 1289 { 1290 return std::find_if(head, end, [](const IPv4AddressData& value) { 1291 return value.origin == "Static"; 1292 }); 1293 } 1294 1295 inline boost::container::flat_set<IPv6AddressData>::const_iterator 1296 getNextStaticIpEntry( 1297 const boost::container::flat_set<IPv6AddressData>::const_iterator& head, 1298 const boost::container::flat_set<IPv6AddressData>::const_iterator& end) 1299 { 1300 return std::find_if(head, end, [](const IPv6AddressData& value) { 1301 return value.origin == "Static"; 1302 }); 1303 } 1304 1305 inline void handleIPv4StaticPatch( 1306 const std::string& ifaceId, nlohmann::json& input, 1307 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1308 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1309 { 1310 if ((!input.is_array()) || input.empty()) 1311 { 1312 messages::propertyValueTypeError( 1313 asyncResp->res, 1314 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1315 "IPv4StaticAddresses"); 1316 return; 1317 } 1318 1319 unsigned entryIdx = 1; 1320 // Find the first static IP address currently active on the NIC and 1321 // match it to the first JSON element in the IPv4StaticAddresses array. 1322 // Match each subsequent JSON element to the next static IP programmed 1323 // into the NIC. 1324 boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry = 1325 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1326 1327 for (nlohmann::json& thisJson : input) 1328 { 1329 std::string pathString = 1330 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1331 1332 if (!thisJson.is_null() && !thisJson.empty()) 1333 { 1334 std::optional<std::string> address; 1335 std::optional<std::string> subnetMask; 1336 std::optional<std::string> gateway; 1337 1338 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1339 address, "SubnetMask", subnetMask, 1340 "Gateway", gateway)) 1341 { 1342 messages::propertyValueFormatError( 1343 asyncResp->res, 1344 thisJson.dump(2, ' ', true, 1345 nlohmann::json::error_handler_t::replace), 1346 pathString); 1347 return; 1348 } 1349 1350 // Find the address/subnet/gateway values. Any values that are 1351 // not explicitly provided are assumed to be unmodified from the 1352 // current state of the interface. Merge existing state into the 1353 // current request. 1354 const std::string* addr = nullptr; 1355 const std::string* gw = nullptr; 1356 uint8_t prefixLength = 0; 1357 bool errorInEntry = false; 1358 if (address) 1359 { 1360 if (ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1361 { 1362 addr = &(*address); 1363 } 1364 else 1365 { 1366 messages::propertyValueFormatError(asyncResp->res, *address, 1367 pathString + "/Address"); 1368 errorInEntry = true; 1369 } 1370 } 1371 else if (nicIpEntry != ipv4Data.cend()) 1372 { 1373 addr = &(nicIpEntry->address); 1374 } 1375 else 1376 { 1377 messages::propertyMissing(asyncResp->res, 1378 pathString + "/Address"); 1379 errorInEntry = true; 1380 } 1381 1382 if (subnetMask) 1383 { 1384 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1385 &prefixLength)) 1386 { 1387 messages::propertyValueFormatError( 1388 asyncResp->res, *subnetMask, 1389 pathString + "/SubnetMask"); 1390 errorInEntry = true; 1391 } 1392 } 1393 else if (nicIpEntry != ipv4Data.cend()) 1394 { 1395 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1396 &prefixLength)) 1397 { 1398 messages::propertyValueFormatError( 1399 asyncResp->res, nicIpEntry->netmask, 1400 pathString + "/SubnetMask"); 1401 errorInEntry = true; 1402 } 1403 } 1404 else 1405 { 1406 messages::propertyMissing(asyncResp->res, 1407 pathString + "/SubnetMask"); 1408 errorInEntry = true; 1409 } 1410 1411 if (gateway) 1412 { 1413 if (ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1414 { 1415 gw = &(*gateway); 1416 } 1417 else 1418 { 1419 messages::propertyValueFormatError(asyncResp->res, *gateway, 1420 pathString + "/Gateway"); 1421 errorInEntry = true; 1422 } 1423 } 1424 else if (nicIpEntry != ipv4Data.cend()) 1425 { 1426 gw = &nicIpEntry->gateway; 1427 } 1428 else 1429 { 1430 messages::propertyMissing(asyncResp->res, 1431 pathString + "/Gateway"); 1432 errorInEntry = true; 1433 } 1434 1435 if (errorInEntry) 1436 { 1437 return; 1438 } 1439 1440 if (nicIpEntry != ipv4Data.cend()) 1441 { 1442 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw, 1443 *addr, asyncResp); 1444 nicIpEntry = 1445 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1446 } 1447 else 1448 { 1449 createIPv4(ifaceId, prefixLength, *gateway, *address, 1450 asyncResp); 1451 } 1452 entryIdx++; 1453 } 1454 else 1455 { 1456 if (nicIpEntry == ipv4Data.cend()) 1457 { 1458 // Requesting a DELETE/DO NOT MODIFY action for an item 1459 // that isn't present on the eth(n) interface. Input JSON is 1460 // in error, so bail out. 1461 if (thisJson.is_null()) 1462 { 1463 messages::resourceCannotBeDeleted(asyncResp->res); 1464 return; 1465 } 1466 messages::propertyValueFormatError( 1467 asyncResp->res, 1468 thisJson.dump(2, ' ', true, 1469 nlohmann::json::error_handler_t::replace), 1470 pathString); 1471 return; 1472 } 1473 1474 if (thisJson.is_null()) 1475 { 1476 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp); 1477 } 1478 if (nicIpEntry != ipv4Data.cend()) 1479 { 1480 nicIpEntry = 1481 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1482 } 1483 entryIdx++; 1484 } 1485 } 1486 } 1487 1488 inline void handleStaticNameServersPatch( 1489 const std::string& ifaceId, 1490 const std::vector<std::string>& updatedStaticNameServers, 1491 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1492 { 1493 crow::connections::systemBus->async_method_call( 1494 [asyncResp](const boost::system::error_code& ec) { 1495 if (ec) 1496 { 1497 messages::internalError(asyncResp->res); 1498 return; 1499 } 1500 }, 1501 "xyz.openbmc_project.Network", 1502 "/xyz/openbmc_project/network/" + ifaceId, 1503 "org.freedesktop.DBus.Properties", "Set", 1504 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1505 dbus::utility::DbusVariantType{updatedStaticNameServers}); 1506 } 1507 1508 inline void handleIPv6StaticAddressesPatch( 1509 const std::string& ifaceId, const nlohmann::json& input, 1510 const boost::container::flat_set<IPv6AddressData>& ipv6Data, 1511 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1512 { 1513 if (!input.is_array() || input.empty()) 1514 { 1515 messages::propertyValueTypeError( 1516 asyncResp->res, 1517 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1518 "IPv6StaticAddresses"); 1519 return; 1520 } 1521 size_t entryIdx = 1; 1522 boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry = 1523 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1524 for (const nlohmann::json& thisJson : input) 1525 { 1526 std::string pathString = 1527 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1528 1529 if (!thisJson.is_null() && !thisJson.empty()) 1530 { 1531 std::optional<std::string> address; 1532 std::optional<uint8_t> prefixLength; 1533 nlohmann::json thisJsonCopy = thisJson; 1534 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1535 address, "PrefixLength", prefixLength)) 1536 { 1537 messages::propertyValueFormatError( 1538 asyncResp->res, 1539 thisJson.dump(2, ' ', true, 1540 nlohmann::json::error_handler_t::replace), 1541 pathString); 1542 return; 1543 } 1544 1545 const std::string* addr = nullptr; 1546 uint8_t prefix = 0; 1547 1548 // Find the address and prefixLength values. Any values that are 1549 // not explicitly provided are assumed to be unmodified from the 1550 // current state of the interface. Merge existing state into the 1551 // current request. 1552 if (address) 1553 { 1554 addr = &(*address); 1555 } 1556 else if (nicIpEntry != ipv6Data.end()) 1557 { 1558 addr = &(nicIpEntry->address); 1559 } 1560 else 1561 { 1562 messages::propertyMissing(asyncResp->res, 1563 pathString + "/Address"); 1564 return; 1565 } 1566 1567 if (prefixLength) 1568 { 1569 prefix = *prefixLength; 1570 } 1571 else if (nicIpEntry != ipv6Data.end()) 1572 { 1573 prefix = nicIpEntry->prefixLength; 1574 } 1575 else 1576 { 1577 messages::propertyMissing(asyncResp->res, 1578 pathString + "/PrefixLength"); 1579 return; 1580 } 1581 1582 if (nicIpEntry != ipv6Data.end()) 1583 { 1584 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr, 1585 asyncResp); 1586 nicIpEntry = 1587 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1588 } 1589 else 1590 { 1591 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1592 } 1593 entryIdx++; 1594 } 1595 else 1596 { 1597 if (nicIpEntry == ipv6Data.end()) 1598 { 1599 // Requesting a DELETE/DO NOT MODIFY action for an item 1600 // that isn't present on the eth(n) interface. Input JSON is 1601 // in error, so bail out. 1602 if (thisJson.is_null()) 1603 { 1604 messages::resourceCannotBeDeleted(asyncResp->res); 1605 return; 1606 } 1607 messages::propertyValueFormatError( 1608 asyncResp->res, 1609 thisJson.dump(2, ' ', true, 1610 nlohmann::json::error_handler_t::replace), 1611 pathString); 1612 return; 1613 } 1614 1615 if (thisJson.is_null()) 1616 { 1617 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp); 1618 } 1619 if (nicIpEntry != ipv6Data.cend()) 1620 { 1621 nicIpEntry = 1622 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1623 } 1624 entryIdx++; 1625 } 1626 } 1627 } 1628 1629 inline void parseInterfaceData( 1630 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1631 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1632 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1633 const boost::container::flat_set<IPv6AddressData>& ipv6Data) 1634 { 1635 constexpr std::array<std::string_view, 1> inventoryForEthernet = { 1636 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1637 1638 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1639 jsonResponse["Id"] = ifaceId; 1640 jsonResponse["@odata.id"] = crow::utility::urlFromPieces( 1641 "redfish", "v1", "Managers", "bmc", "EthernetInterfaces", ifaceId); 1642 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1643 1644 auto health = std::make_shared<HealthPopulate>(asyncResp); 1645 1646 dbus::utility::getSubTreePaths( 1647 "/", 0, inventoryForEthernet, 1648 [health](const boost::system::error_code& ec, 1649 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1650 if (ec) 1651 { 1652 return; 1653 } 1654 1655 health->inventory = resp; 1656 }); 1657 1658 health->populate(); 1659 1660 if (ethData.nicEnabled) 1661 { 1662 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1663 jsonResponse["Status"]["State"] = "Enabled"; 1664 } 1665 else 1666 { 1667 jsonResponse["LinkStatus"] = "NoLink"; 1668 jsonResponse["Status"]["State"] = "Disabled"; 1669 } 1670 1671 jsonResponse["SpeedMbps"] = ethData.speed; 1672 jsonResponse["MTUSize"] = ethData.mtuSize; 1673 jsonResponse["MACAddress"] = ethData.macAddress; 1674 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1675 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1676 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled; 1677 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled; 1678 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled; 1679 1680 jsonResponse["DHCPv6"]["OperatingMode"] = 1681 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful" 1682 : "Disabled"; 1683 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled; 1684 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled; 1685 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled; 1686 1687 if (!ethData.hostName.empty()) 1688 { 1689 jsonResponse["HostName"] = ethData.hostName; 1690 1691 // When domain name is empty then it means, that it is a network 1692 // without domain names, and the host name itself must be treated as 1693 // FQDN 1694 std::string fqdn = ethData.hostName; 1695 if (!ethData.domainnames.empty()) 1696 { 1697 fqdn += "." + ethData.domainnames[0]; 1698 } 1699 jsonResponse["FQDN"] = fqdn; 1700 } 1701 1702 jsonResponse["VLANs"]["@odata.id"] = 1703 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 1704 "EthernetInterfaces", ifaceId, "VLANs"); 1705 1706 jsonResponse["NameServers"] = ethData.nameServers; 1707 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1708 1709 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1710 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1711 ipv4Array = nlohmann::json::array(); 1712 ipv4StaticArray = nlohmann::json::array(); 1713 for (const auto& ipv4Config : ipv4Data) 1714 { 1715 std::string gatewayStr = ipv4Config.gateway; 1716 if (gatewayStr.empty()) 1717 { 1718 gatewayStr = "0.0.0.0"; 1719 } 1720 nlohmann::json::object_t ipv4; 1721 ipv4["AddressOrigin"] = ipv4Config.origin; 1722 ipv4["SubnetMask"] = ipv4Config.netmask; 1723 ipv4["Address"] = ipv4Config.address; 1724 ipv4["Gateway"] = gatewayStr; 1725 1726 if (ipv4Config.origin == "Static") 1727 { 1728 ipv4StaticArray.push_back(ipv4); 1729 } 1730 1731 ipv4Array.push_back(std::move(ipv4)); 1732 } 1733 1734 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1735 if (ipv6GatewayStr.empty()) 1736 { 1737 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1738 } 1739 1740 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1741 1742 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1743 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1744 ipv6Array = nlohmann::json::array(); 1745 ipv6StaticArray = nlohmann::json::array(); 1746 nlohmann::json& ipv6AddrPolicyTable = 1747 jsonResponse["IPv6AddressPolicyTable"]; 1748 ipv6AddrPolicyTable = nlohmann::json::array(); 1749 for (const auto& ipv6Config : ipv6Data) 1750 { 1751 nlohmann::json::object_t ipv6; 1752 ipv6["Address"] = ipv6Config.address; 1753 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1754 ipv6["AddressOrigin"] = ipv6Config.origin; 1755 ipv6["AddressState"] = nullptr; 1756 ipv6Array.push_back(std::move(ipv6)); 1757 if (ipv6Config.origin == "Static") 1758 { 1759 nlohmann::json::object_t ipv6Static; 1760 ipv6Static["Address"] = ipv6Config.address; 1761 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1762 ipv6StaticArray.push_back(std::move(ipv6Static)); 1763 } 1764 } 1765 } 1766 1767 inline bool verifyNames(const std::string& parent, const std::string& iface) 1768 { 1769 return iface.starts_with(parent + "_"); 1770 } 1771 1772 inline void requestEthernetInterfacesRoutes(App& app) 1773 { 1774 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1775 .privileges(redfish::privileges::getEthernetInterfaceCollection) 1776 .methods(boost::beast::http::verb::get)( 1777 [&app](const crow::Request& req, 1778 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1779 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1780 { 1781 return; 1782 } 1783 1784 asyncResp->res.jsonValue["@odata.type"] = 1785 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1786 asyncResp->res.jsonValue["@odata.id"] = 1787 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1788 asyncResp->res.jsonValue["Name"] = 1789 "Ethernet Network Interface Collection"; 1790 asyncResp->res.jsonValue["Description"] = 1791 "Collection of EthernetInterfaces for this Manager"; 1792 1793 // Get eth interface list, and call the below callback for JSON 1794 // preparation 1795 getEthernetIfaceList( 1796 [asyncResp]( 1797 const bool& success, 1798 const boost::container::flat_set<std::string>& ifaceList) { 1799 if (!success) 1800 { 1801 messages::internalError(asyncResp->res); 1802 return; 1803 } 1804 1805 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 1806 ifaceArray = nlohmann::json::array(); 1807 std::string tag = "_"; 1808 for (const std::string& ifaceItem : ifaceList) 1809 { 1810 std::size_t found = ifaceItem.find(tag); 1811 if (found == std::string::npos) 1812 { 1813 nlohmann::json::object_t iface; 1814 iface["@odata.id"] = crow::utility::urlFromPieces( 1815 "redfish", "v1", "Managers", "bmc", 1816 "EthernetInterfaces", ifaceItem); 1817 ifaceArray.push_back(std::move(iface)); 1818 } 1819 } 1820 1821 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 1822 asyncResp->res.jsonValue["@odata.id"] = 1823 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1824 }); 1825 }); 1826 1827 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1828 .privileges(redfish::privileges::getEthernetInterface) 1829 .methods(boost::beast::http::verb::get)( 1830 [&app](const crow::Request& req, 1831 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1832 const std::string& ifaceId) { 1833 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1834 { 1835 return; 1836 } 1837 getEthernetIfaceData( 1838 ifaceId, 1839 [asyncResp, ifaceId]( 1840 const bool& success, const EthernetInterfaceData& ethData, 1841 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1842 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1843 if (!success) 1844 { 1845 // TODO(Pawel)consider distinguish between non 1846 // existing object, and other errors 1847 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1848 ifaceId); 1849 return; 1850 } 1851 1852 // Keep using the v1.6.0 schema here as currently bmcweb have to use 1853 // "VLANs" property deprecated in v1.7.0 for VLAN creation/deletion. 1854 asyncResp->res.jsonValue["@odata.type"] = 1855 "#EthernetInterface.v1_6_0.EthernetInterface"; 1856 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1857 asyncResp->res.jsonValue["Description"] = 1858 "Management Network Interface"; 1859 1860 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data); 1861 }); 1862 }); 1863 1864 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1865 .privileges(redfish::privileges::patchEthernetInterface) 1866 .methods(boost::beast::http::verb::patch)( 1867 [&app](const crow::Request& req, 1868 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1869 const std::string& ifaceId) { 1870 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1871 { 1872 return; 1873 } 1874 std::optional<std::string> hostname; 1875 std::optional<std::string> fqdn; 1876 std::optional<std::string> macAddress; 1877 std::optional<std::string> ipv6DefaultGateway; 1878 std::optional<nlohmann::json> ipv4StaticAddresses; 1879 std::optional<nlohmann::json> ipv6StaticAddresses; 1880 std::optional<std::vector<std::string>> staticNameServers; 1881 std::optional<nlohmann::json> dhcpv4; 1882 std::optional<nlohmann::json> dhcpv6; 1883 std::optional<bool> interfaceEnabled; 1884 std::optional<size_t> mtuSize; 1885 DHCPParameters v4dhcpParms; 1886 DHCPParameters v6dhcpParms; 1887 1888 if (!json_util::readJsonPatch( 1889 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1890 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1891 macAddress, "StaticNameServers", staticNameServers, 1892 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1893 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1894 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled)) 1895 { 1896 return; 1897 } 1898 if (dhcpv4) 1899 { 1900 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 1901 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1902 v4dhcpParms.useDnsServers, "UseNTPServers", 1903 v4dhcpParms.useNtpServers, "UseDomainName", 1904 v4dhcpParms.useDomainName)) 1905 { 1906 return; 1907 } 1908 } 1909 1910 if (dhcpv6) 1911 { 1912 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 1913 v6dhcpParms.dhcpv6OperatingMode, 1914 "UseDNSServers", v6dhcpParms.useDnsServers, 1915 "UseNTPServers", v6dhcpParms.useNtpServers, 1916 "UseDomainName", 1917 v6dhcpParms.useDomainName)) 1918 { 1919 return; 1920 } 1921 } 1922 1923 // Get single eth interface data, and call the below callback 1924 // for JSON preparation 1925 getEthernetIfaceData( 1926 ifaceId, 1927 [asyncResp, ifaceId, hostname = std::move(hostname), 1928 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1929 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1930 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1931 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1932 staticNameServers = std::move(staticNameServers), 1933 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 1934 v4dhcpParms = std::move(v4dhcpParms), 1935 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1936 const bool& success, const EthernetInterfaceData& ethData, 1937 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1938 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1939 if (!success) 1940 { 1941 // ... otherwise return error 1942 // TODO(Pawel)consider distinguish between non 1943 // existing object, and other errors 1944 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1945 ifaceId); 1946 return; 1947 } 1948 1949 if (dhcpv4 || dhcpv6) 1950 { 1951 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 1952 asyncResp); 1953 } 1954 1955 if (hostname) 1956 { 1957 handleHostnamePatch(*hostname, asyncResp); 1958 } 1959 1960 if (fqdn) 1961 { 1962 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 1963 } 1964 1965 if (macAddress) 1966 { 1967 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 1968 } 1969 1970 if (ipv4StaticAddresses) 1971 { 1972 // TODO(ed) for some reason the capture of 1973 // ipv4Addresses above is returning a const value, 1974 // not a non-const value. This doesn't really work 1975 // for us, as we need to be able to efficiently move 1976 // out the intermedia nlohmann::json objects. This 1977 // makes a copy of the structure, and operates on 1978 // that, but could be done more efficiently 1979 nlohmann::json ipv4Static = *ipv4StaticAddresses; 1980 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 1981 } 1982 1983 if (staticNameServers) 1984 { 1985 handleStaticNameServersPatch(ifaceId, *staticNameServers, 1986 asyncResp); 1987 } 1988 1989 if (ipv6DefaultGateway) 1990 { 1991 messages::propertyNotWritable(asyncResp->res, 1992 "IPv6DefaultGateway"); 1993 } 1994 1995 if (ipv6StaticAddresses) 1996 { 1997 const nlohmann::json& ipv6Static = *ipv6StaticAddresses; 1998 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data, 1999 asyncResp); 2000 } 2001 2002 if (interfaceEnabled) 2003 { 2004 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 2005 *interfaceEnabled, asyncResp); 2006 } 2007 2008 if (mtuSize) 2009 { 2010 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 2011 } 2012 }); 2013 }); 2014 2015 BMCWEB_ROUTE( 2016 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2017 .privileges(redfish::privileges::getVLanNetworkInterface) 2018 .methods(boost::beast::http::verb::get)( 2019 [&app](const crow::Request& req, 2020 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2021 const std::string& parentIfaceId, 2022 const std::string& ifaceId) { 2023 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2024 { 2025 return; 2026 } 2027 asyncResp->res.jsonValue["@odata.type"] = 2028 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 2029 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 2030 2031 if (!verifyNames(parentIfaceId, ifaceId)) 2032 { 2033 return; 2034 } 2035 2036 // Get single eth interface data, and call the below callback 2037 // for JSON preparation 2038 getEthernetIfaceData( 2039 ifaceId, 2040 [asyncResp, parentIfaceId, 2041 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2042 const boost::container::flat_set<IPv4AddressData>&, 2043 const boost::container::flat_set<IPv6AddressData>&) { 2044 if (success && ethData.vlanId) 2045 { 2046 asyncResp->res.jsonValue["Id"] = ifaceId; 2047 asyncResp->res.jsonValue["@odata.id"] = 2048 crow::utility::urlFromPieces( 2049 "redfish", "v1", "Managers", "bmc", 2050 "EthernetInterfaces", parentIfaceId, "VLANs", ifaceId); 2051 2052 asyncResp->res.jsonValue["VLANEnable"] = ethData.nicEnabled; 2053 asyncResp->res.jsonValue["VLANId"] = *ethData.vlanId; 2054 } 2055 else 2056 { 2057 // ... otherwise return error 2058 // TODO(Pawel)consider distinguish between non 2059 // existing object, and other errors 2060 messages::resourceNotFound(asyncResp->res, 2061 "VLanNetworkInterface", ifaceId); 2062 } 2063 }); 2064 }); 2065 2066 BMCWEB_ROUTE( 2067 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2068 .privileges(redfish::privileges::patchVLanNetworkInterface) 2069 .methods(boost::beast::http::verb::patch)( 2070 [&app](const crow::Request& req, 2071 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2072 const std::string& parentIfaceId, 2073 const std::string& ifaceId) { 2074 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2075 { 2076 return; 2077 } 2078 if (!verifyNames(parentIfaceId, ifaceId)) 2079 { 2080 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2081 ifaceId); 2082 return; 2083 } 2084 2085 std::optional<bool> vlanEnable; 2086 std::optional<uint32_t> vlanId; 2087 2088 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable", 2089 vlanEnable, "VLANId", vlanId)) 2090 { 2091 return; 2092 } 2093 2094 if (vlanId) 2095 { 2096 messages::propertyNotWritable(asyncResp->res, "VLANId"); 2097 return; 2098 } 2099 2100 // Get single eth interface data, and call the below callback 2101 // for JSON preparation 2102 getEthernetIfaceData( 2103 ifaceId, 2104 [asyncResp, parentIfaceId, ifaceId, vlanEnable]( 2105 const bool& success, const EthernetInterfaceData& ethData, 2106 const boost::container::flat_set<IPv4AddressData>&, 2107 const boost::container::flat_set<IPv6AddressData>&) { 2108 if (success && ethData.vlanId) 2109 { 2110 if (vlanEnable) 2111 { 2112 crow::connections::systemBus->async_method_call( 2113 [asyncResp](const boost::system::error_code& ec) { 2114 if (ec) 2115 { 2116 messages::internalError(asyncResp->res); 2117 return; 2118 } 2119 }, 2120 "xyz.openbmc_project.Network", 2121 "/xyz/openbmc_project/network/" + ifaceId, 2122 "org.freedesktop.DBus.Properties", "Set", 2123 "xyz.openbmc_project.Network.EthernetInterface", 2124 "NICEnabled", 2125 dbus::utility::DbusVariantType(*vlanEnable)); 2126 } 2127 } 2128 else 2129 { 2130 // TODO(Pawel)consider distinguish between non 2131 // existing object, and other errors 2132 messages::resourceNotFound(asyncResp->res, 2133 "VLanNetworkInterface", ifaceId); 2134 return; 2135 } 2136 }); 2137 }); 2138 2139 BMCWEB_ROUTE( 2140 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2141 .privileges(redfish::privileges::deleteVLanNetworkInterface) 2142 .methods(boost::beast::http::verb::delete_)( 2143 [&app](const crow::Request& req, 2144 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2145 const std::string& parentIfaceId, 2146 const std::string& ifaceId) { 2147 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2148 { 2149 return; 2150 } 2151 if (!verifyNames(parentIfaceId, ifaceId)) 2152 { 2153 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2154 ifaceId); 2155 return; 2156 } 2157 2158 // Get single eth interface data, and call the below callback 2159 // for JSON preparation 2160 getEthernetIfaceData( 2161 ifaceId, 2162 [asyncResp, parentIfaceId, 2163 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2164 const boost::container::flat_set<IPv4AddressData>&, 2165 const boost::container::flat_set<IPv6AddressData>&) { 2166 if (success && ethData.vlanId) 2167 { 2168 auto callback = 2169 [asyncResp](const boost::system::error_code& ec) { 2170 if (ec) 2171 { 2172 messages::internalError(asyncResp->res); 2173 } 2174 }; 2175 crow::connections::systemBus->async_method_call( 2176 std::move(callback), "xyz.openbmc_project.Network", 2177 std::string("/xyz/openbmc_project/network/") + ifaceId, 2178 "xyz.openbmc_project.Object.Delete", "Delete"); 2179 } 2180 else 2181 { 2182 // ... otherwise return error 2183 // TODO(Pawel)consider distinguish between non 2184 // existing object, and other errors 2185 messages::resourceNotFound(asyncResp->res, 2186 "VLanNetworkInterface", ifaceId); 2187 } 2188 }); 2189 }); 2190 2191 BMCWEB_ROUTE(app, 2192 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2193 2194 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2195 .methods(boost::beast::http::verb::get)( 2196 [&app](const crow::Request& req, 2197 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2198 const std::string& rootInterfaceName) { 2199 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2200 { 2201 return; 2202 } 2203 // Get eth interface list, and call the below callback for JSON 2204 // preparation 2205 getEthernetIfaceList( 2206 [asyncResp, rootInterfaceName]( 2207 const bool& success, 2208 const boost::container::flat_set<std::string>& ifaceList) { 2209 if (!success) 2210 { 2211 messages::internalError(asyncResp->res); 2212 return; 2213 } 2214 2215 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2216 { 2217 messages::resourceNotFound(asyncResp->res, 2218 "VLanNetworkInterfaceCollection", 2219 rootInterfaceName); 2220 return; 2221 } 2222 2223 asyncResp->res.jsonValue["@odata.type"] = 2224 "#VLanNetworkInterfaceCollection." 2225 "VLanNetworkInterfaceCollection"; 2226 asyncResp->res.jsonValue["Name"] = 2227 "VLAN Network Interface Collection"; 2228 2229 nlohmann::json ifaceArray = nlohmann::json::array(); 2230 2231 for (const std::string& ifaceItem : ifaceList) 2232 { 2233 if (ifaceItem.starts_with(rootInterfaceName + "_")) 2234 { 2235 nlohmann::json::object_t iface; 2236 iface["@odata.id"] = crow::utility::urlFromPieces( 2237 "redfish", "v1", "Managers", "bmc", 2238 "EthernetInterfaces", rootInterfaceName, "VLANs", 2239 ifaceItem); 2240 ifaceArray.push_back(std::move(iface)); 2241 } 2242 } 2243 2244 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2245 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2246 asyncResp->res.jsonValue["@odata.id"] = 2247 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 2248 "EthernetInterfaces", 2249 rootInterfaceName, "VLANs"); 2250 }); 2251 }); 2252 2253 BMCWEB_ROUTE(app, 2254 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2255 .privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2256 .methods(boost::beast::http::verb::post)( 2257 [&app](const crow::Request& req, 2258 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2259 const std::string& rootInterfaceName) { 2260 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2261 { 2262 return; 2263 } 2264 bool vlanEnable = false; 2265 uint32_t vlanId = 0; 2266 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId, 2267 "VLANEnable", vlanEnable)) 2268 { 2269 return; 2270 } 2271 // Need both vlanId and vlanEnable to service this request 2272 if (vlanId == 0U) 2273 { 2274 messages::propertyMissing(asyncResp->res, "VLANId"); 2275 } 2276 if (!vlanEnable) 2277 { 2278 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2279 } 2280 if (static_cast<bool>(vlanId) ^ vlanEnable) 2281 { 2282 return; 2283 } 2284 2285 auto callback = [asyncResp](const boost::system::error_code& ec) { 2286 if (ec) 2287 { 2288 // TODO(ed) make more consistent error messages 2289 // based on phosphor-network responses 2290 messages::internalError(asyncResp->res); 2291 return; 2292 } 2293 messages::created(asyncResp->res); 2294 }; 2295 crow::connections::systemBus->async_method_call( 2296 std::move(callback), "xyz.openbmc_project.Network", 2297 "/xyz/openbmc_project/network", 2298 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2299 rootInterfaceName, vlanId); 2300 }); 2301 } 2302 2303 } // namespace redfish 2304