Enhance DHCP beyond just OFF and IPv4/IPv6 enabled.

DHCP is not a binary option.  The network interface can have DHCP
disabled, IPv4 only, IPv6 only, and IPv4/IPv6.

Tested:
Using dbus-send or busctl:
Disabled DHCP, and confirmed only link local addresses were present.

Assigned only static addresses.  Both with/and without the gateway set
to 0.0.0.0

Deleted static IPv4 addresses.
Reassigned static addresses.

Enabled DHCP for ipv4 only, and witnessed a DHCP server assign a valid
address.

Assigned static IPv4 address.
Assigned static IPv6 address.
Confirmed both IPv4 and IPv6 static addresses are active.

Enabled DHCP for ipv6 only, and confirmed the static v4 address
remains. The ipv6 address is removed, waiting for a DHCP6 server.

Enabled DHCP for both ipv4 and ipv6. IPv4 address was assigned. IPv6
address is assumed to succeed, as systemd config file enables IPv6
DHCP.

Change-Id: I2e0ff80ac3a5e88bcff28adac419bf21e37be162
Signed-off-by: Johnathan Mantey <johnathanx.mantey@intel.com>
diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
index d7e0baa..76d7f92 100644
--- a/ethernet_interface.cpp
+++ b/ethernet_interface.cpp
@@ -3,7 +3,6 @@
 #include "ethernet_interface.hpp"
 
 #include "config_parser.hpp"
-#include "ipaddress.hpp"
 #include "neighbor.hpp"
 #include "network_manager.hpp"
 #include "vlan_interface.hpp"
@@ -67,9 +66,15 @@
     int sock{-1};
 };
 
+std::map<EthernetInterface::DHCPConf, std::string> mapDHCPToSystemd = {
+    {EthernetInterface::DHCPConf::both, "true"},
+    {EthernetInterface::DHCPConf::v4, "ipv4"},
+    {EthernetInterface::DHCPConf::v6, "ipv6"},
+    {EthernetInterface::DHCPConf::none, "false"}};
+
 EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
                                      const std::string& objPath,
-                                     bool dhcpEnabled, Manager& parent,
+                                     DHCPConf dhcpEnabled, Manager& parent,
                                      bool emitSignal) :
     Ifaces(bus, objPath.c_str(), true),
     bus(bus), manager(parent), objPath(objPath)
@@ -117,6 +122,65 @@
     throw std::invalid_argument("Bad address family");
 }
 
