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 PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config"; 49 50 constexpr auto INTF_SYSTEMCONFIG = 51 "xyz.openbmc_project.Network.SystemConfiguration"; 52 constexpr auto INTF_ETHERNET = "xyz.openbmc_project.Network.EthernetInterface"; 53 constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP"; 54 constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create"; 55 constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress"; 56 constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor"; 57 constexpr auto INTF_NEIGHBOR_CREATE_STATIC = 58 "xyz.openbmc_project.Network.Neighbor.CreateStatic"; 59 constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN"; 60 constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create"; 61 62 /** @brief IPMI LAN Parameters */ 63 enum 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 */ 91 enum 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 */ 101 enum class SetStatus : uint8_t 102 { 103 Complete = 0, 104 InProgress = 1, 105 Commit = 2, 106 }; 107 108 /** @brief IPMI Family Suport Bits */ 109 namespace IPFamilySupportFlag 110 { 111 constexpr uint8_t IPv6Only = 0; 112 constexpr uint8_t DualStack = 1; 113 constexpr uint8_t IPv6Alerts = 2; 114 } // namespace IPFamilySupportFlag 115 116 /** @brief IPMI IPFamily Enables Flag */ 117 enum class IPFamilyEnables : uint8_t 118 { 119 IPv4Only = 0, 120 IPv6Only = 1, 121 DualStack = 2, 122 }; 123 124 /** @brief IPMI IPv6 Dyanmic Status Bits */ 125 namespace IPv6StatusFlag 126 { 127 constexpr uint8_t DHCP = 0; 128 constexpr uint8_t SLAAC = 1; 129 }; // namespace IPv6StatusFlag 130 131 /** @brief IPMI IPv6 Source */ 132 enum class IPv6Source : uint8_t 133 { 134 Static = 0, 135 SLAAC = 1, 136 DHCP = 2, 137 }; 138 139 /** @brief IPMI IPv6 Address Status */ 140 enum class IPv6AddressStatus : uint8_t 141 { 142 Active = 0, 143 Disabled = 1, 144 }; 145 146 namespace IPv6RouterControlFlag 147 { 148 constexpr uint8_t Static = 0; 149 constexpr uint8_t Dynamic = 1; 150 }; // namespace IPv6RouterControlFlag 151 152 // LAN Handler specific response codes 153 constexpr Cc ccParamNotSupported = 0x80; 154 constexpr Cc ccParamSetLocked = 0x81; 155 constexpr Cc ccParamReadOnly = 0x82; 156 157 // VLANs are a 12-bit value 158 constexpr uint16_t VLAN_VALUE_MASK = 0x0fff; 159 constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000; 160 161 // Arbitrary v6 Address Limits to prevent too much output in ipmitool 162 constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15; 163 constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15; 164 165 /** @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 */ 169 struct 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 */ 189 template <typename T> 190 bool 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 */ 201 template <size_t N, typename T> 202 void 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 */ 214 template <typename T> 215 std::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 */ 229 std::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 */ 239 ChannelParams 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 */ 247 template <auto func, typename... Args> 248 auto 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 */ 256 template <int family> 257 struct AddrFamily 258 { 259 }; 260 261 /** @brief Parameter specialization for IPv4 */ 262 template <> 263 struct 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 */ 274 template <> 275 struct 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 */ 286 template <int family> 287 struct 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 */ 295 template <int family> 296 struct 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 */ 305 static 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}; 309 static 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 */ 322 class 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 */ 409 template <int family> 410 std::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 */ 427 template <int family> 428 typename 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 */ 448 template <int family> 449 std::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 */ 462 ether_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 */ 474 template <int family> 475 std::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 */ 526 template <int family> 527 auto 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 */ 543 sdbusplus::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 */ 554 void 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 */ 567 void 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 */ 577 template <int family> 578 std::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 591 template <int family> 592 std::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 629 template <int family> 630 void 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 */ 649 void 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 */ 658 template <int family> 659 void 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 683 } // namespace transport 684 } // namespace ipmi 685