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