+void EthernetInterface::disableDHCP(IP::Protocol protocol)
+{
+    DHCPConf dhcpState = EthernetInterfaceIntf::dHCPEnabled();
+    if (dhcpState == EthernetInterface::DHCPConf::both)
+    {
+        if (protocol == IP::Protocol::IPv4)
+        {
+            dHCPEnabled(EthernetInterface::DHCPConf::v6);
+        }
+        else if (protocol == IP::Protocol::IPv6)
+        {
+            dHCPEnabled(EthernetInterface::DHCPConf::v4);
+        }
+    }
+    else if ((dhcpState == EthernetInterface::DHCPConf::v4) &&
+             (protocol == IP::Protocol::IPv4))
+    {
+        dHCPEnabled(EthernetInterface::DHCPConf::none);
+    }
+    else if ((dhcpState == EthernetInterface::DHCPConf::v6) &&
+             (protocol == IP::Protocol::IPv6))
+    {
+        dHCPEnabled(EthernetInterface::DHCPConf::none);
+    }
+}
+
+bool EthernetInterface::dhcpIsEnabled(IP::Protocol family, bool ignoreProtocol)
+{
+    return ((EthernetInterfaceIntf::dHCPEnabled() ==
+             EthernetInterface::DHCPConf::both) ||
+            ((EthernetInterfaceIntf::dHCPEnabled() ==
+              EthernetInterface::DHCPConf::v6) &&
+             ((family == IP::Protocol::IPv6) || ignoreProtocol)) ||
+            ((EthernetInterfaceIntf::dHCPEnabled() ==
+              EthernetInterface::DHCPConf::v4) &&
+             ((family == IP::Protocol::IPv4) || ignoreProtocol)));
+}
+
+bool EthernetInterface::dhcpToBeEnabled(IP::Protocol family,
+                                        const std::string& nextDHCPState)
+{
+    return ((nextDHCPState == "true") ||
+            ((nextDHCPState == "ipv6") && (family == IP::Protocol::IPv6)) ||
+            ((nextDHCPState == "ipv4") && (family == IP::Protocol::IPv4)));
+}
+
+bool EthernetInterface::originIsManuallyAssigned(IP::AddressOrigin origin)
+{
+    return (
+#ifdef LINK_LOCAL_AUTOCONFIGURATION
+        (origin == IP::AddressOrigin::Static)
+#else
+        (origin == IP::AddressOrigin::Static ||
+         origin == IP::AddressOrigin::LinkLocal)
+#endif
+
+    );
+}
+
 void EthernetInterface::createIPAddressObjects()
 {
     addrs.clear();
@@ -127,7 +191,7 @@
     {
         IP::Protocol addressType = convertFamily(addr.addrType);
         IP::AddressOrigin origin = IP::AddressOrigin::Static;
-        if (dHCPEnabled())
+        if (dhcpIsEnabled(addressType))
         {
             origin = IP::AddressOrigin::DHCP;
         }
@@ -188,11 +252,11 @@
                                  uint8_t prefixLength, std::string gateway)
 {
 
-    if (dHCPEnabled())
+    if (dhcpIsEnabled(protType))
     {
         log<level::INFO>("DHCP enabled on the interface"),
             entry("INTERFACE=%s", interfaceName().c_str());
-        dHCPEnabled(false);
+        disableDHCP(protType);
     }
 
     IP::AddressOrigin origin = IP::AddressOrigin::Static;
@@ -467,7 +531,7 @@
     return value;
 }
 
-bool EthernetInterface::dHCPEnabled(bool value)
+EthernetInterface::DHCPConf EthernetInterface::dHCPEnabled(DHCPConf value)
 {
     if (value == EthernetInterfaceIntf::dHCPEnabled())
     {
@@ -683,10 +747,11 @@
     std::string path = objPath;
     path += "_" + std::to_string(id);
 
-    auto dhcpEnabled =
+    DHCPConf dhcpEnabled =
         getDHCPValue(manager.getConfDir().string(), vlanInterfaceName);
     auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
-        bus, path.c_str(), dhcpEnabled, id, *this, manager);
+        bus, path.c_str(), dhcpEnabled, EthernetInterfaceIntf::nICEnabled(), id,
+        *this, manager);
 
     // Fetch the ip address from the system
     // and create the dbus object.
@@ -707,8 +772,8 @@
     // VLAN interface can inherit.
 
     auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
-        bus, path.c_str(), false, EthernetInterfaceIntf::nICEnabled(), id,
-        *this, manager);
+        bus, path.c_str(), EthernetInterface::DHCPConf::none,
+        EthernetInterfaceIntf::nICEnabled(), id, *this, manager);
 
     // write the device file for the vlan interface.
     vlanIntf->writeDeviceFile();
@@ -781,8 +846,6 @@
     // write all the static ip address in the systemd-network conf file
 
     using namespace std::string_literals;
-    using AddressOrigin =
-        sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin;
     namespace fs = std::filesystem;
 
     // if there is vlan interafce then write the configuration file
@@ -856,42 +919,38 @@
     }
 
     // Add the DHCP entry
-    auto value = dHCPEnabled() ? "true"s : "false"s;
-    stream << "DHCP="s + value + "\n";
+    stream << "DHCP="s +
+                  mapDHCPToSystemd[EthernetInterfaceIntf::dHCPEnabled()] + "\n";
 
