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