ncsid: Set MAC address using ioctl calls

Set the MAC address directly via ioctl calls, removing the
dependency on phosphor-networkd. This simplifies the process
and reduces overhead as phosphor-networkd is not extensively
utilized in this context.

Change-Id: If3465f52834ba38c551d5152ef354cbf455f6a90
Signed-off-by: Mo Elbadry <elbadrym@google.com>
diff --git a/subprojects/ncsid/src/net_config.cpp b/subprojects/ncsid/src/net_config.cpp
index e8c00d2..65e50a7 100644
--- a/subprojects/ncsid/src/net_config.cpp
+++ b/subprojects/ncsid/src/net_config.cpp
@@ -14,6 +14,9 @@
 
 #include "net_config.h"
 
+#include <linux/if.h>
+#include <net/if_arp.h>
+#include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -28,6 +31,7 @@
 #include <cstring>
 #include <filesystem>
 #include <format>
+#include <thread>
 #include <utility>
 #include <variant>
 
@@ -49,15 +53,6 @@
 constexpr auto NETWORK_SERVICE = "xyz.openbmc_project.Network";
 constexpr auto PROP_INTERFACE = "org.freedesktop.DBus.Properties";
 
-int parse_mac(const std::string& mac_addr, mac_addr_t* mac)
-{
-    int ret = sscanf(mac_addr.c_str(), MAC_FORMAT, mac->octet, mac->octet + 1,
-                     mac->octet + 2, mac->octet + 3, mac->octet + 4,
-                     mac->octet + 5);
-
-    return ret < 6 ? -1 : 0;
-}
-
 std::string format_mac(const mac_addr_t& mac)
 {
     // 2 chars for every byte + 5 colons + Null byte
@@ -76,18 +71,6 @@
     bus(sdbusplus::bus::new_default())
 {}
 
-sdbusplus::message_t
-    PhosphorConfig::new_networkd_call(sdbusplus::bus_t* dbus, bool get) const
-{
-    auto networkd_call =
-        dbus->new_method_call(NETWORK_SERVICE, iface_path_.c_str(),
-                              PROP_INTERFACE, get ? "Get" : "Set");
-
-    networkd_call.append(MAC_INTERFACE, "MACAddress");
-
-    return networkd_call;
-}
-
 int PhosphorConfig::get_mac_addr(mac_addr_t* mac)
 {
     if (mac == nullptr)
@@ -101,41 +84,41 @@
     {
         *mac = shared_host_mac_.value();
     }
-    else // Cache miss: read MAC over DBus, and store in cache.
+    else // Cache miss: read from interface, cache it for future requests.
     {
-        std::string mac_string;
+        struct ifreq ifr = {};
         try
         {
-            auto networkd_call = new_networkd_call(&bus, true);
-            auto reply = bus.call(networkd_call);
-            std::variant<std::string> result;
-            reply.read(result);
-            mac_string = std::get<std::string>(result);
+            auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6,
+                                          stdplus::fd::SocketType::Datagram,
+                                          stdplus::fd::SocketProto::IP);
+            call_nic(fd, ifr, SIOCGIFHWADDR);
         }
-        catch (const sdbusplus::exception::SdBusError& ex)
+        catch (const std::exception& ex)
         {
-            stdplus::println(stderr, "Failed to get MACAddress: {}", ex.what());
+            stdplus::println(
+                stderr,
+                "Failed to get MAC Addr for Interface {} writing file: {}",
+                iface_name_, ex.what());
             return -1;
         }
-
-        if (parse_mac(mac_string, mac) < 0)
-        {
-            stdplus::println(stderr, "Failed to parse MAC Address `{}`",
-                             mac_string);
-            return -1;
-        }
-
+        std::copy_n(ifr.ifr_addr.sa_data, sizeof(*mac), mac->octet);
         shared_host_mac_ = *mac;
     }
-
     return 0;
 }
 
+void PhosphorConfig::call_nic(auto fd, struct ifreq& ifr, int op)
+{
+    std::copy_n(iface_name_.c_str(), iface_name_.size() + 1, ifr.ifr_name);
+    fd.ioctl(op, &ifr);
+}
+
 int PhosphorConfig::set_mac_addr(const mac_addr_t& mac)
 {
-    auto networkd_call = new_networkd_call(&bus, false);
     std::variant<std::string> mac_value(format_mac(mac));
-    networkd_call.append(mac_value);
+    struct ifreq ifr = {};
+    short flags_copy;
 
     try
     {
@@ -158,18 +141,62 @@
                          std::get<std::string>(mac_value), ex.what());
         return -1;
     }
