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