Backend changes for Populating Nameservers(DNS & Static)

- As per the proposed design :
https://lists.ozlabs.org/pipermail/openbmc/2019-September/018399.html

Depends on :
https://gerrit.openbmc-project.xyz/#/c/openbmc/phosphor-dbus-interfaces/+/26060/

The idea of this commit is to

- Support NameServers(Read Only) property to display current
  Nameservers (both DHCP provided& Static) configured on the
  interface.

- Support StaticNameServers(Writable) property by which user
  can set the Nameservers on an interface.

Tested By:

1.Configure a DNS via DHCP Server & Make sure NameServer property
  populates accordingly.
2.With DNS from DHCP existing, set a name server using PATCH on
  StaticNameServer property & make sure both properties populate
  the data as per the proposal.
3.Make sure /etc/resolv.conf is populated with the right content
  in case of DHCP/Static and DHCP & Static (Co-existing)
4.Make sure network configuration file is updated properly when
  user sets a staic name server.

Signed-off-by: Manojkiran Eda <manojkiran.eda@gmail.com>
Change-Id: If10b9aa683d2b50e51780e91323c6d10a5ec3710
diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
index ed1e1ba..600ab83 100644
--- a/ethernet_interface.cpp
+++ b/ethernet_interface.cpp
@@ -35,7 +35,14 @@
 
 using namespace phosphor::logging;
 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
+using NotAllowedArgument = xyz::openbmc_project::Common::NotAllowed;
 using Argument = xyz::openbmc_project::Common::InvalidArgument;
+constexpr auto RESOLVED_SERVICE = "org.freedesktop.resolve1";
+constexpr auto RESOLVED_INTERFACE = "org.freedesktop.resolve1.Link";
+constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
+constexpr auto RESOLVED_SERVICE_PATH = "/org/freedesktop/resolve1/link/";
+constexpr auto METHOD_GET = "Get";
 
 struct EthernetIntfSocket
 {
@@ -78,7 +85,6 @@
         MacAddressIntf::mACAddress(getMACAddress(intfName));
     }
     EthernetInterfaceIntf::nTPServers(getNTPServersFromConf());
-    EthernetInterfaceIntf::nameservers(getNameServerFromConf());
 #if NIC_SUPPORTS_ETHTOOL
     InterfaceInfo ifInfo = EthernetInterface::getInterfaceInfo();
 
@@ -494,6 +500,12 @@
 
 ServerList EthernetInterface::nameservers(ServerList value)
 {
+    elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property"));
+    return EthernetInterfaceIntf::nameservers();
+}
+
+ServerList EthernetInterface::staticNameServers(ServerList value)
+{
     for (const auto& nameserverip : value)
     {
         if (!isValidIP(AF_INET, nameserverip) &&
@@ -502,13 +514,13 @@
             log<level::ERR>("Not a valid IP address"),
                 entry("ADDRESS=%s", nameserverip.c_str());
             elog<InvalidArgument>(
-                Argument::ARGUMENT_NAME("Nameserver"),
+                Argument::ARGUMENT_NAME("StaticNameserver"),
                 Argument::ARGUMENT_VALUE(nameserverip.c_str()));
         }
     }
     try
     {
-        EthernetInterfaceIntf::nameservers(value);
+        EthernetInterfaceIntf::staticNameServers(value);
         writeConfigurationFile();
         // resolved reads the DNS server configuration from the
         // network file.
@@ -518,10 +530,16 @@
     {
         log<level::ERR>("Exception processing DNS entries");
     }
-    return EthernetInterfaceIntf::nameservers();
+    return EthernetInterfaceIntf::staticNameServers();
 }
 
-ServerList EthernetInterface::getNameServerFromConf()
+void EthernetInterface::loadNameServers()
+{
+    EthernetInterfaceIntf::nameservers(getNameServerFromResolvd());
+    EthernetInterfaceIntf::staticNameServers(getstaticNameServerFromConf());
+}
+
+ServerList EthernetInterface::getstaticNameServerFromConf()
 {
     fs::path confPath = manager.getConfDir();
 
@@ -541,6 +559,58 @@
     return servers;
 }
 
+ServerList EthernetInterface::getNameServerFromResolvd()
+{
+    ServerList servers;
+    std::string OBJ_PATH = RESOLVED_SERVICE_PATH + std::to_string(ifIndex());
+
+    /*
+      The DNS property under org.freedesktop.resolve1.Link interface contains
+      an array containing all DNS servers currently used by resolved. It
+      contains similar information as the DNS server data written to
+      /run/systemd/resolve/resolv.conf.
+
+      Each structure in the array consists of a numeric network interface index,
+      an address family, and a byte array containing the DNS server address
+      (either 4 bytes in length for IPv4 or 16 bytes in lengths for IPv6).
+      The array contains DNS servers configured system-wide, including those
+      possibly read from a foreign /etc/resolv.conf or the DNS= setting in
+      /etc/systemd/resolved.conf, as well as per-interface DNS server
+      information either retrieved from systemd-networkd or configured by
+      external software via SetLinkDNS().
+    */
+
+    using type = std::vector<std::tuple<int32_t, std::vector<uint8_t>>>;
+    std::variant<type> name; // Variable to capture the DNS property
+    auto method = bus.new_method_call(RESOLVED_SERVICE, OBJ_PATH.c_str(),
+                                      PROPERTY_INTERFACE, METHOD_GET);
+
+    method.append(RESOLVED_INTERFACE, "DNS");
+    auto reply = bus.call(method);
+
+    try
+    {
+        reply.read(name);
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        log<level::ERR>("Failed to get DNS information from Systemd-Resolved");
+    }
+    auto tupleVector = std::get_if<type>(&name);
+    for (auto i = tupleVector->begin(); i != tupleVector->end(); ++i)
+    {
+        std::vector<uint8_t> ipaddress = std::get<1>(*i);
+        std::string address;
+        for (auto byte : ipaddress)
+        {
+            address += std::to_string(byte) + ".";
+        }
+        address.pop_back();
+        servers.push_back(address);
+    }
+    return servers;
+}
+
 void EthernetInterface::loadVLAN(VlanId id)
 {
     std::string vlanInterfaceName = interfaceName() + "." + std::to_string(id);
@@ -706,7 +776,7 @@
     }
 
     // Add the DNS entry
-    for (const auto& dns : EthernetInterfaceIntf::nameservers())
+    for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
     {
         stream << "DNS=" << dns << "\n";
     }
diff --git a/ethernet_interface.hpp b/ethernet_interface.hpp
index 68668d6..4a0f030 100644
--- a/ethernet_interface.hpp
+++ b/ethernet_interface.hpp
@@ -86,6 +86,10 @@
                       bool dhcpEnabled, Manager& parent,
                       bool emitSignal = true);
 
