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