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