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