blob: a5ec6ea2fc8146f9e7b7e06f70c4d84bd7b32894 [file] [log] [blame]
Patrick Venture690a2342020-05-17 11:51:31 -07001#pragma once
2
John Wang8a7236a2021-01-04 15:31:44 +08003#include "app/channel.hpp"
4#include "user_channel/cipher_mgmt.hpp"
5
6#include <arpa/inet.h>
7#include <netinet/ether.h>
8
9#include <array>
10#include <bitset>
11#include <cinttypes>
Patrick Venture690a2342020-05-17 11:51:31 -070012#include <cstdint>
John Wang8a7236a2021-01-04 15:31:44 +080013#include <cstring>
14#include <fstream>
15#include <functional>
Patrick Venture690a2342020-05-17 11:51:31 -070016#include <ipmid/api-types.hpp>
John Wang8a7236a2021-01-04 15:31:44 +080017#include <ipmid/api.hpp>
18#include <ipmid/message.hpp>
19#include <ipmid/message/types.hpp>
20#include <ipmid/types.hpp>
21#include <ipmid/utils.hpp>
22#include <optional>
23#include <phosphor-logging/elog-errors.hpp>
24#include <phosphor-logging/elog.hpp>
25#include <phosphor-logging/log.hpp>
26#include <sdbusplus/bus.hpp>
27#include <sdbusplus/exception.hpp>
28#include <string>
29#include <string_view>
30#include <type_traits>
31#include <unordered_map>
32#include <unordered_set>
33#include <user_channel/channel_layer.hpp>
34#include <utility>
35#include <vector>
36#include <xyz/openbmc_project/Common/error.hpp>
37#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
38#include <xyz/openbmc_project/Network/IP/server.hpp>
39#include <xyz/openbmc_project/Network/Neighbor/server.hpp>
Patrick Venture690a2342020-05-17 11:51:31 -070040
41namespace ipmi
42{
43namespace transport
44{
45
John Wang8a7236a2021-01-04 15:31:44 +080046// D-Bus Network Daemon definitions
47constexpr auto PATH_ROOT = "/xyz/openbmc_project/network";
48constexpr auto PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config";
49
50constexpr auto INTF_SYSTEMCONFIG =
51 "xyz.openbmc_project.Network.SystemConfiguration";
52constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface";
53constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP";
54constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create";
55constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress";
56constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor";
57constexpr auto INTF_NEIGHBOR_CREATE_STATIC =
58 "xyz.openbmc_project.Network.Neighbor.CreateStatic";
59constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
60constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create";
61
Patrick Venture690a2342020-05-17 11:51:31 -070062/** @brief IPMI LAN Parameters */
63enum class LanParam : uint8_t
64{
65 SetStatus = 0,
66 AuthSupport = 1,
67 AuthEnables = 2,
68 IP = 3,
69 IPSrc = 4,
70 MAC = 5,
71 SubnetMask = 6,
72 Gateway1 = 12,
73 Gateway1MAC = 13,
74 VLANId = 20,
75 CiphersuiteSupport = 22,
76 CiphersuiteEntries = 23,
77 cipherSuitePrivilegeLevels = 24,
78 IPFamilySupport = 50,
79 IPFamilyEnables = 51,
80 IPv6Status = 55,
81 IPv6StaticAddresses = 56,
82 IPv6DynamicAddresses = 59,
83 IPv6RouterControl = 64,
84 IPv6StaticRouter1IP = 65,
85 IPv6StaticRouter1MAC = 66,
86 IPv6StaticRouter1PrefixLength = 67,
87 IPv6StaticRouter1PrefixValue = 68,
88};
89
90/** @brief IPMI IP Origin Types */
91enum class IPSrc : uint8_t
92{
93 Unspecified = 0,
94 Static = 1,
95 DHCP = 2,
96 BIOS = 3,
97 BMC = 4,
98};
99
100/** @brief IPMI Set Status */
101enum class SetStatus : uint8_t
102{
103 Complete = 0,
104 InProgress = 1,
105 Commit = 2,
106};
107
108/** @brief IPMI Family Suport Bits */
109namespace IPFamilySupportFlag
110{
111constexpr uint8_t IPv6Only = 0;
112constexpr uint8_t DualStack = 1;
113constexpr uint8_t IPv6Alerts = 2;
114} // namespace IPFamilySupportFlag
115
116/** @brief IPMI IPFamily Enables Flag */
117enum class IPFamilyEnables : uint8_t
118{
119 IPv4Only = 0,
120 IPv6Only = 1,
121 DualStack = 2,
122};
123
124/** @brief IPMI IPv6 Dyanmic Status Bits */
125namespace IPv6StatusFlag
126{
127constexpr uint8_t DHCP = 0;
128constexpr uint8_t SLAAC = 1;
129}; // namespace IPv6StatusFlag
130
131/** @brief IPMI IPv6 Source */
132enum class IPv6Source : uint8_t
133{
134 Static = 0,
135 SLAAC = 1,
136 DHCP = 2,
137};
138
139/** @brief IPMI IPv6 Address Status */
140enum class IPv6AddressStatus : uint8_t
141{
142 Active = 0,
143 Disabled = 1,
144};
145
146namespace IPv6RouterControlFlag
147{
148constexpr uint8_t Static = 0;
149constexpr uint8_t Dynamic = 1;
150}; // namespace IPv6RouterControlFlag
151
152// LAN Handler specific response codes
153constexpr Cc ccParamNotSupported = 0x80;
154constexpr Cc ccParamSetLocked = 0x81;
155constexpr Cc ccParamReadOnly = 0x82;
156
157// VLANs are a 12-bit value
158constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
159constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000;
160
161// Arbitrary v6 Address Limits to prevent too much output in ipmitool
162constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15;
163constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15;
164
John Wang8a7236a2021-01-04 15:31:44 +0800165/** @brief The dbus parameters for the interface corresponding to a channel
166 * This helps reduce the number of mapper lookups we need for each
167 * query and simplifies finding the VLAN interface if needed.
168 */
169struct ChannelParams
170{
171 /** @brief The channel ID */
172 int id;
173 /** @brief channel name for the interface */
174 std::string ifname;
175 /** @brief Name of the service on the bus */
176 std::string service;
177 /** @brief Lower level adapter path that is guaranteed to not be a VLAN */
178 std::string ifPath;
179 /** @brief Logical adapter path used for address assignment */
180 std::string logicalPath;
181};
182
183/** @brief A trivial helper used to determine if two PODs are equal
184 *
185 * @params[in] a - The first object to compare
186 * @params[in] b - The second object to compare
187 * @return True if the objects are the same bytewise
188 */
189template <typename T>
190bool equal(const T& a, const T& b)
191{
192 static_assert(std::is_trivially_copyable_v<T>);
193 return std::memcmp(&a, &b, sizeof(T)) == 0;
194}
195
196/** @brief Copies bytes from an array into a trivially copyable container
197 *
198 * @params[out] t - The container receiving the data
199 * @params[in] bytes - The data to copy
200 */
201template <size_t N, typename T>
202void copyInto(T& t, const std::array<uint8_t, N>& bytes)
203{
204 static_assert(std::is_trivially_copyable_v<T>);
205 static_assert(N == sizeof(T));
206 std::memcpy(&t, bytes.data(), bytes.size());
207}
208
209/** @brief Gets a generic view of the bytes in the input container
210 *
211 * @params[in] t - The data to reference
212 * @return A string_view referencing the bytes in the container
213 */
214template <typename T>
215std::string_view dataRef(const T& t)
216{
217 static_assert(std::is_trivially_copyable_v<T>);
218 return {reinterpret_cast<const char*>(&t), sizeof(T)};
219}
220
221/** @brief Determines the ethernet interface name corresponding to a channel
222 * Tries to map a VLAN object first so that the address information
223 * is accurate. Otherwise it gets the standard ethernet interface.
224 *
225 * @param[in] bus - The bus object used for lookups
226 * @param[in] channel - The channel id corresponding to an ethernet interface
227 * @return Ethernet interface service and object path if it exists
228 */
229std::optional<ChannelParams> maybeGetChannelParams(sdbusplus::bus::bus& bus,
230 uint8_t channel);
231
232/** @brief A trivial helper around maybeGetChannelParams() that throws an
233 * exception when it is unable to acquire parameters for the channel.
234 *
235 * @param[in] bus - The bus object used for lookups
236 * @param[in] channel - The channel id corresponding to an ethernet interface
237 * @return Ethernet interface service and object path
238 */
239ChannelParams getChannelParams(sdbusplus::bus::bus& bus, uint8_t channel);
240
241/** @brief Trivializes using parameter getter functions by providing a bus
242 * and channel parameters automatically.
243 *
244 * @param[in] channel - The channel id corresponding to an ethernet interface
245 * ...
246 */
247template <auto func, typename... Args>
248auto channelCall(uint8_t channel, Args&&... args)
249{
250 sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection());
251 auto params = getChannelParams(bus, channel);
252 return std::invoke(func, bus, params, std::forward<Args>(args)...);
253}
254
255/** @brief Generic paramters for different address families */
256template <int family>
257struct AddrFamily
258{
259};
260
261/** @brief Parameter specialization for IPv4 */
262template <>
263struct AddrFamily<AF_INET>
264{
265 using addr = in_addr;
266 static constexpr auto protocol =
267 sdbusplus::xyz::openbmc_project::Network::server::IP::Protocol::IPv4;
268 static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
269 static constexpr uint8_t defaultPrefix = 32;
270 static constexpr char propertyGateway[] = "DefaultGateway";
271};
272
273/** @brief Parameter specialization for IPv6 */
274template <>
275struct AddrFamily<AF_INET6>
276{
277 using addr = in6_addr;
278 static constexpr auto protocol =
279 sdbusplus::xyz::openbmc_project::Network::server::IP::Protocol::IPv6;
280 static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
281 static constexpr uint8_t defaultPrefix = 128;
282 static constexpr char propertyGateway[] = "DefaultGateway6";
283};
284
285/** @brief Interface Neighbor configuration parameters */
286template <int family>
287struct IfNeigh
288{
289 std::string path;
290 typename AddrFamily<family>::addr ip;
291 ether_addr mac;
292};
293
294/** @brief Interface IP Address configuration parameters */
295template <int family>
296struct IfAddr
297{
298 std::string path;
299 typename AddrFamily<family>::addr address;
300 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin origin;
301 uint8_t prefix;
302};
303
304/** @brief Valid address origins for IPv6 */
305static inline const std::unordered_set<
306 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin>
307 originsV6Static = {sdbusplus::xyz::openbmc_project::Network::server::IP::
308 AddressOrigin::Static};
309static inline const std::unordered_set<
310 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin>
311 originsV6Dynamic = {
312 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin::
313 DHCP,
314 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin::
315 SLAAC,
316};
317
318/** @brief A lazy lookup mechanism for iterating over object properties stored
319 * in DBus. This will only perform the object lookup when needed, and
320 * retains a cache of previous lookups to speed up future iterations.
321 */
322class ObjectLookupCache
323{
324 public:
325 using PropertiesCache = std::unordered_map<std::string, PropertyMap>;
326
327 /** @brief Creates a new ObjectLookupCache for the interface on the bus
328 * NOTE: The inputs to this object must outlive the object since
329 * they are only referenced by it.
330 *
331 * @param[in] bus - The bus object used for lookups
332 * @param[in] params - The parameters for the channel
333 * @param[in] intf - The interface we are looking up
334 */
335 ObjectLookupCache(sdbusplus::bus::bus& bus, const ChannelParams& params,
336 const char* intf) :
337 bus(bus),
338 params(params), intf(intf),
339 objs(getAllDbusObjects(bus, params.logicalPath, intf, ""))
340 {
341 }
342
343 class iterator : public ObjectTree::const_iterator
344 {
345 public:
346 using value_type = PropertiesCache::value_type;
347
348 iterator(ObjectTree::const_iterator it, ObjectLookupCache& container) :
349 ObjectTree::const_iterator(it), container(container),
350 ret(container.cache.end())
351 {
352 }
353 value_type& operator*()
354 {
355 ret = container.get(ObjectTree::const_iterator::operator*().first);
356 return *ret;
357 }
358 value_type* operator->()
359 {
360 return &operator*();
361 }
362
363 private:
364 ObjectLookupCache& container;
365 PropertiesCache::iterator ret;
366 };
367
368 iterator begin() noexcept
369 {
370 return iterator(objs.begin(), *this);
371 }
372
373 iterator end() noexcept
374 {
375 return iterator(objs.end(), *this);
376 }
377
378 private:
379 sdbusplus::bus::bus& bus;
380 const ChannelParams& params;
381 const char* const intf;
382 const ObjectTree objs;
383 PropertiesCache cache;
384
385 /** @brief Gets a cached copy of the object properties if possible
386 * Otherwise performs a query on DBus to look them up
387 *
388 * @param[in] path - The object path to lookup
389 * @return An iterator for the specified object path + properties
390 */
391 PropertiesCache::iterator get(const std::string& path)
392 {
393 auto it = cache.find(path);
394 if (it != cache.end())
395 {
396 return it;
397 }
398 auto properties = getAllDbusProperties(bus, params.service, path, intf);
399 return cache.insert({path, std::move(properties)}).first;
400 }
401};
402
403/** @brief Turns an IP address string into the network byte order form
404 * NOTE: This version strictly validates family matches
405 *
406 * @param[in] address - The string form of the address
407 * @return A network byte order address or none if conversion failed
408 */
409template <int family>
410std::optional<typename AddrFamily<family>::addr>
411 maybeStringToAddr(const char* address)
412{
413 typename AddrFamily<family>::addr ret;
414 if (inet_pton(family, address, &ret) == 1)
415 {
416 return ret;
417 }
418 return std::nullopt;
419}
420
421/** @brief Turns an IP address string into the network byte order form
422 * NOTE: This version strictly validates family matches
423 *
424 * @param[in] address - The string form of the address
425 * @return A network byte order address
426 */
427template <int family>
428typename AddrFamily<family>::addr stringToAddr(const char* address)
429{
430 auto ret = maybeStringToAddr<family>(address);
431 if (!ret)
432 {
433 phosphor::logging::log<phosphor::logging::level::ERR>(
434 "Failed to convert IP Address",
435 phosphor::logging::entry("FAMILY=%d", family),
436 phosphor::logging::entry("ADDRESS=%s", address));
437 phosphor::logging::elog<
438 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure>();
439 }
440 return *ret;
441}
442
443/** @brief Turns an IP address in network byte order into a string
444 *
445 * @param[in] address - The string form of the address
446 * @return A network byte order address
447 */
448template <int family>
449std::string addrToString(const typename AddrFamily<family>::addr& address)
450{
451 std::string ret(AddrFamily<family>::maxStrLen, '\0');
452 inet_ntop(family, &address, ret.data(), ret.size());
453 ret.resize(strlen(ret.c_str()));
454 return ret;
455}
456
457/** @brief Converts a human readable MAC string into MAC bytes
458 *
459 * @param[in] mac - The MAC string
460 * @return MAC in bytes
461 */
462ether_addr stringToMAC(const char* mac);
463/** @brief Searches the ip object lookup cache for an address matching
464 * the input parameters. NOTE: The index lacks stability across address
465 * changes since the network daemon has no notion of stable indicies.
466 *
467 * @param[in] bus - The bus object used for lookups
468 * @param[in] params - The parameters for the channel
469 * @param[in] idx - The index of the desired address on the interface
470 * @param[in] origins - The allowed origins for the address objects
471 * @param[in] ips - The object lookup cache holding all of the address info
472 * @return The address and prefix if it was found
473 */
474template <int family>
475std::optional<IfAddr<family>> findIfAddr(
476 sdbusplus::bus::bus& bus, const ChannelParams& params, uint8_t idx,
477 const std::unordered_set<
478 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin>&
479 origins,
480 ObjectLookupCache& ips)
481{
482 for (const auto& [path, properties] : ips)
483 {
484 const auto& addrStr = std::get<std::string>(properties.at("Address"));
485 auto addr = maybeStringToAddr<family>(addrStr.c_str());
486 if (!addr)
487 {
488 continue;
489 }
490
491 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin
492 origin = sdbusplus::xyz::openbmc_project::Network::server::IP::
493 convertAddressOriginFromString(
494 std::get<std::string>(properties.at("Origin")));
495 if (origins.find(origin) == origins.end())
496 {
497 continue;
498 }
499
500 if (idx > 0)
501 {
502 idx--;
503 continue;
504 }
505
506 IfAddr<family> ifaddr;
507 ifaddr.path = path;
508 ifaddr.address = *addr;
509 ifaddr.prefix = std::get<uint8_t>(properties.at("PrefixLength"));
510 ifaddr.origin = origin;
511 return std::move(ifaddr);
512 }
513
514 return std::nullopt;
515}
516/** @brief Trivial helper around findIfAddr that simplifies calls
517 * for one off lookups. Don't use this if you intend to do multiple
518 * lookups at a time.
519 *
520 * @param[in] bus - The bus object used for lookups
521 * @param[in] params - The parameters for the channel
522 * @param[in] idx - The index of the desired address on the interface
523 * @param[in] origins - The allowed origins for the address objects
524 * @return The address and prefix if it was found
525 */
526template <int family>
527auto getIfAddr(
528 sdbusplus::bus::bus& bus, const ChannelParams& params, uint8_t idx,
529 const std::unordered_set<
530 sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin>&
531 origins)
532{
533 ObjectLookupCache ips(bus, params, INTF_IP);
534 return findIfAddr<family>(bus, params, idx, origins, ips);
535}
536
537/** @brief Determines if the ethernet interface is using DHCP
538 *
539 * @param[in] bus - The bus object used for lookups
540 * @param[in] params - The parameters for the channel
541 * @return DHCPConf enumeration
542 */
543sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface::DHCPConf
544 getDHCPProperty(sdbusplus::bus::bus& bus, const ChannelParams& params);
545
546/** @brief Sets the DHCP v6 state on the given interface
547 *
548 * @param[in] bus - The bus object used for lookups
549 * @param[in] params - The parameters for the channel
550 * @param[in] requestedDhcp - DHCP state to assign (none, v6, both)
551 * @param[in] defaultMode - True: Use algorithmic assignment
552 * False: requestedDhcp assigned unconditionally
553 */
554void setDHCPv6Property(sdbusplus::bus::bus& bus, const ChannelParams& params,
555 const sdbusplus::xyz::openbmc_project::Network::server::
556 EthernetInterface::DHCPConf requestedDhcp,
557 const bool defaultMode);
558
559/** @brief Reconfigures the IPv6 address info configured for the interface
560 *
561 * @param[in] bus - The bus object used for lookups
562 * @param[in] params - The parameters for the channel
563 * @param[in] idx - The address index to operate on
564 * @param[in] address - The new address
565 * @param[in] prefix - The new address prefix
566 */
567void reconfigureIfAddr6(sdbusplus::bus::bus& bus, const ChannelParams& params,
568 uint8_t idx, const in6_addr& address, uint8_t prefix);
569
570/** @brief Retrieves the current gateway for the address family on the system
571 * NOTE: The gateway is currently system wide and not per channel
572 *
573 * @param[in] bus - The bus object used for lookups
574 * @param[in] params - The parameters for the channel
575 * @return An address representing the gateway address if it exists
576 */
577template <int family>
578std::optional<typename AddrFamily<family>::addr>
579 getGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params)
580{
581 auto gatewayStr = std::get<std::string>(getDbusProperty(
582 bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
583 AddrFamily<family>::propertyGateway));
584 if (gatewayStr.empty())
585 {
586 return std::nullopt;
587 }
588 return stringToAddr<family>(gatewayStr.c_str());
589}
590
591template <int family>
592std::optional<IfNeigh<family>>
593 findStaticNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params,
594 const typename AddrFamily<family>::addr& ip,
595 ObjectLookupCache& neighbors)
596{
597 using sdbusplus::xyz::openbmc_project::Network::server::Neighbor;
598 const auto state =
599 sdbusplus::xyz::openbmc_project::Network::server::convertForMessage(
600 Neighbor::State::Permanent);
601 for (const auto& [path, neighbor] : neighbors)
602 {
603 const auto& ipStr = std::get<std::string>(neighbor.at("IPAddress"));
604 auto neighIP = maybeStringToAddr<family>(ipStr.c_str());
605 if (!neighIP)
606 {
607 continue;
608 }
609 if (!equal(*neighIP, ip))
610 {
611 continue;
612 }
613 if (state != std::get<std::string>(neighbor.at("State")))
614 {
615 continue;
616 }
617
618 IfNeigh<family> ret;
619 ret.path = path;
620 ret.ip = ip;
621 const auto& macStr = std::get<std::string>(neighbor.at("MACAddress"));
622 ret.mac = stringToMAC(macStr.c_str());
623 return std::move(ret);
624 }
625
626 return std::nullopt;
627}
628
629template <int family>
630void createNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params,
631 const typename AddrFamily<family>::addr& address,
632 const ether_addr& mac)
633{
634 auto newreq =
635 bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
636 INTF_NEIGHBOR_CREATE_STATIC, "Neighbor");
637 std::string macStr = ether_ntoa(&mac);
638 newreq.append(addrToString<family>(address), macStr);
639 bus.call_noreply(newreq);
640}
641
642/** @brief Deletes the dbus object. Ignores empty objects or objects that are
643 * missing from the bus.
644 *
645 * @param[in] bus - The bus object used for lookups
646 * @param[in] service - The name of the service
647 * @param[in] path - The path of the object to delete
648 */
649void deleteObjectIfExists(sdbusplus::bus::bus& bus, const std::string& service,
650 const std::string& path);
651
652/** @brief Sets the system wide value for the default gateway
653 *
654 * @param[in] bus - The bus object used for lookups
655 * @param[in] params - The parameters for the channel
656 * @param[in] gateway - Gateway address to apply
657 */
658template <int family>
659void setGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
660 const typename AddrFamily<family>::addr& address)
661{
662 // Save the old gateway MAC address if it exists so we can recreate it
663 auto gateway = getGatewayProperty<family>(bus, params);
664 std::optional<IfNeigh<family>> neighbor;
665 if (gateway)
666 {
667 ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
668 neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors);
669 }
670
671 setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
672 AddrFamily<family>::propertyGateway,
673 addrToString<family>(address));
674
675 // Restore the gateway MAC if we had one
676 if (neighbor)
677 {
678 deleteObjectIfExists(bus, params.service, neighbor->path);
679 createNeighbor<family>(bus, params, address, neighbor->mac);
680 }
681}
682
Patrick Venture690a2342020-05-17 11:51:31 -0700683} // namespace transport
684} // namespace ipmi