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/config/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", 1125 "/xyz/openbmc_project/network/config/dhcp", 1126 "org.freedesktop.DBus.Properties", "Set", 1127 "xyz.openbmc_project.Network.DHCPConfiguration", propertyName, 1128 dbus::utility::DbusVariantType{value}); 1129 } 1130 1131 inline void handleDHCPPatch(const std::string& ifaceId, 1132 const EthernetInterfaceData& ethData, 1133 const DHCPParameters& v4dhcpParms, 1134 const DHCPParameters& v6dhcpParms, 1135 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1136 { 1137 bool ipv4Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1138 bool ipv6Active = translateDhcpEnabledToBool(ethData.dhcpEnabled, false); 1139 1140 bool nextv4DHCPState = 1141 v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active; 1142 1143 bool nextv6DHCPState{}; 1144 if (v6dhcpParms.dhcpv6OperatingMode) 1145 { 1146 if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") && 1147 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") && 1148 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled")) 1149 { 1150 messages::propertyValueFormatError(asyncResp->res, 1151 *v6dhcpParms.dhcpv6OperatingMode, 1152 "OperatingMode"); 1153 return; 1154 } 1155 nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful"); 1156 } 1157 else 1158 { 1159 nextv6DHCPState = ipv6Active; 1160 } 1161 1162 bool nextDNS{}; 1163 if (v4dhcpParms.useDnsServers && v6dhcpParms.useDnsServers) 1164 { 1165 if (*v4dhcpParms.useDnsServers != *v6dhcpParms.useDnsServers) 1166 { 1167 messages::generalError(asyncResp->res); 1168 return; 1169 } 1170 nextDNS = *v4dhcpParms.useDnsServers; 1171 } 1172 else if (v4dhcpParms.useDnsServers) 1173 { 1174 nextDNS = *v4dhcpParms.useDnsServers; 1175 } 1176 else if (v6dhcpParms.useDnsServers) 1177 { 1178 nextDNS = *v6dhcpParms.useDnsServers; 1179 } 1180 else 1181 { 1182 nextDNS = ethData.dnsEnabled; 1183 } 1184 1185 bool nextNTP{}; 1186 if (v4dhcpParms.useNtpServers && v6dhcpParms.useNtpServers) 1187 { 1188 if (*v4dhcpParms.useNtpServers != *v6dhcpParms.useNtpServers) 1189 { 1190 messages::generalError(asyncResp->res); 1191 return; 1192 } 1193 nextNTP = *v4dhcpParms.useNtpServers; 1194 } 1195 else if (v4dhcpParms.useNtpServers) 1196 { 1197 nextNTP = *v4dhcpParms.useNtpServers; 1198 } 1199 else if (v6dhcpParms.useNtpServers) 1200 { 1201 nextNTP = *v6dhcpParms.useNtpServers; 1202 } 1203 else 1204 { 1205 nextNTP = ethData.ntpEnabled; 1206 } 1207 1208 bool nextUseDomain{}; 1209 if (v4dhcpParms.useDomainName && v6dhcpParms.useDomainName) 1210 { 1211 if (*v4dhcpParms.useDomainName != *v6dhcpParms.useDomainName) 1212 { 1213 messages::generalError(asyncResp->res); 1214 return; 1215 } 1216 nextUseDomain = *v4dhcpParms.useDomainName; 1217 } 1218 else if (v4dhcpParms.useDomainName) 1219 { 1220 nextUseDomain = *v4dhcpParms.useDomainName; 1221 } 1222 else if (v6dhcpParms.useDomainName) 1223 { 1224 nextUseDomain = *v6dhcpParms.useDomainName; 1225 } 1226 else 1227 { 1228 nextUseDomain = ethData.hostNameEnabled; 1229 } 1230 1231 BMCWEB_LOG_DEBUG << "set DHCPEnabled..."; 1232 setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState, 1233 asyncResp); 1234 BMCWEB_LOG_DEBUG << "set DNSEnabled..."; 1235 setDHCPv4Config("DNSEnabled", nextDNS, asyncResp); 1236 BMCWEB_LOG_DEBUG << "set NTPEnabled..."; 1237 setDHCPv4Config("NTPEnabled", nextNTP, asyncResp); 1238 BMCWEB_LOG_DEBUG << "set HostNameEnabled..."; 1239 setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp); 1240 } 1241 1242 inline boost::container::flat_set<IPv4AddressData>::const_iterator 1243 getNextStaticIpEntry( 1244 const boost::container::flat_set<IPv4AddressData>::const_iterator& head, 1245 const boost::container::flat_set<IPv4AddressData>::const_iterator& end) 1246 { 1247 return std::find_if(head, end, [](const IPv4AddressData& value) { 1248 return value.origin == "Static"; 1249 }); 1250 } 1251 1252 inline boost::container::flat_set<IPv6AddressData>::const_iterator 1253 getNextStaticIpEntry( 1254 const boost::container::flat_set<IPv6AddressData>::const_iterator& head, 1255 const boost::container::flat_set<IPv6AddressData>::const_iterator& end) 1256 { 1257 return std::find_if(head, end, [](const IPv6AddressData& value) { 1258 return value.origin == "Static"; 1259 }); 1260 } 1261 1262 inline void handleIPv4StaticPatch( 1263 const std::string& ifaceId, nlohmann::json& input, 1264 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1265 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1266 { 1267 if ((!input.is_array()) || input.empty()) 1268 { 1269 messages::propertyValueTypeError( 1270 asyncResp->res, 1271 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1272 "IPv4StaticAddresses"); 1273 return; 1274 } 1275 1276 unsigned entryIdx = 1; 1277 // Find the first static IP address currently active on the NIC and 1278 // match it to the first JSON element in the IPv4StaticAddresses array. 1279 // Match each subsequent JSON element to the next static IP programmed 1280 // into the NIC. 1281 boost::container::flat_set<IPv4AddressData>::const_iterator nicIpEntry = 1282 getNextStaticIpEntry(ipv4Data.cbegin(), ipv4Data.cend()); 1283 1284 for (nlohmann::json& thisJson : input) 1285 { 1286 std::string pathString = 1287 "IPv4StaticAddresses/" + std::to_string(entryIdx); 1288 1289 if (!thisJson.is_null() && !thisJson.empty()) 1290 { 1291 std::optional<std::string> address; 1292 std::optional<std::string> subnetMask; 1293 std::optional<std::string> gateway; 1294 1295 if (!json_util::readJson(thisJson, asyncResp->res, "Address", 1296 address, "SubnetMask", subnetMask, 1297 "Gateway", gateway)) 1298 { 1299 messages::propertyValueFormatError( 1300 asyncResp->res, 1301 thisJson.dump(2, ' ', true, 1302 nlohmann::json::error_handler_t::replace), 1303 pathString); 1304 return; 1305 } 1306 1307 // Find the address/subnet/gateway values. Any values that are 1308 // not explicitly provided are assumed to be unmodified from the 1309 // current state of the interface. Merge existing state into the 1310 // current request. 1311 const std::string* addr = nullptr; 1312 const std::string* gw = nullptr; 1313 uint8_t prefixLength = 0; 1314 bool errorInEntry = false; 1315 if (address) 1316 { 1317 if (ip_util::ipv4VerifyIpAndGetBitcount(*address)) 1318 { 1319 addr = &(*address); 1320 } 1321 else 1322 { 1323 messages::propertyValueFormatError(asyncResp->res, *address, 1324 pathString + "/Address"); 1325 errorInEntry = true; 1326 } 1327 } 1328 else if (nicIpEntry != ipv4Data.cend()) 1329 { 1330 addr = &(nicIpEntry->address); 1331 } 1332 else 1333 { 1334 messages::propertyMissing(asyncResp->res, 1335 pathString + "/Address"); 1336 errorInEntry = true; 1337 } 1338 1339 if (subnetMask) 1340 { 1341 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask, 1342 &prefixLength)) 1343 { 1344 messages::propertyValueFormatError( 1345 asyncResp->res, *subnetMask, 1346 pathString + "/SubnetMask"); 1347 errorInEntry = true; 1348 } 1349 } 1350 else if (nicIpEntry != ipv4Data.cend()) 1351 { 1352 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask, 1353 &prefixLength)) 1354 { 1355 messages::propertyValueFormatError( 1356 asyncResp->res, nicIpEntry->netmask, 1357 pathString + "/SubnetMask"); 1358 errorInEntry = true; 1359 } 1360 } 1361 else 1362 { 1363 messages::propertyMissing(asyncResp->res, 1364 pathString + "/SubnetMask"); 1365 errorInEntry = true; 1366 } 1367 1368 if (gateway) 1369 { 1370 if (ip_util::ipv4VerifyIpAndGetBitcount(*gateway)) 1371 { 1372 gw = &(*gateway); 1373 } 1374 else 1375 { 1376 messages::propertyValueFormatError(asyncResp->res, *gateway, 1377 pathString + "/Gateway"); 1378 errorInEntry = true; 1379 } 1380 } 1381 else if (nicIpEntry != ipv4Data.cend()) 1382 { 1383 gw = &nicIpEntry->gateway; 1384 } 1385 else 1386 { 1387 messages::propertyMissing(asyncResp->res, 1388 pathString + "/Gateway"); 1389 errorInEntry = true; 1390 } 1391 1392 if (errorInEntry) 1393 { 1394 return; 1395 } 1396 1397 if (nicIpEntry != ipv4Data.cend()) 1398 { 1399 deleteAndCreateIPv4(ifaceId, nicIpEntry->id, prefixLength, *gw, 1400 *addr, asyncResp); 1401 nicIpEntry = 1402 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1403 } 1404 else 1405 { 1406 createIPv4(ifaceId, prefixLength, *gateway, *address, 1407 asyncResp); 1408 } 1409 entryIdx++; 1410 } 1411 else 1412 { 1413 if (nicIpEntry == ipv4Data.cend()) 1414 { 1415 // Requesting a DELETE/DO NOT MODIFY action for an item 1416 // that isn't present on the eth(n) interface. Input JSON is 1417 // in error, so bail out. 1418 if (thisJson.is_null()) 1419 { 1420 messages::resourceCannotBeDeleted(asyncResp->res); 1421 return; 1422 } 1423 messages::propertyValueFormatError( 1424 asyncResp->res, 1425 thisJson.dump(2, ' ', true, 1426 nlohmann::json::error_handler_t::replace), 1427 pathString); 1428 return; 1429 } 1430 1431 if (thisJson.is_null()) 1432 { 1433 deleteIPv4(ifaceId, nicIpEntry->id, asyncResp); 1434 } 1435 if (nicIpEntry != ipv4Data.cend()) 1436 { 1437 nicIpEntry = 1438 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend()); 1439 } 1440 entryIdx++; 1441 } 1442 } 1443 } 1444 1445 inline void handleStaticNameServersPatch( 1446 const std::string& ifaceId, 1447 const std::vector<std::string>& updatedStaticNameServers, 1448 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1449 { 1450 crow::connections::systemBus->async_method_call( 1451 [asyncResp](const boost::system::error_code ec) { 1452 if (ec) 1453 { 1454 messages::internalError(asyncResp->res); 1455 return; 1456 } 1457 }, 1458 "xyz.openbmc_project.Network", 1459 "/xyz/openbmc_project/network/" + ifaceId, 1460 "org.freedesktop.DBus.Properties", "Set", 1461 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers", 1462 dbus::utility::DbusVariantType{updatedStaticNameServers}); 1463 } 1464 1465 inline void handleIPv6StaticAddressesPatch( 1466 const std::string& ifaceId, const nlohmann::json& input, 1467 const boost::container::flat_set<IPv6AddressData>& ipv6Data, 1468 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1469 { 1470 if (!input.is_array() || input.empty()) 1471 { 1472 messages::propertyValueTypeError( 1473 asyncResp->res, 1474 input.dump(2, ' ', true, nlohmann::json::error_handler_t::replace), 1475 "IPv6StaticAddresses"); 1476 return; 1477 } 1478 size_t entryIdx = 1; 1479 boost::container::flat_set<IPv6AddressData>::const_iterator nicIpEntry = 1480 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend()); 1481 for (const nlohmann::json& thisJson : input) 1482 { 1483 std::string pathString = 1484 "IPv6StaticAddresses/" + std::to_string(entryIdx); 1485 1486 if (!thisJson.is_null() && !thisJson.empty()) 1487 { 1488 std::optional<std::string> address; 1489 std::optional<uint8_t> prefixLength; 1490 nlohmann::json thisJsonCopy = thisJson; 1491 if (!json_util::readJson(thisJsonCopy, asyncResp->res, "Address", 1492 address, "PrefixLength", prefixLength)) 1493 { 1494 messages::propertyValueFormatError( 1495 asyncResp->res, 1496 thisJson.dump(2, ' ', true, 1497 nlohmann::json::error_handler_t::replace), 1498 pathString); 1499 return; 1500 } 1501 1502 const std::string* addr = nullptr; 1503 uint8_t prefix = 0; 1504 1505 // Find the address and prefixLength values. Any values that are 1506 // not explicitly provided are assumed to be unmodified from the 1507 // current state of the interface. Merge existing state into the 1508 // current request. 1509 if (address) 1510 { 1511 addr = &(*address); 1512 } 1513 else if (nicIpEntry != ipv6Data.end()) 1514 { 1515 addr = &(nicIpEntry->address); 1516 } 1517 else 1518 { 1519 messages::propertyMissing(asyncResp->res, 1520 pathString + "/Address"); 1521 return; 1522 } 1523 1524 if (prefixLength) 1525 { 1526 prefix = *prefixLength; 1527 } 1528 else if (nicIpEntry != ipv6Data.end()) 1529 { 1530 prefix = nicIpEntry->prefixLength; 1531 } 1532 else 1533 { 1534 messages::propertyMissing(asyncResp->res, 1535 pathString + "/PrefixLength"); 1536 return; 1537 } 1538 1539 if (nicIpEntry != ipv6Data.end()) 1540 { 1541 deleteAndCreateIPv6(ifaceId, nicIpEntry->id, prefix, *addr, 1542 asyncResp); 1543 nicIpEntry = 1544 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1545 } 1546 else 1547 { 1548 createIPv6(ifaceId, *prefixLength, *addr, asyncResp); 1549 } 1550 entryIdx++; 1551 } 1552 else 1553 { 1554 if (nicIpEntry == ipv6Data.end()) 1555 { 1556 // Requesting a DELETE/DO NOT MODIFY action for an item 1557 // that isn't present on the eth(n) interface. Input JSON is 1558 // in error, so bail out. 1559 if (thisJson.is_null()) 1560 { 1561 messages::resourceCannotBeDeleted(asyncResp->res); 1562 return; 1563 } 1564 messages::propertyValueFormatError( 1565 asyncResp->res, 1566 thisJson.dump(2, ' ', true, 1567 nlohmann::json::error_handler_t::replace), 1568 pathString); 1569 return; 1570 } 1571 1572 if (thisJson.is_null()) 1573 { 1574 deleteIPv6(ifaceId, nicIpEntry->id, asyncResp); 1575 } 1576 if (nicIpEntry != ipv6Data.cend()) 1577 { 1578 nicIpEntry = 1579 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend()); 1580 } 1581 entryIdx++; 1582 } 1583 } 1584 } 1585 1586 inline void parseInterfaceData( 1587 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1588 const std::string& ifaceId, const EthernetInterfaceData& ethData, 1589 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1590 const boost::container::flat_set<IPv6AddressData>& ipv6Data) 1591 { 1592 constexpr const std::array<const char*, 1> inventoryForEthernet = { 1593 "xyz.openbmc_project.Inventory.Item.Ethernet"}; 1594 1595 nlohmann::json& jsonResponse = asyncResp->res.jsonValue; 1596 jsonResponse["Id"] = ifaceId; 1597 jsonResponse["@odata.id"] = 1598 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + ifaceId; 1599 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled; 1600 1601 auto health = std::make_shared<HealthPopulate>(asyncResp); 1602 1603 crow::connections::systemBus->async_method_call( 1604 [health](const boost::system::error_code ec, 1605 const dbus::utility::MapperGetSubTreePathsResponse& resp) { 1606 if (ec) 1607 { 1608 return; 1609 } 1610 1611 health->inventory = resp; 1612 }, 1613 "xyz.openbmc_project.ObjectMapper", 1614 "/xyz/openbmc_project/object_mapper", 1615 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/", int32_t(0), 1616 inventoryForEthernet); 1617 1618 health->populate(); 1619 1620 if (ethData.nicEnabled) 1621 { 1622 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1623 jsonResponse["Status"]["State"] = "Enabled"; 1624 } 1625 else 1626 { 1627 jsonResponse["LinkStatus"] = "NoLink"; 1628 jsonResponse["Status"]["State"] = "Disabled"; 1629 } 1630 1631 jsonResponse["SpeedMbps"] = ethData.speed; 1632 jsonResponse["MTUSize"] = ethData.mtuSize; 1633 jsonResponse["MACAddress"] = ethData.macAddress; 1634 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1635 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1636 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled; 1637 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled; 1638 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled; 1639 1640 jsonResponse["DHCPv6"]["OperatingMode"] = 1641 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful" 1642 : "Disabled"; 1643 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled; 1644 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled; 1645 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled; 1646 1647 if (!ethData.hostName.empty()) 1648 { 1649 jsonResponse["HostName"] = ethData.hostName; 1650 1651 // When domain name is empty then it means, that it is a network 1652 // without domain names, and the host name itself must be treated as 1653 // FQDN 1654 std::string fqdn = ethData.hostName; 1655 if (!ethData.domainnames.empty()) 1656 { 1657 fqdn += "." + ethData.domainnames[0]; 1658 } 1659 jsonResponse["FQDN"] = fqdn; 1660 } 1661 1662 jsonResponse["VLANs"]["@odata.id"] = 1663 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 1664 "EthernetInterfaces", ifaceId, "VLANs"); 1665 1666 jsonResponse["NameServers"] = ethData.nameServers; 1667 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1668 1669 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1670 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1671 ipv4Array = nlohmann::json::array(); 1672 ipv4StaticArray = nlohmann::json::array(); 1673 for (const auto& ipv4Config : ipv4Data) 1674 { 1675 std::string gatewayStr = ipv4Config.gateway; 1676 if (gatewayStr.empty()) 1677 { 1678 gatewayStr = "0.0.0.0"; 1679 } 1680 nlohmann::json::object_t ipv4; 1681 ipv4["AddressOrigin"] = ipv4Config.origin; 1682 ipv4["SubnetMask"] = ipv4Config.netmask; 1683 ipv4["Address"] = ipv4Config.address; 1684 ipv4["Gateway"] = gatewayStr; 1685 1686 if (ipv4Config.origin == "Static") 1687 { 1688 ipv4StaticArray.push_back(ipv4); 1689 } 1690 1691 ipv4Array.push_back(std::move(ipv4)); 1692 } 1693 1694 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1695 if (ipv6GatewayStr.empty()) 1696 { 1697 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1698 } 1699 1700 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1701 1702 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1703 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1704 ipv6Array = nlohmann::json::array(); 1705 ipv6StaticArray = nlohmann::json::array(); 1706 nlohmann::json& ipv6AddrPolicyTable = 1707 jsonResponse["IPv6AddressPolicyTable"]; 1708 ipv6AddrPolicyTable = nlohmann::json::array(); 1709 for (const auto& ipv6Config : ipv6Data) 1710 { 1711 nlohmann::json::object_t ipv6; 1712 ipv6["Address"] = ipv6Config.address; 1713 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1714 ipv6["AddressOrigin"] = ipv6Config.origin; 1715 ipv6["AddressState"] = nullptr; 1716 ipv6Array.push_back(std::move(ipv6)); 1717 if (ipv6Config.origin == "Static") 1718 { 1719 nlohmann::json::object_t ipv6Static; 1720 ipv6Static["Address"] = ipv6Config.address; 1721 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1722 ipv6StaticArray.push_back(std::move(ipv6Static)); 1723 } 1724 } 1725 } 1726 1727 inline bool verifyNames(const std::string& parent, const std::string& iface) 1728 { 1729 return iface.starts_with(parent + "_"); 1730 } 1731 1732 inline void requestEthernetInterfacesRoutes(App& app) 1733 { 1734 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1735 .privileges(redfish::privileges::getEthernetInterfaceCollection) 1736 .methods(boost::beast::http::verb::get)( 1737 [&app](const crow::Request& req, 1738 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1739 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1740 { 1741 return; 1742 } 1743 1744 asyncResp->res.jsonValue["@odata.type"] = 1745 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1746 asyncResp->res.jsonValue["@odata.id"] = 1747 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1748 asyncResp->res.jsonValue["Name"] = 1749 "Ethernet Network Interface Collection"; 1750 asyncResp->res.jsonValue["Description"] = 1751 "Collection of EthernetInterfaces for this Manager"; 1752 1753 // Get eth interface list, and call the below callback for JSON 1754 // preparation 1755 getEthernetIfaceList( 1756 [asyncResp]( 1757 const bool& success, 1758 const boost::container::flat_set<std::string>& ifaceList) { 1759 if (!success) 1760 { 1761 messages::internalError(asyncResp->res); 1762 return; 1763 } 1764 1765 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 1766 ifaceArray = nlohmann::json::array(); 1767 std::string tag = "_"; 1768 for (const std::string& ifaceItem : ifaceList) 1769 { 1770 std::size_t found = ifaceItem.find(tag); 1771 if (found == std::string::npos) 1772 { 1773 nlohmann::json::object_t iface; 1774 iface["@odata.id"] = 1775 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1776 ifaceItem; 1777 ifaceArray.push_back(std::move(iface)); 1778 } 1779 } 1780 1781 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 1782 asyncResp->res.jsonValue["@odata.id"] = 1783 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1784 }); 1785 }); 1786 1787 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1788 .privileges(redfish::privileges::getEthernetInterface) 1789 .methods(boost::beast::http::verb::get)( 1790 [&app](const crow::Request& req, 1791 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1792 const std::string& ifaceId) { 1793 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1794 { 1795 return; 1796 } 1797 getEthernetIfaceData( 1798 ifaceId, 1799 [asyncResp, ifaceId]( 1800 const bool& success, const EthernetInterfaceData& ethData, 1801 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1802 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1803 if (!success) 1804 { 1805 // TODO(Pawel)consider distinguish between non 1806 // existing object, and other errors 1807 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1808 ifaceId); 1809 return; 1810 } 1811 1812 // Keep using the v1.6.0 schema here as currently bmcweb have to use 1813 // "VLANs" property deprecated in v1.7.0 for VLAN creation/deletion. 1814 asyncResp->res.jsonValue["@odata.type"] = 1815 "#EthernetInterface.v1_6_0.EthernetInterface"; 1816 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1817 asyncResp->res.jsonValue["Description"] = 1818 "Management Network Interface"; 1819 1820 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data); 1821 }); 1822 }); 1823 1824 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1825 .privileges(redfish::privileges::patchEthernetInterface) 1826 .methods(boost::beast::http::verb::patch)( 1827 [&app](const crow::Request& req, 1828 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1829 const std::string& ifaceId) { 1830 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1831 { 1832 return; 1833 } 1834 std::optional<std::string> hostname; 1835 std::optional<std::string> fqdn; 1836 std::optional<std::string> macAddress; 1837 std::optional<std::string> ipv6DefaultGateway; 1838 std::optional<nlohmann::json> ipv4StaticAddresses; 1839 std::optional<nlohmann::json> ipv6StaticAddresses; 1840 std::optional<std::vector<std::string>> staticNameServers; 1841 std::optional<nlohmann::json> dhcpv4; 1842 std::optional<nlohmann::json> dhcpv6; 1843 std::optional<bool> interfaceEnabled; 1844 std::optional<size_t> mtuSize; 1845 DHCPParameters v4dhcpParms; 1846 DHCPParameters v6dhcpParms; 1847 1848 if (!json_util::readJsonPatch( 1849 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1850 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1851 macAddress, "StaticNameServers", staticNameServers, 1852 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1853 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1854 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled)) 1855 { 1856 return; 1857 } 1858 if (dhcpv4) 1859 { 1860 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 1861 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1862 v4dhcpParms.useDnsServers, "UseNTPServers", 1863 v4dhcpParms.useNtpServers, "UseDomainName", 1864 v4dhcpParms.useDomainName)) 1865 { 1866 return; 1867 } 1868 } 1869 1870 if (dhcpv6) 1871 { 1872 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 1873 v6dhcpParms.dhcpv6OperatingMode, 1874 "UseDNSServers", v6dhcpParms.useDnsServers, 1875 "UseNTPServers", v6dhcpParms.useNtpServers, 1876 "UseDomainName", 1877 v6dhcpParms.useDomainName)) 1878 { 1879 return; 1880 } 1881 } 1882 1883 // Get single eth interface data, and call the below callback 1884 // for JSON preparation 1885 getEthernetIfaceData( 1886 ifaceId, 1887 [asyncResp, ifaceId, hostname = std::move(hostname), 1888 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1889 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1890 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1891 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1892 staticNameServers = std::move(staticNameServers), 1893 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 1894 v4dhcpParms = std::move(v4dhcpParms), 1895 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1896 const bool& success, const EthernetInterfaceData& ethData, 1897 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1898 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1899 if (!success) 1900 { 1901 // ... otherwise return error 1902 // TODO(Pawel)consider distinguish between non 1903 // existing object, and other errors 1904 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1905 ifaceId); 1906 return; 1907 } 1908 1909 if (dhcpv4 || dhcpv6) 1910 { 1911 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 1912 asyncResp); 1913 } 1914 1915 if (hostname) 1916 { 1917 handleHostnamePatch(*hostname, asyncResp); 1918 } 1919 1920 if (fqdn) 1921 { 1922 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 1923 } 1924 1925 if (macAddress) 1926 { 1927 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 1928 } 1929 1930 if (ipv4StaticAddresses) 1931 { 1932 // TODO(ed) for some reason the capture of 1933 // ipv4Addresses above is returning a const value, 1934 // not a non-const value. This doesn't really work 1935 // for us, as we need to be able to efficiently move 1936 // out the intermedia nlohmann::json objects. This 1937 // makes a copy of the structure, and operates on 1938 // that, but could be done more efficiently 1939 nlohmann::json ipv4Static = *ipv4StaticAddresses; 1940 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 1941 } 1942 1943 if (staticNameServers) 1944 { 1945 handleStaticNameServersPatch(ifaceId, *staticNameServers, 1946 asyncResp); 1947 } 1948 1949 if (ipv6DefaultGateway) 1950 { 1951 messages::propertyNotWritable(asyncResp->res, 1952 "IPv6DefaultGateway"); 1953 } 1954 1955 if (ipv6StaticAddresses) 1956 { 1957 const nlohmann::json& ipv6Static = *ipv6StaticAddresses; 1958 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data, 1959 asyncResp); 1960 } 1961 1962 if (interfaceEnabled) 1963 { 1964 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 1965 *interfaceEnabled, asyncResp); 1966 } 1967 1968 if (mtuSize) 1969 { 1970 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 1971 } 1972 }); 1973 }); 1974 1975 BMCWEB_ROUTE( 1976 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 1977 .privileges(redfish::privileges::getVLanNetworkInterface) 1978 .methods(boost::beast::http::verb::get)( 1979 [&app](const crow::Request& req, 1980 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1981 const std::string& parentIfaceId, 1982 const std::string& ifaceId) { 1983 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1984 { 1985 return; 1986 } 1987 asyncResp->res.jsonValue["@odata.type"] = 1988 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1989 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 1990 1991 if (!verifyNames(parentIfaceId, ifaceId)) 1992 { 1993 return; 1994 } 1995 1996 // Get single eth interface data, and call the below callback 1997 // for JSON preparation 1998 getEthernetIfaceData( 1999 ifaceId, 2000 [asyncResp, parentIfaceId, 2001 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2002 const boost::container::flat_set<IPv4AddressData>&, 2003 const boost::container::flat_set<IPv6AddressData>&) { 2004 if (success && ethData.vlanId) 2005 { 2006 asyncResp->res.jsonValue["Id"] = ifaceId; 2007 asyncResp->res.jsonValue["@odata.id"] = 2008 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2009 parentIfaceId + "/VLANs/" + ifaceId; 2010 2011 asyncResp->res.jsonValue["VLANEnable"] = ethData.nicEnabled; 2012 asyncResp->res.jsonValue["VLANId"] = *ethData.vlanId; 2013 } 2014 else 2015 { 2016 // ... otherwise return error 2017 // TODO(Pawel)consider distinguish between non 2018 // existing object, and other errors 2019 messages::resourceNotFound(asyncResp->res, 2020 "VLanNetworkInterface", ifaceId); 2021 } 2022 }); 2023 }); 2024 2025 BMCWEB_ROUTE( 2026 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2027 .privileges(redfish::privileges::patchVLanNetworkInterface) 2028 .methods(boost::beast::http::verb::patch)( 2029 [&app](const crow::Request& req, 2030 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2031 const std::string& parentIfaceId, 2032 const std::string& ifaceId) { 2033 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2034 { 2035 return; 2036 } 2037 if (!verifyNames(parentIfaceId, ifaceId)) 2038 { 2039 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2040 ifaceId); 2041 return; 2042 } 2043 2044 std::optional<bool> vlanEnable; 2045 std::optional<uint32_t> vlanId; 2046 2047 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable", 2048 vlanEnable, "VLANId", vlanId)) 2049 { 2050 return; 2051 } 2052 2053 if (vlanId) 2054 { 2055 messages::propertyNotWritable(asyncResp->res, "VLANId"); 2056 return; 2057 } 2058 2059 // Get single eth interface data, and call the below callback 2060 // for JSON preparation 2061 getEthernetIfaceData( 2062 ifaceId, 2063 [asyncResp, parentIfaceId, ifaceId, vlanEnable]( 2064 const bool& success, const EthernetInterfaceData& ethData, 2065 const boost::container::flat_set<IPv4AddressData>&, 2066 const boost::container::flat_set<IPv6AddressData>&) { 2067 if (success && ethData.vlanId) 2068 { 2069 if (vlanEnable) 2070 { 2071 crow::connections::systemBus->async_method_call( 2072 [asyncResp](const boost::system::error_code ec) { 2073 if (ec) 2074 { 2075 messages::internalError(asyncResp->res); 2076 return; 2077 } 2078 }, 2079 "xyz.openbmc_project.Network", 2080 "/xyz/openbmc_project/network/" + ifaceId, 2081 "org.freedesktop.DBus.Properties", "Set", 2082 "xyz.openbmc_project.Network.EthernetInterface", 2083 "NICEnabled", 2084 dbus::utility::DbusVariantType(*vlanEnable)); 2085 } 2086 } 2087 else 2088 { 2089 // TODO(Pawel)consider distinguish between non 2090 // existing object, and other errors 2091 messages::resourceNotFound(asyncResp->res, 2092 "VLanNetworkInterface", ifaceId); 2093 return; 2094 } 2095 }); 2096 }); 2097 2098 BMCWEB_ROUTE( 2099 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2100 .privileges(redfish::privileges::deleteVLanNetworkInterface) 2101 .methods(boost::beast::http::verb::delete_)( 2102 [&app](const crow::Request& req, 2103 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2104 const std::string& parentIfaceId, 2105 const std::string& ifaceId) { 2106 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2107 { 2108 return; 2109 } 2110 if (!verifyNames(parentIfaceId, ifaceId)) 2111 { 2112 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2113 ifaceId); 2114 return; 2115 } 2116 2117 // Get single eth interface data, and call the below callback 2118 // for JSON preparation 2119 getEthernetIfaceData( 2120 ifaceId, 2121 [asyncResp, parentIfaceId, 2122 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2123 const boost::container::flat_set<IPv4AddressData>&, 2124 const boost::container::flat_set<IPv6AddressData>&) { 2125 if (success && ethData.vlanId) 2126 { 2127 auto callback = 2128 [asyncResp](const boost::system::error_code ec) { 2129 if (ec) 2130 { 2131 messages::internalError(asyncResp->res); 2132 } 2133 }; 2134 crow::connections::systemBus->async_method_call( 2135 std::move(callback), "xyz.openbmc_project.Network", 2136 std::string("/xyz/openbmc_project/network/") + ifaceId, 2137 "xyz.openbmc_project.Object.Delete", "Delete"); 2138 } 2139 else 2140 { 2141 // ... otherwise return error 2142 // TODO(Pawel)consider distinguish between non 2143 // existing object, and other errors 2144 messages::resourceNotFound(asyncResp->res, 2145 "VLanNetworkInterface", ifaceId); 2146 } 2147 }); 2148 }); 2149 2150 BMCWEB_ROUTE(app, 2151 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2152 2153 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2154 .methods(boost::beast::http::verb::get)( 2155 [&app](const crow::Request& req, 2156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2157 const std::string& rootInterfaceName) { 2158 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2159 { 2160 return; 2161 } 2162 // Get eth interface list, and call the below callback for JSON 2163 // preparation 2164 getEthernetIfaceList( 2165 [asyncResp, rootInterfaceName]( 2166 const bool& success, 2167 const boost::container::flat_set<std::string>& ifaceList) { 2168 if (!success) 2169 { 2170 messages::internalError(asyncResp->res); 2171 return; 2172 } 2173 2174 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2175 { 2176 messages::resourceNotFound(asyncResp->res, 2177 "VLanNetworkInterfaceCollection", 2178 rootInterfaceName); 2179 return; 2180 } 2181 2182 asyncResp->res.jsonValue["@odata.type"] = 2183 "#VLanNetworkInterfaceCollection." 2184 "VLanNetworkInterfaceCollection"; 2185 asyncResp->res.jsonValue["Name"] = 2186 "VLAN Network Interface Collection"; 2187 2188 nlohmann::json ifaceArray = nlohmann::json::array(); 2189 2190 for (const std::string& ifaceItem : ifaceList) 2191 { 2192 if (ifaceItem.starts_with(rootInterfaceName + "_")) 2193 { 2194 std::string path = 2195 "/redfish/v1/Managers/bmc/EthernetInterfaces/"; 2196 path += rootInterfaceName; 2197 path += "/VLANs/"; 2198 path += ifaceItem; 2199 nlohmann::json::object_t iface; 2200 iface["@odata.id"] = std::move(path); 2201 ifaceArray.push_back(std::move(iface)); 2202 } 2203 } 2204 2205 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2206 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2207 asyncResp->res.jsonValue["@odata.id"] = 2208 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2209 rootInterfaceName + "/VLANs"; 2210 }); 2211 }); 2212 2213 BMCWEB_ROUTE(app, 2214 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2215 .privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2216 .methods(boost::beast::http::verb::post)( 2217 [&app](const crow::Request& req, 2218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2219 const std::string& rootInterfaceName) { 2220 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2221 { 2222 return; 2223 } 2224 bool vlanEnable = false; 2225 uint32_t vlanId = 0; 2226 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId, 2227 "VLANEnable", vlanEnable)) 2228 { 2229 return; 2230 } 2231 // Need both vlanId and vlanEnable to service this request 2232 if (vlanId == 0U) 2233 { 2234 messages::propertyMissing(asyncResp->res, "VLANId"); 2235 } 2236 if (!vlanEnable) 2237 { 2238 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2239 } 2240 if (static_cast<bool>(vlanId) ^ vlanEnable) 2241 { 2242 return; 2243 } 2244 2245 auto callback = [asyncResp](const boost::system::error_code ec) { 2246 if (ec) 2247 { 2248 // TODO(ed) make more consistent error messages 2249 // based on phosphor-network responses 2250 messages::internalError(asyncResp->res); 2251 return; 2252 } 2253 messages::created(asyncResp->res); 2254 }; 2255 crow::connections::systemBus->async_method_call( 2256 std::move(callback), "xyz.openbmc_project.Network", 2257 "/xyz/openbmc_project/network", 2258 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2259 rootInterfaceName, vlanId); 2260 }); 2261 } 2262 2263 } // namespace redfish 2264