transporthandler: Static IPv6 Support

This change adds basic IPv6 support, with a focus on being able to
configure static addresses and routers. The support for dynamic
configuration is unfortunately tied to IPv4 at the dbus network
interface level, so we can't decouple those settings here.

Change-Id: I72842a374c40a1537437a597020ea898961d67d7
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/transporthandler.cpp b/transporthandler.cpp
index 61065ad..1a06ae5 100644
--- a/transporthandler.cpp
+++ b/transporthandler.cpp
@@ -89,6 +89,10 @@
 constexpr uint16_t VLAN_VALUE_MASK = 0x0fff;
 constexpr uint16_t VLAN_ENABLE_FLAG = 0x8000;
 
+// Arbitrary v6 Address Limits to prevent too much output in ipmitool
+constexpr uint8_t MAX_IPV6_STATIC_ADDRESSES = 15;
+constexpr uint8_t MAX_IPV6_DYNAMIC_ADDRESSES = 15;
+
 // D-Bus Network Daemon definitions
 constexpr auto PATH_ROOT = "/xyz/openbmc_project/network";
 constexpr auto PATH_SYSTEMCONFIG = "/xyz/openbmc_project/network/config";
@@ -122,12 +126,31 @@
     static constexpr char propertyGateway[] = "DefaultGateway";
 };
 
+/** @brief Parameter specialization for IPv6 */
+template <>
+struct AddrFamily<AF_INET6>
+{
+    using addr = in6_addr;
+    static constexpr auto protocol = IP::Protocol::IPv6;
+    static constexpr size_t maxStrLen = INET6_ADDRSTRLEN;
+    static constexpr uint8_t defaultPrefix = 128;
+    static constexpr char propertyGateway[] = "DefaultGateway6";
+};
+
 /** @brief Valid address origins for IPv4 */
 const std::unordered_set<IP::AddressOrigin> originsV4 = {
     IP::AddressOrigin::Static,
     IP::AddressOrigin::DHCP,
 };
 
+/** @brief Valid address origins for IPv6 */
+const std::unordered_set<IP::AddressOrigin> originsV6Static = {
+    IP::AddressOrigin::Static};
+const std::unordered_set<IP::AddressOrigin> originsV6Dynamic = {
+    IP::AddressOrigin::DHCP,
+    IP::AddressOrigin::SLAAC,
+};
+
 /** @brief Interface IP Address configuration parameters */
 template <int family>
 struct IfAddr
@@ -162,6 +185,16 @@
     VLANId = 20,
     CiphersuiteSupport = 22,
     CiphersuiteEntries = 23,
+    IPFamilySupport = 50,
+    IPFamilyEnables = 51,
+    IPv6Status = 55,
+    IPv6StaticAddresses = 56,
+    IPv6DynamicAddresses = 59,
+    IPv6RouterControl = 64,
+    IPv6StaticRouter1IP = 65,
+    IPv6StaticRouter1MAC = 66,
+    IPv6StaticRouter1PrefixLength = 67,
+    IPv6StaticRouter1PrefixValue = 68,
 };
 
 static constexpr uint8_t oemCmdStart = 192;
@@ -185,6 +218,50 @@
     Commit = 2,
 };
 
