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