+    /** @brief Function used to load the nameservers.
+     */
+    virtual void loadNameServers();
+
     /** @brief Function to create ipaddress dbus object.
      *  @param[in] addressType - Type of ip address.
      *  @param[in] ipaddress- IP address.
@@ -180,6 +184,12 @@
      */
     ServerList nameservers(ServerList value) override;
 
+    /** @brief sets the Static DNS/nameservers.
+     *  @param[in] value - vector of DNS servers.
+     */
+
+    ServerList staticNameServers(ServerList value) override;
+
     /** @brief create Vlan interface.
      *  @param[in] id- VLAN identifier.
      */
@@ -275,7 +285,8 @@
     /** @brief get the name server details from the network conf
      *
      */
-    ServerList getNameServerFromConf();
+    virtual ServerList getNameServerFromResolvd();
+    ServerList getstaticNameServerFromConf();
 
     /** @brief Persistent sdbusplus DBus bus connection. */
     sdbusplus::bus::bus& bus;
diff --git a/network_manager.cpp b/network_manager.cpp
index 043d7a2..637092b 100644
--- a/network_manager.cpp
+++ b/network_manager.cpp
@@ -151,6 +151,7 @@
 
         intf->createIPAddressObjects();
         intf->createStaticNeighborObjects();
+        intf->loadNameServers();
 
         this->interfaces.emplace(
             std::make_pair(std::move(interface), std::move(intf)));
diff --git a/network_manager.hpp b/network_manager.hpp
index edb341f..80d017e 100644
--- a/network_manager.hpp
+++ b/network_manager.hpp
@@ -66,7 +66,7 @@
      *         from the system and create the ethernet interraces
      *         dbus object.
      */
-    void createInterfaces();
+    virtual void createInterfaces();
 
     /** @brief create child interface object and the system conf object.
      */
diff --git a/test/mock_ethernet_interface.hpp b/test/mock_ethernet_interface.hpp
new file mode 100644
index 0000000..c7c4022
--- /dev/null
+++ b/test/mock_ethernet_interface.hpp
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "ethernet_interface.hpp"
+#include "mock_syscall.hpp"
+
+#include <gmock/gmock.h>
+
+namespace phosphor
+{
+namespace network
+{
+class MockEthernetInterface : public EthernetInterface
+{
+  public:
+    MockEthernetInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
+                          bool dhcpEnabled, Manager& parent, bool emitSignal) :
+        EthernetInterface(bus, objPath, dhcpEnabled, parent, emitSignal)
+    {
+    }
+
+    MOCK_METHOD((ServerList), getNameServerFromResolvd, (), (override));
+    friend class TestEthernetInterface;
+};
+} // namespace network
+} // namespace phosphor
diff --git a/test/mock_network_manager.hpp b/test/mock_network_manager.hpp
index df3efdf..b4a6894 100644
--- a/test/mock_network_manager.hpp
+++ b/test/mock_network_manager.hpp
@@ -1,5 +1,8 @@
+#pragma once
+
 #include "config.h"
 
