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 
653 void EthernetInterface::writeConfigurationFile()
654 {
655     config::Parser config;
656     config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
657     {
658         auto& link = config.map["Link"].emplace_back();
659 #ifdef PERSIST_MAC
660         auto mac = MacAddressIntf::macAddress();
661         if (!mac.empty())
662         {
663             link["MACAddress"].emplace_back(mac);
664         }
665 #endif
666         if (!EthernetInterfaceIntf::nicEnabled())
667         {
668             link["Unmanaged"].emplace_back("yes");
669         }
670     }
671     {
672         auto& network = config.map["Network"].emplace_back();
673         auto& lla = network["LinkLocalAddressing"];
674 #ifdef LINK_LOCAL_AUTOCONFIGURATION
675         lla.emplace_back("yes");
676 #else
677         lla.emplace_back("no");
678 #endif
679         network["IPv6AcceptRA"].emplace_back(ipv6AcceptRA() ? "true" : "false");
680         network["DHCP"].emplace_back(dhcp4() ? (dhcp6() ? "true" : "ipv4")
681                                              : (dhcp6() ? "ipv6" : "false"));
682         {
683             auto& vlans = network["VLAN"];
684             for (const auto& [_, intf] : manager.get().interfaces)
685             {
686                 if (intf->vlan && intf->vlan->parentIdx == ifIdx)
687                 {
688                     vlans.emplace_back(intf->interfaceName());
689                 }
690             }
691         }
692         {
693             auto& ntps = network["NTP"];
694             for (const auto& ntp : EthernetInterfaceIntf::staticNTPServers())
695             {
696                 ntps.emplace_back(ntp);
697             }
698         }
699         {
700             auto& dnss = network["DNS"];
701             for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
702             {
703                 dnss.emplace_back(dns);
704             }
705         }
706         {
707             auto& address = network["Address"];
708             for (const auto& addr : addrs)
709             {
710                 if (originIsManuallyAssigned(addr.second->origin()))
711                 {
712                     address.emplace_back(stdplus::toStr(addr.first));
713                 }
714             }
715         }
716         {
717             auto& gateways = network["Gateway"];
718             if (!dhcp4())
719             {
720                 auto gateway = EthernetInterfaceIntf::defaultGateway();
721                 if (!gateway.empty())
722                 {
723                     gateways.emplace_back(gateway);
724                 }
725             }
726 
727             if (!ipv6AcceptRA())
728             {
729                 auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
730                 if (!gateway6.empty())
731                 {
732                     gateways.emplace_back(gateway6);
733                 }
734             }
735         }
736     }
737     config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
738         dhcp6() ? "true" : "false");
739     {
740         auto& neighbors = config.map["Neighbor"];
741         for (const auto& sneighbor : staticNeighbors)
742         {
743             auto& neighbor = neighbors.emplace_back();
744             neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
745             neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
746         }
747     }
748     {
749         auto& dhcp = config.map["DHCP"].emplace_back();
750         dhcp["ClientIdentifier"].emplace_back("mac");
751         const auto& conf = manager.get().getDHCPConf();
752         auto dns_enabled = conf.dnsEnabled() ? "true" : "false";
753         dhcp["UseDNS"].emplace_back(dns_enabled);
754         dhcp["UseDomains"].emplace_back(dns_enabled);
755         dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false");
756         dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true"
757                                                                 : "false");
758         dhcp["SendHostname"].emplace_back(conf.sendHostNameEnabled() ? "true"
759                                                                      : "false");
760     }
761     auto path = config::pathForIntfConf(manager.get().getConfDir(),
762                                         interfaceName());
763     config.writeFile(path);
764     lg2::info("Wrote networkd file: {CFG_FILE}", "CFG_FILE", path);
765 }
766 
767 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
768 {
769     if (vlan)
770     {
771         lg2::error("Tried to set MAC address on VLAN");
772         elog<InternalFailure>();
773     }
774 #ifdef PERSIST_MAC
775     stdplus::EtherAddr newMAC;
776     try
777     {
778         newMAC = stdplus::fromStr<stdplus::EtherAddr>(value);
779     }
780     catch (const std::invalid_argument&)
781     {
782         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
783         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
784                               Argument::ARGUMENT_VALUE(value.c_str()));
785     }
786     if (!newMAC.isUnicast())
787     {
788         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
789         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
790                               Argument::ARGUMENT_VALUE(value.c_str()));
791     }
792 
793     auto interface = interfaceName();
794     auto validMAC = stdplus::toStr(newMAC);
795 
796     // We don't need to update the system if the address is unchanged
797     auto oldMAC =
798         stdplus::fromStr<stdplus::EtherAddr>(MacAddressIntf::macAddress());
799     if (newMAC != oldMAC)
800     {
801         auto path = config::pathForIntfConf(manager.get().getConfDir(),
802                                             interface);
803         // Update everything that depends on the MAC value
804         for (const auto& [_, intf] : manager.get().interfaces)
805         {
806             if (intf->vlan && intf->vlan->parentIdx == ifIdx)
807             {
808                 intf->MacAddressIntf::macAddress(validMAC);
809             }
810         }
811         MacAddressIntf::macAddress(validMAC);
812 
813         writeConfigurationFile();
814         manager.get().addReloadPreHook([interface, path]() {
815             // The MAC and LLADDRs will only update if the NIC is already down
816             system::setNICUp(interface, false);
817             utimensat(AT_FDCWD, path.c_str(), NULL, 0);
818         });
819         manager.get().reloadConfigs();
820     }
821 
822 #ifdef HAVE_UBOOT_ENV
823     // Ensure that the valid address is stored in the u-boot-env
824     auto envVar = interfaceToUbootEthAddr(interface);
825     if (envVar)
826     {
827         // Trimming MAC addresses that are out of range. eg: AA:FF:FF:FF:FF:100;
828         // and those having more than 6 bytes. eg: AA:AA:AA:AA:AA:AA:BB
829         execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
830                 validMAC.c_str());
831     }
832 #endif // HAVE_UBOOT_ENV
833 
834     return value;
835 #else
836     elog<NotAllowed>(
837         NotAllowedArgument::REASON("Writing MAC address is not allowed"));
838 #endif // PERSIST_MAC
839 }
840 
841 void EthernetInterface::deleteAll()
842 {
843     // clear all the ip on the interface
844     addrs.clear();
845 
846     writeConfigurationFile();
847     manager.get().reloadConfigs();
848 }
849 
850 template <typename Addr>
851 static void normalizeGateway(std::string& gw)
852 {
853     if (gw.empty())
854     {
855         return;
856     }
857     try
858     {
859         auto ip = stdplus::fromStr<Addr>(gw);
860         if (ip == Addr{})
861         {
862             gw.clear();
863             return;
864         }
865         if (!validIntfIP(ip))
866         {
867             throw std::invalid_argument("Invalid unicast");
868         }
869         gw = stdplus::toStr(ip);
870     }
871     catch (const std::exception& e)
872     {
873         lg2::error("Invalid GW `{NET_GW}`: {ERROR}", "NET_GW", gw, "ERROR", e);
874         elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
875                               Argument::ARGUMENT_VALUE(gw.c_str()));
876     }
877 }
878 
879 std::string EthernetInterface::defaultGateway(std::string gateway)
880 {
881     normalizeGateway<stdplus::In4Addr>(gateway);
882     if (gateway != defaultGateway())
883     {
884         gateway = EthernetInterfaceIntf::defaultGateway(std::move(gateway));
885         writeConfigurationFile();
886         manager.get().reloadConfigs();
887     }
888     return gateway;
889 }
890 
891 std::string EthernetInterface::defaultGateway6(std::string gateway)
892 {
893     normalizeGateway<stdplus::In6Addr>(gateway);
894     if (gateway != defaultGateway6())
895     {
896         gateway = EthernetInterfaceIntf::defaultGateway6(std::move(gateway));
897         writeConfigurationFile();
898         manager.get().reloadConfigs();
899     }
900     return gateway;
901 }
902 
903 EthernetInterface::VlanProperties::VlanProperties(
904     sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
905     const InterfaceInfo& info, stdplus::PinnedRef<EthernetInterface> eth) :
906     VlanIfaces(bus, objPath.c_str(), VlanIfaces::action::defer_emit),
907     parentIdx(*info.parent_idx), eth(eth)
908 {
909     VlanIntf::id(*info.vlan_id, true);
910     emit_object_added();
911 }
912 
913 void EthernetInterface::VlanProperties::delete_()
914 {
915     auto intf = eth.get().interfaceName();
916 
917     // Remove all configs for the current interface
918     const auto& confDir = eth.get().manager.get().getConfDir();
919     std::error_code ec;
920     std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
921     std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
922 
923     if (eth.get().ifIdx > 0)
924     {
925         eth.get().manager.get().interfacesByIdx.erase(eth.get().ifIdx);
926     }
927     auto it = eth.get().manager.get().interfaces.find(intf);
928     auto obj = std::move(it->second);
929     eth.get().manager.get().interfaces.erase(it);
930 
931     // Write an updated parent interface since it has a VLAN entry
932     for (const auto& [_, intf] : eth.get().manager.get().interfaces)
933     {
934         if (intf->ifIdx == parentIdx)
935         {
936             intf->writeConfigurationFile();
937         }
938     }
939 
940     if (eth.get().ifIdx > 0)
941     {
942         // We need to forcibly delete the interface as systemd does not
943         eth.get().manager.get().addReloadPostHook(
944             [idx = eth.get().ifIdx]() { system::deleteIntf(idx); });
945 
946         // Ignore the interface so the reload doesn't re-query it
947         eth.get().manager.get().ignoredIntf.emplace(eth.get().ifIdx);
948     }
949 
950     eth.get().manager.get().reloadConfigs();
951 }
952 
953 } // namespace network
954 } // namespace phosphor
955