xref: /openbmc/phosphor-networkd/src/ethernet_interface.cpp (revision b740196bd220202cc2dd4f04c4ff409a79338e33)
1 #include "config.h"
2 
3 #include "ethernet_interface.hpp"
4 
5 #include "config_parser.hpp"
6 #include "network_manager.hpp"
7 #include "system_queries.hpp"
8 #include "util.hpp"
9 
10 #include <fmt/compile.h>
11 #include <fmt/format.h>
12 #include <linux/rtnetlink.h>
13 #include <net/if.h>
14 #include <net/if_arp.h>
15 
16 #include <phosphor-logging/elog-errors.hpp>
17 #include <phosphor-logging/lg2.hpp>
18 #include <stdplus/raw.hpp>
19 #include <stdplus/zstring.hpp>
20 #include <xyz/openbmc_project/Common/error.hpp>
21 
22 #include <algorithm>
23 #include <filesystem>
24 #include <string>
25 #include <unordered_map>
26 #include <variant>
27 
28 namespace phosphor
29 {
30 namespace network
31 {
32 
33 using namespace phosphor::logging;
34 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
35 using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
36 using NotAllowedArgument = xyz::openbmc_project::Common::NotAllowed;
37 using Argument = xyz::openbmc_project::Common::InvalidArgument;
38 using std::literals::string_view_literals::operator""sv;
39 constexpr auto RESOLVED_SERVICE = "org.freedesktop.resolve1";
40 constexpr auto RESOLVED_INTERFACE = "org.freedesktop.resolve1.Link";
41 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
42 constexpr auto RESOLVED_SERVICE_PATH = "/org/freedesktop/resolve1/link/";
43 
44 constexpr auto TIMESYNCD_SERVICE = "org.freedesktop.timesync1";
45 constexpr auto TIMESYNCD_INTERFACE = "org.freedesktop.timesync1.Manager";
46 constexpr auto TIMESYNCD_SERVICE_PATH = "/org/freedesktop/timesync1";
47 
48 constexpr auto METHOD_GET = "Get";
49 
50 template <typename Func>
51 inline decltype(std::declval<Func>()())
52     ignoreError(std::string_view msg, stdplus::zstring_view intf,
53                 decltype(std::declval<Func>()()) fallback, Func&& func) noexcept
54 {
55     try
56     {
57         return func();
58     }
59     catch (const std::exception& e)
60     {
61         lg2::error("{MSG} failed on {NET_INTF}: {ERROR}", "MSG", msg,
62                    "NET_INTF", intf, "ERROR", e);
63     }
64     return fallback;
65 }
66 
67 static std::string makeObjPath(std::string_view root, std::string_view intf)
68 {
69     auto ret = fmt::format(FMT_COMPILE("{}/{}"), root, intf);
70     std::replace(ret.begin() + ret.size() - intf.size(), ret.end(), '.', '_');
71     return ret;
72 }
73 
74 template <typename Addr>
75 static bool validIntfIP(Addr a) noexcept
76 {
77     return a.isUnicast() && !a.isLoopback();
78 }
79 
80 EthernetInterface::EthernetInterface(stdplus::PinnedRef<sdbusplus::bus_t> bus,
81                                      stdplus::PinnedRef<Manager> manager,
82                                      const AllIntfInfo& info,
83                                      std::string_view objRoot,
84                                      const config::Parser& config,
85                                      bool enabled) :
86     EthernetInterface(bus, manager, info, makeObjPath(objRoot, *info.intf.name),
87                       config, enabled)
88 {}
89 
90 EthernetInterface::EthernetInterface(stdplus::PinnedRef<sdbusplus::bus_t> bus,
91                                      stdplus::PinnedRef<Manager> manager,
92                                      const AllIntfInfo& info,
93                                      std::string&& objPath,
94                                      const config::Parser& config,
95                                      bool enabled) :
96     Ifaces(bus, objPath.c_str(), Ifaces::action::defer_emit),
97     manager(manager), bus(bus), objPath(std::move(objPath))
98 {
99     interfaceName(*info.intf.name, true);
100     auto dhcpVal = getDHCPValue(config);
101     EthernetInterfaceIntf::dhcp4(dhcpVal.v4, true);
102     EthernetInterfaceIntf::dhcp6(dhcpVal.v6, true);
103     EthernetInterfaceIntf::ipv6AcceptRA(getIPv6AcceptRA(config), true);
104     EthernetInterfaceIntf::nicEnabled(enabled, true);
105 
106     EthernetInterfaceIntf::ntpServers(
107         config.map.getValueStrings("Network", "NTP"), true);
108 
109     updateInfo(info.intf, true);
110 
111     if (info.defgw4)
112     {
113         EthernetInterface::defaultGateway(stdplus::toStr(*info.defgw4), true);
114     }
115     if (info.defgw6)
116     {
117         EthernetInterface::defaultGateway6(stdplus::toStr(*info.defgw6), true);
118     }
119     emit_object_added();
120 
121     if (info.intf.vlan_id)
122     {
123         if (!info.intf.parent_idx)
124         {
125             std::runtime_error("Missing parent link");
126         }
127         vlan.emplace(bus, this->objPath.c_str(), info.intf, *this);
128     }
129     for (const auto& [_, addr] : info.addrs)
130     {
131         addAddr(addr);
132     }
133     for (const auto& [_, neigh] : info.staticNeighs)
134     {
135         addStaticNeigh(neigh);
136     }
137 }
138 
139 void EthernetInterface::updateInfo(const InterfaceInfo& info, bool skipSignal)
140 {
141     ifIdx = info.idx;
142     EthernetInterfaceIntf::linkUp(info.flags & IFF_RUNNING, skipSignal);
143     if (info.mac)
144     {
145         MacAddressIntf::macAddress(stdplus::toStr(*info.mac), skipSignal);
146     }
147     if (info.mtu)
148     {
149         EthernetInterfaceIntf::mtu(*info.mtu, skipSignal);
150     }
151     if (ifIdx > 0)
152     {
153         auto ethInfo = ignoreError("GetEthInfo", *info.name, {}, [&] {
154             return system::getEthInfo(*info.name);
155         });
156         EthernetInterfaceIntf::autoNeg(ethInfo.autoneg, skipSignal);
157         EthernetInterfaceIntf::speed(ethInfo.speed, skipSignal);
158     }
159 }
160 
161 bool EthernetInterface::originIsManuallyAssigned(IP::AddressOrigin origin)
162 {
163     return (
164 #ifdef LINK_LOCAL_AUTOCONFIGURATION
165         (origin == IP::AddressOrigin::Static ||
166          origin == IP::AddressOrigin::LinkLocal)
167 #else
168         (origin == IP::AddressOrigin::Static)
169 #endif
170 
171     );
172 }
173 
174 void EthernetInterface::addAddr(const AddressInfo& info)
175 {
176     IP::AddressOrigin origin = IP::AddressOrigin::Static;
177     if (dhcpIsEnabled(info.ifaddr.getAddr()))
178     {
179         origin = IP::AddressOrigin::DHCP;
180     }
181 
182 #ifdef LINK_LOCAL_AUTOCONFIGURATION
183     if (info.scope == RT_SCOPE_LINK)
184     {
185         origin = IP::AddressOrigin::LinkLocal;
186     }
187 #endif
188 
189     if ((info.scope == RT_SCOPE_UNIVERSE) && (info.flags & IFA_F_PERMANENT))
190     {
191         origin = IP::AddressOrigin::Static;
192     }
193     if ((info.scope == RT_SCOPE_UNIVERSE) &&
194         ((info.flags & IFA_F_NOPREFIXROUTE) &&
195          (info.flags & IFA_F_MANAGETEMPADDR)))
196     {
197         origin = IP::AddressOrigin::SLAAC;
198     }
199     else if ((info.scope == RT_SCOPE_UNIVERSE) &&
200              ((info.flags & IFA_F_NOPREFIXROUTE)))
201     {
202         origin = IP::AddressOrigin::DHCP;
203     }
204 
205     auto it = addrs.find(info.ifaddr);
206     if (it == addrs.end())
207     {
208         addrs.emplace(info.ifaddr, std::make_unique<IPAddress>(
209                                        bus, std::string_view(objPath), *this,
210                                        info.ifaddr, origin));
211     }
212     else
213     {
214         it->second->IPIfaces::origin(origin);
215     }
216 }
217 
218 void EthernetInterface::addStaticNeigh(const NeighborInfo& info)
219 {
220     if (!info.mac || !info.addr)
221     {
222         lg2::error("Missing neighbor mac on {NET_INTF}", "NET_INTF",
223                    interfaceName());
224         return;
225     }
226 
227     if (auto it = staticNeighbors.find(*info.addr); it != staticNeighbors.end())
228     {
229         it->second->NeighborObj::macAddress(stdplus::toStr(*info.mac));
230     }
231     else
232     {
233         staticNeighbors.emplace(*info.addr, std::make_unique<Neighbor>(
234                                                 bus, std::string_view(objPath),
235                                                 *this, *info.addr, *info.mac,
236                                                 Neighbor::State::Permanent));
237     }
238 }
239 
240 ObjectPath EthernetInterface::ip(IP::Protocol protType, std::string ipaddress,
241                                  uint8_t prefixLength, std::string)
242 {
243     std::optional<stdplus::InAnyAddr> addr;
244     try
245     {
246         switch (protType)
247         {
248             case IP::Protocol::IPv4:
249                 addr.emplace(stdplus::fromStr<stdplus::In4Addr>(ipaddress));
250                 break;
251             case IP::Protocol::IPv6:
252                 addr.emplace(stdplus::fromStr<stdplus::In6Addr>(ipaddress));
253                 break;
254             default:
255                 throw std::logic_error("Exhausted protocols");
256         }
257         if (!std::visit([](auto ip) { return validIntfIP(ip); }, *addr))
258         {
259             throw std::invalid_argument("not unicast");
260         }
261     }
262     catch (const std::exception& e)
263     {
264         lg2::error("Invalid IP {NET_IP}: {ERROR}", "NET_IP", ipaddress, "ERROR",
265                    e);
266         elog<InvalidArgument>(Argument::ARGUMENT_NAME("ipaddress"),
267                               Argument::ARGUMENT_VALUE(ipaddress.c_str()));
268     }
269     std::optional<stdplus::SubnetAny> ifaddr;
270     try
271     {
272         if (prefixLength == 0)
273         {
274             throw std::invalid_argument("default route");
275         }
276         ifaddr.emplace(*addr, prefixLength);
277     }
278     catch (const std::exception& e)
279     {
280         lg2::error("Invalid prefix length {NET_PFX}: {ERROR}", "NET_PFX",
281                    prefixLength, "ERROR", e);
282         elog<InvalidArgument>(
283             Argument::ARGUMENT_NAME("prefixLength"),
284             Argument::ARGUMENT_VALUE(stdplus::toStr(prefixLength).c_str()));
285     }
286 
287     auto it = addrs.find(*ifaddr);
288     if (it == addrs.end())
289     {
290         it = std::get<0>(addrs.emplace(
291             *ifaddr,
292             std::make_unique<IPAddress>(bus, std::string_view(objPath), *this,
293                                         *ifaddr, IP::AddressOrigin::Static)));
294     }
295     else
296     {
297         if (it->second->origin() == IP::AddressOrigin::Static)
298         {
299             return it->second->getObjPath();
300         }
301         it->second->IPIfaces::origin(IP::AddressOrigin::Static);
302     }
303 
304     writeConfigurationFile();
305     manager.get().reloadConfigs();
306 
307     return it->second->getObjPath();
308 }
309 
310 ObjectPath EthernetInterface::neighbor(std::string ipAddress,
311                                        std::string macAddress)
312 {
313     std::optional<stdplus::InAnyAddr> addr;
314     try
315     {
316         addr.emplace(stdplus::fromStr<stdplus::InAnyAddr>(ipAddress));
317     }
318     catch (const std::exception& e)
319     {
320         lg2::error("Not a valid IP address {NET_IP}: {ERROR}", "NET_IP",
321                    ipAddress, "ERROR", e);
322         elog<InvalidArgument>(Argument::ARGUMENT_NAME("ipAddress"),
323                               Argument::ARGUMENT_VALUE(ipAddress.c_str()));
324     }
325 
326     std::optional<stdplus::EtherAddr> lladdr;
327     try
328     {
329         lladdr.emplace(stdplus::fromStr<stdplus::EtherAddr>(macAddress));
330     }
331     catch (const std::exception& e)
332     {
333         lg2::error("Not a valid MAC address {NET_MAC}: {ERROR}", "NET_MAC",
334                    macAddress, "ERROR", e);
335         elog<InvalidArgument>(Argument::ARGUMENT_NAME("macAddress"),
336                               Argument::ARGUMENT_VALUE(macAddress.c_str()));
337     }
338 
339     auto it = staticNeighbors.find(*addr);
340     if (it == staticNeighbors.end())
341     {
342         it = std::get<0>(staticNeighbors.emplace(
343             *addr, std::make_unique<Neighbor>(bus, std::string_view(objPath),
344                                               *this, *addr, *lladdr,
345                                               Neighbor::State::Permanent)));
346     }
347     else
348     {
349         auto str = stdplus::toStr(*lladdr);
350         if (it->second->macAddress() == str)
351         {
352             return it->second->getObjPath();
353         }
354         it->second->NeighborObj::macAddress(str);
355     }
356 
357     writeConfigurationFile();
358     manager.get().reloadConfigs();
359 
360     return it->second->getObjPath();
361 }
362 
363 bool EthernetInterface::ipv6AcceptRA(bool value)
364 {
365     if (ipv6AcceptRA() != EthernetInterfaceIntf::ipv6AcceptRA(value))
366     {
367         writeConfigurationFile();
368         manager.get().reloadConfigs();
369     }
370     return value;
371 }
372 
373 bool EthernetInterface::dhcp4(bool value)
374 {
375     if (dhcp4() != EthernetInterfaceIntf::dhcp4(value))
376     {
377         writeConfigurationFile();
378         manager.get().reloadConfigs();
379     }
380     return value;
381 }
382 
383 bool EthernetInterface::dhcp6(bool value)
384 {
385     if (dhcp6() != EthernetInterfaceIntf::dhcp6(value))
386     {
387         writeConfigurationFile();
388         manager.get().reloadConfigs();
389     }
390     return value;
391 }
392 
393 EthernetInterface::DHCPConf EthernetInterface::dhcpEnabled(DHCPConf value)
394 {
395     auto old4 = EthernetInterfaceIntf::dhcp4();
396     auto new4 = EthernetInterfaceIntf::dhcp4(value == DHCPConf::v4 ||
397                                              value == DHCPConf::v4v6stateless ||
398                                              value == DHCPConf::both);
399     auto old6 = EthernetInterfaceIntf::dhcp6();
400     auto new6 = EthernetInterfaceIntf::dhcp6(value == DHCPConf::v6 ||
401                                              value == DHCPConf::both);
402     auto oldra = EthernetInterfaceIntf::ipv6AcceptRA();
403     auto newra = EthernetInterfaceIntf::ipv6AcceptRA(
404         value == DHCPConf::v6stateless || value == DHCPConf::v4v6stateless ||
405         value == DHCPConf::v6 || value == DHCPConf::both);
406 
407     if (old4 != new4 || old6 != new6 || oldra != newra)
408     {
409         writeConfigurationFile();
410         manager.get().reloadConfigs();
411     }
412     return value;
413 }
414 
415 EthernetInterface::DHCPConf EthernetInterface::dhcpEnabled() const
416 {
417     if (dhcp6())
418     {
419         return dhcp4() ? DHCPConf::both : DHCPConf::v6;
420     }
421     else if (dhcp4())
422     {
423         return ipv6AcceptRA() ? DHCPConf::v4v6stateless : DHCPConf::v4;
424     }
425     return ipv6AcceptRA() ? DHCPConf::v6stateless : DHCPConf::none;
426 }
427 
428 size_t EthernetInterface::mtu(size_t value)
429 {
430     const size_t old = EthernetInterfaceIntf::mtu();
431     if (value == old)
432     {
433         return value;
434     }
435     const auto ifname = interfaceName();
436     return EthernetInterfaceIntf::mtu(ignoreError("SetMTU", ifname, old, [&] {
437         system::setMTU(ifname, value);
438         return value;
439     }));
440 }
441 
442 bool EthernetInterface::nicEnabled(bool value)
443 {
444     if (value == EthernetInterfaceIntf::nicEnabled())
445     {
446         return value;
447     }
448 
449     EthernetInterfaceIntf::nicEnabled(value);
450     writeConfigurationFile();
451     if (!value)
452     {
453         // We only need to bring down the interface, networkd will always bring
454         // up managed interfaces
455         manager.get().addReloadPreHook(
456             [ifname = interfaceName()]() { system::setNICUp(ifname, false); });
457     }
458     manager.get().reloadConfigs();
459 
460     return value;
461 }
462 
463 ServerList EthernetInterface::staticNameServers(ServerList value)
464 {
465     for (auto& ip : value)
466     {
467         try
468         {
469             ip = stdplus::toStr(stdplus::fromStr<stdplus::InAnyAddr>(ip));
470         }
471         catch (const std::exception& e)
472         {
473             lg2::error("Not a valid IP address {NET_IP}: {ERROR}", "NET_IP", ip,
474                        "ERROR", e);
475             elog<InvalidArgument>(Argument::ARGUMENT_NAME("StaticNameserver"),
476                                   Argument::ARGUMENT_VALUE(ip.c_str()));
477         }
478     }
479 
480     value = EthernetInterfaceIntf::staticNameServers(std::move(value));
481 
482     writeConfigurationFile();
483     manager.get().reloadConfigs();
484 
485     return value;
486 }
487 
488 void EthernetInterface::loadNTPServers(const config::Parser& config)
489 {
490     EthernetInterfaceIntf::ntpServers(getNTPServerFromTimeSyncd());
491     EthernetInterfaceIntf::staticNTPServers(
492         config.map.getValueStrings("Network", "NTP"));
493 }
494 
495 void EthernetInterface::loadNameServers(const config::Parser& config)
496 {
497     EthernetInterfaceIntf::nameservers(getNameServerFromResolvd());
498     EthernetInterfaceIntf::staticNameServers(
499         config.map.getValueStrings("Network", "DNS"));
500 }
501 
502 ServerList EthernetInterface::getNTPServerFromTimeSyncd()
503 {
504     ServerList servers; // Variable to capture the NTP Server IPs
505     auto method = bus.get().new_method_call(TIMESYNCD_SERVICE,
506                                             TIMESYNCD_SERVICE_PATH,
507                                             PROPERTY_INTERFACE, METHOD_GET);
508 
509     method.append(TIMESYNCD_INTERFACE, "LinkNTPServers");
510 
511     try
512     {
513         auto reply = bus.get().call(method);
514         std::variant<ServerList> response;
515         reply.read(response);
516         servers = std::get<ServerList>(response);
517     }
518     catch (const sdbusplus::exception::SdBusError& e)
519     {
520         lg2::error("Failed to get NTP server information from "
521                    "systemd-timesyncd: {ERROR}",
522                    "ERROR", e);
523     }
524 
525     return servers;
526 }
527 
528 ServerList EthernetInterface::getNameServerFromResolvd()
529 {
530     ServerList servers;
531     auto OBJ_PATH = fmt::format("{}{}", RESOLVED_SERVICE_PATH, ifIdx);
532 
533     /*
534       The DNS property under org.freedesktop.resolve1.Link interface contains
535       an array containing all DNS servers currently used by resolved. It
536       contains similar information as the DNS server data written to
537       /run/systemd/resolve/resolv.conf.
538 
539       Each structure in the array consists of a numeric network interface index,
540       an address family, and a byte array containing the DNS server address
541       (either 4 bytes in length for IPv4 or 16 bytes in lengths for IPv6).
542       The array contains DNS servers configured system-wide, including those
543       possibly read from a foreign /etc/resolv.conf or the DNS= setting in
544       /etc/systemd/resolved.conf, as well as per-interface DNS server
545       information either retrieved from systemd-networkd or configured by
546       external software via SetLinkDNS().
547     */
548 
549     using type = std::vector<std::tuple<int32_t, std::vector<uint8_t>>>;
550     std::variant<type> name; // Variable to capture the DNS property
551     auto method = bus.get().new_method_call(RESOLVED_SERVICE, OBJ_PATH.c_str(),
552                                             PROPERTY_INTERFACE, METHOD_GET);
553 
554     method.append(RESOLVED_INTERFACE, "DNS");
555 
556     try
557     {
558         auto reply = bus.get().call(method);
559         reply.read(name);
560     }
561     catch (const sdbusplus::exception_t& e)
562     {
563         lg2::error(
564             "Failed to get DNS information from systemd-resolved: {ERROR}",
565             "ERROR", e);
566     }
567     auto tupleVector = std::get_if<type>(&name);
568     for (auto i = tupleVector->begin(); i != tupleVector->end(); ++i)
569     {
570         int addressFamily = std::get<0>(*i);
571         std::vector<uint8_t>& ipaddress = std::get<1>(*i);
572         servers.push_back(stdplus::toStr(
573             addrFromBuf(addressFamily, stdplus::raw::asView<char>(ipaddress))));
574     }
575     return servers;
576 }
577 
578 ObjectPath EthernetInterface::createVLAN(uint16_t id)
579 {
580     auto idStr = stdplus::toStr(id);
581     auto intfName = fmt::format(FMT_COMPILE("{}.{}"), interfaceName(), idStr);
582     if (manager.get().interfaces.find(intfName) !=
583         manager.get().interfaces.end())
584     {
585         lg2::error("VLAN {NET_VLAN} already exists", "NET_VLAN", id);
586         elog<InvalidArgument>(Argument::ARGUMENT_NAME("VLANId"),
587                               Argument::ARGUMENT_VALUE(idStr.c_str()));
588     }
589 
590     auto objRoot = std::string_view(objPath).substr(0, objPath.rfind('/'));
591     auto macStr = MacAddressIntf::macAddress();
592     std::optional<stdplus::EtherAddr> mac;
593     if (!macStr.empty())
594     {
595         mac.emplace(stdplus::fromStr<stdplus::EtherAddr>(macStr));
596     }
597     auto info = AllIntfInfo{InterfaceInfo{
598         .type = ARPHRD_ETHER,
599         .idx = 0, // TODO: Query the correct value after creation
600         .flags = 0,
601         .name = intfName,
602         .mac = std::move(mac),
603         .mtu = mtu(),
604         .parent_idx = ifIdx,
605         .vlan_id = id,
606     }};
607 
608     // Pass the parents nicEnabled property, so that the child
609     // VLAN interface can inherit.
610     auto vlanIntf = std::make_unique<EthernetInterface>(
611         bus, manager, info, objRoot, config::Parser(), nicEnabled());
612     ObjectPath ret = vlanIntf->objPath;
613 
614     manager.get().interfaces.emplace(intfName, std::move(vlanIntf));
615 
616     // write the device file for the vlan interface.
617     config::Parser config;
618     auto& netdev = config.map["NetDev"].emplace_back();
619     netdev["Name"].emplace_back(intfName);
620     netdev["Kind"].emplace_back("vlan");
621     config.map["VLAN"].emplace_back()["Id"].emplace_back(std::move(idStr));
622     config.writeFile(
623         config::pathForIntfDev(manager.get().getConfDir(), intfName));
624 
625     writeConfigurationFile();
626     manager.get().reloadConfigs();
627 
628     return ret;
629 }
630 
631 ServerList EthernetInterface::staticNTPServers(ServerList value)
632 {
633     value = EthernetInterfaceIntf::staticNTPServers(std::move(value));
634 
635     writeConfigurationFile();
636     manager.get().reloadConfigs();
637 
638     return value;
639 }
640 
641 ServerList EthernetInterface::ntpServers(ServerList /*servers*/)
642 {
643     elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property"));
644 }
645 // Need to merge the below function with the code which writes the
646 // config file during factory reset.
647 // TODO openbmc/openbmc#1751
648 
649 void EthernetInterface::writeConfigurationFile()
650 {
651     config::Parser config;
652     config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
653     {
654         auto& link = config.map["Link"].emplace_back();
655 #ifdef PERSIST_MAC
656         auto mac = MacAddressIntf::macAddress();
657         if (!mac.empty())
658         {
659             link["MACAddress"].emplace_back(mac);
660         }
661 #endif
662         if (!EthernetInterfaceIntf::nicEnabled())
663         {
664             link["Unmanaged"].emplace_back("yes");
665         }
666     }
667     {
668         auto& network = config.map["Network"].emplace_back();
669         auto& lla = network["LinkLocalAddressing"];
670 #ifdef LINK_LOCAL_AUTOCONFIGURATION
671         lla.emplace_back("yes");
672 #else
673         lla.emplace_back("no");
674 #endif
675         network["IPv6AcceptRA"].emplace_back(ipv6AcceptRA() ? "true" : "false");
676         network["DHCP"].emplace_back(dhcp4() ? (dhcp6() ? "true" : "ipv4")
677                                              : (dhcp6() ? "ipv6" : "false"));
678         {
679             auto& vlans = network["VLAN"];
680             for (const auto& [_, intf] : manager.get().interfaces)
681             {
682                 if (intf->vlan && intf->vlan->parentIdx == ifIdx)
683                 {
684                     vlans.emplace_back(intf->interfaceName());
685                 }
686             }
687         }
688         {
689             auto& ntps = network["NTP"];
690             for (const auto& ntp : EthernetInterfaceIntf::staticNTPServers())
691             {
692                 ntps.emplace_back(ntp);
693             }
694         }
695         {
696             auto& dnss = network["DNS"];
697             for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
698             {
699                 dnss.emplace_back(dns);
700             }
701         }
702         {
703             auto& address = network["Address"];
704             for (const auto& addr : addrs)
705             {
706                 if (originIsManuallyAssigned(addr.second->origin()))
707                 {
708                     address.emplace_back(
709                         fmt::format("{}/{}", addr.second->address(),
710                                     addr.second->prefixLength()));
711                 }
712             }
713         }
714         {
715             auto& gateways = network["Gateway"];
716             if (!dhcp4())
717             {
718                 auto gateway = EthernetInterfaceIntf::defaultGateway();
719                 if (!gateway.empty())
720                 {
721                     gateways.emplace_back(gateway);
722                 }
723             }
724 
725             if (!dhcp6())
726             {
727                 auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
728                 if (!gateway6.empty())
729                 {
730                     gateways.emplace_back(gateway6);
731                 }
732             }
733         }
734     }
735     config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
736         dhcp6() ? "true" : "false");
737     {
738         auto& neighbors = config.map["Neighbor"];
739         for (const auto& sneighbor : staticNeighbors)
740         {
741             auto& neighbor = neighbors.emplace_back();
742             neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
743             neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
744         }
745     }
746     {
747         auto& dhcp = config.map["DHCP"].emplace_back();
748         dhcp["ClientIdentifier"].emplace_back("mac");
749         const auto& conf = manager.get().getDHCPConf();
750         auto dns_enabled = conf.dnsEnabled() ? "true" : "false";
751         dhcp["UseDNS"].emplace_back(dns_enabled);
752         dhcp["UseDomains"].emplace_back(dns_enabled);
753         dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false");
754         dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true"
755                                                                 : "false");
756         dhcp["SendHostname"].emplace_back(conf.sendHostNameEnabled() ? "true"
757                                                                      : "false");
758     }
759     auto path = config::pathForIntfConf(manager.get().getConfDir(),
760                                         interfaceName());
761     config.writeFile(path);
762     lg2::info("Wrote networkd file: {CFG_FILE}", "CFG_FILE", path);
763 }
764 
765 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
766 {
767     if (vlan)
768     {
769         lg2::error("Tried to set MAC address on VLAN");
770         elog<InternalFailure>();
771     }
772 #ifdef PERSIST_MAC
773     stdplus::EtherAddr newMAC;
774     try
775     {
776         newMAC = stdplus::fromStr<stdplus::EtherAddr>(value);
777     }
778     catch (const std::invalid_argument&)
779     {
780         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
781         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
782                               Argument::ARGUMENT_VALUE(value.c_str()));
783     }
784     if (!newMAC.isUnicast())
785     {
786         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
787         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
788                               Argument::ARGUMENT_VALUE(value.c_str()));
789     }
790 
791     auto interface = interfaceName();
792     auto validMAC = stdplus::toStr(newMAC);
793 
794     // We don't need to update the system if the address is unchanged
795     auto oldMAC =
796         stdplus::fromStr<stdplus::EtherAddr>(MacAddressIntf::macAddress());
797     if (newMAC != oldMAC)
798     {
799         // Update everything that depends on the MAC value
800         for (const auto& [_, intf] : manager.get().interfaces)
801         {
802             if (intf->vlan && intf->vlan->parentIdx == ifIdx)
803             {
804                 intf->MacAddressIntf::macAddress(validMAC);
805             }
806         }
807         MacAddressIntf::macAddress(validMAC);
808 
809         writeConfigurationFile();
810         manager.get().addReloadPreHook([interface]() {
811             // The MAC and LLADDRs will only update if the NIC is already down
812             system::setNICUp(interface, false);
813         });
814         manager.get().reloadConfigs();
815     }
816 
817 #ifdef HAVE_UBOOT_ENV
818     // Ensure that the valid address is stored in the u-boot-env
819     auto envVar = interfaceToUbootEthAddr(interface);
820     if (envVar)
821     {
822         // Trimming MAC addresses that are out of range. eg: AA:FF:FF:FF:FF:100;
823         // and those having more than 6 bytes. eg: AA:AA:AA:AA:AA:AA:BB
824         execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
825                 validMAC.c_str());
826     }
827 #endif // HAVE_UBOOT_ENV
828 
829     return value;
830 #else
831     elog<NotAllowed>(
832         NotAllowedArgument::REASON("Writing MAC address is not allowed"));
833 #endif // PERSIST_MAC
834 }
835 
836 void EthernetInterface::deleteAll()
837 {
838     // clear all the ip on the interface
839     addrs.clear();
840 
841     writeConfigurationFile();
842     manager.get().reloadConfigs();
843 }
844 
845 template <typename Addr>
846 static void normalizeGateway(std::string& gw)
847 {
848     if (gw.empty())
849     {
850         return;
851     }
852     try
853     {
854         auto ip = stdplus::fromStr<Addr>(gw);
855         if (ip == Addr{})
856         {
857             gw.clear();
858             return;
859         }
860         if (!validIntfIP(ip))
861         {
862             throw std::invalid_argument("Invalid unicast");
863         }
864         gw = stdplus::toStr(ip);
865     }
866     catch (const std::exception& e)
867     {
868         lg2::error("Invalid GW `{NET_GW}`: {ERROR}", "NET_GW", gw, "ERROR", e);
869         elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
870                               Argument::ARGUMENT_VALUE(gw.c_str()));
871     }
872 }
873 
874 std::string EthernetInterface::defaultGateway(std::string gateway)
875 {
876     normalizeGateway<stdplus::In4Addr>(gateway);
877     if (gateway != defaultGateway())
878     {
879         gateway = EthernetInterfaceIntf::defaultGateway(std::move(gateway));
880         manager.get().reloadConfigs();
881     }
882     return gateway;
883 }
884 
885 std::string EthernetInterface::defaultGateway6(std::string gateway)
886 {
887     normalizeGateway<stdplus::In6Addr>(gateway);
888     if (gateway != defaultGateway6())
889     {
890         gateway = EthernetInterfaceIntf::defaultGateway6(std::move(gateway));
891         manager.get().reloadConfigs();
892     }
893     return gateway;
894 }
895 
896 EthernetInterface::VlanProperties::VlanProperties(
897     sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
898     const InterfaceInfo& info, stdplus::PinnedRef<EthernetInterface> eth) :
899     VlanIfaces(bus, objPath.c_str(), VlanIfaces::action::defer_emit),
900     parentIdx(*info.parent_idx), eth(eth)
901 {
902     VlanIntf::id(*info.vlan_id, true);
903     emit_object_added();
904 }
905 
906 void EthernetInterface::VlanProperties::delete_()
907 {
908     auto intf = eth.get().interfaceName();
909 
910     // Remove all configs for the current interface
911     const auto& confDir = eth.get().manager.get().getConfDir();
912     std::error_code ec;
913     std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
914     std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
915 
916     if (eth.get().ifIdx > 0)
917     {
918         eth.get().manager.get().interfacesByIdx.erase(eth.get().ifIdx);
919     }
920     auto it = eth.get().manager.get().interfaces.find(intf);
921     auto obj = std::move(it->second);
922     eth.get().manager.get().interfaces.erase(it);
923 
924     // Write an updated parent interface since it has a VLAN entry
925     for (const auto& [_, intf] : eth.get().manager.get().interfaces)
926     {
927         if (intf->ifIdx == parentIdx)
928         {
929             intf->writeConfigurationFile();
930         }
931     }
932 
933     if (eth.get().ifIdx > 0)
934     {
935         // We need to forcibly delete the interface as systemd does not
936         eth.get().manager.get().addReloadPostHook(
937             [idx = eth.get().ifIdx]() { system::deleteIntf(idx); });
938 
939         // Ignore the interface so the reload doesn't re-query it
940         eth.get().manager.get().ignoredIntf.emplace(eth.get().ifIdx);
941     }
942 
943     eth.get().manager.get().reloadConfigs();
944 }
945 
946 } // namespace network
947 } // namespace phosphor
948