-    // When the interface configured as dhcp, we don't need below given entries
-    // in config file.
-    if (dHCPEnabled() == false)
+    // Static IP addresses
+    for (const auto& addr : addrs)
     {
-        // Static
-        for (const auto& addr : addrs)
+        if (originIsManuallyAssigned(addr.second->origin()) &&
+            !dhcpIsEnabled(addr.second->type()))
         {
-            if (addr.second->origin() == AddressOrigin::Static
-#ifndef LINK_LOCAL_AUTOCONFIGURATION
-                || addr.second->origin() == AddressOrigin::LinkLocal
-#endif
-            )
-            {
-                std::string address =
-                    addr.second->address() + "/" +
-                    std::to_string(addr.second->prefixLength());
+            // Process all static addresses
+            std::string address = addr.second->address() + "/" +
+                                  std::to_string(addr.second->prefixLength());
 
-                stream << "Address=" << address << "\n";
-            }
+            // build the address entries. Do not use [Network] shortcuts to
+            // insert address entries.
+            stream << "[Address]\n";
+            stream << "Address=" << address << "\n";
         }
+    }
 
-        if (manager.getSystemConf())
+    if (manager.getSystemConf())
+    {
+        stream << "[Route]\n";
+        const auto& gateway = manager.getSystemConf()->defaultGateway();
+        if (!gateway.empty())
         {
-            const auto& gateway = manager.getSystemConf()->defaultGateway();
-            if (!gateway.empty())
-            {
-                stream << "Gateway=" << gateway << "\n";
-            }
-            const auto& gateway6 = manager.getSystemConf()->defaultGateway6();
-            if (!gateway6.empty())
-            {
-                stream << "Gateway=" << gateway6 << "\n";
-            }
+            stream << "Gateway=" << gateway << "\n";
+        }
+        const auto& gateway6 = manager.getSystemConf()->defaultGateway6();
+        if (!gateway6.empty())
+        {
+            stream << "Gateway=" << gateway6 << "\n";
         }
     }
 
@@ -1006,7 +1065,7 @@
 
 void EthernetInterface::deleteAll()
 {
-    if (EthernetInterfaceIntf::dHCPEnabled())
+    if (dhcpIsEnabled(IP::Protocol::IPv4, true))
     {
         log<level::INFO>("DHCP enabled on the interface"),
             entry("INTERFACE=%s", interfaceName().c_str());
diff --git a/ethernet_interface.hpp b/ethernet_interface.hpp
index 7192e26..abaf43e 100644
--- a/ethernet_interface.hpp
+++ b/ethernet_interface.hpp
@@ -85,7 +85,7 @@
      *                          send.
      */
     EthernetInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
-                      bool dhcpEnabled, Manager& parent,
+                      DHCPConf dhcpEnabled, Manager& parent,
                       bool emitSignal = true);
 
     /** @brief Function used to load the nameservers.
@@ -155,7 +155,13 @@
     }
 
     /** Set value of DHCPEnabled */
-    bool dHCPEnabled(bool value) override;
+    DHCPConf dHCPEnabled(DHCPConf value) override;
+
+    /** @brief Selectively disables DHCP
+     *  @param[in] protocol - The IPv4 or IPv6 protocol to return to static
+     *                        addressing mode
+     */
+    void disableDHCP(IP::Protocol protocol);
 
     /** Retrieve Link State */
     bool linkUp() const override;
@@ -316,6 +322,28 @@
     std::string objPath;
 
     friend class TestEthernetInterface;
+
+  private:
+    /** @brief Determines if DHCP is active for the IP::Protocol supplied.
+     *  @param[in] protocol - Either IPv4 or IPv6
+     *  @param[in] ignoreProtocol - Allows IPv4 and IPv6 to be checked using a
+     *                              single call.
+     *  @returns true/false value if DHCP is active for the input protocol
+     */
+    bool dhcpIsEnabled(IP::Protocol protocol, bool ignoreProtocol = false);
+
+    /** @brief Determines if DHCP will be active following next reconfig
+     *  @param[in] protocol - Either IPv4 or IPv6
+     *  @param[in] nextDHCPState - The new DHCP mode to take affect
+     *  @returns true/false value if DHCP is active for the input protocol
+     */
+    bool dhcpToBeEnabled(IP::Protocol family, const std::string& nextDHCPState);
+
+    /** @brief Determines if the address is manually assigned
+     *  @param[in] origin - The origin entry of the IP::Address
+     *  @returns true/false value if the address is static
+     */
+    bool originIsManuallyAssigned(IP::AddressOrigin origin);
 };
 
 } // namespace network