+#include "mock_ethernet_interface.hpp"
 #include "network_manager.hpp"
 
 #include <gmock/gmock.h>
@@ -18,6 +21,27 @@
     {
     }
 
+    void createInterfaces() override
+    {
+        // clear all the interfaces first
+        interfaces.clear();
+        auto interfaceStrList = getInterfaces();
+        for (auto& interface : interfaceStrList)
+        {
+            fs::path objPath = objectPath;
+            // normal ethernet interface
+            objPath /= interface;
+            auto dhcp = getDHCPValue(confDir, interface);
+            auto intf =
+                std::make_shared<phosphor::network::MockEthernetInterface>(
+                    bus, objPath.string(), dhcp, *this, true);
+            intf->createIPAddressObjects();
+            intf->createStaticNeighborObjects();
+            intf->loadNameServers();
+            this->interfaces.emplace(
+                std::make_pair(std::move(interface), std::move(intf)));
+        }
+    }
     MOCK_METHOD1(restartSystemdUnit, void(const std::string& service));
 };
 
diff --git a/test/test_ethernet_interface.cpp b/test/test_ethernet_interface.cpp
index 30dee8a..d0beef7 100644
--- a/test/test_ethernet_interface.cpp
+++ b/test/test_ethernet_interface.cpp
@@ -12,6 +12,7 @@
 #include <exception>
 #include <fstream>
 #include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
 
 #include <gtest/gtest.h>
 
@@ -25,7 +26,7 @@
   public:
     sdbusplus::bus::bus bus;
     MockManager manager;
-    EthernetInterface interface;
+    MockEthernetInterface interface;
     std::string confDir;
     TestEthernetInterface() :
         bus(sdbusplus::bus::new_default()),
@@ -53,12 +54,12 @@
 
     static constexpr ether_addr mac{0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
 
-    static EthernetInterface makeInterface(sdbusplus::bus::bus& bus,
-                                           MockManager& manager)
+    static MockEthernetInterface makeInterface(sdbusplus::bus::bus& bus,
+                                               MockManager& manager)
     {
         mock_clear();
         mock_addIF("test0", 1, mac);
-        return {bus, "/xyz/openbmc_test/network/test0", false, manager};
+        return {bus, "/xyz/openbmc_test/network/test0", false, manager, true};
     }
 
     int countIPObjects()
@@ -159,11 +160,11 @@
     EXPECT_EQ(expectedObjectPath, getObjectPath(ipaddress, prefix, gateway));
 }
 
-TEST_F(TestEthernetInterface, addNameServers)
+TEST_F(TestEthernetInterface, addStaticNameServers)
 {
     ServerList servers = {"9.1.1.1", "9.2.2.2", "9.3.3.3"};
     EXPECT_CALL(manager, restartSystemdUnit(networkdService)).Times(1);
-    interface.nameservers(servers);
+    interface.staticNameServers(servers);
     fs::path filePath = confDir;
     filePath /= "00-bmc-test0.network";
     config::Parser parser(filePath.string());
@@ -173,6 +174,21 @@
     EXPECT_EQ(servers, values);
 }
 
+TEST_F(TestEthernetInterface, addDynamicNameServers)
+{
+    using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+    ServerList servers = {"9.1.1.1", "9.2.2.2", "9.3.3.3"};
+    EXPECT_THROW(interface.nameservers(servers), NotAllowed);
+}
+
+TEST_F(TestEthernetInterface, getDynamicNameServers)
+{
+    ServerList servers = {"9.1.1.1", "9.2.2.2", "9.3.3.3"};
+    EXPECT_CALL(interface, getNameServerFromResolvd())
+        .WillRepeatedly(testing::Return(servers));
+    EXPECT_EQ(interface.getNameServerFromResolvd(), servers);
+}
+
 TEST_F(TestEthernetInterface, addNTPServers)
 {
     ServerList servers = {"10.1.1.1", "10.2.2.2", "10.3.3.3"};
diff --git a/test/test_network_manager.cpp b/test/test_network_manager.cpp
index 64ca87d..7943057 100644
--- a/test/test_network_manager.cpp
+++ b/test/test_network_manager.cpp
@@ -27,7 +27,7 @@
 {
   public:
     sdbusplus::bus::bus bus;
-    Manager manager;
+    MockManager manager;
     std::string confDir;
     TestNetworkManager() :
         bus(sdbusplus::bus::new_default()),
@@ -63,7 +63,6 @@
     using namespace sdbusplus::xyz::openbmc_project::Common::Error;
     EXPECT_THROW(createInterfaces(), InternalFailure);
 }
-
 // getifaddrs returns single interface.
 TEST_F(TestNetworkManager, WithSingleInterface)
 {
@@ -96,6 +95,5 @@
     EXPECT_EQ(true, manager.hasInterface("igb0"));
     EXPECT_EQ(true, manager.hasInterface("igb1"));
 }
-
 } // namespace network
 } // namespace phosphor