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