ethernet_interface: Fix u-boot MAC environment variable

Previously, no matter what interface we were changing we would always
set the MAC address for eth0 on the next boot. There is a loose
mapping between ethaddr <-> eth0 and eth1addr <-> eth1 for storing the
MAC address in the u-boot environment.

https://www.denx.de/wiki/view/DULG/UBootEnvVariables

Change-Id: I90f608a876c03e74c32561cf24947cfc44da0e34
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
index 82f6ae3..f4f8d57 100644
--- a/ethernet_interface.cpp
+++ b/ethernet_interface.cpp
@@ -768,7 +768,12 @@
         MacAddressIntf::mACAddress(value);
 
         auto interface = interfaceName();
-        execute("/sbin/fw_setenv", "fw_setenv", "ethaddr", value.c_str());
+        auto envVar = interfaceToUbootEthAddr(interface.c_str());
+        if (envVar)
+        {
+            execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
+                    value.c_str());
+        }
         // TODO: would remove the call below and
         //      just restart systemd-netwokd
         //      through https://github.com/systemd/systemd/issues/6696
diff --git a/test/test_util.cpp b/test/test_util.cpp
index 33b5c48..4bc9d5b 100644
--- a/test/test_util.cpp
+++ b/test/test_util.cpp
@@ -200,6 +200,19 @@
     EXPECT_EQ(mask, "255.255.255.224");
 }
 
+TEST_F(TestUtil, InterfaceToUbootEthAddr)
+{
+    EXPECT_EQ(std::nullopt, interfaceToUbootEthAddr("et"));
+    EXPECT_EQ(std::nullopt, interfaceToUbootEthAddr("eth"));
+    EXPECT_EQ(std::nullopt, interfaceToUbootEthAddr("sit0"));
+    EXPECT_EQ(std::nullopt, interfaceToUbootEthAddr("ethh0"));
+    EXPECT_EQ(std::nullopt, interfaceToUbootEthAddr("eth0h"));
+    EXPECT_EQ("ethaddr", interfaceToUbootEthAddr("eth0"));
+    EXPECT_EQ("eth1addr", interfaceToUbootEthAddr("eth1"));
+    EXPECT_EQ("eth5addr", interfaceToUbootEthAddr("eth5"));
+    EXPECT_EQ("eth28addr", interfaceToUbootEthAddr("eth28"));
+}
+
 TEST_F(TestUtil, CopyFromTooSmall)
 {
     constexpr auto expected = "abcde"sv;
diff --git a/util.cpp b/util.cpp
index b01d644..f1fd4cd 100644
--- a/util.cpp
+++ b/util.cpp
@@ -9,6 +9,8 @@
 #include <sys/wait.h>
 
 #include <algorithm>
+#include <cstdlib>
+#include <cstring>
 #include <experimental/filesystem>
 #include <iostream>
 #include <list>
@@ -382,6 +384,32 @@
     }
 }
 
+std::optional<std::string> interfaceToUbootEthAddr(const char* intf)
+{
+    constexpr char ethPrefix[] = "eth";
+    constexpr size_t ethPrefixLen = sizeof(ethPrefix) - 1;
+    if (strncmp(ethPrefix, intf, ethPrefixLen) != 0)
+    {
+        return std::nullopt;
+    }
+    const auto intfSuffix = intf + ethPrefixLen;
+    if (intfSuffix[0] == '\0')
+    {
+        return std::nullopt;
+    }
+    char* end;
+    unsigned long idx = strtoul(intfSuffix, &end, 10);
+    if (end[0] != '\0')
+    {
+        return std::nullopt;
+    }
+    if (idx == 0)
+    {
+        return "ethaddr";
+    }
+    return "eth" + std::to_string(idx) + "addr";
+}
+
 bool getDHCPValue(const std::string& confDir, const std::string& intf)
 {
     bool dhcp = false;
diff --git a/util.hpp b/util.hpp
index 1f86815..3aad4a1 100644
--- a/util.hpp
+++ b/util.hpp
@@ -8,6 +8,7 @@
 #include <unistd.h>
 
 #include <cstring>
+#include <optional>
 #include <sdbusplus/bus.hpp>
 #include <string>
 #include <string_view>
@@ -140,6 +141,14 @@
  */
 void deleteInterface(const std::string& intf);
 
+/** @brief Converts the interface name into a u-boot environment
+ *         variable that would hold its ethernet address.
+ *
+ *  @param[in] intf - interface name
+ *  @return The name of th environment key
+ */
+std::optional<std::string> interfaceToUbootEthAddr(const char* intf);
+
 /** @brief read the DHCP value from the configuration file
  *  @param[in] confDir - Network configuration directory.
  *  @param[in] intf - Interface name.