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