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     std::vector<std::string> dnsUniqueValues;
468     for (auto& ip : value)
469     {
470         try
471         {
472             ip = stdplus::toStr(stdplus::fromStr<stdplus::InAnyAddr>(ip));
473         }
474         catch (const std::exception& e)
475         {
476             lg2::error("Not a valid IP address {NET_IP}: {ERROR}", "NET_IP", ip,
477                        "ERROR", e);
478             elog<InvalidArgument>(Argument::ARGUMENT_NAME("StaticNameserver"),
479                                   Argument::ARGUMENT_VALUE(ip.c_str()));
480         }
481         if (std::find(dnsUniqueValues.begin(), dnsUniqueValues.end(), ip) ==
482             dnsUniqueValues.end())
483         {
484             dnsUniqueValues.push_back(ip);
485         }
486     }
487 
488     value =
489         EthernetInterfaceIntf::staticNameServers(std::move(dnsUniqueValues));
490 
491     writeConfigurationFile();
492     manager.get().reloadConfigs();
493 
494     return value;
495 }
496 
497 void EthernetInterface::loadNTPServers(const config::Parser& config)
498 {
499     EthernetInterfaceIntf::ntpServers(getNTPServerFromTimeSyncd());
500     EthernetInterfaceIntf::staticNTPServers(
501         config.map.getValueStrings("Network", "NTP"));
502 }
503 
504 void EthernetInterface::loadNameServers(const config::Parser& config)
505 {
506     EthernetInterfaceIntf::nameservers(getNameServerFromResolvd());
507     EthernetInterfaceIntf::staticNameServers(
508         config.map.getValueStrings("Network", "DNS"));
509 }
510 
511 ServerList EthernetInterface::getNTPServerFromTimeSyncd()
512 {
513     ServerList servers; // Variable to capture the NTP Server IPs
514     auto method = bus.get().new_method_call(TIMESYNCD_SERVICE,
515                                             TIMESYNCD_SERVICE_PATH,
516                                             PROPERTY_INTERFACE, METHOD_GET);
517 
518     method.append(TIMESYNCD_INTERFACE, "LinkNTPServers");
519 
520     try
521     {
522         auto reply = bus.get().call(method);
523         std::variant<ServerList> response;
524         reply.read(response);
525         servers = std::get<ServerList>(response);
526     }
527     catch (const sdbusplus::exception::SdBusError& e)
528     {
529         lg2::error("Failed to get NTP server information from "
530                    "systemd-timesyncd: {ERROR}",
531                    "ERROR", e);
532     }
533 
534     return servers;
535 }
536 
537 ServerList EthernetInterface::nameservers() const
538 {
539     return getNameServerFromResolvd();
540 }
541 
542 ServerList EthernetInterface::getNameServerFromResolvd() const
543 {
544     ServerList servers;
545     auto OBJ_PATH = std::format("{}{}", RESOLVED_SERVICE_PATH, ifIdx);
546 
547     /*
548       The DNS property under org.freedesktop.resolve1.Link interface contains
549       an array containing all DNS servers currently used by resolved. It
550       contains similar information as the DNS server data written to
551       /run/systemd/resolve/resolv.conf.
552 
553       Each structure in the array consists of a numeric network interface index,
554       an address family, and a byte array containing the DNS server address
555       (either 4 bytes in length for IPv4 or 16 bytes in lengths for IPv6).
556       The array contains DNS servers configured system-wide, including those
557       possibly read from a foreign /etc/resolv.conf or the DNS= setting in
558       /etc/systemd/resolved.conf, as well as per-interface DNS server
559       information either retrieved from systemd-networkd or configured by
560       external software via SetLinkDNS().
561     */
562 
563     using type = std::vector<std::tuple<int32_t, std::vector<uint8_t>>>;
564     std::variant<type> name; // Variable to capture the DNS property
565     auto method = bus.get().new_method_call(RESOLVED_SERVICE, OBJ_PATH.c_str(),
566                                             PROPERTY_INTERFACE, METHOD_GET);
567 
568     method.append(RESOLVED_INTERFACE, "DNS");
569 
570     try
571     {
572         auto reply = bus.get().call(method);
573         reply.read(name);
574     }
575     catch (const sdbusplus::exception_t& e)
576     {
577         lg2::error(
578             "Failed to get DNS information from systemd-resolved: {ERROR}",
579             "ERROR", e);
580     }
581     auto tupleVector = std::get_if<type>(&name);
582     for (auto i = tupleVector->begin(); i != tupleVector->end(); ++i)
583     {
584         int addressFamily = std::get<0>(*i);
585         std::vector<uint8_t>& ipaddress = std::get<1>(*i);
586         servers.push_back(stdplus::toStr(
587             addrFromBuf(addressFamily, stdplus::raw::asView<char>(ipaddress))));
588     }
589     return servers;
590 }
591 
592 ObjectPath EthernetInterface::createVLAN(uint16_t id)
593 {
594     auto idStr = stdplus::toStr(id);
595     auto intfName = stdplus::strCat(interfaceName(), "."sv, idStr);
596     if (manager.get().interfaces.find(intfName) !=
597         manager.get().interfaces.end())
598     {
599         lg2::error("VLAN {NET_VLAN} already exists", "NET_VLAN", id);
600         elog<InvalidArgument>(Argument::ARGUMENT_NAME("VLANId"),
601                               Argument::ARGUMENT_VALUE(idStr.c_str()));
602     }
603 
604     auto objRoot = std::string_view(objPath).substr(0, objPath.rfind('/'));
605     auto macStr = MacAddressIntf::macAddress();
606     std::optional<stdplus::EtherAddr> mac;
607     if (!macStr.empty())
608     {
609         mac.emplace(stdplus::fromStr<stdplus::EtherAddr>(macStr));
610     }
611     auto info = AllIntfInfo{InterfaceInfo{
612         .type = ARPHRD_ETHER,
613         .idx = 0, // TODO: Query the correct value after creation
614         .flags = 0,
615         .name = intfName,
616         .mac = std::move(mac),
617         .mtu = mtu(),
618         .parent_idx = ifIdx,
619         .vlan_id = id,
620     }};
621 
622     // Pass the parents nicEnabled property, so that the child
623     // VLAN interface can inherit.
624     auto vlanIntf = std::make_unique<EthernetInterface>(
625         bus, manager, info, objRoot, config::Parser(), nicEnabled());
626     ObjectPath ret = vlanIntf->objPath;
627 
628     manager.get().interfaces.emplace(intfName, std::move(vlanIntf));
629 
630     // write the device file for the vlan interface.
631     config::Parser config;
632     auto& netdev = config.map["NetDev"].emplace_back();
633     netdev["Name"].emplace_back(intfName);
634     netdev["Kind"].emplace_back("vlan");
635     config.map["VLAN"].emplace_back()["Id"].emplace_back(std::move(idStr));
636     config.writeFile(
637         config::pathForIntfDev(manager.get().getConfDir(), intfName));
638 
639     writeConfigurationFile();
640     manager.get().reloadConfigs();
641 
642     return ret;
643 }
644 
645 ServerList EthernetInterface::staticNTPServers(ServerList value)
646 {
647     value = EthernetInterfaceIntf::staticNTPServers(std::move(value));
648 
649     writeConfigurationFile();
650     manager.get().reloadConfigs();
651 
652     return value;
653 }
654 
655 ServerList EthernetInterface::ntpServers(ServerList /*servers*/)
656 {
657     elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property"));
658 }
659 
660 void EthernetInterface::writeConfigurationFile()
661 {
662     config::Parser config;
663     config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
664     {
665         auto& link = config.map["Link"].emplace_back();
666 #ifdef PERSIST_MAC
667         auto mac = MacAddressIntf::macAddress();
668         if (!mac.empty())
669         {
670             link["MACAddress"].emplace_back(mac);
671         }
672 #endif
673         if (!EthernetInterfaceIntf::nicEnabled())
674         {
675             link["Unmanaged"].emplace_back("yes");
676         }
677     }
678     {
679         auto& network = config.map["Network"].emplace_back();
680         auto& lla = network["LinkLocalAddressing"];
681 #ifdef LINK_LOCAL_AUTOCONFIGURATION
682         lla.emplace_back("yes");
683 #else
684         lla.emplace_back("no");
685 #endif
686         network["IPv6AcceptRA"].emplace_back(ipv6AcceptRA() ? "true" : "false");
687         network["DHCP"].emplace_back(dhcp4() ? (dhcp6() ? "true" : "ipv4")
688                                              : (dhcp6() ? "ipv6" : "false"));
689         {
690             auto& vlans = network["VLAN"];
691             for (const auto& [_, intf] : manager.get().interfaces)
692             {
693                 if (intf->vlan && intf->vlan->parentIdx == ifIdx)
694                 {
695                     vlans.emplace_back(intf->interfaceName());
696                 }
697             }
698         }
699         {
700             auto& ntps = network["NTP"];
701             for (const auto& ntp : EthernetInterfaceIntf::staticNTPServers())
702             {
703                 ntps.emplace_back(ntp);
704             }
705         }
706         {
707             auto& dnss = network["DNS"];
708             for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
709             {
710                 dnss.emplace_back(dns);
711             }
712         }
713         {
714             auto& address = network["Address"];
715             for (const auto& addr : addrs)
716             {
717                 if (originIsManuallyAssigned(addr.second->origin()))
718                 {
719                     address.emplace_back(stdplus::toStr(addr.first));
720                 }
721             }
722         }
723         {
724             auto& gateways = network["Gateway"];
725             if (!dhcp4())
726             {
727                 auto gateway = EthernetInterfaceIntf::defaultGateway();
728                 if (!gateway.empty())
729                 {
730                     gateways.emplace_back(gateway);
731                 }
732             }
733 
734             if (!ipv6AcceptRA())
735             {
736                 auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
737                 if (!gateway6.empty())
738                 {
739                     gateways.emplace_back(gateway6);
740                 }
741             }
742         }
743     }
744     config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
745         dhcp6() ? "true" : "false");
746     {
747         auto& neighbors = config.map["Neighbor"];
748         for (const auto& sneighbor : staticNeighbors)
749         {
750             auto& neighbor = neighbors.emplace_back();
751             neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
752             neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
753         }
754     }
755     {
756         auto& dhcp = config.map["DHCP"].emplace_back();
757         dhcp["ClientIdentifier"].emplace_back("mac");
758         const auto& conf = manager.get().getDHCPConf();
759         auto dns_enabled = conf.dnsEnabled() ? "true" : "false";
760         dhcp["UseDNS"].emplace_back(dns_enabled);
761         dhcp["UseDomains"].emplace_back(dns_enabled);
762         dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false");
763         dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true"
764                                                                 : "false");
765         dhcp["SendHostname"].emplace_back(conf.sendHostNameEnabled() ? "true"
766                                                                      : "false");
767     }
768     auto path = config::pathForIntfConf(manager.get().getConfDir(),
769                                         interfaceName());
770     config.writeFile(path);
771     lg2::info("Wrote networkd file: {CFG_FILE}", "CFG_FILE", path);
772 }
773 
774 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
775 {
776     if (vlan)
777     {
778         lg2::error("Tried to set MAC address on VLAN");
779         elog<InternalFailure>();
780     }
781 #ifdef PERSIST_MAC
782     stdplus::EtherAddr newMAC;
783     try
784     {
785         newMAC = stdplus::fromStr<stdplus::EtherAddr>(value);
786     }
787     catch (const std::invalid_argument&)
788     {
789         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
790         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
791                               Argument::ARGUMENT_VALUE(value.c_str()));
792     }
793     if (!newMAC.isUnicast())
794     {
795         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
796         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
797                               Argument::ARGUMENT_VALUE(value.c_str()));
798     }
799 
800     auto interface = interfaceName();
801     auto validMAC = stdplus::toStr(newMAC);
802 
803     // We don't need to update the system if the address is unchanged
804     auto oldMAC =
805         stdplus::fromStr<stdplus::EtherAddr>(MacAddressIntf::macAddress());
806     if (newMAC != oldMAC)
807     {
808         auto path = config::pathForIntfConf(manager.get().getConfDir(),
809                                             interface);
810         // Update everything that depends on the MAC value
811         for (const auto& [_, intf] : manager.get().interfaces)
812         {
813             if (intf->vlan && intf->vlan->parentIdx == ifIdx)
814             {
815                 intf->MacAddressIntf::macAddress(validMAC);
816             }
817         }
818         MacAddressIntf::macAddress(validMAC);
819 
820         writeConfigurationFile();
821         manager.get().addReloadPreHook([interface, path]() {
822             // The MAC and LLADDRs will only update if the NIC is already down
823             system::setNICUp(interface, false);
824             utimensat(AT_FDCWD, path.c_str(), NULL, 0);
825         });
826         manager.get().reloadConfigs();
827     }
828 
829 #ifdef HAVE_UBOOT_ENV
830     // Ensure that the valid address is stored in the u-boot-env
831     auto envVar = interfaceToUbootEthAddr(interface);
832     if (envVar)
833     {
834         // Trimming MAC addresses that are out of range. eg: AA:FF:FF:FF:FF:100;
835         // and those having more than 6 bytes. eg: AA:AA:AA:AA:AA:AA:BB
836         execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
837                 validMAC.c_str());
838     }
839 #endif // HAVE_UBOOT_ENV
840 
841     return value;
842 #else
843     elog<NotAllowed>(
844         NotAllowedArgument::REASON("Writing MAC address is not allowed"));
845 #endif // PERSIST_MAC
846 }
847 
848 void EthernetInterface::deleteAll()
849 {
850     // clear all the ip on the interface
851     addrs.clear();
852 
853     writeConfigurationFile();
854     manager.get().reloadConfigs();
855 }
856 
857 template <typename Addr>
858 static void normalizeGateway(std::string& gw)
859 {
860     if (gw.empty())
861     {
862         return;
863     }
864     try
865     {
866         auto ip = stdplus::fromStr<Addr>(gw);
867         if (ip == Addr{})
868         {
869             gw.clear();
870             return;
871         }
872         if (!validIntfIP(ip))
873         {
874             throw std::invalid_argument("Invalid unicast");
875         }
876         gw = stdplus::toStr(ip);
877     }
878     catch (const std::exception& e)
879     {
880         lg2::error("Invalid GW `{NET_GW}`: {ERROR}", "NET_GW", gw, "ERROR", e);
881         elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
882                               Argument::ARGUMENT_VALUE(gw.c_str()));
883     }
884 }
885 
886 std::string EthernetInterface::defaultGateway(std::string gateway)
887 {
888     normalizeGateway<stdplus::In4Addr>(gateway);
889     if (gateway != defaultGateway())
890     {
891         gateway = EthernetInterfaceIntf::defaultGateway(std::move(gateway));
892         writeConfigurationFile();
893         manager.get().reloadConfigs();
894     }
895     return gateway;
896 }
897 
898 std::string EthernetInterface::defaultGateway6(std::string gateway)
899 {
900     normalizeGateway<stdplus::In6Addr>(gateway);
901     if (gateway != defaultGateway6())
902     {
903         gateway = EthernetInterfaceIntf::defaultGateway6(std::move(gateway));
904         writeConfigurationFile();
905         manager.get().reloadConfigs();
906     }
907     return gateway;
908 }
909 
910 EthernetInterface::VlanProperties::VlanProperties(
911     sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
912     const InterfaceInfo& info, stdplus::PinnedRef<EthernetInterface> eth) :
913     VlanIfaces(bus, objPath.c_str(), VlanIfaces::action::defer_emit),
914     parentIdx(*info.parent_idx), eth(eth)
915 {
916     VlanIntf::id(*info.vlan_id, true);
917     emit_object_added();
918 }
919 
920 void EthernetInterface::VlanProperties::delete_()
921 {
922     auto intf = eth.get().interfaceName();
923 
924     // Remove all configs for the current interface
925     const auto& confDir = eth.get().manager.get().getConfDir();
926     std::error_code ec;
927     std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
928     std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
929 
930     if (eth.get().ifIdx > 0)
931     {
932         eth.get().manager.get().interfacesByIdx.erase(eth.get().ifIdx);
933     }
934     auto it = eth.get().manager.get().interfaces.find(intf);
935     auto obj = std::move(it->second);
936     eth.get().manager.get().interfaces.erase(it);
937 
938     // Write an updated parent interface since it has a VLAN entry
939     for (const auto& [_, intf] : eth.get().manager.get().interfaces)
940     {
941         if (intf->ifIdx == parentIdx)
942         {
943             intf->writeConfigurationFile();
944         }
945     }
946 
947     if (eth.get().ifIdx > 0)
948     {
949         // We need to forcibly delete the interface as systemd does not
950         eth.get().manager.get().addReloadPostHook(
951             [idx = eth.get().ifIdx]() { system::deleteIntf(idx); });
952 
953         // Ignore the interface so the reload doesn't re-query it
954         eth.get().manager.get().ignoredIntf.emplace(eth.get().ifIdx);
955     }
956 
957     eth.get().manager.get().reloadConfigs();
958 }
959 
960 } // namespace network
961 } // namespace phosphor
962