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";
     }