xref: /openbmc/phosphor-networkd/src/ethernet_interface.cpp (revision c990007964a41128b74e5bd018c13cf6f0bc2140)
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 <linux/rtnetlink.h>
10 #include <net/if.h>
11 #include <net/if_arp.h>
12 
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <phosphor-logging/lg2.hpp>
15 #include <stdplus/raw.hpp>
16 #include <stdplus/str/cat.hpp>
17 #include <stdplus/zstring.hpp>
18 #include <xyz/openbmc_project/Common/error.hpp>
19 
20 #include <algorithm>
21 #include <filesystem>
22 #include <format>
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 = std::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(stdplus::toStr(addr.first));
708                 }
709             }
710         }
711         {
712             auto& gateways = network["Gateway"];
713             if (!dhcp4())
714             {
715                 auto gateway = EthernetInterfaceIntf::defaultGateway();
716                 if (!gateway.empty())
717                 {
718                     gateways.emplace_back(gateway);
719                 }
720             }
721 
722             if (!ipv6AcceptRA())
723             {
724                 auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
725                 if (!gateway6.empty())
726                 {
727                     gateways.emplace_back(gateway6);
728                 }
729             }
730         }
731     }
732     config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
733         dhcp6() ? "true" : "false");
734     {
735         auto& neighbors = config.map["Neighbor"];
736         for (const auto& sneighbor : staticNeighbors)
737         {
738             auto& neighbor = neighbors.emplace_back();
739             neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
740             neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
741         }
742     }
743     {
744         auto& dhcp = config.map["DHCP"].emplace_back();
745         dhcp["ClientIdentifier"].emplace_back("mac");
746         const auto& conf = manager.get().getDHCPConf();
747         auto dns_enabled = conf.dnsEnabled() ? "true" : "false";
748         dhcp["UseDNS"].emplace_back(dns_enabled);
749         dhcp["UseDomains"].emplace_back(dns_enabled);
750         dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false");
751         dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true"
752                                                                 : "false");
753         dhcp["SendHostname"].emplace_back(conf.sendHostNameEnabled() ? "true"
754                                                                      : "false");
755     }
756     auto path = config::pathForIntfConf(manager.get().getConfDir(),
757                                         interfaceName());
758     config.writeFile(path);
759     lg2::info("Wrote networkd file: {CFG_FILE}", "CFG_FILE", path);
760 }
761 
762 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
763 {
764     if (vlan)
765     {
766         lg2::error("Tried to set MAC address on VLAN");
767         elog<InternalFailure>();
768     }
769 #ifdef PERSIST_MAC
770     stdplus::EtherAddr newMAC;
771     try
772     {
773         newMAC = stdplus::fromStr<stdplus::EtherAddr>(value);
774     }
775     catch (const std::invalid_argument&)
776     {
777         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
778         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
779                               Argument::ARGUMENT_VALUE(value.c_str()));
780     }
781     if (!newMAC.isUnicast())
782     {
783         lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
784         elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
785                               Argument::ARGUMENT_VALUE(value.c_str()));
786     }
787 
788     auto interface = interfaceName();
789     auto validMAC = stdplus::toStr(newMAC);
790 
791     // We don't need to update the system if the address is unchanged
792     auto oldMAC =
793         stdplus::fromStr<stdplus::EtherAddr>(MacAddressIntf::macAddress());
794     if (newMAC != oldMAC)
795     {
796         // Update everything that depends on the MAC value
797         for (const auto& [_, intf] : manager.get().interfaces)
798         {
799             if (intf->vlan && intf->vlan->parentIdx == ifIdx)
800             {
801                 intf->MacAddressIntf::macAddress(validMAC);
802             }
803         }
804         MacAddressIntf::macAddress(validMAC);
805 
806         writeConfigurationFile();
807         manager.get().addReloadPreHook([interface]() {
808             // The MAC and LLADDRs will only update if the NIC is already down
809             system::setNICUp(interface, false);
810         });
811         manager.get().reloadConfigs();
812     }
813 
814 #ifdef HAVE_UBOOT_ENV
815     // Ensure that the valid address is stored in the u-boot-env
816     auto envVar = interfaceToUbootEthAddr(interface);
817     if (envVar)
818     {
819         // Trimming MAC addresses that are out of range. eg: AA:FF:FF:FF:FF:100;
820         // and those having more than 6 bytes. eg: AA:AA:AA:AA:AA:AA:BB
821         execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
822                 validMAC.c_str());
823     }
824 #endif // HAVE_UBOOT_ENV
825 
826     return value;
827 #else
828     elog<NotAllowed>(
829         NotAllowedArgument::REASON("Writing MAC address is not allowed"));
830 #endif // PERSIST_MAC
831 }
832 
833 void EthernetInterface::deleteAll()
834 {
835     // clear all the ip on the interface
836     addrs.clear();
837 
838     writeConfigurationFile();
839     manager.get().reloadConfigs();
840 }
841 
842 template <typename Addr>
843 static void normalizeGateway(std::string& gw)
844 {
845     if (gw.empty())
846     {
847         return;
848     }
849     try
850     {
851         auto ip = stdplus::fromStr<Addr>(gw);
852         if (ip == Addr{})
853         {
854             gw.clear();
855             return;
856         }
857         if (!validIntfIP(ip))
858         {
859             throw std::invalid_argument("Invalid unicast");
860         }
861         gw = stdplus::toStr(ip);
862     }
863     catch (const std::exception& e)
864     {
865         lg2::error("Invalid GW `{NET_GW}`: {ERROR}", "NET_GW", gw, "ERROR", e);
866         elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
867                               Argument::ARGUMENT_VALUE(gw.c_str()));
868     }
869 }
870 
871 std::string EthernetInterface::defaultGateway(std::string gateway)
872 {
873     normalizeGateway<stdplus::In4Addr>(gateway);
874     if (gateway != defaultGateway())
875     {
876         gateway = EthernetInterfaceIntf::defaultGateway(std::move(gateway));
877         manager.get().reloadConfigs();
878     }
879     return gateway;
880 }
881 
882 std::string EthernetInterface::defaultGateway6(std::string gateway)
883 {
884     normalizeGateway<stdplus::In6Addr>(gateway);
885     if (gateway != defaultGateway6())
886     {
887         gateway = EthernetInterfaceIntf::defaultGateway6(std::move(gateway));
888         manager.get().reloadConfigs();
889     }
890     return gateway;
891 }
892 
893 EthernetInterface::VlanProperties::VlanProperties(
894     sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
895     const InterfaceInfo& info, stdplus::PinnedRef<EthernetInterface> eth) :
896     VlanIfaces(bus, objPath.c_str(), VlanIfaces::action::defer_emit),
897     parentIdx(*info.parent_idx), eth(eth)
898 {
899     VlanIntf::id(*info.vlan_id, true);
900     emit_object_added();
901 }
902 
903 void EthernetInterface::VlanProperties::delete_()
904 {
905     auto intf = eth.get().interfaceName();
906 
907     // Remove all configs for the current interface
908     const auto& confDir = eth.get().manager.get().getConfDir();
909     std::error_code ec;
910     std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
911     std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
912 
913     if (eth.get().ifIdx > 0)
914     {
915         eth.get().manager.get().interfacesByIdx.erase(eth.get().ifIdx);
916     }
917     auto it = eth.get().manager.get().interfaces.find(intf);
918     auto obj = std::move(it->second);
919     eth.get().manager.get().interfaces.erase(it);
920 
921     // Write an updated parent interface since it has a VLAN entry
922     for (const auto& [_, intf] : eth.get().manager.get().interfaces)
923     {
924         if (intf->ifIdx == parentIdx)
925         {
926             intf->writeConfigurationFile();
927         }
928     }
929 
930     if (eth.get().ifIdx > 0)
931     {
932         // We need to forcibly delete the interface as systemd does not
933         eth.get().manager.get().addReloadPostHook(
934             [idx = eth.get().ifIdx]() { system::deleteIntf(idx); });
935 
936         // Ignore the interface so the reload doesn't re-query it
937         eth.get().manager.get().ignoredIntf.emplace(eth.get().ifIdx);
938     }
939 
940     eth.get().manager.get().reloadConfigs();
941 }
942 
943 } // namespace network
944 } // namespace phosphor
945