-
     try
     {
-        auto reply = bus.call(networkd_call);
+        auto fd = stdplus::fd::socket(stdplus::fd::SocketDomain::INet6,
+                                      stdplus::fd::SocketType::Datagram,
+                                      stdplus::fd::SocketProto::IP);
+        // Try setting MAC Address directly without bringing interface down
+        try
+        {
+            std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data);
+            call_nic(fd, ifr, SIOCSIFHWADDR);
+        }
+        catch (const std::exception& e)
+        {
+            // Regardless of error attempt to set MAC Address again after
+            // bringing interface down
+            stdplus::println(
+                stderr,
+                "Could not set MAC Address directly, retrying after bringing interface down, error = {}",
+                e.what());
+            try
+            {
+                // Read interface flags configuration and store (once interface
+                // is brought down, existing state is lost)
+                call_nic(fd, ifr, SIOCGIFFLAGS);
+                flags_copy = ifr.ifr_flags;
+                // set interface down
+                ifr.ifr_flags &= ~IFF_UP;
+                call_nic(fd, ifr, SIOCSIFFLAGS);
+                // Wait for 1 milliseconds - sometimes interface is still
+                // going down
+                std::this_thread::sleep_for(std::chrono::milliseconds(1));
+                // set MAC Address
+                ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
+                std::copy_n(mac.octet, 6, ifr.ifr_hwaddr.sa_data);
+                call_nic(fd, ifr, SIOCSIFHWADDR);
+                // set interface up with the flags state prior to bringing
+                // it down
+                ifr.ifr_flags = flags_copy | IFF_UP;
+                call_nic(fd, ifr, SIOCSIFFLAGS);
+            }
+            catch (const std::exception& e)
+            {
+                stdplus::println(
+                    stderr, "Failed to set MAC Address {} writing file: {}",
+                    std::get<std::string>(mac_value), e.what());
+                return -1;
+            }
+        }
     }
-    catch (const sdbusplus::exception::SdBusError& ex)
+    catch (const std::exception& e)
     {
-        stdplus::println(stderr, "Failed to set MAC Addr `{}`: {}",
-                         std::get<std::string>(mac_value), ex.what());
+        stdplus::println(stderr, "Error creating socket: {}", e.what());
         return -1;
     }
-
+    stdplus::println(stderr, "Success setting Mac address for {}: {}",
+                     iface_name_, std::get<std::string>(mac_value));
     shared_host_mac_ = std::experimental::nullopt;
     return 0;
 }
diff --git a/subprojects/ncsid/src/net_config.h b/subprojects/ncsid/src/net_config.h
index 7aeb18b..02c9aff 100644
--- a/subprojects/ncsid/src/net_config.h
+++ b/subprojects/ncsid/src/net_config.h
@@ -53,22 +53,22 @@
     virtual int set_nic_hostless(bool is_nic_hostless) = 0;
 };
 
-// Calls phosphord-networkd
+// Calls Socket Ioctl to obtain information about NIC (previously
+// phosphor-networkd)
 class PhosphorConfig : public ConfigBase
 {
   public:
     explicit PhosphorConfig(const std::string& iface_name);
 
-    // Reads the MAC address from phosphor-networkd interface or internal
+    // Reads the MAC address from socket interface or internal
     // cache, and store in the mac pointer.
     // Returns -1 if failed, 0 if succeeded.
     int get_mac_addr(mac_addr_t* mac) override;
 
-    // Sets the MAC address over phosphor-networkd, and update internal
+    // Sets the MAC address over socket, and update internal
     // cache.
     // Returns -1 if failed, 0 if succeeded.
     int set_mac_addr(const mac_addr_t& mac) override;
-
     virtual int set_nic_hostless(bool is_nic_hostless) override;
 
   private:
@@ -81,6 +81,10 @@
     // Stores the currently configured nic state, if previously set
     std::optional<bool> was_nic_hostless_;
 
+    // Function helper allows get_mac_addr and set_mac_addr to do
+    // ioctl calls to get and set different states of NIC.
+    void call_nic(auto fd, struct ifreq& ifr, int op);
+
     // The MAC address obtained from NIC.
     // ncsid will commit this MAC address over DBus to phosphor-networkd
     // and expect it to be persisted. If actual host MAC address changes or