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::message& 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"] = "LinkUp"; 1623 jsonResponse["Status"]["State"] = "Enabled"; 1624 } 1625 else 1626 { 1627 jsonResponse["LinkStatus"] = "NoLink"; 1628 jsonResponse["Status"]["State"] = "Disabled"; 1629 } 1630 1631 jsonResponse["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown"; 1632 jsonResponse["SpeedMbps"] = ethData.speed; 1633 jsonResponse["MTUSize"] = ethData.mtuSize; 1634 jsonResponse["MACAddress"] = ethData.macAddress; 1635 jsonResponse["DHCPv4"]["DHCPEnabled"] = 1636 translateDhcpEnabledToBool(ethData.dhcpEnabled, true); 1637 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpEnabled; 1638 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsEnabled; 1639 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.hostNameEnabled; 1640 1641 jsonResponse["DHCPv6"]["OperatingMode"] = 1642 translateDhcpEnabledToBool(ethData.dhcpEnabled, false) ? "Stateful" 1643 : "Disabled"; 1644 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpEnabled; 1645 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsEnabled; 1646 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.hostNameEnabled; 1647 1648 if (!ethData.hostName.empty()) 1649 { 1650 jsonResponse["HostName"] = ethData.hostName; 1651 1652 // When domain name is empty then it means, that it is a network 1653 // without domain names, and the host name itself must be treated as 1654 // FQDN 1655 std::string fqdn = ethData.hostName; 1656 if (!ethData.domainnames.empty()) 1657 { 1658 fqdn += "." + ethData.domainnames[0]; 1659 } 1660 jsonResponse["FQDN"] = fqdn; 1661 } 1662 1663 jsonResponse["VLANs"]["@odata.id"] = 1664 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 1665 "EthernetInterfaces", ifaceId, "VLANs"); 1666 1667 jsonResponse["NameServers"] = ethData.nameServers; 1668 jsonResponse["StaticNameServers"] = ethData.staticNameServers; 1669 1670 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"]; 1671 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"]; 1672 ipv4Array = nlohmann::json::array(); 1673 ipv4StaticArray = nlohmann::json::array(); 1674 for (const auto& ipv4Config : ipv4Data) 1675 { 1676 std::string gatewayStr = ipv4Config.gateway; 1677 if (gatewayStr.empty()) 1678 { 1679 gatewayStr = "0.0.0.0"; 1680 } 1681 nlohmann::json::object_t ipv4; 1682 ipv4["AddressOrigin"] = ipv4Config.origin; 1683 ipv4["SubnetMask"] = ipv4Config.netmask; 1684 ipv4["Address"] = ipv4Config.address; 1685 ipv4["Gateway"] = gatewayStr; 1686 1687 if (ipv4Config.origin == "Static") 1688 { 1689 ipv4StaticArray.push_back(ipv4); 1690 } 1691 1692 ipv4Array.push_back(std::move(ipv4)); 1693 } 1694 1695 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway; 1696 if (ipv6GatewayStr.empty()) 1697 { 1698 ipv6GatewayStr = "0:0:0:0:0:0:0:0"; 1699 } 1700 1701 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr; 1702 1703 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"]; 1704 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"]; 1705 ipv6Array = nlohmann::json::array(); 1706 ipv6StaticArray = nlohmann::json::array(); 1707 nlohmann::json& ipv6AddrPolicyTable = 1708 jsonResponse["IPv6AddressPolicyTable"]; 1709 ipv6AddrPolicyTable = nlohmann::json::array(); 1710 for (const auto& ipv6Config : ipv6Data) 1711 { 1712 nlohmann::json::object_t ipv6; 1713 ipv6["Address"] = ipv6Config.address; 1714 ipv6["PrefixLength"] = ipv6Config.prefixLength; 1715 ipv6["AddressOrigin"] = ipv6Config.origin; 1716 ipv6["AddressState"] = nullptr; 1717 ipv6Array.push_back(std::move(ipv6)); 1718 if (ipv6Config.origin == "Static") 1719 { 1720 nlohmann::json::object_t ipv6Static; 1721 ipv6Static["Address"] = ipv6Config.address; 1722 ipv6Static["PrefixLength"] = ipv6Config.prefixLength; 1723 ipv6StaticArray.push_back(std::move(ipv6Static)); 1724 } 1725 } 1726 } 1727 1728 inline bool verifyNames(const std::string& parent, const std::string& iface) 1729 { 1730 return iface.starts_with(parent + "_"); 1731 } 1732 1733 inline void requestEthernetInterfacesRoutes(App& app) 1734 { 1735 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/") 1736 .privileges(redfish::privileges::getEthernetInterfaceCollection) 1737 .methods(boost::beast::http::verb::get)( 1738 [&app](const crow::Request& req, 1739 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1740 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1741 { 1742 return; 1743 } 1744 1745 asyncResp->res.jsonValue["@odata.type"] = 1746 "#EthernetInterfaceCollection.EthernetInterfaceCollection"; 1747 asyncResp->res.jsonValue["@odata.id"] = 1748 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1749 asyncResp->res.jsonValue["Name"] = 1750 "Ethernet Network Interface Collection"; 1751 asyncResp->res.jsonValue["Description"] = 1752 "Collection of EthernetInterfaces for this Manager"; 1753 1754 // Get eth interface list, and call the below callback for JSON 1755 // preparation 1756 getEthernetIfaceList( 1757 [asyncResp]( 1758 const bool& success, 1759 const boost::container::flat_set<std::string>& ifaceList) { 1760 if (!success) 1761 { 1762 messages::internalError(asyncResp->res); 1763 return; 1764 } 1765 1766 nlohmann::json& ifaceArray = asyncResp->res.jsonValue["Members"]; 1767 ifaceArray = nlohmann::json::array(); 1768 std::string tag = "_"; 1769 for (const std::string& ifaceItem : ifaceList) 1770 { 1771 std::size_t found = ifaceItem.find(tag); 1772 if (found == std::string::npos) 1773 { 1774 nlohmann::json::object_t iface; 1775 iface["@odata.id"] = 1776 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 1777 ifaceItem; 1778 ifaceArray.push_back(std::move(iface)); 1779 } 1780 } 1781 1782 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 1783 asyncResp->res.jsonValue["@odata.id"] = 1784 "/redfish/v1/Managers/bmc/EthernetInterfaces"; 1785 }); 1786 }); 1787 1788 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1789 .privileges(redfish::privileges::getEthernetInterface) 1790 .methods(boost::beast::http::verb::get)( 1791 [&app](const crow::Request& req, 1792 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1793 const std::string& ifaceId) { 1794 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1795 { 1796 return; 1797 } 1798 getEthernetIfaceData( 1799 ifaceId, 1800 [asyncResp, ifaceId]( 1801 const bool& success, const EthernetInterfaceData& ethData, 1802 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1803 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1804 if (!success) 1805 { 1806 // TODO(Pawel)consider distinguish between non 1807 // existing object, and other errors 1808 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1809 ifaceId); 1810 return; 1811 } 1812 1813 // Keep using the v1.6.0 schema here as currently bmcweb have to use 1814 // "VLANs" property deprecated in v1.7.0 for VLAN creation/deletion. 1815 asyncResp->res.jsonValue["@odata.type"] = 1816 "#EthernetInterface.v1_6_0.EthernetInterface"; 1817 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface"; 1818 asyncResp->res.jsonValue["Description"] = 1819 "Management Network Interface"; 1820 1821 parseInterfaceData(asyncResp, ifaceId, ethData, ipv4Data, ipv6Data); 1822 }); 1823 }); 1824 1825 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/") 1826 .privileges(redfish::privileges::patchEthernetInterface) 1827 .methods(boost::beast::http::verb::patch)( 1828 [&app](const crow::Request& req, 1829 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1830 const std::string& ifaceId) { 1831 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1832 { 1833 return; 1834 } 1835 std::optional<std::string> hostname; 1836 std::optional<std::string> fqdn; 1837 std::optional<std::string> macAddress; 1838 std::optional<std::string> ipv6DefaultGateway; 1839 std::optional<nlohmann::json> ipv4StaticAddresses; 1840 std::optional<nlohmann::json> ipv6StaticAddresses; 1841 std::optional<std::vector<std::string>> staticNameServers; 1842 std::optional<nlohmann::json> dhcpv4; 1843 std::optional<nlohmann::json> dhcpv6; 1844 std::optional<bool> interfaceEnabled; 1845 std::optional<size_t> mtuSize; 1846 DHCPParameters v4dhcpParms; 1847 DHCPParameters v6dhcpParms; 1848 1849 if (!json_util::readJsonPatch( 1850 req, asyncResp->res, "HostName", hostname, "FQDN", fqdn, 1851 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress", 1852 macAddress, "StaticNameServers", staticNameServers, 1853 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses", 1854 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, 1855 "MTUSize", mtuSize, "InterfaceEnabled", interfaceEnabled)) 1856 { 1857 return; 1858 } 1859 if (dhcpv4) 1860 { 1861 if (!json_util::readJson(*dhcpv4, asyncResp->res, "DHCPEnabled", 1862 v4dhcpParms.dhcpv4Enabled, "UseDNSServers", 1863 v4dhcpParms.useDnsServers, "UseNTPServers", 1864 v4dhcpParms.useNtpServers, "UseDomainName", 1865 v4dhcpParms.useDomainName)) 1866 { 1867 return; 1868 } 1869 } 1870 1871 if (dhcpv6) 1872 { 1873 if (!json_util::readJson(*dhcpv6, asyncResp->res, "OperatingMode", 1874 v6dhcpParms.dhcpv6OperatingMode, 1875 "UseDNSServers", v6dhcpParms.useDnsServers, 1876 "UseNTPServers", v6dhcpParms.useNtpServers, 1877 "UseDomainName", 1878 v6dhcpParms.useDomainName)) 1879 { 1880 return; 1881 } 1882 } 1883 1884 // Get single eth interface data, and call the below callback 1885 // for JSON preparation 1886 getEthernetIfaceData( 1887 ifaceId, 1888 [asyncResp, ifaceId, hostname = std::move(hostname), 1889 fqdn = std::move(fqdn), macAddress = std::move(macAddress), 1890 ipv4StaticAddresses = std::move(ipv4StaticAddresses), 1891 ipv6DefaultGateway = std::move(ipv6DefaultGateway), 1892 ipv6StaticAddresses = std::move(ipv6StaticAddresses), 1893 staticNameServers = std::move(staticNameServers), 1894 dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6), mtuSize, 1895 v4dhcpParms = std::move(v4dhcpParms), 1896 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled]( 1897 const bool& success, const EthernetInterfaceData& ethData, 1898 const boost::container::flat_set<IPv4AddressData>& ipv4Data, 1899 const boost::container::flat_set<IPv6AddressData>& ipv6Data) { 1900 if (!success) 1901 { 1902 // ... otherwise return error 1903 // TODO(Pawel)consider distinguish between non 1904 // existing object, and other errors 1905 messages::resourceNotFound(asyncResp->res, "EthernetInterface", 1906 ifaceId); 1907 return; 1908 } 1909 1910 if (dhcpv4 || dhcpv6) 1911 { 1912 handleDHCPPatch(ifaceId, ethData, v4dhcpParms, v6dhcpParms, 1913 asyncResp); 1914 } 1915 1916 if (hostname) 1917 { 1918 handleHostnamePatch(*hostname, asyncResp); 1919 } 1920 1921 if (fqdn) 1922 { 1923 handleFqdnPatch(ifaceId, *fqdn, asyncResp); 1924 } 1925 1926 if (macAddress) 1927 { 1928 handleMACAddressPatch(ifaceId, *macAddress, asyncResp); 1929 } 1930 1931 if (ipv4StaticAddresses) 1932 { 1933 // TODO(ed) for some reason the capture of 1934 // ipv4Addresses above is returning a const value, 1935 // not a non-const value. This doesn't really work 1936 // for us, as we need to be able to efficiently move 1937 // out the intermedia nlohmann::json objects. This 1938 // makes a copy of the structure, and operates on 1939 // that, but could be done more efficiently 1940 nlohmann::json ipv4Static = *ipv4StaticAddresses; 1941 handleIPv4StaticPatch(ifaceId, ipv4Static, ipv4Data, asyncResp); 1942 } 1943 1944 if (staticNameServers) 1945 { 1946 handleStaticNameServersPatch(ifaceId, *staticNameServers, 1947 asyncResp); 1948 } 1949 1950 if (ipv6DefaultGateway) 1951 { 1952 messages::propertyNotWritable(asyncResp->res, 1953 "IPv6DefaultGateway"); 1954 } 1955 1956 if (ipv6StaticAddresses) 1957 { 1958 const nlohmann::json& ipv6Static = *ipv6StaticAddresses; 1959 handleIPv6StaticAddressesPatch(ifaceId, ipv6Static, ipv6Data, 1960 asyncResp); 1961 } 1962 1963 if (interfaceEnabled) 1964 { 1965 setEthernetInterfaceBoolProperty(ifaceId, "NICEnabled", 1966 *interfaceEnabled, asyncResp); 1967 } 1968 1969 if (mtuSize) 1970 { 1971 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp); 1972 } 1973 }); 1974 }); 1975 1976 BMCWEB_ROUTE( 1977 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 1978 .privileges(redfish::privileges::getVLanNetworkInterface) 1979 .methods(boost::beast::http::verb::get)( 1980 [&app](const crow::Request& req, 1981 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1982 const std::string& parentIfaceId, 1983 const std::string& ifaceId) { 1984 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1985 { 1986 return; 1987 } 1988 asyncResp->res.jsonValue["@odata.type"] = 1989 "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface"; 1990 asyncResp->res.jsonValue["Name"] = "VLAN Network Interface"; 1991 1992 if (!verifyNames(parentIfaceId, ifaceId)) 1993 { 1994 return; 1995 } 1996 1997 // Get single eth interface data, and call the below callback 1998 // for JSON preparation 1999 getEthernetIfaceData( 2000 ifaceId, 2001 [asyncResp, parentIfaceId, 2002 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2003 const boost::container::flat_set<IPv4AddressData>&, 2004 const boost::container::flat_set<IPv6AddressData>&) { 2005 if (success && ethData.vlanId) 2006 { 2007 asyncResp->res.jsonValue["Id"] = ifaceId; 2008 asyncResp->res.jsonValue["@odata.id"] = 2009 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2010 parentIfaceId + "/VLANs/" + ifaceId; 2011 2012 asyncResp->res.jsonValue["VLANEnable"] = ethData.nicEnabled; 2013 asyncResp->res.jsonValue["VLANId"] = *ethData.vlanId; 2014 } 2015 else 2016 { 2017 // ... otherwise return error 2018 // TODO(Pawel)consider distinguish between non 2019 // existing object, and other errors 2020 messages::resourceNotFound(asyncResp->res, 2021 "VLanNetworkInterface", ifaceId); 2022 } 2023 }); 2024 }); 2025 2026 BMCWEB_ROUTE( 2027 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2028 .privileges(redfish::privileges::patchVLanNetworkInterface) 2029 .methods(boost::beast::http::verb::patch)( 2030 [&app](const crow::Request& req, 2031 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2032 const std::string& parentIfaceId, 2033 const std::string& ifaceId) { 2034 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2035 { 2036 return; 2037 } 2038 if (!verifyNames(parentIfaceId, ifaceId)) 2039 { 2040 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2041 ifaceId); 2042 return; 2043 } 2044 2045 std::optional<bool> vlanEnable; 2046 std::optional<uint32_t> vlanId; 2047 2048 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANEnable", 2049 vlanEnable, "VLANId", vlanId)) 2050 { 2051 return; 2052 } 2053 2054 if (vlanId) 2055 { 2056 messages::propertyNotWritable(asyncResp->res, "VLANId"); 2057 return; 2058 } 2059 2060 // Get single eth interface data, and call the below callback 2061 // for JSON preparation 2062 getEthernetIfaceData( 2063 ifaceId, 2064 [asyncResp, parentIfaceId, ifaceId, vlanEnable]( 2065 const bool& success, const EthernetInterfaceData& ethData, 2066 const boost::container::flat_set<IPv4AddressData>&, 2067 const boost::container::flat_set<IPv6AddressData>&) { 2068 if (success && ethData.vlanId) 2069 { 2070 if (vlanEnable) 2071 { 2072 crow::connections::systemBus->async_method_call( 2073 [asyncResp](const boost::system::error_code ec) { 2074 if (ec) 2075 { 2076 messages::internalError(asyncResp->res); 2077 return; 2078 } 2079 }, 2080 "xyz.openbmc_project.Network", 2081 "/xyz/openbmc_project/network/" + ifaceId, 2082 "org.freedesktop.DBus.Properties", "Set", 2083 "xyz.openbmc_project.Network.EthernetInterface", 2084 "NICEnabled", 2085 dbus::utility::DbusVariantType(*vlanEnable)); 2086 } 2087 } 2088 else 2089 { 2090 // TODO(Pawel)consider distinguish between non 2091 // existing object, and other errors 2092 messages::resourceNotFound(asyncResp->res, 2093 "VLanNetworkInterface", ifaceId); 2094 return; 2095 } 2096 }); 2097 }); 2098 2099 BMCWEB_ROUTE( 2100 app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>/") 2101 .privileges(redfish::privileges::deleteVLanNetworkInterface) 2102 .methods(boost::beast::http::verb::delete_)( 2103 [&app](const crow::Request& req, 2104 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2105 const std::string& parentIfaceId, 2106 const std::string& ifaceId) { 2107 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2108 { 2109 return; 2110 } 2111 if (!verifyNames(parentIfaceId, ifaceId)) 2112 { 2113 messages::resourceNotFound(asyncResp->res, "VLanNetworkInterface", 2114 ifaceId); 2115 return; 2116 } 2117 2118 // Get single eth interface data, and call the below callback 2119 // for JSON preparation 2120 getEthernetIfaceData( 2121 ifaceId, 2122 [asyncResp, parentIfaceId, 2123 ifaceId](const bool& success, const EthernetInterfaceData& ethData, 2124 const boost::container::flat_set<IPv4AddressData>&, 2125 const boost::container::flat_set<IPv6AddressData>&) { 2126 if (success && ethData.vlanId) 2127 { 2128 auto callback = 2129 [asyncResp](const boost::system::error_code ec) { 2130 if (ec) 2131 { 2132 messages::internalError(asyncResp->res); 2133 } 2134 }; 2135 crow::connections::systemBus->async_method_call( 2136 std::move(callback), "xyz.openbmc_project.Network", 2137 std::string("/xyz/openbmc_project/network/") + ifaceId, 2138 "xyz.openbmc_project.Object.Delete", "Delete"); 2139 } 2140 else 2141 { 2142 // ... otherwise return error 2143 // TODO(Pawel)consider distinguish between non 2144 // existing object, and other errors 2145 messages::resourceNotFound(asyncResp->res, 2146 "VLanNetworkInterface", ifaceId); 2147 } 2148 }); 2149 }); 2150 2151 BMCWEB_ROUTE(app, 2152 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2153 2154 .privileges(redfish::privileges::getVLanNetworkInterfaceCollection) 2155 .methods(boost::beast::http::verb::get)( 2156 [&app](const crow::Request& req, 2157 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2158 const std::string& rootInterfaceName) { 2159 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2160 { 2161 return; 2162 } 2163 // Get eth interface list, and call the below callback for JSON 2164 // preparation 2165 getEthernetIfaceList( 2166 [asyncResp, rootInterfaceName]( 2167 const bool& success, 2168 const boost::container::flat_set<std::string>& ifaceList) { 2169 if (!success) 2170 { 2171 messages::internalError(asyncResp->res); 2172 return; 2173 } 2174 2175 if (ifaceList.find(rootInterfaceName) == ifaceList.end()) 2176 { 2177 messages::resourceNotFound(asyncResp->res, 2178 "VLanNetworkInterfaceCollection", 2179 rootInterfaceName); 2180 return; 2181 } 2182 2183 asyncResp->res.jsonValue["@odata.type"] = 2184 "#VLanNetworkInterfaceCollection." 2185 "VLanNetworkInterfaceCollection"; 2186 asyncResp->res.jsonValue["Name"] = 2187 "VLAN Network Interface Collection"; 2188 2189 nlohmann::json ifaceArray = nlohmann::json::array(); 2190 2191 for (const std::string& ifaceItem : ifaceList) 2192 { 2193 if (ifaceItem.starts_with(rootInterfaceName + "_")) 2194 { 2195 std::string path = 2196 "/redfish/v1/Managers/bmc/EthernetInterfaces/"; 2197 path += rootInterfaceName; 2198 path += "/VLANs/"; 2199 path += ifaceItem; 2200 nlohmann::json::object_t iface; 2201 iface["@odata.id"] = std::move(path); 2202 ifaceArray.push_back(std::move(iface)); 2203 } 2204 } 2205 2206 asyncResp->res.jsonValue["Members@odata.count"] = ifaceArray.size(); 2207 asyncResp->res.jsonValue["Members"] = std::move(ifaceArray); 2208 asyncResp->res.jsonValue["@odata.id"] = 2209 "/redfish/v1/Managers/bmc/EthernetInterfaces/" + 2210 rootInterfaceName + "/VLANs"; 2211 }); 2212 }); 2213 2214 BMCWEB_ROUTE(app, 2215 "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/") 2216 .privileges(redfish::privileges::postVLanNetworkInterfaceCollection) 2217 .methods(boost::beast::http::verb::post)( 2218 [&app](const crow::Request& req, 2219 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2220 const std::string& rootInterfaceName) { 2221 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2222 { 2223 return; 2224 } 2225 bool vlanEnable = false; 2226 uint32_t vlanId = 0; 2227 if (!json_util::readJsonPatch(req, asyncResp->res, "VLANId", vlanId, 2228 "VLANEnable", vlanEnable)) 2229 { 2230 return; 2231 } 2232 // Need both vlanId and vlanEnable to service this request 2233 if (vlanId == 0U) 2234 { 2235 messages::propertyMissing(asyncResp->res, "VLANId"); 2236 } 2237 if (!vlanEnable) 2238 { 2239 messages::propertyMissing(asyncResp->res, "VLANEnable"); 2240 } 2241 if (static_cast<bool>(vlanId) ^ vlanEnable) 2242 { 2243 return; 2244 } 2245 2246 auto callback = [asyncResp](const boost::system::error_code ec) { 2247 if (ec) 2248 { 2249 // TODO(ed) make more consistent error messages 2250 // based on phosphor-network responses 2251 messages::internalError(asyncResp->res); 2252 return; 2253 } 2254 messages::created(asyncResp->res); 2255 }; 2256 crow::connections::systemBus->async_method_call( 2257 std::move(callback), "xyz.openbmc_project.Network", 2258 "/xyz/openbmc_project/network", 2259 "xyz.openbmc_project.Network.VLAN.Create", "VLAN", 2260 rootInterfaceName, vlanId); 2261 }); 2262 } 2263 2264 } // namespace redfish 2265