summaryrefslogtreecommitdiff
path: root/meta-openbmc-mods/meta-common/recipes-network/network
diff options
context:
space:
mode:
Diffstat (limited to 'meta-openbmc-mods/meta-common/recipes-network/network')
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0001-Enhance-DHCP-beyond-just-OFF-and-IPv4-IPv6-enabled.patch657
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0003-Adding-channel-specific-privilege-to-network.patch405
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network_%.bbappend9
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr.bb24
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/mac-check79
-rw-r--r--meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/static-mac-addr.service11
6 files changed, 1185 insertions, 0 deletions
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0001-Enhance-DHCP-beyond-just-OFF-and-IPv4-IPv6-enabled.patch b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0001-Enhance-DHCP-beyond-just-OFF-and-IPv4-IPv6-enabled.patch
new file mode 100644
index 000000000..41541500f
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0001-Enhance-DHCP-beyond-just-OFF-and-IPv4-IPv6-enabled.patch
@@ -0,0 +1,657 @@
+From c7050f4a1f87d49e8a619d5d8752d1c98bfed3e8 Mon Sep 17 00:00:00 2001
+From: Johnathan Mantey <johnathanx.mantey@intel.com>
+Date: Wed, 3 Jul 2019 14:12:49 -0700
+Subject: [PATCH] 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. It also correctly managed the routing table.
+
+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>
+---
+ Makefile.am | 5 +
+ configure.ac | 1 +
+ ethernet_interface.cpp | 170 ++++++++++++++++++++++---------
+ ethernet_interface.hpp | 31 +++++-
+ ipaddress.cpp | 2 +-
+ network_manager.cpp | 2 +-
+ test/test_ethernet_interface.cpp | 3 +-
+ test/test_vlan_interface.cpp | 3 +-
+ types.hpp | 3 +
+ util.cpp | 69 ++++++++++++-
+ util.hpp | 13 ++-
+ vlan_interface.cpp | 2 +-
+ vlan_interface.hpp | 4 +-
+ 13 files changed, 246 insertions(+), 62 deletions(-)
+
+diff --git a/Makefile.am b/Makefile.am
+index 79db184..2768e38 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -97,6 +97,11 @@ phosphor_network_manager_CXXFLAGS = \
+ $(SDEVENTPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS) \
++ -DBOOST_ERROR_CODE_HEADER_ONLY \
++ -DBOOST_SYSTEM_NO_DEPRECATED \
++ -DBOOST_COROUTINES_NO_DEPRECATION_WARNING \
++ -DBOOST_ASIO_DISABLE_THREADS \
++ -DBOOST_ALL_NO_LIB \
+ -flto
+
+ xyz/openbmc_project/Network/VLAN/Create/server.cpp: xyz/openbmc_project/Network/VLAN/Create.interface.yaml xyz/openbmc_project/Network/VLAN/Create/server.hpp
+diff --git a/configure.ac b/configure.ac
+index 8870fcd..00b23bc 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -36,6 +36,7 @@ AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++])
+ PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging])
+ PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces])
+ PKG_CHECK_MODULES([LIBNL], [libnl-3.0 libnl-genl-3.0])
++AC_CHECK_HEADER(boost/algorithm/string/split.hpp, [], [AC_MSG_ERROR([Could not find boost/algorithm/string/split.hpp])])
+
+ # Checks for header files.
+ AC_CHECK_HEADER(systemd/sd-bus.h, ,\
+diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
+index c3edd4b..537054f 100644
+--- a/ethernet_interface.cpp
++++ b/ethernet_interface.cpp
+@@ -3,9 +3,9 @@
+ #include "ethernet_interface.hpp"
+
+ #include "config_parser.hpp"
+-#include "ipaddress.hpp"
+ #include "neighbor.hpp"
+ #include "network_manager.hpp"
++#include "util.hpp"
+ #include "vlan_interface.hpp"
+
+ #include <arpa/inet.h>
+@@ -40,9 +40,12 @@ using Argument = xyz::openbmc_project::Common::InvalidArgument;
+ static constexpr const char* networkChannelCfgFile =
+ "/var/channel_intf_data.json";
+ static constexpr const char* defaultChannelPriv = "priv-admin";
++std::map<std::string, std::string> mapDHCPToSystemd = {
++ {"both", "true"}, {"v4", "ipv4"}, {"v6", "ipv6"}, {"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)
+@@ -81,24 +84,78 @@ static IP::Protocol convertFamily(int family)
+ 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,
++ std::string& nextDHCPState)
++{
++ return ((nextDHCPState == "true") ||
++ ((nextDHCPState == "ipv6") && (family == IP::Protocol::IPv6)) ||
++ ((nextDHCPState == "ipv4") && (family == IP::Protocol::IPv4)));
++}
++
++bool EthernetInterface::addressIsStatic(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();
+
+ auto addrs = getInterfaceAddrs()[interfaceName()];
++ if (getIPAddrOrigins(addrs))
++ {
++ return;
++ }
+
+ for (auto& addr : addrs)
+ {
+ IP::Protocol addressType = convertFamily(addr.addrType);
+- IP::AddressOrigin origin = IP::AddressOrigin::Static;
+- if (dHCPEnabled())
+- {
+- origin = IP::AddressOrigin::DHCP;
+- }
+- if (isLinkLocalIP(addr.ipaddress))
+- {
+- origin = IP::AddressOrigin::LinkLocal;
+- }
+ // Obsolete parameter
+ std::string gateway = "";
+
+@@ -108,7 +165,7 @@ void EthernetInterface::createIPAddressObjects()
+ this->addrs.emplace(addr.ipaddress,
+ std::make_shared<phosphor::network::IPAddress>(
+ bus, ipAddressObjectPath.c_str(), *this,
+- addressType, addr.ipaddress, origin,
++ addressType, addr.ipaddress, addr.origin,
+ addr.prefix, gateway));
+ }
+ }
+@@ -152,11 +209,11 @@ ObjectPath EthernetInterface::iP(IP::Protocol protType, std::string ipaddress,
+ 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;
+@@ -438,7 +495,7 @@ bool EthernetInterface::iPv6AcceptRA(bool value)
+ return value;
+ }
+
+-bool EthernetInterface::dHCPEnabled(bool value)
++EthernetInterface::DHCPConf EthernetInterface::dHCPEnabled(DHCPConf value)
+ {
+ if (value == EthernetInterfaceIntf::dHCPEnabled())
+ {
+@@ -505,7 +562,7 @@ void EthernetInterface::loadVLAN(VlanId id)
+ 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>(
+@@ -527,7 +584,8 @@ ObjectPath EthernetInterface::createVLAN(VlanId id)
+ path += "_" + std::to_string(id);
+
+ auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
+- bus, path.c_str(), false, id, *this, manager);
++ bus, path.c_str(), EthernetInterface::DHCPConf::none, id, *this,
++ manager);
+
+ // write the device file for the vlan interface.
+ vlanIntf->writeDeviceFile();
+@@ -600,8 +658,6 @@ void EthernetInterface::writeConfigurationFile()
+ // 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::experimental::filesystem;
+
+ // if there is vlan interafce then write the configuration file
+@@ -670,41 +726,57 @@ void EthernetInterface::writeConfigurationFile()
+ }
+
+ // Add the DHCP entry
+- auto value = dHCPEnabled() ? "true"s : "false"s;
+- stream << "DHCP="s + value + "\n";
+-
+- // When the interface configured as dhcp, we don't need below given entries
+- // in config file.
+- if (dHCPEnabled() == false)
+- {
+- // Static
+- for (const auto& addr : addrs)
++ std::string requestedDHCPState;
++ std::string::size_type loc;
++ std::string value = convertForMessage(EthernetInterfaceIntf::dHCPEnabled());
++ loc = value.rfind(".");
++ requestedDHCPState = value.substr(loc + 1);
++ std::string mappedDHCPState = mapDHCPToSystemd[requestedDHCPState];
++ stream << "DHCP="s + mappedDHCPState + "\n";
++
++ bool dhcpv6Requested = dhcpToBeEnabled(IP::Protocol::IPv6, mappedDHCPState);
++ bool dhcpv4Requested = dhcpToBeEnabled(IP::Protocol::IPv4, mappedDHCPState);
++ // Static IP addresses
++ for (const auto& addr : addrs)
++ {
++ bool isValidIPv4 = isValidIP(AF_INET, addr.second->address());
++ bool isValidIPv6 = isValidIP(AF_INET6, addr.second->address());
++ if (((!dhcpv4Requested && isValidIPv4) ||
++ (!dhcpv6Requested && isValidIPv6)) &&
++ addressIsStatic(addr.second->origin()))
+ {
+- 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());
++
++ // build the address entries. Do not use [Network] shortcuts to
++ // insert address entries.
++ stream << "[Address]\n";
++ stream << "Address=" << address << "\n";
++
++ // build the route section. Do not use [Network] shortcuts to apply
++ // default gateway values.
++ std::string gw = "0.0.0.0";
++ if (addr.second->gateway() != "0.0.0.0" &&
++ addr.second->gateway() != "")
+ {
+- std::string address =
+- addr.second->address() + "/" +
+- std::to_string(addr.second->prefixLength());
+-
+- stream << "Address=" << address << "\n";
++ gw = addr.second->gateway();
+ }
+- }
+-
+- if (manager.getSystemConf())
+- {
+- const auto& gateway = manager.getSystemConf()->defaultGateway();
+- if (!gateway.empty())
++ else
+ {
+- stream << "Gateway=" << gateway << "\n";
++ if (isValidIPv4)
++ {
++ gw = manager.getSystemConf()->defaultGateway();
++ }
++ else if (isValidIPv6)
++ {
++ gw = manager.getSystemConf()->defaultGateway6();
++ }
+ }
+- const auto& gateway6 = manager.getSystemConf()->defaultGateway6();
+- if (!gateway6.empty())
++
++ if (!gw.empty())
+ {
+- stream << "Gateway=" << gateway6 << "\n";
++ stream << "[Route]\n";
++ stream << "Gateway=" << gw << "\n";
+ }
+ }
+ }
+@@ -816,7 +888,7 @@ std::string EthernetInterface::mACAddress(std::string value)
+
+ 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 3e4cf12..a962751 100644
+--- a/ethernet_interface.hpp
++++ b/ethernet_interface.hpp
+@@ -91,7 +91,7 @@ class EthernetInterface : public Ifaces
+ * send.
+ */
+ EthernetInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
+- bool dhcpEnabled, Manager& parent,
++ DHCPConf dhcpEnabled, Manager& parent,
+ bool emitSignal = true);
+
+ /** @brief Function to create ipaddress dbus object.
+@@ -157,7 +157,34 @@ class EthernetInterface : public Ifaces
+ }
+
+ /** Set value of DHCPEnabled */
+- bool dHCPEnabled(bool value) override;
++ DHCPConf dHCPEnabled(DHCPConf value) override;
++
++ /** @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, 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 addressIsStatic(IP::AddressOrigin origin);
++
++ /** @brief Selectively disables DHCP
++ * @param[in] protocol - The IPv4 or IPv6 protocol to return to static
++ * addressing mode
++ */
++ void disableDHCP(IP::Protocol protocol);
+
+ /** @brief sets the MAC address.
+ * @param[in] value - MAC address which needs to be set on the system.
+diff --git a/ipaddress.cpp b/ipaddress.cpp
+index 10a22b2..5b2bf56 100644
+--- a/ipaddress.cpp
++++ b/ipaddress.cpp
+@@ -57,7 +57,7 @@ IP::AddressOrigin IPAddress::origin(IP::AddressOrigin origin)
+ }
+ void IPAddress::delete_()
+ {
+- if (origin() != IP::AddressOrigin::Static)
++ if (parent.dhcpIsEnabled(type()))
+ {
+ log<level::ERR>("Tried to delete a non-static address"),
+ entry("ADDRESS=%s", address().c_str()),
+diff --git a/network_manager.cpp b/network_manager.cpp
+index 75f4e5f..f7e8a75 100644
+--- a/network_manager.cpp
++++ b/network_manager.cpp
+@@ -248,7 +248,7 @@ void Manager::createInterfaces()
+ // normal ethernet interface
+ objPath /= interface;
+
+- auto dhcp = getDHCPValue(confDir, interface);
++ EthernetInterfaceIntf::DHCPConf dhcp = getDHCPValue(confDir, interface);
+
+ auto intf = std::make_shared<phosphor::network::EthernetInterface>(
+ bus, objPath.string(), dhcp, *this);
+diff --git a/test/test_ethernet_interface.cpp b/test/test_ethernet_interface.cpp
+index 30dee8a..87fd68d 100644
+--- a/test/test_ethernet_interface.cpp
++++ b/test/test_ethernet_interface.cpp
+@@ -58,7 +58,8 @@ class TestEthernetInterface : public testing::Test
+ {
+ mock_clear();
+ mock_addIF("test0", 1, mac);
+- return {bus, "/xyz/openbmc_test/network/test0", false, manager};
++ return {bus, "/xyz/openbmc_test/network/test0",
++ EthernetInterface::DHCPConf::none, manager};
+ }
+
+ int countIPObjects()
+diff --git a/test/test_vlan_interface.cpp b/test/test_vlan_interface.cpp
+index 1dffc7e..e49b43f 100644
+--- a/test/test_vlan_interface.cpp
++++ b/test/test_vlan_interface.cpp
+@@ -50,7 +50,8 @@ class TestVlanInterface : public testing::Test
+ {
+ 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..c4409fe 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>
+@@ -50,6 +52,7 @@ struct AddrInfo
+ {
+ uint8_t addrType;
+ std::string ipaddress;
++ IP::AddressOrigin origin;
+ uint16_t prefix;
+ };
+
+diff --git a/util.cpp b/util.cpp
+index afbc229..2e5b164 100644
+--- a/util.cpp
++++ b/util.cpp
+@@ -6,12 +6,17 @@
+ #include <arpa/inet.h>
+ #include <dirent.h>
+ #include <net/if.h>
++#include <sys/stat.h>
+ #include <sys/wait.h>
+
+ #include <algorithm>
++#include <boost/algorithm/string/classification.hpp>
++#include <boost/algorithm/string/split.hpp>
++#include <boost/process.hpp>
+ #include <cstdlib>
+ #include <cstring>
+ #include <experimental/filesystem>
++#include <fstream>
+ #include <iostream>
+ #include <list>
+ #include <phosphor-logging/elog-errors.hpp>
+@@ -26,6 +31,54 @@ namespace phosphor
+ namespace network
+ {
+
++int getIPAddrOrigins(AddrList& addressList)
++{
++ boost::process::ipstream inputStream;
++ boost::process::child ipaddr("ip -o addr",
++ boost::process::std_out > inputStream);
++ std::string ipaddrLine;
++
++ while (inputStream && std::getline(inputStream, ipaddrLine) &&
++ !ipaddrLine.empty())
++ {
++ std::vector<std::string> addressElements;
++ std::vector<std::string> addrPrefixVec;
++
++ boost::split(addressElements, ipaddrLine, boost::is_any_of(" "),
++ boost::token_compress_on);
++ boost::split(addrPrefixVec, addressElements[3], boost::is_any_of("/"),
++ boost::token_compress_on);
++ std::string& nic = addressElements[1];
++ std::string& ipClass = addressElements[2]; // inet | inet6
++ std::string& address = addrPrefixVec[0];
++ if (nic != "lo")
++ {
++ for (auto it = addressList.begin(); it != addressList.end(); it++)
++ {
++ if (it->ipaddress == address)
++ {
++ bool isIPv6 = (ipClass == "inet6");
++ int globalStrIdx = isIPv6 ? 5 : 7;
++ if (addressElements[globalStrIdx] == "global")
++ {
++ it->origin = (addressElements[8] == "dynamic")
++ ? IP::AddressOrigin::DHCP
++ : IP::AddressOrigin::Static;
++ }
++ else if (addressElements[globalStrIdx] == "link")
++ {
++ it->origin = isIPv6 ? IP::AddressOrigin::SLAAC
++ : IP::AddressOrigin::LinkLocal;
++ }
++ break;
++ }
++ }
++ }
++ }
++ ipaddr.wait();
++ return 0;
++}
++
+ namespace
+ {
+
+@@ -410,9 +463,11 @@ std::optional<std::string> interfaceToUbootEthAddr(const char* intf)
+ 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;
+@@ -434,7 +489,15 @@ bool getDHCPValue(const std::string& confDir, const std::string& intf)
+ // 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 251aa0d..b3f7bba 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 @@ std::optional<std::string> interfaceToUbootEthAddr(const char* intf);
+ * @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
+ {
+@@ -183,6 +188,12 @@ void execute(const char* path, ArgTypes&&... tArgs)
+ internal::executeCommandinChildProcess(path, args);
+ }
+
++/* @brief Retrieve the source (DHCP, Static, Local/Self assigned) for
++ * each IP address supplied
++ * @param[in] addressList - List of IP addresses active on one interface
++ */
++int getIPAddrOrigins(AddrList& addressList);
++
+ } // namespace network
+
+ /** @brief Copies data from a buffer into a copyable type
+diff --git a/vlan_interface.cpp b/vlan_interface.cpp
+index 73de4e8..26282cb 100644
+--- a/vlan_interface.cpp
++++ b/vlan_interface.cpp
+@@ -22,7 +22,7 @@ using namespace phosphor::logging;
+ 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,
+ uint32_t vlanID, EthernetInterface& intf,
+ Manager& parent) :
+ VlanIface(bus, objPath.c_str()),
+diff --git a/vlan_interface.hpp b/vlan_interface.hpp
+index a994d05..37ae7ee 100644
+--- a/vlan_interface.hpp
++++ b/vlan_interface.hpp
+@@ -45,8 +45,8 @@ class VlanInterface : public VlanIface,
+ * @param[in] manager - network manager object.
+ */
+ VlanInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
+- bool dhcpEnabled, uint32_t vlanID, EthernetInterface& intf,
+- Manager& manager);
++ DHCPConf dhcpEnabled, uint32_t vlanID,
++ EthernetInterface& intf, Manager& manager);
+
+ /** @brief Delete this d-bus object.
+ */
+--
+2.21.0
+
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0003-Adding-channel-specific-privilege-to-network.patch b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0003-Adding-channel-specific-privilege-to-network.patch
new file mode 100644
index 000000000..53f381af8
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network/0003-Adding-channel-specific-privilege-to-network.patch
@@ -0,0 +1,405 @@
+From 8a127e2054683479d3999ad99ba7ff76c193aa1a Mon Sep 17 00:00:00 2001
+From: AppaRao Puli <apparao.puli@linux.intel.com>
+Date: Wed, 5 Sep 2018 14:16:54 +0530
+Subject: [PATCH] Adding channel specific privilege to network
+
+ - Adding the channel access information to the network
+ interface object. This privilege will be used in
+ channel specific authorization.
+ - Get supported priv from user manager service dynamically.
+ - Signal handling for capturing the supported priv list
+ changes from user managerment.
+
+Tested-by:
+Verified channel access through ipmitool get/set channel
+access command
+
+Change-Id: I3b592a19363eef684e31d5f7c34dad8f2f9211df
+Signed-off-by: AppaRao Puli <apparao.puli@linux.intel.com>
+Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
+---
+ ethernet_interface.cpp | 116 +++++++++++++++++++++++++++++++++++++++++
+ ethernet_interface.hpp | 39 +++++++++++++-
+ network_manager.cpp | 104 ++++++++++++++++++++++++++++++++++++
+ network_manager.hpp | 9 ++++
+ 4 files changed, 267 insertions(+), 1 deletion(-)
+
+diff --git a/ethernet_interface.cpp b/ethernet_interface.cpp
+index 2375482..c3edd4b 100644
+--- a/ethernet_interface.cpp
++++ b/ethernet_interface.cpp
+@@ -37,6 +37,9 @@ using namespace phosphor::logging;
+ using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+ using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
++static constexpr const char* networkChannelCfgFile =
++ "/var/channel_intf_data.json";
++static constexpr const char* defaultChannelPriv = "priv-admin";
+ EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
+ const std::string& objPath,
+ bool dhcpEnabled, Manager& parent,
+@@ -56,6 +59,7 @@ EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
+
+ EthernetInterfaceIntf::autoNeg(std::get<2>(ifInfo));
+ EthernetInterfaceIntf::speed(std::get<0>(ifInfo));
++ getChannelPrivilege(intfName);
+
+ // Emit deferred signal.
+ if (emitSignal)
+@@ -823,5 +827,117 @@ void EthernetInterface::deleteAll()
+ manager.writeToConfigurationFile();
+ }
+
++nlohmann::json EthernetInterface::readJsonFile(const std::string& configFile)
++{
++ std::ifstream jsonFile(configFile);
++ if (!jsonFile.good())
++ {
++ log<level::ERR>("JSON file not found");
++ return nullptr;
++ }
++
++ nlohmann::json data = nullptr;
++ try
++ {
++ data = nlohmann::json::parse(jsonFile, nullptr, false);
++ }
++ catch (nlohmann::json::parse_error& e)
++ {
++ log<level::DEBUG>("Corrupted channel config.",
++ entry("MSG: %s", e.what()));
++ throw std::runtime_error("Corrupted channel config file");
++ }
++
++ return data;
++}
++
++int EthernetInterface::writeJsonFile(const std::string& configFile,
++ const nlohmann::json& jsonData)
++{
++ std::ofstream jsonFile(configFile);
++ if (!jsonFile.good())
++ {
++ log<level::ERR>("JSON file open failed",
++ entry("FILE=%s", networkChannelCfgFile));
++ return -1;
++ }
++
++ // Write JSON to file
++ jsonFile << jsonData;
++
++ jsonFile.flush();
++ return 0;
++}
++
++std::string
++ EthernetInterface::getChannelPrivilege(const std::string& interfaceName)
++{
++ std::string priv(defaultChannelPriv);
++ std::string retPriv;
++
++ nlohmann::json jsonData = readJsonFile(networkChannelCfgFile);
++ if (jsonData != nullptr)
++ {
++ try
++ {
++ priv = jsonData[interfaceName].get<std::string>();
++ retPriv = ChannelAccessIntf::maxPrivilege(std::move(priv));
++ return retPriv;
++ }
++ catch (const nlohmann::json::exception& e)
++ {
++ jsonData[interfaceName] = priv;
++ }
++ }
++ else
++ {
++ jsonData[interfaceName] = priv;
++ }
++
++ if (writeJsonFile(networkChannelCfgFile, jsonData) != 0)
++ {
++ log<level::DEBUG>("Error in write JSON data to file",
++ entry("FILE=%s", networkChannelCfgFile));
++ elog<InternalFailure>();
++ }
++
++ retPriv = ChannelAccessIntf::maxPrivilege(std::move(priv));
++
++ return retPriv;
++}
++
++std::string EthernetInterface::maxPrivilege(std::string priv)
++{
++ std::string intfName = interfaceName();
++
++ if (!priv.empty() && (std::find(manager.supportedPrivList.begin(),
++ manager.supportedPrivList.end(),
++ priv) == manager.supportedPrivList.end()))
++ {
++ log<level::ERR>("Invalid privilege");
++ elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
++ Argument::ARGUMENT_VALUE(priv.c_str()));
++ }
++
++ if (ChannelAccessIntf::maxPrivilege() == priv)
++ {
++ // No change in privilege so just return.
++ return priv;
++ }
++
++ nlohmann::json jsonData = readJsonFile(networkChannelCfgFile);
++ jsonData[intfName] = priv;
++
++ if (writeJsonFile(networkChannelCfgFile, jsonData) != 0)
++ {
++ log<level::DEBUG>("Error in write JSON data to file",
++ entry("FILE=%s", networkChannelCfgFile));
++ elog<InternalFailure>();
++ }
++
++ // Property change signal will be sent
++ return ChannelAccessIntf::maxPrivilege(std::move(priv));
++}
++
+ } // namespace network
+ } // namespace phosphor
+diff --git a/ethernet_interface.hpp b/ethernet_interface.hpp
+index 60c56e3..3e4cf12 100644
+--- a/ethernet_interface.hpp
++++ b/ethernet_interface.hpp
+@@ -2,11 +2,14 @@
+
+ #include "types.hpp"
+ #include "util.hpp"
++#include "xyz/openbmc_project/Channel/ChannelAccess/server.hpp"
+ #include "xyz/openbmc_project/Network/IP/Create/server.hpp"
+ #include "xyz/openbmc_project/Network/Neighbor/CreateStatic/server.hpp"
+
+ #include <experimental/filesystem>
++#include <nlohmann/json.hpp>
+ #include <sdbusplus/bus.hpp>
++#include <sdbusplus/bus/match.hpp>
+ #include <sdbusplus/server/object.hpp>
+ #include <string>
+ #include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
+@@ -23,7 +26,8 @@ using Ifaces = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Network::server::MACAddress,
+ sdbusplus::xyz::openbmc_project::Network::IP::server::Create,
+ sdbusplus::xyz::openbmc_project::Network::Neighbor::server::CreateStatic,
+- sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll>;
++ sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll,
++ sdbusplus::xyz::openbmc_project::Channel::server::ChannelAccess>;
+
+ using IP = sdbusplus::xyz::openbmc_project::Network::server::IP;
+
+@@ -31,10 +35,15 @@ using EthernetInterfaceIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface;
+ using MacAddressIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::MACAddress;
++using ChannelAccessIntf =
++ sdbusplus::xyz::openbmc_project::Channel::server::ChannelAccess;
+
+ using ServerList = std::vector<std::string>;
+ using ObjectPath = sdbusplus::message::object_path;
+
++using DbusVariant =
++ sdbusplus::message::variant<std::string, std::vector<std::string>>;
++
+ namespace fs = std::experimental::filesystem;
+
+ class Manager; // forward declaration of network manager.
+@@ -195,6 +204,14 @@ class EthernetInterface : public Ifaces
+ */
+ void deleteAll();
+
++ /** @brief sets the channel maxium privilege.
++ * @param[in] value - Channel privilege which needs to be set on the
++ * system.
++ * @returns privilege of the interface or throws an error.
++ */
++ std::string maxPrivilege(std::string value) override;
++
++ using ChannelAccessIntf::maxPrivilege;
+ using EthernetInterfaceIntf::dHCPEnabled;
+ using EthernetInterfaceIntf::interfaceName;
+ using MacAddressIntf::mACAddress;
+@@ -291,6 +308,26 @@ class EthernetInterface : public Ifaces
+ std::string objPath;
+
+ friend class TestEthernetInterface;
++
++ /** @brief gets the channel privilege.
++ * @param[in] interfaceName - Network interface name.
++ * @returns privilege of the interface
++ */
++ std::string getChannelPrivilege(const std::string& interfaceName);
++
++ /** @brief reads the channel access info from file.
++ * @param[in] configFile - channel access filename
++ * @returns json file data
++ */
++ nlohmann::json readJsonFile(const std::string& configFile);
++
++ /** @brief writes the channel access info to file.
++ * @param[in] configFile - channel access filename
++ * @param[in] jsonData - json data to write
++ * @returns success or failure
++ */
++ int writeJsonFile(const std::string& configFile,
++ const nlohmann::json& jsonData);
+ };
+
+ } // namespace network
+diff --git a/network_manager.cpp b/network_manager.cpp
+index 043d7a2..75f4e5f 100644
+--- a/network_manager.cpp
++++ b/network_manager.cpp
+@@ -34,6 +34,13 @@ extern std::unique_ptr<Timer> restartTimer;
+ using namespace phosphor::logging;
+ using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
++static constexpr const char* userMgrObjBasePath = "/xyz/openbmc_project/user";
++static constexpr const char* userMgrInterface =
++ "xyz.openbmc_project.User.Manager";
++static constexpr const char* propNameAllPrivileges = "AllPrivileges";
++
++std::unique_ptr<sdbusplus::bus::match_t> usrMgmtSignal(nullptr);
++
+ Manager::Manager(sdbusplus::bus::bus& bus, const char* objPath,
+ const std::string& path) :
+ details::VLANCreateIface(bus, objPath, true),
+@@ -41,6 +48,103 @@ Manager::Manager(sdbusplus::bus::bus& bus, const char* objPath,
+ {
+ fs::path confDir(path);
+ setConfDir(confDir);
++ initSupportedPrivilges();
++}
++
++std::string getUserService(sdbusplus::bus::bus& bus, const std::string& intf,
++ const std::string& path)
++{
++ auto mapperCall =
++ bus.new_method_call("xyz.openbmc_project.ObjectMapper",
++ "/xyz/openbmc_project/object_mapper",
++ "xyz.openbmc_project.ObjectMapper", "GetObject");
++
++ mapperCall.append(path);
++ mapperCall.append(std::vector<std::string>({intf}));
++
++ auto mapperResponseMsg = bus.call(mapperCall);
++
++ std::map<std::string, std::vector<std::string>> mapperResponse;
++ mapperResponseMsg.read(mapperResponse);
++
++ if (mapperResponse.begin() == mapperResponse.end())
++ {
++ throw std::runtime_error("ERROR in reading the mapper response");
++ }
++
++ return mapperResponse.begin()->first;
++}
++
++std::string Manager::getUserServiceName()
++{
++ static std::string userMgmtService;
++ if (userMgmtService.empty())
++ {
++ try
++ {
++ userMgmtService =
++ getUserService(bus, userMgrInterface, userMgrObjBasePath);
++ }
++ catch (const std::exception& e)
++ {
++ log<level::ERR>("Exception caught in getUserServiceName.");
++ userMgmtService.clear();
++ }
++ }
++ return userMgmtService;
++}
++
++void Manager::initSupportedPrivilges()
++{
++ std::string userServiceName = getUserServiceName();
++ if (!userServiceName.empty())
++ {
++ auto method = bus.new_method_call(
++ getUserServiceName().c_str(), userMgrObjBasePath,
++ "org.freedesktop.DBus.Properties", "Get");
++ method.append(userMgrInterface, propNameAllPrivileges);
++
++ auto reply = bus.call(method);
++ if (reply.is_method_error())
++ {
++ log<level::DEBUG>("get-property AllPrivileges failed",
++ entry("OBJPATH:%s", userMgrObjBasePath),
++ entry("INTERFACE:%s", userMgrInterface));
++ return;
++ }
++
++ sdbusplus::message::variant<std::vector<std::string>> result;
++ reply.read(result);
++
++ supportedPrivList =
++ sdbusplus::message::variant_ns::get<std::vector<std::string>>(
++ result);
++ }
++
++ // Resgister the signal
++ if (usrMgmtSignal == nullptr)
++ {
++ log<level::DEBUG>("Registering User.Manager propertychange signal.");
++ usrMgmtSignal = std::make_unique<sdbusplus::bus::match_t>(
++ bus,
++ sdbusplus::bus::match::rules::propertiesChanged(userMgrObjBasePath,
++ userMgrInterface),
++ [&](sdbusplus::message::message& msg) {
++ log<level::DEBUG>("UserMgr properties changed signal");
++ std::map<std::string, DbusVariant> props;
++ std::string iface;
++ msg.read(iface, props);
++ for (const auto& t : props)
++ {
++ if (t.first == propNameAllPrivileges)
++ {
++ supportedPrivList = sdbusplus::message::variant_ns::get<
++ std::vector<std::string>>(t.second);
++ }
++ }
++ });
++ }
++ return;
+ }
+
+ bool Manager::createDefaultNetworkFiles(bool force)
+diff --git a/network_manager.hpp b/network_manager.hpp
+index edb341f..e16b205 100644
+--- a/network_manager.hpp
++++ b/network_manager.hpp
+@@ -137,6 +137,9 @@ class Manager : public details::VLANCreateIface
+ return (interfaces.find(intf) != interfaces.end());
+ }
+
++ /** supported privilege list **/
++ std::vector<std::string> supportedPrivList;
++
+ protected:
+ /** @brief Persistent sdbusplus DBus bus connection. */
+ sdbusplus::bus::bus& bus;
+@@ -159,6 +162,12 @@ class Manager : public details::VLANCreateIface
+
+ /** @brief Network Configuration directory. */
+ fs::path confDir;
++
++ /** Get the user management service name dynamically **/
++ std::string getUserServiceName();
++
++ /** @brief initializes the supportedPrivilege List */
++ void initSupportedPrivilges();
+ };
+
+ } // namespace network
+--
+2.21.0
+
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network_%.bbappend b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network_%.bbappend
new file mode 100644
index 000000000..db231d42f
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/phosphor-network_%.bbappend
@@ -0,0 +1,9 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+DEPENDS += "nlohmann-json boost"
+
+SRC_URI += "git://github.com/openbmc/phosphor-networkd"
+SRC_URI += "file://0003-Adding-channel-specific-privilege-to-network.patch \
+ file://0001-Enhance-DHCP-beyond-just-OFF-and-IPv4-IPv6-enabled.patch \
+ "
+SRCREV = "cb42fe26febc9e457a9c4279278bd8c85f60851a"
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr.bb b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr.bb
new file mode 100644
index 000000000..0dab0fc1a
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr.bb
@@ -0,0 +1,24 @@
+SUMMARY = "Enforce static MAC addresses"
+DESCRIPTION = "Set a priority on MAC addresses to run with: \
+ factory-specified > u-boot-specified > random"
+
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+
+PV = "1.0"
+
+LICENSE = "Apache-2.0"
+LIC_FILES_CHKSUM = "file://${INTELBASE}/COPYING.apache-2.0;md5=34400b68072d710fecd0a2940a0d1658"
+
+SRC_URI = "\
+ file://mac-check \
+ file://${PN}.service \
+ "
+
+inherit obmc-phosphor-systemd
+
+SYSTEMD_SERVICE_${PN} += "${PN}.service"
+
+do_install() {
+ install -d ${D}${bindir}
+ install -m 0755 ${WORKDIR}/mac-check ${D}${bindir}
+}
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/mac-check b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/mac-check
new file mode 100644
index 000000000..639b6c5ee
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/mac-check
@@ -0,0 +1,79 @@
+#!/bin/sh
+# Copyright 2018 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+read_hw_mac() {
+ local iface="$1"
+ cat /sys/class/net/"$iface"/address
+}
+
+set_hw_mac() {
+ local iface="$1"
+ local mac="$2"
+ ip link show dev "$iface" | grep -q "${iface}:.*\<UP\>" 2>/dev/null
+ local up=$?
+ [[ $up -eq 0 ]] && ip link set dev "$iface" down
+ ip link set dev "$iface" address "$mac"
+ [[ $up -eq 0 ]] && ip link set dev "$iface" up
+}
+
+SOFS_MNT=/var/sofs
+read_sofs_mac() {
+ local iface="$1"
+ cat "${SOFS_MNT}/factory-settings/network/mac/${iface}" 2>/dev/null
+}
+
+read_fw_env_mac() {
+ local envname="$1"
+ fw_printenv "$envname" 2>/dev/null | sed "s/^$envname=//"
+}
+
+set_fw_env_mac() {
+ local envname="$1"
+ local mac="$2"
+ fw_setenv "$envname" "$mac"
+}
+
+mac_check() {
+ local iface="$1"
+ local envname="$2"
+
+ # read current HW MAC addr
+ local hw_mac=$(read_hw_mac "$iface")
+
+ # read saved sofs MAC addr
+ local sofs_mac=$(read_sofs_mac "$iface")
+
+ # if set and not the same as HW addr, set HW addr
+ if [ -n "$sofs_mac" ] && [ "$hw_mac" != "$sofs_mac" ]; then
+ set_hw_mac "$iface" "$sofs_mac"
+ hw_mac="$sofs_mac"
+ fi
+
+ # read saved fw_env MAC addr
+ local fw_env_mac=$(read_fw_env_mac "$envname")
+
+ # save to fw_env if not the same as HW addr
+ if [ -z "$fw_env_mac" ] || [ "$fw_env_mac" != "$hw_mac" ]; then
+ set_fw_env_mac "$envname" "$hw_mac"
+ fi
+}
+
+mkdir -p ${SOFS_MNT}/factory-settings/network/mac
+while read IFACE UBDEV; do
+ mac_check "$IFACE" "$UBDEV"
+done <<-END_CONF
+ eth0 eth1addr
+ eth1 ethaddr
+END_CONF
diff --git a/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/static-mac-addr.service b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/static-mac-addr.service
new file mode 100644
index 000000000..86371db11
--- /dev/null
+++ b/meta-openbmc-mods/meta-common/recipes-network/network/static-mac-addr/static-mac-addr.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Enforce Static MAC addr mapping
+
+[Service]
+Type=oneshot
+Restart=no
+ExecStart=/usr/bin/mac-check
+
+[Install]
+WantedBy=network.target
+