transporthandler: Support Gateway MAC

Adds support for setting the MAC address of the gateway. Most of the
interesting code in this change is around saving / restoring the gateway
MAC address when the gateway or interface change.

Change-Id: I85b7c665c44af4f030f51456be355f3eb11ab2fc
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/transporthandler.cpp b/transporthandler.cpp
index b48cbd0..777171c 100644
--- a/transporthandler.cpp
+++ b/transporthandler.cpp
@@ -31,6 +31,7 @@
 #include <vector>
 #include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/Network/IP/server.hpp>
+#include <xyz/openbmc_project/Network/Neighbor/server.hpp>
 
 using phosphor::logging::commit;
 using phosphor::logging::elog;
@@ -39,6 +40,7 @@
 using phosphor::logging::log;
 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 using sdbusplus::xyz::openbmc_project::Network::server::IP;
+using sdbusplus::xyz::openbmc_project::Network::server::Neighbor;
 
 namespace cipher
 {
@@ -97,6 +99,9 @@
 constexpr auto INTF_IP = "xyz.openbmc_project.Network.IP";
 constexpr auto INTF_IP_CREATE = "xyz.openbmc_project.Network.IP.Create";
 constexpr auto INTF_MAC = "xyz.openbmc_project.Network.MACAddress";
+constexpr auto INTF_NEIGHBOR = "xyz.openbmc_project.Network.Neighbor";
+constexpr auto INTF_NEIGHBOR_CREATE_STATIC =
+    "xyz.openbmc_project.Network.Neighbor.CreateStatic";
 constexpr auto INTF_VLAN = "xyz.openbmc_project.Network.VLAN";
 constexpr auto INTF_VLAN_CREATE = "xyz.openbmc_project.Network.VLAN.Create";
 
@@ -133,6 +138,15 @@
     uint8_t prefix;
 };
 
+/** @brief Interface Neighbor configuration parameters */
+template <int family>
+struct IfNeigh
+{
+    std::string path;
+    typename AddrFamily<family>::addr ip;
+    ether_addr mac;
+};
+
 /** @brief IPMI LAN Parameters */
 enum class LanParam : uint8_t
 {
@@ -144,6 +158,7 @@
     MAC = 5,
     SubnetMask = 6,
     Gateway1 = 12,
+    Gateway1MAC = 13,
     VLANId = 20,
     CiphersuiteSupport = 22,
     CiphersuiteEntries = 23,
@@ -167,6 +182,19 @@
     Commit = 2,
 };
 