diff --git a/test/mock_ethernet_interface.hpp b/test/mock_ethernet_interface.hpp
index c7c4022..fc1e259 100644
--- a/test/mock_ethernet_interface.hpp
+++ b/test/mock_ethernet_interface.hpp
@@ -13,7 +13,8 @@
 {
   public:
     MockEthernetInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
-                          bool dhcpEnabled, Manager& parent, bool emitSignal) :
+                          DHCPConf dhcpEnabled, Manager& parent,
+                          bool emitSignal) :
         EthernetInterface(bus, objPath, dhcpEnabled, parent, emitSignal)
     {
     }
diff --git a/test/test_ethernet_interface.cpp b/test/test_ethernet_interface.cpp
index d0beef7..ab2279f 100644
--- a/test/test_ethernet_interface.cpp
+++ b/test/test_ethernet_interface.cpp
@@ -59,7 +59,8 @@
     {
         mock_clear();
         mock_addIF("test0", 1, mac);
-        return {bus, "/xyz/openbmc_test/network/test0", false, manager, true};
+        return {bus, "/xyz/openbmc_test/network/test0",
+                EthernetInterface::DHCPConf::none, manager, true};
     }
 
     int countIPObjects()
diff --git a/test/test_vlan_interface.cpp b/test/test_vlan_interface.cpp
index 76c0828..85f4f40 100644
--- a/test/test_vlan_interface.cpp
+++ b/test/test_vlan_interface.cpp
@@ -50,7 +50,8 @@
     {
         mock_clear();
         mock_addIF("test0", 1);
-        return {bus, "/xyz/openbmc_test/network/test0", false, manager};
+        return {bus, "/xyz/openbmc_test/network/test0",
+                EthernetInterface::DHCPConf::none, manager};
     }
 
     void setConfDir()
diff --git a/types.hpp b/types.hpp
index 123067a..3279256 100644
--- a/types.hpp
+++ b/types.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "ipaddress.hpp"
+
 #include <ifaddrs.h>
 #include <netinet/in.h>
 #include <systemd/sd-event.h>
diff --git a/util.cpp b/util.cpp
index 63b8f7e..f8a1e34 100644
--- a/util.cpp
+++ b/util.cpp
@@ -412,9 +412,11 @@
     return "eth" + std::to_string(idx) + "addr";
 }
 
-bool getDHCPValue(const std::string& confDir, const std::string& intf)
+EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
+                                             const std::string& intf)
 {
-    bool dhcp = false;
+    EthernetInterfaceIntf::DHCPConf dhcp =
+        EthernetInterfaceIntf::DHCPConf::none;
     // Get the interface mode value from systemd conf
     // using namespace std::string_literals;
     fs::path confPath = confDir;
@@ -436,7 +438,15 @@
     // There will be only single value for DHCP key.
     if (values[0] == "true")
     {
-        dhcp = true;
+        dhcp = EthernetInterfaceIntf::DHCPConf::both;
+    }
+    else if (values[0] == "ipv4")
+    {
+        dhcp = EthernetInterfaceIntf::DHCPConf::v4;
+    }
+    else if (values[0] == "ipv6")
+    {
+        dhcp = EthernetInterfaceIntf::DHCPConf::v6;
     }
     return dhcp;
 }
