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