+/** @brief A trivial helper used to determine if two PODs are equal
+ *
+ *  @params[in] a - The first object to compare
+ *  @params[in] b - The second object to compare
+ *  @return True if the objects are the same bytewise
+ */
+template <typename T>
+bool equal(const T& a, const T& b)
+{
+    static_assert(std::is_trivially_copyable_v<T>);
+    return std::memcmp(&a, &b, sizeof(T)) == 0;
+}
+
 /** @brief Copies bytes from an array into a trivially copyable container
  *
  *  @params[out] t     - The container receiving the data
@@ -485,21 +513,6 @@
     return stringToAddr<family>(gatewayStr.c_str());
 }
 
-/** @brief Sets the system wide value for the default gateway
- *
- *  @param[in] bus     - The bus object used for lookups
- *  @param[in] params  - The parameters for the channel
- *  @param[in] gateway - Gateway address to apply
- */
-template <int family>
-void setGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
-                        const typename AddrFamily<family>::addr& address)
-{
-    setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
-                    AddrFamily<family>::propertyGateway,
-                    addrToString<family>(address));
-}
-
 /** @brief A lazy lookup mechanism for iterating over object properties stored
  *         in DBus. This will only perform the object lookup when needed, and
  *         retains a cache of previous lookups to speed up future iterations.
@@ -747,6 +760,131 @@
                           prefix.value_or(fallbackPrefix));
 }
 
+template <int family>
+std::optional<IfNeigh<family>>
+    findStaticNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params,
+                       const typename AddrFamily<family>::addr& ip,
+                       ObjectLookupCache& neighbors)
+{
+    const auto state =
+        sdbusplus::xyz::openbmc_project::Network::server::convertForMessage(
+            Neighbor::State::Permanent);
+    for (const auto& [path, neighbor] : neighbors)
+    {
+        const auto& ipStr = std::get<std::string>(neighbor.at("IPAddress"));
+        auto neighIP = maybeStringToAddr<family>(ipStr.c_str());
+        if (!neighIP)
+        {
+            continue;
+        }
+        if (!equal(*neighIP, ip))
+        {
+            continue;
+        }
+        if (state != std::get<std::string>(neighbor.at("State")))
+        {
+            continue;
+        }
+
+        IfNeigh<family> ret;
+        ret.path = path;
+        ret.ip = ip;
+        const auto& macStr = std::get<std::string>(neighbor.at("MACAddress"));
+        ret.mac = stringToMAC(macStr.c_str());
+        return std::move(ret);
+    }
+
+    return std::nullopt;
+}
+
+template <int family>
+void createNeighbor(sdbusplus::bus::bus& bus, const ChannelParams& params,
+                    const typename AddrFamily<family>::addr& address,
+                    const ether_addr& mac)
+{
+    auto newreq =
+        bus.new_method_call(params.service.c_str(), params.logicalPath.c_str(),
+                            INTF_NEIGHBOR_CREATE_STATIC, "Neighbor");
+    std::string macStr = ether_ntoa(&mac);
+    newreq.append(addrToString<family>(address), macStr);
+    bus.call_noreply(newreq);
+}
+
+/** @brief Sets the system wide value for the default gateway
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] gateway - Gateway address to apply
+ */
+template <int family>
+void setGatewayProperty(sdbusplus::bus::bus& bus, const ChannelParams& params,
+                        const typename AddrFamily<family>::addr& address)
+{
+    // Save the old gateway MAC address if it exists so we can recreate it
+    auto gateway = getGatewayProperty<family>(bus, params);
+    std::optional<IfNeigh<family>> neighbor;
+    if (gateway)
+    {
+        ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+        neighbor = findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+    }
+
+    setDbusProperty(bus, params.service, PATH_SYSTEMCONFIG, INTF_SYSTEMCONFIG,
+                    AddrFamily<family>::propertyGateway,
+                    addrToString<family>(address));
+
+    // Restore the gateway MAC if we had one
+    if (neighbor)
+    {
+        deleteObjectIfExists(bus, params.service, neighbor->path);
+        createNeighbor<family>(bus, params, address, neighbor->mac);
+    }
+}
+
+template <int family>
+std::optional<IfNeigh<family>> findGatewayNeighbor(sdbusplus::bus::bus& bus,
+                                                   const ChannelParams& params,
+                                                   ObjectLookupCache& neighbors)
+{
+    auto gateway = getGatewayProperty<family>(bus, params);
+    if (!gateway)
+    {
+        return std::nullopt;
+    }
+
+    return findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+}
+
+template <int family>
+std::optional<IfNeigh<family>> getGatewayNeighbor(sdbusplus::bus::bus& bus,
+                                                  const ChannelParams& params)
+{
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    return findGatewayNeighbor<family>(bus, params, neighbors);
+}
+
+template <int family>
+void reconfigureGatewayMAC(sdbusplus::bus::bus& bus,
+                           const ChannelParams& params, const ether_addr& mac)
+{
+    auto gateway = getGatewayProperty<family>(bus, params);
+    if (!gateway)
+    {
+        log<level::ERR>("Tried to set Gateway MAC without Gateway");
+        elog<InternalFailure>();
+    }
+
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    auto neighbor =
+        findStaticNeighbor<family>(bus, params, *gateway, neighbors);
+    if (neighbor)
+    {
+        deleteObjectIfExists(bus, params.service, neighbor->path);
+    }
+
+    createNeighbor<family>(bus, params, *gateway, mac);
+}
+
 /** @brief Gets the vlan ID configured on the interface
  *
  *  @param[in] bus    - The bus object used for lookups
@@ -847,6 +985,8 @@
     ObjectLookupCache ips(bus, params, INTF_IP);
     auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips);
     auto dhcp = getDHCPProperty(bus, params);
+    ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
+    auto neighbor4 = findGatewayNeighbor<AF_INET>(bus, params, neighbors);
 
     deconfigureChannel(bus, params);
     createVLAN(bus, params, vlan);
@@ -857,6 +997,10 @@
     {
         createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix);
     }
+    if (neighbor4)
+    {
+        createNeighbor<AF_INET>(bus, params, neighbor4->ip, neighbor4->mac);
+    }
 }
 
 /** @brief Turns a prefix into a netmask
@@ -1055,6 +1199,18 @@
             channelCall<setGatewayProperty<AF_INET>>(channel, gateway);
             return responseSuccess();
         }
+        case LanParam::Gateway1MAC:
+        {
+            ether_addr gatewayMAC;
+            std::array<uint8_t, sizeof(gatewayMAC)> bytes;
+            if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            copyInto(gatewayMAC, bytes);
+            channelCall<reconfigureGatewayMAC<AF_INET>>(channel, gatewayMAC);
+            return responseSuccess();
+        }
         case LanParam::VLANId:
         {
             uint16_t vlanData;
@@ -1193,6 +1349,17 @@
             ret.pack(dataRef(gateway));
             return responseSuccess(std::move(ret));
         }
+        case LanParam::Gateway1MAC:
+        {
+            ether_addr mac{};
+            auto neighbor = channelCall<getGatewayNeighbor<AF_INET>>(channel);
+            if (neighbor)
+            {
+                mac = neighbor->mac;
+            }
+            ret.pack(dataRef(mac));
+            return responseSuccess(std::move(ret));
+        }
         case LanParam::VLANId:
         {
             uint16_t vlan = channelCall<getVLANProperty>(channel);