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