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