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 manager.get().interfaces.emplace(intfName, std::move(vlanIntf));
763
764 // write the device file for the vlan interface.
765 config::Parser config;
766 auto& netdev = config.map["NetDev"].emplace_back();
767 netdev["Name"].emplace_back(intfName);
768 netdev["Kind"].emplace_back("vlan");
769 config.map["VLAN"].emplace_back()["Id"].emplace_back(std::move(idStr));
770 config.writeFile(
771 config::pathForIntfDev(manager.get().getConfDir(), intfName));
772
773 writeConfigurationFile();
774 manager.get().reloadConfigs();
775
776 return ret;
777 }
778
staticNTPServers(ServerList value)779 ServerList EthernetInterface::staticNTPServers(ServerList value)
780 {
781 value = EthernetInterfaceIntf::staticNTPServers(std::move(value));
782
783 writeConfigurationFile();
784 manager.get().reloadConfigs();
785
786 return value;
787 }
788
ntpServers(ServerList)789 ServerList EthernetInterface::ntpServers(ServerList /*servers*/)
790 {
791 elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property"));
792 }
793
tfStr(bool value)794 static constexpr std::string_view tfStr(bool value)
795 {
796 return value ? "true"sv : "false"sv;
797 }
798
writeUpdatedTime(const Manager & manager,const std::filesystem::path & netFile)799 static void writeUpdatedTime(const Manager& manager,
800 const std::filesystem::path& netFile)
801 {
802 // JFFS2 doesn't have the time granularity to deal with sub-second
803 // updates. Since we can have multiple file updates within a second
804 // around a reload, we need a location which gives that precision for
805 // future networkd detected reloads. TMPFS gives us this property.
806 if (manager.getConfDir() == "/etc/systemd/network"sv)
807 {
808 auto dir = stdplus::strCat(netFile.native(), ".d");
809 dir.replace(1, 3, "run"); // Replace /etc with /run
810 auto file = dir + "/updated.conf";
811 try
812 {
813 std::filesystem::create_directories(dir);
814 using namespace stdplus::fd;
815 futimens(
816 open(file,
817 OpenFlags(OpenAccess::WriteOnly).set(OpenFlag::Create),
818 0644)
819 .get(),
820 nullptr);
821 }
822 catch (const std::exception& e)
823 {
824 lg2::error("Failed to write time updated file {FILE}: {ERROR}",
825 "FILE", file, "ERROR", e.what());
826 }
827 }
828 }
829
writeConfigurationFile()830 void EthernetInterface::writeConfigurationFile()
831 {
832 config::Parser config;
833 config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName());
834 {
835 auto& link = config.map["Link"].emplace_back();
836 #ifdef PERSIST_MAC
837 auto mac = MacAddressIntf::macAddress();
838 if (!mac.empty())
839 {
840 link["MACAddress"].emplace_back(mac);
841 }
842 #endif
843 if (!EthernetInterfaceIntf::nicEnabled())
844 {
845 link["ActivationPolicy"].emplace_back("down");
846 }
847 }
848 {
849 auto& network = config.map["Network"].emplace_back();
850 auto& lla = network["LinkLocalAddressing"];
851 #ifdef LINK_LOCAL_AUTOCONFIGURATION
852 lla.emplace_back("yes");
853 #else
854 lla.emplace_back("no");
855 #endif
856 network["IPv6AcceptRA"].emplace_back(ipv6AcceptRA() ? "true" : "false");
857 network["DHCP"].emplace_back(dhcp4() ? (dhcp6() ? "true" : "ipv4")
858 : (dhcp6() ? "ipv6" : "false"));
859 {
860 auto& vlans = network["VLAN"];
861 for (const auto& [_, intf] : manager.get().interfaces)
862 {
863 if (intf->vlan && intf->vlan->parentIdx == ifIdx)
864 {
865 vlans.emplace_back(intf->interfaceName());
866 }
867 }
868 }
869 {
870 auto& ntps = network["NTP"];
871 for (const auto& ntp : EthernetInterfaceIntf::staticNTPServers())
872 {
873 ntps.emplace_back(ntp);
874 }
875 }
876 {
877 auto& dnss = network["DNS"];
878 for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
879 {
880 dnss.emplace_back(dns);
881 }
882 }
883 {
884 auto& address = network["Address"];
885 for (const auto& addr : addrs)
886 {
887 if (addr.second->origin() == IP::AddressOrigin::Static)
888 {
889 address.emplace_back(stdplus::toStr(addr.first));
890 }
891 }
892 }
893 {
894 if (!dhcp4())
895 {
896 auto gateway4 = EthernetInterfaceIntf::defaultGateway();
897 if (!gateway4.empty())
898 {
899 auto& gateway4route = config.map["Route"].emplace_back();
900 gateway4route["Gateway"].emplace_back(gateway4);
901 gateway4route["GatewayOnLink"].emplace_back("true");
902 }
903 }
904
905 if (!ipv6AcceptRA())
906 {
907 auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
908 if (!gateway6.empty())
909 {
910 auto& gateway6route = config.map["Route"].emplace_back();
911 gateway6route["Gateway"].emplace_back(gateway6);
912 gateway6route["GatewayOnLink"].emplace_back("true");
913 }
914 }
915 }
916 }
917 config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back(
918 dhcp6() ? "true" : "false");
919 {
920 auto& neighbors = config.map["Neighbor"];
921 for (const auto& sneighbor : staticNeighbors)
922 {
923 auto& neighbor = neighbors.emplace_back();
924 neighbor["Address"].emplace_back(sneighbor.second->ipAddress());
925 neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress());
926 }
927 }
928 {
929 auto& dhcp4 = config.map["DHCPv4"].emplace_back();
930 dhcp4["ClientIdentifier"].emplace_back("mac");
931 dhcp4["UseDNS"].emplace_back(tfStr(dhcp4Conf->dnsEnabled()));
932 dhcp4["UseDomains"].emplace_back(tfStr(dhcp4Conf->domainEnabled()));
933 dhcp4["UseNTP"].emplace_back(tfStr(dhcp4Conf->ntpEnabled()));
934 dhcp4["UseHostname"].emplace_back(tfStr(dhcp4Conf->hostNameEnabled()));
935 dhcp4["SendHostname"].emplace_back(
936 tfStr(dhcp4Conf->sendHostNameEnabled()));
937 }
938 {
939 auto& dhcp6 = config.map["DHCPv6"].emplace_back();
940 dhcp6["UseDNS"].emplace_back(tfStr(dhcp6Conf->dnsEnabled()));
941 dhcp6["UseDomains"].emplace_back(tfStr(dhcp6Conf->domainEnabled()));
942 dhcp6["UseNTP"].emplace_back(tfStr(dhcp6Conf->ntpEnabled()));
943 dhcp6["UseHostname"].emplace_back(tfStr(dhcp6Conf->hostNameEnabled()));
944 dhcp6["SendHostname"].emplace_back(
945 tfStr(dhcp6Conf->sendHostNameEnabled()));
946 }
947
948 {
949 auto& sroutes = config.map["Route"];
950 for (const auto& temp : staticGateways)
951 {
952 auto& staticGateway = sroutes.emplace_back();
953 staticGateway["Gateway"].emplace_back(temp.second->gateway());
954 staticGateway["GatewayOnLink"].emplace_back("true");
955 }
956 }
957
958 auto path =
959 config::pathForIntfConf(manager.get().getConfDir(), interfaceName());
960 config.writeFile(path);
961 lg2::info("Wrote networkd file: {CFG_FILE}", "CFG_FILE", path);
962 writeUpdatedTime(manager, path);
963 }
964
macAddress(std::string value)965 std::string EthernetInterface::macAddress([[maybe_unused]] std::string value)
966 {
967 if (vlan)
968 {
969 lg2::error("Tried to set MAC address on VLAN");
970 elog<InternalFailure>();
971 }
972 #ifdef PERSIST_MAC
973 stdplus::EtherAddr newMAC;
974 try
975 {
976 newMAC = stdplus::fromStr<stdplus::EtherAddr>(value);
977 }
978 catch (const std::exception& e)
979 {
980 lg2::error("Invalid MAC address {NET_MAC}: {ERROR}", "NET_MAC", value,
981 "ERROR", e);
982 elog<InvalidArgument>(Argument::ARGUMENT_NAME("netmac"),
983 Argument::ARGUMENT_VALUE(value.c_str()));
984 }
985
986 if (!newMAC.isUnicast())
987 {
988 lg2::error("MAC Address {NET_MAC} is not valid", "NET_MAC", value);
989 elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
990 Argument::ARGUMENT_VALUE(value.c_str()));
991 }
992
993 auto interface = interfaceName();
994 auto validMAC = stdplus::toStr(newMAC);
995
996 // We don't need to update the system if the address is unchanged
997 auto oldMAC =
998 stdplus::fromStr<stdplus::EtherAddr>(MacAddressIntf::macAddress());
999 if (newMAC != oldMAC)
1000 {
1001 // Update everything that depends on the MAC value
1002 for (const auto& [_, intf] : manager.get().interfaces)
1003 {
1004 if (intf->vlan && intf->vlan->parentIdx == ifIdx)
1005 {
1006 intf->MacAddressIntf::macAddress(validMAC);
1007 }
1008 }
1009 MacAddressIntf::macAddress(validMAC);
1010
1011 writeConfigurationFile();
1012 manager.get().addReloadPreHook([interface, manager = manager]() {
1013 // The MAC and LLADDRs will only update if the NIC is already down
1014 system::setNICUp(interface, false);
1015 writeUpdatedTime(
1016 manager,
1017 config::pathForIntfConf(manager.get().getConfDir(), interface));
1018 });
1019 manager.get().reloadConfigs();
1020 }
1021
1022 std::error_code ec;
1023 const auto fw_setenv = std::filesystem::path("/sbin/fw_setenv");
1024 if (std::filesystem::exists(fw_setenv, ec))
1025 {
1026 // Ensure that the valid address is stored in the u-boot-env
1027 auto envVar = interfaceToUbootEthAddr(interface);
1028 if (envVar)
1029 {
1030 execute(fw_setenv.native(), "fw_setenv", envVar->c_str(),
1031 validMAC.c_str());
1032 }
1033 }
1034
1035 return value;
1036 #else
1037 elog<NotAllowed>(
1038 NotAllowedArgument::REASON("Writing MAC address is not allowed"));
1039 #endif // PERSIST_MAC
1040 }
1041
deleteAll()1042 void EthernetInterface::deleteAll()
1043 {
1044 // clear all the ip on the interface
1045 addrs.clear();
1046
1047 writeConfigurationFile();
1048 manager.get().reloadConfigs();
1049 }
1050
1051 template <typename Addr>
normalizeGateway(std::string & gw)1052 static void normalizeGateway(std::string& gw)
1053 {
1054 if (gw.empty())
1055 {
1056 return;
1057 }
1058 try
1059 {
1060 auto ip = stdplus::fromStr<Addr>(gw);
1061 if (ip == Addr{})
1062 {
1063 gw.clear();
1064 return;
1065 }
1066 if (!validIntfIP(ip))
1067 {
1068 throw std::invalid_argument("Invalid unicast");
1069 }
1070 gw = stdplus::toStr(ip);
1071 }
1072 catch (const std::exception& e)
1073 {
1074 lg2::error("Invalid GW `{NET_GW}`: {ERROR}", "NET_GW", gw, "ERROR", e);
1075 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
1076 Argument::ARGUMENT_VALUE(gw.c_str()));
1077 }
1078 }
1079
defaultGateway(std::string gateway)1080 std::string EthernetInterface::defaultGateway(std::string gateway)
1081 {
1082 normalizeGateway<stdplus::In4Addr>(gateway);
1083 if (gateway != defaultGateway())
1084 {
1085 gateway = EthernetInterfaceIntf::defaultGateway(std::move(gateway));
1086 writeConfigurationFile();
1087 manager.get().reloadConfigs();
1088 }
1089 return gateway;
1090 }
1091
defaultGateway6(std::string gateway)1092 std::string EthernetInterface::defaultGateway6(std::string gateway)
1093 {
1094 normalizeGateway<stdplus::In6Addr>(gateway);
1095 if (gateway != defaultGateway6())
1096 {
1097 gateway = EthernetInterfaceIntf::defaultGateway6(std::move(gateway));
1098 writeConfigurationFile();
1099 manager.get().reloadConfigs();
1100 }
1101 return gateway;
1102 }
1103
VlanProperties(sdbusplus::bus_t & bus,stdplus::const_zstring objPath,const InterfaceInfo & info,stdplus::PinnedRef<EthernetInterface> eth)1104 EthernetInterface::VlanProperties::VlanProperties(
1105 sdbusplus::bus_t& bus, stdplus::const_zstring objPath,
1106 const InterfaceInfo& info, stdplus::PinnedRef<EthernetInterface> eth) :
1107 VlanIfaces(bus, objPath.c_str(), VlanIfaces::action::defer_emit),
1108 parentIdx(*info.parent_idx), eth(eth)
1109 {
1110 VlanIntf::id(*info.vlan_id, true);
1111 emit_object_added();
1112 }
1113
delete_()1114 void EthernetInterface::VlanProperties::delete_()
1115 {
1116 auto intf = eth.get().interfaceName();
1117
1118 // Remove all configs for the current interface
1119 const auto& confDir = eth.get().manager.get().getConfDir();
1120 std::error_code ec;
1121 std::filesystem::remove(config::pathForIntfConf(confDir, intf), ec);
1122 std::filesystem::remove(config::pathForIntfDev(confDir, intf), ec);
1123
1124 if (eth.get().ifIdx > 0)
1125 {
1126 eth.get().manager.get().interfacesByIdx.erase(eth.get().ifIdx);
1127 }
1128 auto it = eth.get().manager.get().interfaces.find(intf);
1129 auto obj = std::move(it->second);
1130 eth.get().manager.get().interfaces.erase(it);
1131
1132 // Write an updated parent interface since it has a VLAN entry
1133 for (const auto& [_, intf] : eth.get().manager.get().interfaces)
1134 {
1135 if (intf->ifIdx == parentIdx)
1136 {
1137 intf->writeConfigurationFile();
1138 }
1139 }
1140
1141 if (eth.get().ifIdx > 0)
1142 {
1143 // We need to forcibly delete the interface as systemd does not
1144 eth.get().manager.get().addReloadPostHook([idx = eth.get().ifIdx]() {
1145 system::deleteIntf(idx);
1146 });
1147
1148 // Ignore the interface so the reload doesn't re-query it
1149 eth.get().manager.get().ignoredIntf.emplace(eth.get().ifIdx);
1150 }
1151
1152 eth.get().manager.get().reloadConfigs();
1153 }
1154
emitLLDP(bool value)1155 bool EthernetInterface::emitLLDP(bool value)
1156 {
1157 if (emitLLDP() != EthernetInterfaceIntf::emitLLDP(value))
1158 {
1159 manager.get().writeLLDPDConfigurationFile();
1160 manager.get().reloadLLDPService();
1161 }
1162 return value;
1163 }
1164
reloadConfigs()1165 void EthernetInterface::reloadConfigs()
1166 {
1167 manager.get().reloadConfigs();
1168 }
1169
1170 } // namespace network
1171 } // namespace phosphor
1172