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( //
1553 *obj, asyncResp->res, //
1554 "Address", address, //
1555 "Gateway", gateway, //
1556 "SubnetMask", subnetMask //
1557 ))
1558 {
1559 messages::propertyValueFormatError(asyncResp->res, *obj,
1560 pathString);
1561 return;
1562 }
1563
1564 // Find the address/subnet/gateway values. Any values that are
1565 // not explicitly provided are assumed to be unmodified from the
1566 // current state of the interface. Merge existing state into the
1567 // current request.
1568 if (address)
1569 {
1570 if (!ip_util::ipv4VerifyIpAndGetBitcount(*address))
1571 {
1572 messages::propertyValueFormatError(asyncResp->res, *address,
1573 pathString + "/Address");
1574 return;
1575 }
1576 }
1577 else if (nicIpEntry != ipv4Data.cend())
1578 {
1579 address = (nicIpEntry->address);
1580 }
1581 else
1582 {
1583 messages::propertyMissing(asyncResp->res,
1584 pathString + "/Address");
1585 return;
1586 }
1587
1588 uint8_t prefixLength = 0;
1589 if (subnetMask)
1590 {
1591 if (!ip_util::ipv4VerifyIpAndGetBitcount(*subnetMask,
1592 &prefixLength))
1593 {
1594 messages::propertyValueFormatError(
1595 asyncResp->res, *subnetMask,
1596 pathString + "/SubnetMask");
1597 return;
1598 }
1599 }
1600 else if (nicIpEntry != ipv4Data.cend())
1601 {
1602 if (!ip_util::ipv4VerifyIpAndGetBitcount(nicIpEntry->netmask,
1603 &prefixLength))
1604 {
1605 messages::propertyValueFormatError(
1606 asyncResp->res, nicIpEntry->netmask,
1607 pathString + "/SubnetMask");
1608 return;
1609 }
1610 }
1611 else
1612 {
1613 messages::propertyMissing(asyncResp->res,
1614 pathString + "/SubnetMask");
1615 return;
1616 }
1617
1618 if (gateway)
1619 {
1620 if (!ip_util::ipv4VerifyIpAndGetBitcount(*gateway))
1621 {
1622 messages::propertyValueFormatError(asyncResp->res, *gateway,
1623 pathString + "/Gateway");
1624 return;
1625 }
1626 }
1627 else if (nicIpEntry != ipv4Data.cend())
1628 {
1629 gateway = nicIpEntry->gateway;
1630 }
1631 else
1632 {
1633 messages::propertyMissing(asyncResp->res,
1634 pathString + "/Gateway");
1635 return;
1636 }
1637
1638 if (gatewayValueAssigned)
1639 {
1640 if (activeGateway != gateway)
1641 {
1642 // A NIC can only have a single active gateway value.
1643 // If any gateway in the array of static addresses
1644 // mismatch the PATCH is in error.
1645 std::string arg1 = pathString + "/Gateway";
1646 std::string arg2 = activePath + "/Gateway";
1647 messages::propertyValueConflict(asyncResp->res, arg1, arg2);
1648 return;
1649 }
1650 }
1651 else
1652 {
1653 // Capture the very first gateway value from the incoming
1654 // JSON record and use it at the default gateway.
1655 updateIPv4DefaultGateway(ifaceId, *gateway, asyncResp);
1656 activeGateway = *gateway;
1657 activePath = pathString;
1658 gatewayValueAssigned = true;
1659 }
1660
1661 if (nicIpEntry != ipv4Data.cend())
1662 {
1663 deleteAndCreateIPAddress(IpVersion::IpV4, ifaceId,
1664 nicIpEntry->id, prefixLength, *address,
1665 *gateway, asyncResp);
1666 nicIpEntry =
1667 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1668 preserveGateway = true;
1669 }
1670 else
1671 {
1672 createIPv4(ifaceId, prefixLength, *gateway, *address,
1673 asyncResp);
1674 preserveGateway = true;
1675 }
1676 entryIdx++;
1677 }
1678 else
1679 {
1680 // Received {}, do not modify this address
1681 if (nicIpEntry != ipv4Data.cend())
1682 {
1683 nicIpEntry =
1684 getNextStaticIpEntry(++nicIpEntry, ipv4Data.cend());
1685 preserveGateway = true;
1686 entryIdx++;
1687 }
1688 else
1689 {
1690 // Requested a DO NOT MODIFY action on an entry not assigned
1691 // to the NIC
1692 messages::propertyValueFormatError(asyncResp->res, *obj,
1693 pathString);
1694 return;
1695 }
1696 }
1697 }
1698 }
1699
handleStaticNameServersPatch(const std::string & ifaceId,const std::vector<std::string> & updatedStaticNameServers,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1700 inline void handleStaticNameServersPatch(
1701 const std::string& ifaceId,
1702 const std::vector<std::string>& updatedStaticNameServers,
1703 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1704 {
1705 setDbusProperty(
1706 asyncResp, "StaticNameServers", "xyz.openbmc_project.Network",
1707 sdbusplus::message::object_path("/xyz/openbmc_project/network") /
1708 ifaceId,
1709 "xyz.openbmc_project.Network.EthernetInterface", "StaticNameServers",
1710 updatedStaticNameServers);
1711 }
1712
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)1713 inline void handleIPv6StaticAddressesPatch(
1714 const std::string& ifaceId,
1715 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input,
1716 const std::vector<IPv6AddressData>& ipv6Data,
1717 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1718 {
1719 size_t entryIdx = 1;
1720 std::vector<IPv6AddressData>::const_iterator nicIpEntry =
1721 getNextStaticIpEntry(ipv6Data.cbegin(), ipv6Data.cend());
1722 for (std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson :
1723 input)
1724 {
1725 std::string pathString =
1726 "IPv6StaticAddresses/" + std::to_string(entryIdx);
1727 nlohmann::json::object_t* obj =
1728 std::get_if<nlohmann::json::object_t>(&thisJson);
1729 if (obj != nullptr && !obj->empty())
1730 {
1731 std::optional<std::string> address;
1732 std::optional<uint8_t> prefixLength;
1733 nlohmann::json::object_t thisJsonCopy = *obj;
1734 if (!json_util::readJsonObject( //
1735 thisJsonCopy, asyncResp->res, //
1736 "Address", address, //
1737 "PrefixLength", prefixLength //
1738 ))
1739 {
1740 messages::propertyValueFormatError(asyncResp->res, thisJsonCopy,
1741 pathString);
1742 return;
1743 }
1744
1745 // Find the address and prefixLength values. Any values that are
1746 // not explicitly provided are assumed to be unmodified from the
1747 // current state of the interface. Merge existing state into the
1748 // current request.
1749 if (!address)
1750 {
1751 if (nicIpEntry == ipv6Data.end())
1752 {
1753 messages::propertyMissing(asyncResp->res,
1754 pathString + "/Address");
1755 return;
1756 }
1757 address = nicIpEntry->address;
1758 }
1759
1760 if (!prefixLength)
1761 {
1762 if (nicIpEntry == ipv6Data.end())
1763 {
1764 messages::propertyMissing(asyncResp->res,
1765 pathString + "/PrefixLength");
1766 return;
1767 }
1768 prefixLength = nicIpEntry->prefixLength;
1769 }
1770
1771 if (nicIpEntry != ipv6Data.end())
1772 {
1773 deleteAndCreateIPAddress(IpVersion::IpV6, ifaceId,
1774 nicIpEntry->id, *prefixLength,
1775 *address, "", asyncResp);
1776 nicIpEntry =
1777 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1778 }
1779 else
1780 {
1781 createIPv6(ifaceId, *prefixLength, *address, asyncResp);
1782 }
1783 entryIdx++;
1784 }
1785 else
1786 {
1787 if (nicIpEntry == ipv6Data.end())
1788 {
1789 // Requesting a DELETE/DO NOT MODIFY action for an item
1790 // that isn't present on the eth(n) interface. Input JSON is
1791 // in error, so bail out.
1792 if (obj == nullptr)
1793 {
1794 messages::resourceCannotBeDeleted(asyncResp->res);
1795 return;
1796 }
1797 messages::propertyValueFormatError(asyncResp->res, *obj,
1798 pathString);
1799 return;
1800 }
1801
1802 if (obj == nullptr)
1803 {
1804 deleteIPAddress(ifaceId, nicIpEntry->id, asyncResp);
1805 }
1806 if (nicIpEntry != ipv6Data.cend())
1807 {
1808 nicIpEntry =
1809 getNextStaticIpEntry(++nicIpEntry, ipv6Data.cend());
1810 }
1811 entryIdx++;
1812 }
1813 }
1814 }
1815
extractParentInterfaceName(const std::string & ifaceId)1816 inline std::string extractParentInterfaceName(const std::string& ifaceId)
1817 {
1818 std::size_t pos = ifaceId.find('_');
1819 return ifaceId.substr(0, pos);
1820 }
1821
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)1822 inline void parseInterfaceData(
1823 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1824 const std::string& ifaceId, const EthernetInterfaceData& ethData,
1825 const std::vector<IPv4AddressData>& ipv4Data,
1826 const std::vector<IPv6AddressData>& ipv6Data,
1827 const std::vector<StaticGatewayData>& ipv6GatewayData)
1828 {
1829 nlohmann::json& jsonResponse = asyncResp->res.jsonValue;
1830 jsonResponse["Id"] = ifaceId;
1831 jsonResponse["@odata.id"] =
1832 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}",
1833 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceId);
1834 jsonResponse["InterfaceEnabled"] = ethData.nicEnabled;
1835
1836 if (ethData.nicEnabled)
1837 {
1838 jsonResponse["LinkStatus"] =
1839 ethData.linkUp ? ethernet_interface::LinkStatus::LinkUp
1840 : ethernet_interface::LinkStatus::LinkDown;
1841 jsonResponse["Status"]["State"] = resource::State::Enabled;
1842 }
1843 else
1844 {
1845 jsonResponse["LinkStatus"] = ethernet_interface::LinkStatus::NoLink;
1846 jsonResponse["Status"]["State"] = resource::State::Disabled;
1847 }
1848
1849 jsonResponse["SpeedMbps"] = ethData.speed;
1850 jsonResponse["MTUSize"] = ethData.mtuSize;
1851 if (ethData.macAddress)
1852 {
1853 jsonResponse["MACAddress"] = *ethData.macAddress;
1854 }
1855 jsonResponse["DHCPv4"]["DHCPEnabled"] =
1856 translateDhcpEnabledToBool(ethData.dhcpEnabled, true);
1857 jsonResponse["DHCPv4"]["UseNTPServers"] = ethData.ntpv4Enabled;
1858 jsonResponse["DHCPv4"]["UseDNSServers"] = ethData.dnsv4Enabled;
1859 jsonResponse["DHCPv4"]["UseDomainName"] = ethData.domainv4Enabled;
1860 jsonResponse["DHCPv6"]["OperatingMode"] =
1861 translateDhcpEnabledToBool(ethData.dhcpEnabled, false)
1862 ? "Enabled"
1863 : "Disabled";
1864 jsonResponse["DHCPv6"]["UseNTPServers"] = ethData.ntpv6Enabled;
1865 jsonResponse["DHCPv6"]["UseDNSServers"] = ethData.dnsv6Enabled;
1866 jsonResponse["DHCPv6"]["UseDomainName"] = ethData.domainv6Enabled;
1867 jsonResponse["StatelessAddressAutoConfig"]["IPv6AutoConfigEnabled"] =
1868 ethData.ipv6AcceptRa;
1869
1870 if (!ethData.hostName.empty())
1871 {
1872 jsonResponse["HostName"] = ethData.hostName;
1873
1874 // When domain name is empty then it means, that it is a network
1875 // without domain names, and the host name itself must be treated as
1876 // FQDN
1877 std::string fqdn = ethData.hostName;
1878 if (!ethData.domainnames.empty())
1879 {
1880 fqdn += "." + ethData.domainnames[0];
1881 }
1882 jsonResponse["FQDN"] = fqdn;
1883 }
1884
1885 if (ethData.vlanId)
1886 {
1887 jsonResponse["EthernetInterfaceType"] =
1888 ethernet_interface::EthernetDeviceType::Virtual;
1889 jsonResponse["VLAN"]["VLANEnable"] = true;
1890 jsonResponse["VLAN"]["VLANId"] = *ethData.vlanId;
1891 jsonResponse["VLAN"]["Tagged"] = true;
1892
1893 nlohmann::json::array_t relatedInterfaces;
1894 nlohmann::json& parentInterface = relatedInterfaces.emplace_back();
1895 parentInterface["@odata.id"] =
1896 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces",
1897 BMCWEB_REDFISH_MANAGER_URI_NAME,
1898 extractParentInterfaceName(ifaceId));
1899 jsonResponse["Links"]["RelatedInterfaces"] =
1900 std::move(relatedInterfaces);
1901 }
1902 else
1903 {
1904 jsonResponse["EthernetInterfaceType"] =
1905 ethernet_interface::EthernetDeviceType::Physical;
1906 }
1907
1908 jsonResponse["NameServers"] = ethData.nameServers;
1909 jsonResponse["StaticNameServers"] = ethData.staticNameServers;
1910
1911 nlohmann::json& ipv4Array = jsonResponse["IPv4Addresses"];
1912 nlohmann::json& ipv4StaticArray = jsonResponse["IPv4StaticAddresses"];
1913 ipv4Array = nlohmann::json::array();
1914 ipv4StaticArray = nlohmann::json::array();
1915 for (const auto& ipv4Config : ipv4Data)
1916 {
1917 std::string gatewayStr = ipv4Config.gateway;
1918 if (gatewayStr.empty())
1919 {
1920 gatewayStr = "0.0.0.0";
1921 }
1922 nlohmann::json::object_t ipv4;
1923 ipv4["AddressOrigin"] = ipv4Config.origin;
1924 ipv4["SubnetMask"] = ipv4Config.netmask;
1925 ipv4["Address"] = ipv4Config.address;
1926 ipv4["Gateway"] = gatewayStr;
1927
1928 if (ipv4Config.origin == "Static")
1929 {
1930 ipv4StaticArray.push_back(ipv4);
1931 }
1932
1933 ipv4Array.emplace_back(std::move(ipv4));
1934 }
1935
1936 std::string ipv6GatewayStr = ethData.ipv6DefaultGateway;
1937 if (ipv6GatewayStr.empty())
1938 {
1939 ipv6GatewayStr = "0:0:0:0:0:0:0:0";
1940 }
1941
1942 jsonResponse["IPv6DefaultGateway"] = ipv6GatewayStr;
1943
1944 nlohmann::json::array_t ipv6StaticGatewayArray;
1945 for (const auto& ipv6GatewayConfig : ipv6GatewayData)
1946 {
1947 nlohmann::json::object_t ipv6Gateway;
1948 ipv6Gateway["Address"] = ipv6GatewayConfig.gateway;
1949 ipv6StaticGatewayArray.emplace_back(std::move(ipv6Gateway));
1950 }
1951 jsonResponse["IPv6StaticDefaultGateways"] =
1952 std::move(ipv6StaticGatewayArray);
1953
1954 nlohmann::json& ipv6Array = jsonResponse["IPv6Addresses"];
1955 nlohmann::json& ipv6StaticArray = jsonResponse["IPv6StaticAddresses"];
1956 ipv6Array = nlohmann::json::array();
1957 ipv6StaticArray = nlohmann::json::array();
1958 nlohmann::json& ipv6AddrPolicyTable =
1959 jsonResponse["IPv6AddressPolicyTable"];
1960 ipv6AddrPolicyTable = nlohmann::json::array();
1961 for (const auto& ipv6Config : ipv6Data)
1962 {
1963 nlohmann::json::object_t ipv6;
1964 ipv6["Address"] = ipv6Config.address;
1965 ipv6["PrefixLength"] = ipv6Config.prefixLength;
1966 ipv6["AddressOrigin"] = ipv6Config.origin;
1967
1968 ipv6Array.emplace_back(std::move(ipv6));
1969 if (ipv6Config.origin == "Static")
1970 {
1971 nlohmann::json::object_t ipv6Static;
1972 ipv6Static["Address"] = ipv6Config.address;
1973 ipv6Static["PrefixLength"] = ipv6Config.prefixLength;
1974 ipv6StaticArray.emplace_back(std::move(ipv6Static));
1975 }
1976 }
1977 }
1978
afterDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & ifaceId,const boost::system::error_code & ec,const sdbusplus::message_t & m)1979 inline void afterDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1980 const std::string& ifaceId,
1981 const boost::system::error_code& ec,
1982 const sdbusplus::message_t& m)
1983 {
1984 if (!ec)
1985 {
1986 return;
1987 }
1988 const sd_bus_error* dbusError = m.get_error();
1989 if (dbusError == nullptr)
1990 {
1991 messages::internalError(asyncResp->res);
1992 return;
1993 }
1994 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
1995
1996 if (std::string_view("org.freedesktop.DBus.Error.UnknownObject") ==
1997 dbusError->name)
1998 {
1999 messages::resourceNotFound(asyncResp->res, "EthernetInterface",
2000 ifaceId);
2001 return;
2002 }
2003 if (std::string_view("org.freedesktop.DBus.Error.UnknownMethod") ==
2004 dbusError->name)
2005 {
2006 messages::resourceCannotBeDeleted(asyncResp->res);
2007 return;
2008 }
2009 messages::internalError(asyncResp->res);
2010 }
2011
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)2012 inline void afterVlanCreate(
2013 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2014 const std::string& parentInterfaceUri, const std::string& vlanInterface,
2015 const boost::system::error_code& ec, const sdbusplus::message_t& m
2016
2017 )
2018 {
2019 if (ec)
2020 {
2021 const sd_bus_error* dbusError = m.get_error();
2022 if (dbusError == nullptr)
2023 {
2024 messages::internalError(asyncResp->res);
2025 return;
2026 }
2027 BMCWEB_LOG_DEBUG("DBus error: {}", dbusError->name);
2028
2029 if (std::string_view(
2030 "xyz.openbmc_project.Common.Error.ResourceNotFound") ==
2031 dbusError->name)
2032 {
2033 messages::propertyValueNotInList(
2034 asyncResp->res, parentInterfaceUri,
2035 "Links/RelatedInterfaces/0/@odata.id");
2036 return;
2037 }
2038 if (std::string_view(
2039 "xyz.openbmc_project.Common.Error.InvalidArgument") ==
2040 dbusError->name)
2041 {
2042 messages::resourceAlreadyExists(asyncResp->res, "EthernetInterface",
2043 "Id", vlanInterface);
2044 return;
2045 }
2046 messages::internalError(asyncResp->res);
2047 return;
2048 }
2049
2050 const boost::urls::url vlanInterfaceUri =
2051 boost::urls::format("/redfish/v1/Managers/{}/EthernetInterfaces/{}",
2052 BMCWEB_REDFISH_MANAGER_URI_NAME, vlanInterface);
2053 asyncResp->res.addHeader("Location", vlanInterfaceUri.buffer());
2054 }
2055
requestEthernetInterfacesRoutes(App & app)2056 inline void requestEthernetInterfacesRoutes(App& app)
2057 {
2058 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/")
2059 .privileges(redfish::privileges::getEthernetInterfaceCollection)
2060 .methods(boost::beast::http::verb::get)(
2061 [&app](const crow::Request& req,
2062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2063 const std::string& managerId) {
2064 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2065 {
2066 return;
2067 }
2068
2069 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2070 {
2071 messages::resourceNotFound(asyncResp->res, "Manager",
2072 managerId);
2073 return;
2074 }
2075
2076 asyncResp->res.jsonValue["@odata.type"] =
2077 "#EthernetInterfaceCollection.EthernetInterfaceCollection";
2078 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2079 "/redfish/v1/Managers/{}/EthernetInterfaces",
2080 BMCWEB_REDFISH_MANAGER_URI_NAME);
2081 asyncResp->res.jsonValue["Name"] =
2082 "Ethernet Network Interface Collection";
2083 asyncResp->res.jsonValue["Description"] =
2084 "Collection of EthernetInterfaces for this Manager";
2085
2086 // Get eth interface list, and call the below callback for JSON
2087 // preparation
2088 getEthernetIfaceList(
2089 [asyncResp](const bool& success,
2090 const std::vector<std::string>& ifaceList) {
2091 if (!success)
2092 {
2093 messages::internalError(asyncResp->res);
2094 return;
2095 }
2096
2097 nlohmann::json& ifaceArray =
2098 asyncResp->res.jsonValue["Members"];
2099 ifaceArray = nlohmann::json::array();
2100 for (const std::string& ifaceItem : ifaceList)
2101 {
2102 nlohmann::json::object_t iface;
2103 iface["@odata.id"] = boost::urls::format(
2104 "/redfish/v1/Managers/{}/EthernetInterfaces/{}",
2105 BMCWEB_REDFISH_MANAGER_URI_NAME, ifaceItem);
2106 ifaceArray.push_back(std::move(iface));
2107 }
2108
2109 asyncResp->res.jsonValue["Members@odata.count"] =
2110 ifaceArray.size();
2111 asyncResp->res.jsonValue["@odata.id"] =
2112 boost::urls::format(
2113 "/redfish/v1/Managers/{}/EthernetInterfaces",
2114 BMCWEB_REDFISH_MANAGER_URI_NAME);
2115 });
2116 });
2117
2118 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/")
2119 .privileges(redfish::privileges::postEthernetInterfaceCollection)
2120 .methods(boost::beast::http::verb::post)(
2121 [&app](const crow::Request& req,
2122 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2123 const std::string& managerId) {
2124 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2125 {
2126 return;
2127 }
2128
2129 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2130 {
2131 messages::resourceNotFound(asyncResp->res, "Manager",
2132 managerId);
2133 return;
2134 }
2135
2136 bool vlanEnable = false;
2137 uint32_t vlanId = 0;
2138 std::vector<nlohmann::json::object_t> relatedInterfaces;
2139
2140 if (!json_util::readJsonPatch( //
2141 req, asyncResp->res, //
2142 "Links/RelatedInterfaces", relatedInterfaces, //
2143 "VLAN/VLANEnable", vlanEnable, //
2144 "VLAN/VLANId", vlanId //
2145 ))
2146 {
2147 return;
2148 }
2149
2150 if (relatedInterfaces.size() != 1)
2151 {
2152 messages::arraySizeTooLong(asyncResp->res,
2153 "Links/RelatedInterfaces",
2154 relatedInterfaces.size());
2155 return;
2156 }
2157
2158 std::string parentInterfaceUri;
2159 if (!json_util::readJsonObject(relatedInterfaces[0],
2160 asyncResp->res, "@odata.id",
2161 parentInterfaceUri))
2162 {
2163 messages::propertyMissing(
2164 asyncResp->res, "Links/RelatedInterfaces/0/@odata.id");
2165 return;
2166 }
2167 BMCWEB_LOG_INFO("Parent Interface URI: {}", parentInterfaceUri);
2168
2169 boost::system::result<boost::urls::url_view> parsedUri =
2170 boost::urls::parse_relative_ref(parentInterfaceUri);
2171 if (!parsedUri)
2172 {
2173 messages::propertyValueFormatError(
2174 asyncResp->res, parentInterfaceUri,
2175 "Links/RelatedInterfaces/0/@odata.id");
2176 return;
2177 }
2178
2179 std::string parentInterface;
2180 if (!crow::utility::readUrlSegments(
2181 *parsedUri, "redfish", "v1", "Managers", "bmc",
2182 "EthernetInterfaces", std::ref(parentInterface)))
2183 {
2184 messages::propertyValueNotInList(
2185 asyncResp->res, parentInterfaceUri,
2186 "Links/RelatedInterfaces/0/@odata.id");
2187 return;
2188 }
2189
2190 if (!vlanEnable)
2191 {
2192 // In OpenBMC implementation, VLANEnable cannot be false on
2193 // create
2194 messages::propertyValueIncorrect(
2195 asyncResp->res, "VLAN/VLANEnable", "false");
2196 return;
2197 }
2198
2199 std::string vlanInterface =
2200 parentInterface + "_" + std::to_string(vlanId);
2201 crow::connections::systemBus->async_method_call(
2202 [asyncResp, parentInterfaceUri,
2203 vlanInterface](const boost::system::error_code& ec,
2204 const sdbusplus::message_t& m) {
2205 afterVlanCreate(asyncResp, parentInterfaceUri,
2206 vlanInterface, ec, m);
2207 },
2208 "xyz.openbmc_project.Network",
2209 "/xyz/openbmc_project/network",
2210 "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
2211 parentInterface, vlanId);
2212 });
2213
2214 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2215 .privileges(redfish::privileges::getEthernetInterface)
2216 .methods(boost::beast::http::verb::get)(
2217 [&app](const crow::Request& req,
2218 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2219 const std::string& managerId, const std::string& ifaceId) {
2220 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2221 {
2222 return;
2223 }
2224
2225 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2226 {
2227 messages::resourceNotFound(asyncResp->res, "Manager",
2228 managerId);
2229 return;
2230 }
2231
2232 getEthernetIfaceData(
2233 ifaceId,
2234 [asyncResp, ifaceId](
2235 const bool& success,
2236 const EthernetInterfaceData& ethData,
2237 const std::vector<IPv4AddressData>& ipv4Data,
2238 const std::vector<IPv6AddressData>& ipv6Data,
2239 const std::vector<StaticGatewayData>& ipv6GatewayData) {
2240 if (!success)
2241 {
2242 // TODO(Pawel)consider distinguish between non
2243 // existing object, and other errors
2244 messages::resourceNotFound(
2245 asyncResp->res, "EthernetInterface", ifaceId);
2246 return;
2247 }
2248
2249 asyncResp->res.jsonValue["@odata.type"] =
2250 "#EthernetInterface.v1_9_0.EthernetInterface";
2251 asyncResp->res.jsonValue["Name"] =
2252 "Manager Ethernet Interface";
2253 asyncResp->res.jsonValue["Description"] =
2254 "Management Network Interface";
2255
2256 parseInterfaceData(asyncResp, ifaceId, ethData,
2257 ipv4Data, ipv6Data, ipv6GatewayData);
2258 });
2259 });
2260
2261 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2262 .privileges(redfish::privileges::patchEthernetInterface)
2263 .methods(boost::beast::http::verb::patch)(
2264 [&app](const crow::Request& req,
2265 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2266 const std::string& managerId, const std::string& ifaceId) {
2267 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2268 {
2269 return;
2270 }
2271
2272 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2273 {
2274 messages::resourceNotFound(asyncResp->res, "Manager",
2275 managerId);
2276 return;
2277 }
2278
2279 std::optional<std::string> hostname;
2280 std::optional<std::string> fqdn;
2281 std::optional<std::string> macAddress;
2282 std::optional<std::string> ipv6DefaultGateway;
2283 std::optional<std::vector<
2284 std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2285 ipv4StaticAddresses;
2286 std::optional<std::vector<
2287 std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2288 ipv6StaticAddresses;
2289 std::optional<std::vector<
2290 std::variant<nlohmann::json::object_t, std::nullptr_t>>>
2291 ipv6StaticDefaultGateways;
2292 std::optional<std::vector<std::string>> staticNameServers;
2293 std::optional<bool> ipv6AutoConfigEnabled;
2294 std::optional<bool> interfaceEnabled;
2295 std::optional<size_t> mtuSize;
2296 DHCPParameters v4dhcpParms;
2297 DHCPParameters v6dhcpParms;
2298
2299 if (!json_util::readJsonPatch( //
2300 req, asyncResp->res, //
2301 "DHCPv4/DHCPEnabled", v4dhcpParms.dhcpv4Enabled, //
2302 "DHCPv4/UseDNSServers", v4dhcpParms.useDnsServers, //
2303 "DHCPv4/UseDomainName", v4dhcpParms.useDomainName, //
2304 "DHCPv4/UseNTPServers", v4dhcpParms.useNtpServers, //
2305 "DHCPv6/OperatingMode",
2306 v6dhcpParms.dhcpv6OperatingMode, //
2307 "DHCPv6/UseDNSServers", v6dhcpParms.useDnsServers, //
2308 "DHCPv6/UseDomainName", v6dhcpParms.useDomainName, //
2309 "DHCPv6/UseNTPServers", v6dhcpParms.useNtpServers, //
2310 "FQDN", fqdn, //
2311 "HostName", hostname, //
2312 "InterfaceEnabled", interfaceEnabled, //
2313 "IPv4StaticAddresses", ipv4StaticAddresses, //
2314 "IPv6DefaultGateway", ipv6DefaultGateway, //
2315 "IPv6StaticAddresses", ipv6StaticAddresses, //
2316 "IPv6StaticDefaultGateways",
2317 ipv6StaticDefaultGateways, //
2318 "InterfaceEnabled", interfaceEnabled, //
2319 "MACAddress", macAddress, //
2320 "MTUSize", mtuSize, //
2321 "StatelessAddressAutoConfig/IPv6AutoConfigEnabled",
2322 ipv6AutoConfigEnabled, //
2323 "StaticNameServers", staticNameServers //
2324 ))
2325 {
2326 return;
2327 }
2328
2329 // Get single eth interface data, and call the below callback
2330 // for JSON preparation
2331 getEthernetIfaceData(
2332 ifaceId,
2333 [asyncResp, ifaceId, hostname = std::move(hostname),
2334 fqdn = std::move(fqdn), macAddress = std::move(macAddress),
2335 ipv4StaticAddresses = std::move(ipv4StaticAddresses),
2336 ipv6DefaultGateway = std::move(ipv6DefaultGateway),
2337 ipv6StaticAddresses = std::move(ipv6StaticAddresses),
2338 ipv6StaticDefaultGateway =
2339 std::move(ipv6StaticDefaultGateways),
2340 staticNameServers = std::move(staticNameServers), mtuSize,
2341 ipv6AutoConfigEnabled,
2342 v4dhcpParms = std::move(v4dhcpParms),
2343 v6dhcpParms = std::move(v6dhcpParms), interfaceEnabled](
2344 const bool success,
2345 const EthernetInterfaceData& ethData,
2346 const std::vector<IPv4AddressData>& ipv4Data,
2347 const std::vector<IPv6AddressData>& ipv6Data,
2348 const std::vector<StaticGatewayData>&
2349 ipv6GatewayData) mutable {
2350 if (!success)
2351 {
2352 // ... otherwise return error
2353 // TODO(Pawel)consider distinguish between non
2354 // existing object, and other errors
2355 messages::resourceNotFound(
2356 asyncResp->res, "EthernetInterface", ifaceId);
2357 return;
2358 }
2359
2360 handleDHCPPatch(ifaceId, ethData, v4dhcpParms,
2361 v6dhcpParms, asyncResp);
2362
2363 if (hostname)
2364 {
2365 handleHostnamePatch(*hostname, asyncResp);
2366 }
2367
2368 if (ipv6AutoConfigEnabled)
2369 {
2370 handleSLAACAutoConfigPatch(
2371 ifaceId, *ipv6AutoConfigEnabled, asyncResp);
2372 }
2373
2374 if (fqdn)
2375 {
2376 handleFqdnPatch(ifaceId, *fqdn, asyncResp);
2377 }
2378
2379 if (macAddress)
2380 {
2381 handleMACAddressPatch(ifaceId, *macAddress,
2382 asyncResp);
2383 }
2384
2385 if (ipv4StaticAddresses)
2386 {
2387 handleIPv4StaticPatch(ifaceId, *ipv4StaticAddresses,
2388 ethData, ipv4Data, asyncResp);
2389 }
2390
2391 if (staticNameServers)
2392 {
2393 handleStaticNameServersPatch(
2394 ifaceId, *staticNameServers, asyncResp);
2395 }
2396
2397 if (ipv6DefaultGateway)
2398 {
2399 messages::propertyNotWritable(asyncResp->res,
2400 "IPv6DefaultGateway");
2401 }
2402
2403 if (ipv6StaticAddresses)
2404 {
2405 handleIPv6StaticAddressesPatch(ifaceId,
2406 *ipv6StaticAddresses,
2407 ipv6Data, asyncResp);
2408 }
2409
2410 if (ipv6StaticDefaultGateway)
2411 {
2412 handleIPv6DefaultGateway(
2413 ifaceId, *ipv6StaticDefaultGateway,
2414 ipv6GatewayData, asyncResp);
2415 }
2416
2417 if (interfaceEnabled)
2418 {
2419 setDbusProperty(
2420 asyncResp, "InterfaceEnabled",
2421 "xyz.openbmc_project.Network",
2422 sdbusplus::message::object_path(
2423 "/xyz/openbmc_project/network") /
2424 ifaceId,
2425 "xyz.openbmc_project.Network.EthernetInterface",
2426 "NICEnabled", *interfaceEnabled);
2427 }
2428
2429 if (mtuSize)
2430 {
2431 handleMTUSizePatch(ifaceId, *mtuSize, asyncResp);
2432 }
2433 });
2434 });
2435
2436 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/EthernetInterfaces/<str>/")
2437 .privileges(redfish::privileges::deleteEthernetInterface)
2438 .methods(boost::beast::http::verb::delete_)(
2439 [&app](const crow::Request& req,
2440 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2441 const std::string& managerId, const std::string& ifaceId) {
2442 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2443 {
2444 return;
2445 }
2446
2447 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
2448 {
2449 messages::resourceNotFound(asyncResp->res, "Manager",
2450 managerId);
2451 return;
2452 }
2453
2454 crow::connections::systemBus->async_method_call(
2455 [asyncResp, ifaceId](const boost::system::error_code& ec,
2456 const sdbusplus::message_t& m) {
2457 afterDelete(asyncResp, ifaceId, ec, m);
2458 },
2459 "xyz.openbmc_project.Network",
2460 std::string("/xyz/openbmc_project/network/") + ifaceId,
2461 "xyz.openbmc_project.Object.Delete", "Delete");
2462 });
2463 }
2464
2465 } // namespace redfish
2466