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