+/** @brief IPMI Family Suport Bits */
+namespace IPFamilySupportFlag
+{
+constexpr uint8_t IPv6Only = 0;
+constexpr uint8_t DualStack = 1;
+constexpr uint8_t IPv6Alerts = 2;
+} // namespace IPFamilySupportFlag
+
+/** @brief IPMI IPFamily Enables Flag */
+enum class IPFamilyEnables : uint8_t
+{
+    IPv4Only = 0,
+    IPv6Only = 1,
+    DualStack = 2,
+};
+
+/** @brief IPMI IPv6 Dyanmic Status Bits */
+namespace IPv6StatusFlag
+{
+constexpr uint8_t DHCP = 0;
+constexpr uint8_t SLAAC = 1;
+}; // namespace IPv6StatusFlag
+
+/** @brief IPMI IPv6 Source */
+enum class IPv6Source : uint8_t
+{
+    Static = 0,
+    SLAAC = 1,
+    DHCP = 2,
+};
+
+/** @brief IPMI IPv6 Address Status */
+enum class IPv6AddressStatus : uint8_t
+{
+    Active = 0,
+    Disabled = 1,
+};
+
+namespace IPv6RouterControlFlag
+{
+constexpr uint8_t Static = 0;
+constexpr uint8_t Dynamic = 1;
+}; // namespace IPv6RouterControlFlag
+
 /** @brief A trivial helper used to determine if two PODs are equal
  *
  *  @params[in] a - The first object to compare
@@ -888,6 +965,97 @@
     createNeighbor<family>(bus, params, *gateway, mac);
 }
 
+/** @brief Deconfigures the IPv6 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The address index to operate on
+ */
+void deconfigureIfAddr6(sdbusplus::bus::bus& bus, const ChannelParams& params,
+                        uint8_t idx)
+{
+    auto ifaddr = getIfAddr<AF_INET6>(bus, params, idx, originsV6Static);
+    if (ifaddr)
+    {
+        deleteObjectIfExists(bus, params.service, ifaddr->path);
+    }
+}
+
+/** @brief Reconfigures the IPv6 address info configured for the interface
+ *
+ *  @param[in] bus     - The bus object used for lookups
+ *  @param[in] params  - The parameters for the channel
+ *  @param[in] idx     - The address index to operate on
+ *  @param[in] address - The new address
+ *  @param[in] prefix  - The new address prefix
+ */
+void reconfigureIfAddr6(sdbusplus::bus::bus& bus, const ChannelParams& params,
+                        uint8_t idx, const in6_addr& address, uint8_t prefix)
+{
+    deconfigureIfAddr6(bus, params, idx);
+    createIfAddr<AF_INET6>(bus, params, address, prefix);
+}
+
+/** @brief Converts the AddressOrigin into an IPv6Source
+ *
+ *  @param[in] origin - The DBus Address Origin to convert
+ *  @return The IPv6Source version of the origin
+ */
+IPv6Source originToSourceType(IP::AddressOrigin origin)
+{
+    switch (origin)
+    {
+        case IP::AddressOrigin::Static:
+            return IPv6Source::Static;
+        case IP::AddressOrigin::DHCP:
+            return IPv6Source::DHCP;
+        case IP::AddressOrigin::SLAAC:
+            return IPv6Source::SLAAC;
+        default:
+        {
+            auto originStr = sdbusplus::xyz::openbmc_project::Network::server::
+                convertForMessage(origin);
+            log<level::ERR>(
+                "Invalid IP::AddressOrigin conversion to IPv6Source",
+                entry("ORIGIN=%s", originStr.c_str()));
+            elog<InternalFailure>();
+        }
+    }
+}
+
+/** @brief Packs the IPMI message response with IPv6 address data
+ *
+ *  @param[out] ret     - The IPMI response payload to be packed
+ *  @param[in]  channel - The channel id corresponding to an ethernet interface
+ *  @param[in]  set     - The set selector for determining address index
+ *  @param[in]  origins - Set of valid origins for address filtering
+ */
+void getLanIPv6Address(message::Payload& ret, uint8_t channel, uint8_t set,
+                       const std::unordered_set<IP::AddressOrigin>& origins)
+{
+    auto source = IPv6Source::Static;
+    bool enabled = false;
+    in6_addr addr{};
+    uint8_t prefix = AddrFamily<AF_INET6>::defaultPrefix;
+    auto status = IPv6AddressStatus::Disabled;
+
+    auto ifaddr = channelCall<getIfAddr<AF_INET6>>(channel, set, origins);
+    if (ifaddr)
+    {
+        source = originToSourceType(ifaddr->origin);
+        enabled = true;
+        addr = ifaddr->address;
+        prefix = ifaddr->prefix;
+        status = IPv6AddressStatus::Active;
+    }
+
+    ret.pack(set);
+    ret.pack(static_cast<uint4_t>(source), uint3_t{}, enabled);
+    ret.pack(std::string_view(reinterpret_cast<char*>(&addr), sizeof(addr)));
+    ret.pack(prefix);
+    ret.pack(static_cast<uint8_t>(status));
+}
+
 /** @brief Gets the vlan ID configured on the interface
  *
  *  @param[in] bus    - The bus object used for lookups
@@ -987,9 +1155,21 @@
     // Save info from the old logical interface
     ObjectLookupCache ips(bus, params, INTF_IP);
     auto ifaddr4 = findIfAddr<AF_INET>(bus, params, 0, originsV4, ips);
+    std::vector<IfAddr<AF_INET6>> ifaddrs6;
+    for (uint8_t i = 0; i < MAX_IPV6_STATIC_ADDRESSES; ++i)
+    {
+        auto ifaddr6 =
+            findIfAddr<AF_INET6>(bus, params, i, originsV6Static, ips);
+        if (!ifaddr6)
+        {
+            break;
+        }
+        ifaddrs6.push_back(std::move(*ifaddr6));
+    }
     auto dhcp = getDHCPProperty(bus, params);
     ObjectLookupCache neighbors(bus, params, INTF_NEIGHBOR);
     auto neighbor4 = findGatewayNeighbor<AF_INET>(bus, params, neighbors);
+    auto neighbor6 = findGatewayNeighbor<AF_INET6>(bus, params, neighbors);
 
     deconfigureChannel(bus, params);
     createVLAN(bus, params, vlan);
@@ -1000,10 +1180,18 @@
     {
         createIfAddr<AF_INET>(bus, params, ifaddr4->address, ifaddr4->prefix);
     }
+    for (const auto& ifaddr6 : ifaddrs6)
+    {
+        createIfAddr<AF_INET6>(bus, params, ifaddr6.address, ifaddr6.prefix);
+    }
     if (neighbor4)
     {
         createNeighbor<AF_INET>(bus, params, neighbor4->ip, neighbor4->mac);
     }
+    if (neighbor6)
+    {
+        createNeighbor<AF_INET6>(bus, params, neighbor6->ip, neighbor6->mac);
+    }
 }
 
 /** @brief Turns a prefix into a netmask
@@ -1287,10 +1475,132 @@
         }
         case LanParam::CiphersuiteSupport:
         case LanParam::CiphersuiteEntries:
+        case LanParam::IPFamilySupport:
         {
             req.trailingOk = true;
             return response(ccParamReadOnly);
         }
+        case LanParam::IPFamilyEnables:
+        {
+            uint8_t enables;
+            if (req.unpack(enables) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            switch (static_cast<IPFamilyEnables>(enables))
+            {
+                case IPFamilyEnables::DualStack:
+                    return responseSuccess();
+                case IPFamilyEnables::IPv4Only:
+                case IPFamilyEnables::IPv6Only:
+                    return response(ccParamNotSupported);
+            }
+            return response(ccParamNotSupported);
+        }
+        case LanParam::IPv6Status:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IPv6StaticAddresses:
+        {
+            uint8_t set;
+            uint7_t rsvd;
+            bool enabled;
+            in6_addr ip;
+            std::array<uint8_t, sizeof(ip)> ipbytes;
+            uint8_t prefix;
+            uint8_t status;
+            if (req.unpack(set, rsvd, enabled, ipbytes, prefix, status) != 0 ||
+                !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            copyInto(ip, ipbytes);
+            if (enabled)
+            {
+                channelCall<reconfigureIfAddr6>(channel, set, ip, prefix);
+            }
+            else
+            {
+                channelCall<deconfigureIfAddr6>(channel, set);
+            }
+            return responseSuccess();
+        }
+        case LanParam::IPv6DynamicAddresses:
+        {
+            req.trailingOk = true;
+            return response(ccParamReadOnly);
+        }
+        case LanParam::IPv6RouterControl:
+        {
+            std::bitset<8> control;
+            if (req.unpack(control) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            std::bitset<8> expected;
+            if (channelCall<getDHCPProperty>(channel))
+            {
+                expected[IPv6RouterControlFlag::Dynamic] = 1;
+            }
+            else
+            {
+                expected[IPv6RouterControlFlag::Static] = 1;
+            }
+            if (expected != control)
+            {
+                return responseInvalidFieldRequest();
+            }
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1IP:
+        {
+            in6_addr gateway;
+            std::array<uint8_t, sizeof(gateway)> bytes;
+            if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            copyInto(gateway, bytes);
+            channelCall<setGatewayProperty<AF_INET6>>(channel, gateway);
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1MAC:
+        {
+            ether_addr mac;
+            std::array<uint8_t, sizeof(mac)> bytes;
+            if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            copyInto(mac, bytes);
+            channelCall<reconfigureGatewayMAC<AF_INET6>>(channel, mac);
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1PrefixLength:
+        {
+            uint8_t prefix;
+            if (req.unpack(prefix) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            if (prefix != 0)
+            {
+                return responseInvalidFieldRequest();
+            }
+            return responseSuccess();
+        }
+        case LanParam::IPv6StaticRouter1PrefixValue:
+        {
+            std::array<uint8_t, sizeof(in6_addr)> bytes;
+            if (req.unpack(bytes) != 0 || !req.fullyUnpacked())
+            {
+                return responseReqDataLenInvalid();
+            }
+            // Accept any prefix value since our prefix length has to be 0
+            return responseSuccess();
+        }
     }
 
     if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd))
@@ -1456,6 +1766,96 @@
             ret.pack(cipherList);
             return responseSuccess(std::move(ret));
         }
+        case LanParam::IPFamilySupport:
+        {
+            std::bitset<8> support;
+            support[IPFamilySupportFlag::IPv6Only] = 0;
+            support[IPFamilySupportFlag::DualStack] = 1;
+            support[IPFamilySupportFlag::IPv6Alerts] = 1;
+            ret.pack(support);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPFamilyEnables:
+        {
+            ret.pack(static_cast<uint8_t>(IPFamilyEnables::DualStack));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6Status:
+        {
+            ret.pack(MAX_IPV6_STATIC_ADDRESSES);
+            ret.pack(MAX_IPV6_DYNAMIC_ADDRESSES);
+            std::bitset<8> support;
+            support[IPv6StatusFlag::DHCP] = 1;
+            support[IPv6StatusFlag::SLAAC] = 1;
+            ret.pack(support);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticAddresses:
+        {
+            if (set >= MAX_IPV6_STATIC_ADDRESSES)
+            {
+                return responseParmOutOfRange();
+            }
+            getLanIPv6Address(ret, channel, set, originsV6Static);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6DynamicAddresses:
+        {
+            if (set >= MAX_IPV6_DYNAMIC_ADDRESSES)
+            {
+                return responseParmOutOfRange();
+            }
+            getLanIPv6Address(ret, channel, set, originsV6Dynamic);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6RouterControl:
+        {
+            std::bitset<8> control;
+            if (channelCall<getDHCPProperty>(channel))
+            {
+                control[IPv6RouterControlFlag::Dynamic] = 1;
+            }
+            else
+            {
+                control[IPv6RouterControlFlag::Static] = 1;
+            }
+            ret.pack(control);
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1IP:
+        {
+            in6_addr gateway{};
+            if (!channelCall<getDHCPProperty>(channel))
+            {
+                gateway =
+                    channelCall<getGatewayProperty<AF_INET6>>(channel).value_or(
+                        in6_addr{});
+            }
+            ret.pack(dataRef(gateway));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1MAC:
+        {
+            ether_addr mac{};
+            auto neighbor = channelCall<getGatewayNeighbor<AF_INET6>>(channel);
+            if (neighbor)
+            {
+                mac = neighbor->mac;
+            }
+            ret.pack(dataRef(mac));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1PrefixLength:
+        {
+            ret.pack(UINT8_C(0));
+            return responseSuccess(std::move(ret));
+        }
+        case LanParam::IPv6StaticRouter1PrefixValue:
+        {
+            in6_addr prefix{};
+            ret.pack(dataRef(prefix));
+            return responseSuccess(std::move(ret));
+        }
     }
 
     if ((parameter >= oemCmdStart) && (parameter <= oemCmdEnd))