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