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