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 "utils/ip_utils.hpp" 19 20 #include <app.hpp> 21 #include <boost/algorithm/string/classification.hpp> 22 #include <boost/algorithm/string/split.hpp> 23 #include <boost/container/flat_set.hpp> 24 #include <dbus_singleton.hpp> 25 #include <dbus_utility.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 <optional> 32 #include <regex> 33 34 namespace redfish 35 { 36 37 enum class LinkType 38 { 39 Local, 40 Global 41 }; 42 43 /** 44 * Structure for keeping IPv4 data required by Redfish 45 */ 46 struct IPv4AddressData 47 { 48 std::string id; 49 std::string address; 50 std::string domain; 51 std::string gateway; 52 std::string netmask; 53 std::string origin; 54 LinkType linktype; 55 bool isActive; 56 57 bool operator<(const IPv4AddressData& obj) const 58 { 59 return id < obj.id; 60 } 61 }; 62 63 /** 64 * Structure for keeping IPv6 data required by Redfish 65 */ 66 struct IPv6AddressData 67 { 68 std::string id; 69 std::string address; 70 std::string origin; 71 uint8_t prefixLength; 72 73 bool operator<(const IPv6AddressData& obj) const 74 { 75 return id < obj.id; 76 } 77 }; 78 /** 79 * Structure for keeping basic single Ethernet Interface information 80 * available from DBus 81 */ 82 struct EthernetInterfaceData 83 { 84 uint32_t speed; 85 size_t mtuSize; 86 bool autoNeg; 87 bool dnsEnabled; 88 bool ntpEnabled; 89 bool hostNameEnabled; 90 bool linkUp; 91 bool nicEnabled; 92 std::string dhcpEnabled; 93 std::string operatingMode; 94 std::string hostName; 95 std::string defaultGateway; 96 std::string ipv6DefaultGateway; 97 std::string macAddress; 98 std::optional<uint32_t> vlanId; 99 std::vector<std::string> nameServers; 100 std::vector<std::string> staticNameServers; 101 std::vector<std::string> domainnames; 102 }; 103 104 struct DHCPParameters 105 { 106 std::optional<bool> dhcpv4Enabled; 107 std::optional<bool> useDnsServers; 108 std::optional<bool> useNtpServers; 109 std::optional<bool> useDomainName; 110 std::optional<std::string> dhcpv6OperatingMode; 111 }; 112 113 // Helper function that changes bits netmask notation (i.e. /24) 114 // into full dot notation 115 inline std::string getNetmask(unsigned int bits) 116 { 117 uint32_t value = 0xffffffff << (32 - bits); 118 std::string netmask = std::to_string((value >> 24) & 0xff) + "." + 119 std::to_string((value >> 16) & 0xff) + "." + 120 std::to_string((value >> 8) & 0xff) + "." + 121 std::to_string(value & 0xff); 122 return netmask; 123 } 124 125 inline bool translateDhcpEnabledToBool(const std::string& inputDHCP, 126 bool isIPv4) 127 { 128 if (isIPv4) 129 { 130 return ( 131 (inputDHCP == 132 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") || 133 (inputDHCP == 134 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 135 } 136 return ((inputDHCP == 137 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") || 138 (inputDHCP == 139 "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both")); 140 } 141 142 inline std::string getDhcpEnabledEnumeration(bool isIPv4, bool isIPv6) 143 { 144 if (isIPv4 && isIPv6) 145 { 146 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"; 147 } 148 if (isIPv4) 149 { 150 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4"; 151 } 152 if (isIPv6) 153 { 154 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6"; 155 } 156 return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none"; 157 } 158 159 inline std::string 160 translateAddressOriginDbusToRedfish(const std::string& inputOrigin, 161 bool isIPv4) 162 { 163 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static") 164 { 165 return "Static"; 166 } 167 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal") 168 { 169 if (isIPv4) 170 { 171 return "IPv4LinkLocal"; 172 } 173 return "LinkLocal"; 174 } 175 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP") 176 { 177 if (isIPv4) 178 { 179 return "DHCP"; 180 } 181 return "DHCPv6"; 182 } 183 if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC") 184 { 185 return "SLAAC"; 186 } 187 return ""; 188 } 189 190 inline bool extractEthernetInterfaceData( 191 const std::string& ethifaceId, 192 const dbus::utility::ManagedObjectType& dbusData, 193 EthernetInterfaceData& ethData) 194 { 195 bool idFound = false; 196 for (const auto& objpath : dbusData) 197 { 198 for (const auto& ifacePair : objpath.second) 199 { 200 if (objpath.first == "/xyz/openbmc_project/network/" + ethifaceId) 201 { 202 idFound = true; 203 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress") 204 { 205 for (const auto& propertyPair : ifacePair.second) 206 { 207 if (propertyPair.first == "MACAddress") 208 { 209 const std::string* mac = 210 std::get_if<std::string>(&propertyPair.second); 211 if (mac != nullptr) 212 { 213 ethData.macAddress = *mac; 214 } 215 } 216 } 217 } 218 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN") 219 { 220 for (const auto& propertyPair : ifacePair.second) 221 { 222 if (propertyPair.first == "Id") 223 { 224 const uint32_t* id = 225 std::get_if<uint32_t>(&propertyPair.second); 226 if (id != nullptr) 227 { 228 ethData.vlanId = *id; 229 } 230 } 231 } 232 } 233 else if (ifacePair.first == 234 "xyz.openbmc_project.Network.EthernetInterface") 235 { 236 for (const auto& propertyPair : ifacePair.second) 237 { 238 if (propertyPair.first == "AutoNeg") 239 { 240 const bool* autoNeg = 241 std::get_if<bool>(&propertyPair.second); 242 if (autoNeg != nullptr) 243 { 244 ethData.autoNeg = *autoNeg; 245 } 246 } 247 else if (propertyPair.first == "Speed") 248 { 249 const uint32_t* speed = 250 std::get_if<uint32_t>(&propertyPair.second); 251 if (speed != nullptr) 252 { 253 ethData.speed = *speed; 254 } 255 } 256 else if (propertyPair.first == "MTU") 257 { 258 const uint32_t* mtuSize = 259 std::get_if<uint32_t>(&propertyPair.second); 260 if (mtuSize != nullptr) 261 { 262 ethData.mtuSize = *mtuSize; 263 } 264 } 265 else if (propertyPair.first == "LinkUp") 266 { 267 const bool* linkUp = 268 std::get_if<bool>(&propertyPair.second); 269 if (linkUp != nullptr) 270 { 271 ethData.linkUp = *linkUp; 272 } 273 } 274 else if (propertyPair.first == "NICEnabled") 275 { 276 const bool* nicEnabled = 277 std::get_if<bool>(&propertyPair.second); 278 if (nicEnabled != nullptr) 279 { 280 ethData.nicEnabled = *nicEnabled; 281 } 282 } 283 else if (propertyPair.first == "Nameservers") 284 { 285 const std::vector<std::string>* nameservers = 286 std::get_if<std::vector<std::string>>( 287 &propertyPair.second); 288 if (nameservers != nullptr) 289 { 290 ethData.nameServers = *nameservers; 291 } 292 } 293 else if (propertyPair.first == "StaticNameServers") 294 { 295 const std::vector<std::string>* staticNameServers = 296 std::get_if<std::vector<std::string>>( 297 &propertyPair.second); 298 if (staticNameServers != nullptr) 299 { 300 ethData.staticNameServers = *staticNameServers; 301 } 302 } 303 else if (propertyPair.first == "DHCPEnabled") 304 { 305 const std::string* dhcpEnabled = 306 std::get_if<std::string>(&propertyPair.second); 307 if (dhcpEnabled != nullptr) 308 { 309 ethData.dhcpEnabled = *dhcpEnabled; 310 } 311 } 312 else if (propertyPair.first == "DomainName") 313 { 314 const std::vector<std::string>* domainNames = 315 std::get_if<std::vector<std::string>>( 316 &propertyPair.second); 317 if (domainNames != nullptr) 318 { 319 ethData.domainnames = *domainNames; 320 } 321 } 322 else if (propertyPair.first == "DefaultGateway") 323 { 324 const std::string* defaultGateway = 325 std::get_if<std::string>(&propertyPair.second); 326 if (defaultGateway != nullptr) 327 { 328 std::string defaultGatewayStr = *defaultGateway; 329 if (defaultGatewayStr.empty()) 330 { 331 ethData.defaultGateway = "0.0.0.0"; 332 } 333 else 334 { 335 ethData.defaultGateway = defaultGatewayStr; 336 } 337 } 338 } 339 else if (propertyPair.first == "DefaultGateway6") 340 { 341 const std::string* defaultGateway6 = 342 std::get_if<std::string>(&propertyPair.second); 343 if (defaultGateway6 != nullptr) 344 { 345 std::string defaultGateway6Str = 346 *defaultGateway6; 347 if (defaultGateway6Str.empty()) 348 { 349 ethData.ipv6DefaultGateway = 350 "0:0:0:0:0:0:0:0"; 351 } 352 else 353 { 354 ethData.ipv6DefaultGateway = 355 defaultGateway6Str; 356 } 357 } 358 } 359 } 360 } 361 } 362 363 if (objpath.first == "/xyz/openbmc_project/network/dhcp") 364 { 365 if (ifacePair.first == 366 "xyz.openbmc_project.Network.DHCPConfiguration") 367 { 368 for (const auto& propertyPair : ifacePair.second) 369 { 370 if (propertyPair.first == "DNSEnabled") 371 { 372 const bool* dnsEnabled = 373 std::get_if<bool>(&propertyPair.second); 374 if (dnsEnabled != nullptr) 375 { 376 ethData.dnsEnabled = *dnsEnabled; 377 } 378 } 379 else if (propertyPair.first == "NTPEnabled") 380 { 381 const bool* ntpEnabled = 382 std::get_if<bool>(&propertyPair.second); 383 if (ntpEnabled != nullptr) 384 { 385 ethData.ntpEnabled = *ntpEnabled; 386 } 387 } 388 else if (propertyPair.first == "HostNameEnabled") 389 { 390 const bool* hostNameEnabled = 391 std::get_if<bool>(&propertyPair.second); 392 if (hostNameEnabled != nullptr) 393 { 394 ethData.hostNameEnabled = *hostNameEnabled; 395 } 396 } 397 } 398 } 399 } 400 // System configuration shows up in the global namespace, so no need 401 // to check eth number 402 if (ifacePair.first == 403 "xyz.openbmc_project.Network.SystemConfiguration") 404 { 405 for (const auto& propertyPair : ifacePair.second) 406 { 407 if (propertyPair.first == "HostName") 408 { 409 const std::string* hostname = 410 std::get_if<std::string>(&propertyPair.second); 411 if (hostname != nullptr) 412 { 413 ethData.hostName = *hostname; 414 } 415 } 416 } 417 } 418 } 419 } 420 return idFound; 421 } 422 423 // Helper function that extracts data for single ethernet ipv6 address 424 inline void 425 extractIPV6Data(const std::string& ethifaceId, 426 const dbus::utility::ManagedObjectType& dbusData, 427 boost::container::flat_set<IPv6AddressData>& ipv6Config) 428 { 429 const std::string ipv6PathStart = 430 "/xyz/openbmc_project/network/" + ethifaceId + "/ipv6/"; 431 432 // Since there might be several IPv6 configurations aligned with 433 // single ethernet interface, loop over all of them 434 for (const auto& objpath : dbusData) 435 { 436 // Check if proper pattern for object path appears 437 if (objpath.first.str.starts_with(ipv6PathStart)) 438 { 439 for (const auto& interface : objpath.second) 440 { 441 if (interface.first == "xyz.openbmc_project.Network.IP") 442 { 443 // Instance IPv6AddressData structure, and set as 444 // appropriate 445 std::pair< 446 boost::container::flat_set<IPv6AddressData>::iterator, 447 bool> 448 it = ipv6Config.insert(IPv6AddressData{}); 449 IPv6AddressData& ipv6Address = *it.first; 450 ipv6Address.id = 451 objpath.first.str.substr(ipv6PathStart.size()); 452 for (const auto& property : interface.second) 453 { 454 if (property.first == "Address") 455 { 456 const std::string* address = 457 std::get_if<std::string>(&property.second); 458 if (address != nullptr) 459 { 460 ipv6Address.address = *address; 461 } 462 } 463 else if (property.first == "Origin") 464 { 465 const std::string* origin = 466 std::get_if<std::string>(&property.second); 467 if (origin != nullptr) 468 { 469 ipv6Address.origin = 470 translateAddressOriginDbusToRedfish(*origin, 471 false); 472 } 473 } 474 else if (property.first == "PrefixLength") 475 { 476 const uint8_t* prefix = 477 std::get_if<uint8_t>(&property.second); 478 if (prefix != nullptr) 479 { 480 ipv6Address.prefixLength = *prefix; 481 } 482 } 483 else if (property.first == "Type" || 484 property.first == "Gateway") 485 { 486 // Type & Gateway is not used 487 } 488 else 489 { 490 BMCWEB_LOG_ERROR 491 << "Got extra property: " << property.first 492 << " on the " << objpath.first.str << " object"; 493 } 494 } 495 } 496 } 497 } 498 } 499 } 500 501 // Helper function that extracts data for single ethernet ipv4 address 502 inline void 503 extractIPData(const std::string& ethifaceId, 504 const dbus::utility::ManagedObjectType& dbusData, 505 boost::container::flat_set<IPv4AddressData>& ipv4Config) 506 { 507 const std::string ipv4PathStart = 508 "/xyz/openbmc_project/network/" + ethifaceId + "/ipv4/"; 509 510 // Since there might be several IPv4 configurations aligned with 511 // single ethernet interface, loop over all of them 512 for (const auto& objpath : dbusData) 513 { 514 // Check if proper pattern for object path appears 515 if (objpath.first.str.starts_with(ipv4PathStart)) 516 { 517 for (const auto& interface : objpath.second) 518 { 519 if (interface.first == "xyz.openbmc_project.Network.IP") 520 { 521 // Instance IPv4AddressData structure, and set as 522 // appropriate 523 std::pair< 524 boost::container::flat_set<IPv4AddressData>::iterator, 525 bool> 526 it = ipv4Config.insert(IPv4AddressData{}); 527 IPv4AddressData& ipv4Address = *it.first; 528 ipv4Address.id = 529 objpath.first.str.substr(ipv4PathStart.size()); 530 for (const auto& property : interface.second) 531 { 532 if (property.first == "Address") 533 { 534 const std::string* address = 535 std::get_if<std::string>(&property.second); 536 if (address != nullptr) 537 { 538 ipv4Address.address = *address; 539 } 540 } 541 else if (property.first == "Origin") 542 { 543 const std::string* origin = 544 std::get_if<std::string>(&property.second); 545 if (origin != nullptr) 546 { 547 ipv4Address.origin = 548 translateAddressOriginDbusToRedfish(*origin, 549 true); 550 } 551 } 552 else if (property.first == "PrefixLength") 553 { 554 const uint8_t* mask = 555 std::get_if<uint8_t>(&property.second); 556 if (mask != nullptr) 557 { 558 // convert it to the string 559 ipv4Address.netmask = getNetmask(*mask); 560 } 561 } 562 else if (property.first == "Type" || 563 property.first == "Gateway") 564 { 565 // Type & Gateway is not used 566 } 567 else 568 { 569 BMCWEB_LOG_ERROR 570 << "Got extra property: " << property.first 571 << " on the " << objpath.first.str << " object"; 572 } 573 } 574 // Check if given address is local, or global 575 ipv4Address.linktype = 576 ipv4Address.address.starts_with("169.254.") 577 ? LinkType::Local 578 : LinkType::Global; 579 } 580 } 581 } 582 } 583 } 584 585 /** 586 * @brief Deletes given IPv4 interface 587 * 588 * @param[in] ifaceId Id of interface whose IP should be deleted 589 * @param[in] ipHash DBus Hash id of IP that should be deleted 590 * @param[io] asyncResp Response object that will be returned to client 591 * 592 * @return None 593 */ 594 inline void deleteIPv4(const std::string& ifaceId, const std::string& ipHash, 595 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 596 { 597 crow::connections::systemBus->async_method_call( 598 [asyncResp](const boost::system::error_code ec) { 599 if (ec) 600 { 601 messages::internalError(asyncResp->res); 602 } 603 }, 604 "xyz.openbmc_project.Network", 605 "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash, 606 "xyz.openbmc_project.Object.Delete", "Delete"); 607 } 608 609 inline void updateIPv4DefaultGateway( 610 const std::string& ifaceId, const std::string& gateway, 611 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 612 { 613 crow::connections::systemBus->async_method_call( 614 [asyncResp](const boost::system::error_code ec) { 615 if (ec) 616 { 617 messages::internalError(asyncResp->res); 618 return; 619 } 620 asyncResp->res.result(boost::beast::http::status::no_content); 621 }, 622 "xyz.openbmc_project.Network", 623 "/xyz/openbmc_project/network/" + ifaceId, 624 "org.freedesktop.DBus.Properties", "Set", 625 "xyz.openbmc_project.Network.EthernetInterface", "DefaultGateway", 626 dbus::utility::DbusVariantType(gateway)); 627 } 628 /** 629 * @brief Creates a static IPv4 entry 630 * 631 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 632 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 633 * @param[in] gateway IPv4 address of this interfaces gateway 634 * @param[in] address IPv4 address to assign to this interface 635 * @param[io] asyncResp Response object that will be returned to client 636 * 637 * @return None 638 */ 639 inline void createIPv4(const std::string& ifaceId, uint8_t prefixLength, 640 const std::string& gateway, const std::string& address, 641 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 642 { 643 auto createIpHandler = 644 [asyncResp, ifaceId, gateway](const boost::system::error_code ec) { 645 if (ec) 646 { 647 messages::internalError(asyncResp->res); 648 return; 649 } 650 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 651 }; 652 653 crow::connections::systemBus->async_method_call( 654 std::move(createIpHandler), "xyz.openbmc_project.Network", 655 "/xyz/openbmc_project/network/" + ifaceId, 656 "xyz.openbmc_project.Network.IP.Create", "IP", 657 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength, 658 gateway); 659 } 660 661 /** 662 * @brief Deletes the IPv4 entry for this interface and creates a replacement 663 * static IPv4 entry 664 * 665 * @param[in] ifaceId Id of interface upon which to create the IPv4 entry 666 * @param[in] id The unique hash entry identifying the DBus entry 667 * @param[in] prefixLength IPv4 prefix syntax for the subnet mask 668 * @param[in] gateway IPv4 address of this interfaces gateway 669 * @param[in] address IPv4 address to assign to this interface 670 * @param[io] asyncResp Response object that will be returned to client 671 * 672 * @return None 673 */ 674 inline void 675 deleteAndCreateIPv4(const std::string& ifaceId, const std::string& id, 676 uint8_t prefixLength, const std::string& gateway, 677 const std::string& address, 678 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 679 { 680 crow::connections::systemBus->async_method_call( 681 [asyncResp, ifaceId, address, prefixLength, 682 gateway](const boost::system::error_code ec) { 683 if (ec) 684 { 685 messages::internalError(asyncResp->res); 686 return; 687 } 688 689 crow::connections::systemBus->async_method_call( 690 [asyncResp, ifaceId, gateway](const boost::system::error_code ec2) { 691 if (ec2) 692 { 693 messages::internalError(asyncResp->res); 694 return; 695 } 696 updateIPv4DefaultGateway(ifaceId, gateway, asyncResp); 697 }, 698 "xyz.openbmc_project.Network", 699 "/xyz/openbmc_project/network/" + ifaceId, 700 "xyz.openbmc_project.Network.IP.Create", "IP", 701 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, 702 prefixLength, gateway); 703 }, 704 "xyz.openbmc_project.Network", 705 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id, 706 "xyz.openbmc_project.Object.Delete", "Delete"); 707 } 708 709 /** 710 * @brief Deletes given IPv6 711 * 712 * @param[in] ifaceId Id of interface whose IP should be deleted 713 * @param[in] ipHash DBus Hash id of IP that should be deleted 714 * @param[io] asyncResp Response object that will be returned to client 715 * 716 * @return None 717 */ 718 inline void deleteIPv6(const std::string& ifaceId, const std::string& ipHash, 719 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 720 { 721 crow::connections::systemBus->async_method_call( 722 [asyncResp](const boost::system::error_code ec) { 723 if (ec) 724 { 725 messages::internalError(asyncResp->res); 726 } 727 }, 728 "xyz.openbmc_project.Network", 729 "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash, 730 "xyz.openbmc_project.Object.Delete", "Delete"); 731 } 732 733 /** 734 * @brief Deletes the IPv6 entry for this interface and creates a replacement 735 * static IPv6 entry 736 * 737 * @param[in] ifaceId Id of interface upon which to create the IPv6 entry 738 * @param[in] id The unique hash entry identifying the DBus entry 739 * @param[in] prefixLength IPv6 prefix syntax for the subnet mask 740 * @param[in] address IPv6 address to assign to this interface 741 * @param[io] asyncResp Response object that will be returned to client 742 * 743 * @return None 744 */ 745 inline void 746 deleteAndCreateIPv6(const std::string& ifaceId, const std::string& id, 747 uint8_t prefixLength, const std::string& address, 748 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 749 { 750 crow::connections::systemBus->async_method_call( 751 [asyncResp, ifaceId, address, 752 prefixLength](const boost::system::error_code ec) { 753 if (ec) 754 { 755 messages::internalError(asyncResp->res); 756 } 757 crow::connections::systemBus->async_method_call( 758 [asyncResp](const boost::system::error_code ec2) { 759 if (ec2) 760 { 761 messages::internalError(asyncResp->res); 762 } 763 }, 764 "xyz.openbmc_project.Network", 765 "/xyz/openbmc_project/network/" + ifaceId, 766 "xyz.openbmc_project.Network.IP.Create", "IP", 767 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, 768 prefixLength, ""); 769 }, 770 "xyz.openbmc_project.Network", 771 +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id, 772 "xyz.openbmc_project.Object.Delete", "Delete"); 773 } 774 775 /** 776 * @brief Creates IPv6 with given data 777 * 778 * @param[in] ifaceId Id of interface whose IP should be added 779 * @param[in] prefixLength Prefix length that needs to be added 780 * @param[in] address IP address that needs to be added 781 * @param[io] asyncResp Response object that will be returned to client 782 * 783 * @return None 784 */ 785 inline void createIPv6(const std::string& ifaceId, uint8_t prefixLength, 786 const std::string& address, 787 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 788 { 789 auto createIpHandler = [asyncResp](const boost::system::error_code ec) { 790 if (ec) 791 { 792 messages::internalError(asyncResp->res); 793 } 794 }; 795 // Passing null for gateway, as per redfish spec IPv6StaticAddresses object 796 // does not have associated gateway property 797 crow::connections::systemBus->async_method_call( 798 std::move(createIpHandler), "xyz.openbmc_project.Network", 799 "/xyz/openbmc_project/network/" + ifaceId, 800 "xyz.openbmc_project.Network.IP.Create", "IP", 801 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength, 802 ""); 803 } 804 805 /** 806 * Function that retrieves all properties for given Ethernet Interface 807 * Object 808 * from EntityManager Network Manager 809 * @param ethiface_id a eth interface id to query on DBus 810 * @param callback a function that shall be called to convert Dbus output 811 * into JSON 812 */ 813 template <typename CallbackFunc> 814 void getEthernetIfaceData(const std::string& ethifaceId, 815 CallbackFunc&& callback) 816 { 817 crow::connections::systemBus->async_method_call( 818 [ethifaceId{std::string{ethifaceId}}, 819 callback{std::forward<CallbackFunc>(callback)}]( 820 const boost::system::error_code errorCode, 821 const dbus::utility::ManagedObjectType& resp) { 822 EthernetInterfaceData ethData{}; 823 boost::container::flat_set<IPv4AddressData> ipv4Data; 824 boost::container::flat_set<IPv6AddressData> ipv6Data; 825 826 if (errorCode) 827 { 828 callback(false, ethData, ipv4Data, ipv6Data); 829 return; 830 } 831 832 bool found = extractEthernetInterfaceData(ethifaceId, resp, ethData); 833 if (!found) 834 { 835 callback(false, ethData, ipv4Data, ipv6Data); 836 return; 837 } 838 839 extractIPData(ethifaceId, resp, ipv4Data); 840 // Fix global GW 841 for (IPv4AddressData& ipv4 : ipv4Data) 842 { 843 if (((ipv4.linktype == LinkType::Global) && 844 (ipv4.gateway == "0.0.0.0")) || 845 (ipv4.origin == "DHCP") || (ipv4.origin == "Static")) 846 { 847 ipv4.gateway = ethData.defaultGateway; 848 } 849 } 850 851 extractIPV6Data(ethifaceId, resp, ipv6Data); 852 // Finally make a callback with useful data 853 callback(true, ethData, ipv4Data, ipv6Data); 854 }, 855 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 856 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 857 } 858 859 /** 860 * Function that retrieves all Ethernet Interfaces available through Network 861 * Manager 862 * @param callback a function that shall be called to convert Dbus output 863 * into JSON. 864 */ 865 template <typename CallbackFunc> 866 void getEthernetIfaceList(CallbackFunc&& callback) 867 { 868 crow::connections::systemBus->async_method_call( 869 [callback{std::forward<CallbackFunc>(callback)}]( 870 const boost::system::error_code errorCode, 871 dbus::utility::ManagedObjectType& resp) { 872 // Callback requires vector<string> to retrieve all available 873 // ethernet interfaces 874 boost::container::flat_set<std::string> ifaceList; 875 ifaceList.reserve(resp.size()); 876 if (errorCode) 877 { 878 callback(false, ifaceList); 879 return; 880 } 881 882 // Iterate over all retrieved ObjectPaths. 883 for (const auto& objpath : resp) 884 { 885 // And all interfaces available for certain ObjectPath. 886 for (const auto& interface : objpath.second) 887 { 888 // If interface is 889 // xyz.openbmc_project.Network.EthernetInterface, this is 890 // what we're looking for. 891 if (interface.first == 892 "xyz.openbmc_project.Network.EthernetInterface") 893 { 894 std::string ifaceId = objpath.first.filename(); 895 if (ifaceId.empty()) 896 { 897 continue; 898 } 899 // and put it into output vector. 900 ifaceList.emplace(ifaceId); 901 } 902 } 903 } 904 // Finally make a callback with useful data 905 callback(true, ifaceList); 906 }, 907 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network", 908 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 909 } 910 911 inline void 912 handleHostnamePatch(const std::string& hostname, 913 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 914 { 915 // SHOULD handle host names of up to 255 characters(RFC 1123) 916 if (hostname.length() > 255) 917 { 918 messages::propertyValueFormatError(asyncResp->res, hostname, 919 "HostName"); 920 return; 921 } 922 crow::connections::systemBus->async_method_call( 923 [asyncResp](const boost::system::error_code ec) { 924 if (ec) 925 { 926 messages::internalError(asyncResp->res); 927 } 928 }, 929 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/config", 930 "org.freedesktop.DBus.Properties", "Set", 931 "xyz.openbmc_project.Network.SystemConfiguration", "HostName", 932 dbus::utility::DbusVariantType(hostname)); 933 } 934 935 inline void 936 handleMTUSizePatch(const std::string& ifaceId, const size_t mtuSize, 937 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 938 { 939 sdbusplus::message::object_path objPath = 940 "/xyz/openbmc_project/network/" + ifaceId; 941 crow::connections::systemBus->async_method_call( 942 [asyncResp](const boost::system::error_code ec) { 943 if (ec) 944 { 945 messages::internalError(asyncResp->res); 946 } 947 }, 948 "xyz.openbmc_project.Network", objPath, 949 "org.freedesktop.DBus.Properties", "Set", 950 "xyz.openbmc_project.Network.EthernetInterface", "MTU", 951 std::variant<size_t>(mtuSize)); 952 } 953 954 inline void 955 handleDomainnamePatch(const std::string& ifaceId, 956 const std::string& domainname, 957 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 958 { 959 std::vector<std::string> vectorDomainname = {domainname}; 960 crow::connections::systemBus->async_method_call( 961 [asyncResp](const boost::system::error_code ec) { 962 if (ec) 963 { 964 messages::internalError(asyncResp->res); 965 } 966 }, 967 "xyz.openbmc_project.Network", 968 "/xyz/openbmc_project/network/" + ifaceId, 969 "org.freedesktop.DBus.Properties", "Set", 970 "xyz.openbmc_project.Network.EthernetInterface", "DomainName", 971 dbus::utility::DbusVariantType(vectorDomainname)); 972 } 973 974 inline bool isHostnameValid(const std::string& hostname) 975 { 976 // A valid host name can never have the dotted-decimal form (RFC 1123) 977 if (std::all_of(hostname.begin(), hostname.end(), ::isdigit)) 978 { 979 return false; 980 } 981 // Each label(hostname/subdomains) within a valid FQDN 982 // MUST handle host names of up to 63 characters (RFC 1123) 983 // labels cannot start or end with hyphens (RFC 952) 984 // labels can start with numbers (RFC 1123) 985 const std::regex pattern( 986 "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$"); 987 988 return std::regex_match(hostname, pattern); 989 } 990 991 inline bool isDomainnameValid(const std::string& domainname) 992 { 993 // Can have multiple subdomains 994 // Top Level Domain's min length is 2 character 995 const std::regex pattern( 996 "^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]{1,30}\\.)*[a-zA-Z]{2,}$"); 997 998 return std::regex_match(domainname, pattern); 999 } 1000 1001 inline void handleFqdnPatch(const std::string& ifaceId, const std::string& fqdn, 1002 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1003 { 1004 // Total length of FQDN must not exceed 255 characters(RFC 1035) 1005 if (fqdn.length() > 255) 1006 { 1007 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1008 return; 1009 } 1010 1011 size_t pos = fqdn.find('.'); 1012 if (pos == std::string::npos) 1013 { 1014 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1015 return; 1016 } 1017 1018 std::string hostname; 1019 std::string domainname; 1020 domainname = (fqdn).substr(pos + 1); 1021 hostname = (fqdn).substr(0, pos); 1022 1023 if (!isHostnameValid(hostname) || !isDomainnameValid(domainname)) 1024 { 1025 messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN"); 1026 return; 1027 } 1028 1029 handleHostnamePatch(hostname, asyncResp); 1030 handleDomainnamePatch(ifaceId, domainname, asyncResp); 1031 } 1032 1033 inline void 1034 handleMACAddressPatch(const std::string& ifaceId, 1035 const std::string& macAddress, 1036 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1037 { 1038 static constexpr std::string_view dbusNotAllowedError = 1039 "xyz.openbmc_project.Common.Error.NotAllowed"; 1040 1041 crow::connections::systemBus->async_method_call( 1042 [asyncResp, macAddress](const boost::system::error_code ec, 1043 const sdbusplus::message_t& msg) { 1044 if (ec) 1045 { 1046 const sd_bus_error* err = msg.get_error(); 1047 if (err == nullptr) 1048 { 1049 messages::internalError(asyncResp->res); 1050 return; 1051 } 1052 if (err->name == dbusNotAllowedError) 1053 { 1054 messages::propertyNotWritable(asyncResp->res, "MACAddress"); 1055 return; 1056 } 1057 messages::internalError(asyncResp->res); 1058 return; 1059 } 1060 }, 1061 "xyz.openbmc_project.Network", 1062 "/xyz/openbmc_project/network/" + ifaceId, 1063 "org.freedesktop.DBus.Properties", "Set", 1064 "xyz.openbmc_project.Network.MACAddress", "MACAddress", 1065 dbus::utility::DbusVariantType(macAddress)); 1066 } 1067 1068 inline void setDHCPEnabled(const std::string& ifaceId, 1069 const std::string& propertyName, const bool v4Value, 1070 const bool v6Value, 1071 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1072 { 1073 const std::string dhcp = getDhcpEnabledEnumeration(v4Value, v6Value); 1074 crow::connections::systemBus->async_method_call( 1075 [asyncResp](const boost::system::error_code ec) { 1076 if (ec) 1077 { 1078 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1079 messages::internalError(asyncResp->res); 1080 return; 1081 } 1082 messages::success(asyncResp->res); 1083 }, 1084 "xyz.openbmc_project.Network", 1085 "/xyz/openbmc_project/network/" + ifaceId, 1086 "org.freedesktop.DBus.Properties", "Set", 1087 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1088 dbus::utility::DbusVariantType{dhcp}); 1089 } 1090 1091 inline void setEthernetInterfaceBoolProperty( 1092 const std::string& ifaceId, const std::string& propertyName, 1093 const bool& value, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1094 { 1095 crow::connections::systemBus->async_method_call( 1096 [asyncResp](const boost::system::error_code ec) { 1097 if (ec) 1098 { 1099 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1100 messages::internalError(asyncResp->res); 1101 return; 1102 } 1103 }, 1104 "xyz.openbmc_project.Network", 1105 "/xyz/openbmc_project/network/" + ifaceId, 1106 "org.freedesktop.DBus.Properties", "Set", 1107 "xyz.openbmc_project.Network.EthernetInterface", propertyName, 1108 dbus::utility::DbusVariantType{value}); 1109 } 1110 1111 inline void setDHCPv4Config(const std::string& propertyName, const bool& value, 1112 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1113 { 1114 BMCWEB_LOG_DEBUG << propertyName << " = " << value; 1115 crow::connections::systemBus->async_method_call( 1116 [asyncResp](const boost::system::error_code ec) { 1117 if (ec) 1118 { 1119 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1120 messages::internalError(asyncResp->res); 1121 return; 1122 } 1123 }, 1124 "xyz.openbmc_project.Network", "/xyz/openbmc_project/network/dhcp", 1125 "org.freedesktop.DBus.Properties", "Set", 1126 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1127 dbus::utility::DbusVariantType{value}); 1128 } 1129 1130 inline void handleDHCPPatch(const std::string& ifaceId, 1131 const EthernetInterfaceData& ethData, 1132 const DHCPParameters& v4dhcpParms, 1133 const DHCPParameters& v6dhcpParms, 1134 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1135 { 1136 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1137 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1138 1139 bool nextv4DHCPState = 1140 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1141 1142 bool nextv6DHCPState{}; 1143 if (v6dhcpParms.dhcpv6OperatingMode) 1144 { 1145 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1146 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1147 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1148 { 1149 messages::propertyValueFormatError(asyncResp->res, 1150 *v6dhcpParms.dhcpv6OperatingMode, 1151 "OperatingMode"); 1152 return; 1153 } 1154 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1155 } 1156 else 1157 { 1158 nextv6DHCPState = ipv6Active; 1159 } 1160 1161 bool nextDNS{}; 1162 if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers) 1163 { 1164 if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers) 1165 { 1166 messages::generalError(asyncResp->res); 1167 return; 1168 } 1169 nextDNS = *v4dhcpParms.useDnsServers; 1170 } 1171 else if (v4dhcpParms.useDnsServers) 1172 { 1173 nextDNS = *v4dhcpParms.useDnsServers; 1174 } 1175 else if (v6dhcpParms.useDnsServers) 1176 { 1177 nextDNS = *v6dhcpParms.useDnsServers; 1178 } 1179 else 1180 { 1181 nextDNS = ethData.dnsEnabled; 1182 } 1183 1184 bool nextNTP{}; 1185 if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers) 1186 { 1187 if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers) 1188 { 1189 messages::generalError(asyncResp->res); 1190 return; 1191 } 1192 nextNTP = *v4dhcpParms.useNtpServers; 1193 } 1194 else if (v4dhcpParms.useNtpServers) 1195 { 1196 nextNTP = *v4dhcpParms.useNtpServers; 1197 } 1198 else if (v6dhcpParms.useNtpServers) 1199 { 1200 nextNTP = *v6dhcpParms.useNtpServers; 1201 } 1202 else 1203 { 1204 nextNTP = ethData.ntpEnabled; 1205 } 1206 1207 bool nextUseDomain{}; 1208 if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName) 1209 { 1210 if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName) 1211 { 1212 messages::generalError(asyncResp->res); 1213 return; 1214 } 1215 nextUseDomain = *v4dhcpParms.useDomainName; 1216 } 1217 else if (v4dhcpParms.useDomainName) 1218 { 1219 nextUseDomain = *v4dhcpParms.useDomainName; 1220 } 1221 else if (v6dhcpParms.useDomainName) 1222 { 1223 nextUseDomain = *v6dhcpParms.useDomainName; 1224 } 1225 else 1226 { 1227 nextUseDomain = ethData.hostNameEnabled; 1228 } 1229 1230 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1231 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1232 asyncResp); 1233 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1234 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1235 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1236 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1237 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1238 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1239 } 1240 1241 inline boost::container::flat_set<IPv4AddressData>::const_iterator 1242 getNextStaticIpEntry( 1243 const boost::container::flat_set<IPv4AddressData>::const_iterator& head, 1244 const boost::container::flat_set<IPv4AddressData>::const_iterator& end) 1245 { 1246 return std::find_if(head, end, [](const IPv4AddressData& value) { 1247 return value.origin == "Static"; 1248 }); 1249 } 1250 1251 inline boost::container::flat_set<IPv6AddressData>::const_iterator 1252 getNextStaticIpEntry( 1253 const boost::container::flat_set<IPv6AddressData>::const_iterator& head, 1254 const boost::container::flat_set<IPv6AddressData>::const_iterator& end) 1255 { 1256 return std::find_if(head, end, [](const IPv6AddressData& value) { 1257 return value.origin == "Static"; 1258 }); 1259 } 1260 1261 inline void handleIPv4StaticPatch( 1262 const std::string& ifaceId, nlohmann::json& input, 1263 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1264 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1265 { 1266 if ((!input.is_array()) || input.empty()) 1267 { 1268 messages::propertyValueTypeError( 1269 asyncResp->res, 1270 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1271 "IPv4StaticAddresses"); 1272 return; 1273 } 1274 1275 unsigned entryIdx = 1; 1276 // Find the first static IP address currently active on the NIC and 1277 // match it to the first JSON element in the IPv4StaticAddresses array. 1278 // Match each subsequent JSON element to the next static IP programmed 1279 // into the NIC. 1280 boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry = 1281 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1282 1283 for (nlohmann::json& thisJson : input) 1284 { 1285 std::string pathString = 1286 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1287 1288 if (!thisJson.is_null() && !thisJson.empty()) 1289 { 1290 std::optional<std::string> address; 1291 std::optional<std::string> subnetMask; 1292 std::optional<std::string> gateway; 1293 1294 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1295 address, "SubnetMask", subnetMask, 1296 "Gateway", gateway)) 1297 { 1298 messages::propertyValueFormatError( 1299 asyncResp->res, 1300 thisJson.dump(2, ' ', true, 1301 nlohmann::json::error_handler_t::replace), 1302 pathString); 1303 return; 1304 } 1305 1306 // Find the address/subnet/gateway values. Any values that are 1307 // not explicitly provided are assumed to be unmodified from the 1308 // current state of the interface. Merge existing state into the 1309 // current request. 1310 const std::string* addr = nullptr; 1311 const std::string* gw = nullptr; 1312 uint8_t prefixLength = 0; 1313 bool errorInEntry = false; 1314 if (address) 1315 { 1316 if (ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1317 { 1318 addr = &(*address); 1319 } 1320 else 1321 { 1322 messages::propertyValueFormatError(asyncResp->res, *address, 1323 pathString + "/Address"); 1324 errorInEntry = true; 1325 } 1326 } 1327 else if (nicIpEntry != ipv4Data.cend()) 1328 { 1329 addr = &(nicIpEntry->address); 1330 } 1331 else 1332 { 1333 messages::propertyMissing(asyncResp->res, 1334 pathString + "/Address"); 1335 errorInEntry = true; 1336 } 1337 1338 if (subnetMask) 1339 { 1340 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1341 &prefixLength)) 1342 { 1343 messages::propertyValueFormatError( 1344 asyncResp->res, *subnetMask, 1345 pathString + "/SubnetMask"); 1346 errorInEntry = true; 1347 } 1348 } 1349 else if (nicIpEntry != ipv4Data.cend()) 1350 { 1351 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1352 &prefixLength)) 1353 { 1354 messages::propertyValueFormatError( 1355 asyncResp->res, nicIpEntry->netmask, 1356 pathString + "/SubnetMask"); 1357 errorInEntry = true; 1358 } 1359 } 1360 else 1361 { 1362 messages::propertyMissing(asyncResp->res, 1363 pathString + "/SubnetMask"); 1364 errorInEntry = true; 1365 } 1366 1367 if (gateway) 1368 { 1369 if (ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1370 { 1371 gw = &(*gateway); 1372 } 1373 else 1374 { 1375 messages::propertyValueFormatError(asyncResp->res, *gateway, 1376 pathString + "/Gateway"); 1377 errorInEntry = true; 1378 } 1379 } 1380 else if (nicIpEntry != ipv4Data.cend()) 1381 { 1382 gw = &nicIpEntry->gateway; 1383 } 1384 else 1385 { 1386 messages::propertyMissing(asyncResp->res, 1387 pathString + "/Gateway"); 1388 errorInEntry = true; 1389 } 1390 1391 if (errorInEntry) 1392 { 1393 return; 1394 } 1395 1396 if (nicIpEntry != ipv4Data.cend()) 1397 { 1398 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw, 1399 *addr, asyncResp); 1400 nicIpEntry = 1401 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1402 } 1403 else 1404 { 1405 createIPv4(ifaceId, prefixLength, *gateway, *address, 1406 asyncResp); 1407 } 1408 entryIdx++; 1409 } 1410 else 1411 { 1412 if (nicIpEntry == ipv4Data.cend()) 1413 { 1414 // Requesting a DELETE/DO NOT MODIFY action for an item 1415 // that isn't present on the eth(n) interface. Input JSON is 1416 // in error, so bail out. 1417 if (thisJson.is_null()) 1418 { 1419 messages::resourceCannotBeDeleted(asyncResp->res); 1420 return; 1421 } 1422 messages::propertyValueFormatError( 1423 asyncResp->res, 1424 thisJson.dump(2, ' ', true, 1425 nlohmann::json::error_handler_t::replace), 1426 pathString); 1427 return; 1428 } 1429 1430 if (thisJson.is_null()) 1431 { 1432 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp); 1433 } 1434 if (nicIpEntry != ipv4Data.cend()) 1435 { 1436 nicIpEntry = 1437 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1438 } 1439 entryIdx++; 1440 } 1441 } 1442 } 1443 1444 inline void handleStaticNameServersPatch( 1445 const std::string& ifaceId, 1446 const std::vector<std::string>& updatedStaticNameServers, 1447 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1448 { 1449 crow::connections::systemBus->async_method_call( 1450 [asyncResp](const boost::system::error_code ec) { 1451 if (ec) 1452 { 1453 messages::internalError(asyncResp->res); 1454 return; 1455 } 1456 }, 1457 "xyz.openbmc_project.Network", 1458 "/xyz/openbmc_project/network/" + ifaceId, 1459 "org.freedesktop.DBus.Properties", "Set", 1460 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1461 dbus::utility::DbusVariantType{updatedStaticNameServers}); 1462 } 1463 1464 inline void handleIPv6StaticAddressesPatch( 1465 const std::string& ifaceId, const nlohmann::json& input, 1466 const boost::container::flat_set<IPv6AddressData>& ipv6Data, 1467 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1468 { 1469 if (!input.is_array() || input.empty()) 1470 { 1471 messages::propertyValueTypeError( 1472 asyncResp->res, 1473 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1474 "IPv6StaticAddresses"); 1475 return; 1476 } 1477 size_t entryIdx = 1; 1478 boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry = 1479 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1480 for (const nlohmann::json& thisJson : input) 1481 { 1482 std::string pathString = 1483 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1484 1485 if (!thisJson.is_null() && !thisJson.empty()) 1486 { 1487 std::optional<std::string> address; 1488 std::optional<uint8_t> prefixLength; 1489 nlohmann::json thisJsonCopy = thisJson; 1490 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1491 address, "PrefixLength", prefixLength)) 1492 { 1493 messages::propertyValueFormatError( 1494 asyncResp->res, 1495 thisJson.dump(2, ' ', true, 1496 nlohmann::json::error_handler_t::replace), 1497 pathString); 1498 return; 1499 } 1500 1501 const std::string* addr = nullptr; 1502 uint8_t prefix = 0; 1503 1504 // Find the address and prefixLength values. Any values that are 1505 // not explicitly provided are assumed to be unmodified from the 1506 // current state of the interface. Merge existing state into the 1507 // current request. 1508 if (address) 1509 { 1510 addr = &(*address); 1511 } 1512 else if (nicIpEntry != ipv6Data.end()) 1513 { 1514 addr = &(nicIpEntry->address); 1515 } 1516 else 1517 { 1518 messages::propertyMissing(asyncResp->res, 1519 pathString + "/Address"); 1520 return; 1521 } 1522 1523 if (prefixLength) 1524 { 1525 prefix = *prefixLength; 1526 } 1527 else if (nicIpEntry != ipv6Data.end()) 1528 { 1529 prefix = nicIpEntry->prefixLength; 1530 } 1531 else 1532 { 1533 messages::propertyMissing(asyncResp->res, 1534 pathString + "/PrefixLength"); 1535 return; 1536 } 1537 1538 if (nicIpEntry != ipv6Data.end()) 1539 { 1540 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr, 1541 asyncResp); 1542 nicIpEntry = 1543 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1544 } 1545 else 1546 { 1547 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1548 } 1549 entryIdx++; 1550 } 1551 else 1552 { 1553 if (nicIpEntry == ipv6Data.end()) 1554 { 1555 // Requesting a DELETE/DO NOT MODIFY action for an item 1556 // that isn't present on the eth(n) interface. Input JSON is 1557 // in error, so bail out. 1558 if (thisJson.is_null()) 1559 { 1560 messages::resourceCannotBeDeleted(asyncResp->res); 1561 return; 1562 } 1563 messages::propertyValueFormatError( 1564 asyncResp->res, 1565 thisJson.dump(2, ' ', true, 1566 nlohmann::json::error_handler_t::replace), 1567 pathString); 1568 return; 1569 } 1570 1571 if (thisJson.is_null()) 1572 { 1573 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp); 1574 } 1575 if (nicIpEntry != ipv6Data.cend()) 1576 { 1577 nicIpEntry = 1578 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1579 } 1580 entryIdx++; 1581 } 1582 } 1583 } 1584 1585 inline void parseInterfaceData( 1586 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1587 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1588 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1589 const boost::container::flat_set<IPv6AddressData>& ipv6Data) 1590 { 1591 constexpr const std::array<const char*, 1> inventoryForEthernet = { 1592 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1593 1594 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1595 jsonResponse["Id"] = ifaceId; 1596 jsonResponse["@odata.id"] = 1597 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId; 1598 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1599 1600 auto health = std::make_shared<HealthPopulate>(asyncResp); 1601 1602 crow::connections::systemBus->async_method_call( 1603 [health](const boost::system::error_code ec, 1604 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1605 if (ec) 1606 { 1607 return; 1608 } 1609 1610 health->inventory = resp; 1611 }, 1612 "xyz.openbmc_project.ObjectMapper", 1613 "/xyz/openbmc_project/object_mapper", 1614 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0), 1615 inventoryForEthernet); 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