diff --git a/util.hpp b/util.hpp
index f591888..3a5e0bc 100644
--- a/util.hpp
+++ b/util.hpp
@@ -13,12 +13,16 @@
 #include <sdbusplus/bus.hpp>
 #include <string>
 #include <string_view>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
 
 namespace phosphor
 {
 namespace network
 {
 
+using EthernetInterfaceIntf =
+    sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface;
+
 constexpr auto IPV4_MIN_PREFIX_LENGTH = 1;
 constexpr auto IPV4_MAX_PREFIX_LENGTH = 32;
 constexpr auto IPV6_MAX_PREFIX_LENGTH = 64;
@@ -156,7 +160,8 @@
  *  @param[in] confDir - Network configuration directory.
  *  @param[in] intf - Interface name.
  */
-bool getDHCPValue(const std::string& confDir, const std::string& intf);
+EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
+                                             const std::string& intf);
 
 namespace internal
 {
diff --git a/vlan_interface.cpp b/vlan_interface.cpp
index 6fa3b84..7762adc 100644
--- a/vlan_interface.cpp
+++ b/vlan_interface.cpp
@@ -22,7 +22,7 @@
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
 
 VlanInterface::VlanInterface(sdbusplus::bus::bus& bus,
-                             const std::string& objPath, bool dhcpEnabled,
+                             const std::string& objPath, DHCPConf dhcpEnabled,
                              bool nICEnabled, uint32_t vlanID,
                              EthernetInterface& intf, Manager& parent) :
     VlanIface(bus, objPath.c_str()),
@@ -38,22 +38,6 @@
     emit_object_added();
 }
 
-VlanInterface::VlanInterface(sdbusplus::bus::bus& bus,
-                             const std::string& objPath, bool dhcpEnabled,
-                             uint32_t vlanID, EthernetInterface& intf,
-                             Manager& parent) :
-    VlanIface(bus, objPath.c_str()),
-    DeleteIface(bus, objPath.c_str()),
-    EthernetInterface(bus, objPath, dhcpEnabled, parent, false),
-    parentInterface(intf)
-{
-    id(vlanID);
-    VlanIface::interfaceName(EthernetInterface::interfaceName());
-    MacAddressIntf::mACAddress(parentInterface.mACAddress());
-
-    emit_object_added();
-}
-
 std::string VlanInterface::mACAddress(std::string)
 {
     log<level::ERR>("Tried to set MAC address on VLAN");
diff --git a/vlan_interface.hpp b/vlan_interface.hpp
index 2c2f48e..a6674ea 100644
--- a/vlan_interface.hpp
+++ b/vlan_interface.hpp
@@ -44,27 +44,11 @@
      *  @param[in] intf - ethernet interface object.
      *  @param[in] manager - network manager object.
      *
-     *  This constructor is called during creation of VLAN Interface
-     *  so that the VLAN interface can inherit the nICEnabled property
-     *  from the parent interface.
-     */
-    VlanInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
-                  bool dhcpEnabled, bool nICEnabled, uint32_t vlanID,
-                  EthernetInterface& intf, Manager& manager);
-
-    /** @brief Constructor to put object onto bus at a dbus path.
-     *  @param[in] bus - Bus to attach to.
-     *  @param[in] objPath - Path to attach at.
-     *  @param[in] dhcpEnabled - DHCP enable value.
-     *  @param[in] vlanID - vlan identifier.
-     *  @param[in] intf - ethernet interface object.
-     *  @param[in] manager - network manager object.
-     *
      *  This constructor is called during loading the VLAN Interface
      */
     VlanInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
-                  bool dhcpEnabled, uint32_t vlanID, EthernetInterface& intf,
-                  Manager& parent);
+                  DHCPConf dhcpEnabled, bool nicEnabled, uint32_t vlanID,
+                  EthernetInterface& intf, Manager& parent);
 
     /** @brief Delete this d-bus object.
      */