build: Split c++ sources into a subdirectory
Change-Id: Iedea50c688189ae4953195105e323f7173d17a4b
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/argument.cpp b/src/argument.cpp
new file mode 100644
index 0000000..bcd5a13
--- /dev/null
+++ b/src/argument.cpp
@@ -0,0 +1,99 @@
+/**
+ * Copyright © 2018 IBM 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.
+ */
+#include "argument.hpp"
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+
+namespace phosphor
+{
+namespace network
+{
+namespace ncsi
+{
+
+ArgumentParser::ArgumentParser(int argc, char** argv)
+{
+ int option = 0;
+ while (-1 != (option = getopt_long(argc, argv, optionStr, options, NULL)))
+ {
+ if ((option == '?') || (option == 'h'))
+ {
+ usage(argv);
+ exit(-1);
+ }
+
+ auto i = &options[0];
+ while ((i->val != option) && (i->val != 0))
+ {
+ ++i;
+ }
+
+ if (i->val)
+ {
+ arguments[i->name] = (i->has_arg ? optarg : trueString);
+ }
+ }
+}
+
+const std::string& ArgumentParser::operator[](const std::string& opt)
+{
+ auto i = arguments.find(opt);
+ if (i == arguments.end())
+ {
+ return emptyString;
+ }
+ else
+ {
+ return i->second;
+ }
+}
+
+void ArgumentParser::usage(char** argv)
+{
+ std::cerr << "Usage: " << argv[0] << " [options]\n";
+ std::cerr << "Options:\n";
+ std::cerr << " --help Print this menu.\n";
+ std::cerr << " --info=<info> Retrieve info about NCSI topology.\n";
+ std::cerr << " --set=<set> Set a specific package/channel.\n";
+ std::cerr
+ << " --clear=<clear> Clear all the settings on the interface.\n";
+ std::cerr << " --package=<package> Specify a package.\n";
+ std::cerr << " --channel=<channel> Specify a channel.\n";
+ std::cerr << " --index=<device index> Specify device ifindex.\n";
+ std::cerr << std::flush;
+}
+
+const option ArgumentParser::options[] = {
+ {"info", no_argument, NULL, 'i'},
+ {"set", no_argument, NULL, 's'},
+ {"clear", no_argument, NULL, 'r'},
+ {"package", required_argument, NULL, 'p'},
+ {"channel", required_argument, NULL, 'c'},
+ {"index", required_argument, NULL, 'x'},
+ {"help", no_argument, NULL, 'h'},
+ {0, 0, 0, 0},
+};
+
+const char* ArgumentParser::optionStr = "i:s:r:p:c:x:h?";
+
+const std::string ArgumentParser::trueString = "true";
+const std::string ArgumentParser::emptyString = "";
+
+} // namespace ncsi
+} // namespace network
+} // namespace phosphor
diff --git a/src/argument.hpp b/src/argument.hpp
new file mode 100644
index 0000000..28e5ce3
--- /dev/null
+++ b/src/argument.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <getopt.h>
+
+#include <map>
+#include <string>
+
+namespace phosphor
+{
+namespace network
+{
+namespace ncsi
+{
+/** @brief Class - Encapsulates parsing command line options and
+ * populating arguments
+ */
+class ArgumentParser
+{
+ public:
+ ArgumentParser() = delete;
+ ~ArgumentParser() = default;
+ ArgumentParser(const ArgumentParser&) = delete;
+ ArgumentParser& operator=(const ArgumentParser&) = delete;
+ ArgumentParser(ArgumentParser&&) = default;
+ ArgumentParser& operator=(ArgumentParser&&) = default;
+
+ /** @brief Constructs Argument object
+ *
+ * @param argc - the main function's argc passed as is
+ * @param argv - the main function's argv passed as is
+ * @return Object constructed
+ */
+ ArgumentParser(int argc, char** argv);
+
+ /** @brief Given an option, returns its argument(optarg)
+ *
+ * @param opt - command line option string
+ *
+ * @return argument which is a standard optarg
+ */
+ const std::string& operator[](const std::string& opt);
+
+ /** @brief Displays usage
+ *
+ * @param argv - the main function's argv passed as is
+ */
+ static void usage(char** argv);
+
+ /** @brief Set to 'true' when an option is passed */
+ static const std::string trueString;
+
+ /** @brief Set to '' when an option is not passed */
+ static const std::string emptyString;
+
+ private:
+ /** @brief Option to argument mapping */
+ std::map<const std::string, std::string> arguments;
+
+ /** @brief Array of struct options as needed by getopt_long */
+ static const option options[];
+
+ /** @brief optstring as needed by getopt_long */
+ static const char* optionStr;
+};
+
+} // namespace ncsi
+} // namespace network
+} // namespace phosphor
diff --git a/src/config_parser.cpp b/src/config_parser.cpp
new file mode 100644
index 0000000..c4af404
--- /dev/null
+++ b/src/config_parser.cpp
@@ -0,0 +1,164 @@
+#include "config_parser.hpp"
+
+#include <algorithm>
+#include <fstream>
+#include <list>
+#include <phosphor-logging/log.hpp>
+#include <regex>
+#include <string>
+#include <unordered_map>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using namespace phosphor::logging;
+
+Parser::Parser(const fs::path& filePath)
+{
+ setFile(filePath);
+}
+
+std::tuple<ReturnCode, KeyValueMap>
+ Parser::getSection(const std::string& section)
+{
+ auto it = sections.find(section);
+ if (it == sections.end())
+ {
+ KeyValueMap keyValues;
+ return std::make_tuple(ReturnCode::SECTION_NOT_FOUND,
+ std::move(keyValues));
+ }
+
+ return std::make_tuple(ReturnCode::SUCCESS, it->second);
+}
+
+std::tuple<ReturnCode, ValueList> Parser::getValues(const std::string& section,
+ const std::string& key)
+{
+ ValueList values;
+ KeyValueMap keyValues{};
+ auto rc = ReturnCode::SUCCESS;
+
+ std::tie(rc, keyValues) = getSection(section);
+ if (rc != ReturnCode::SUCCESS)
+ {
+ return std::make_tuple(rc, std::move(values));
+ }
+
+ auto it = keyValues.find(key);
+ if (it == keyValues.end())
+ {
+ return std::make_tuple(ReturnCode::KEY_NOT_FOUND, std::move(values));
+ }
+
+ for (; it != keyValues.end() && key == it->first; it++)
+ {
+ values.push_back(it->second);
+ }
+
+ return std::make_tuple(ReturnCode::SUCCESS, std::move(values));
+}
+
+bool Parser::isValueExist(const std::string& section, const std::string& key,
+ const std::string& value)
+{
+ auto rc = ReturnCode::SUCCESS;
+ ValueList values;
+ std::tie(rc, values) = getValues(section, key);
+
+ if (rc != ReturnCode::SUCCESS)
+ {
+ return false;
+ }
+ auto it = std::find(values.begin(), values.end(), value);
+ return it != std::end(values) ? true : false;
+}
+
+void Parser::setValue(const std::string& section, const std::string& key,
+ const std::string& value)
+{
+ KeyValueMap values;
+ auto it = sections.find(section);
+ if (it != sections.end())
+ {
+ values = std::move(it->second);
+ }
+ values.insert(std::make_pair(key, value));
+
+ if (it != sections.end())
+ {
+ it->second = std::move(values);
+ }
+ else
+ {
+ sections.insert(std::make_pair(section, std::move(values)));
+ }
+}
+
+#if 0
+void Parser::print()
+{
+ for (auto section : sections)
+ {
+ std::cout << "[" << section.first << "]\n\n";
+ for (auto keyValue : section.second)
+ {
+ std::cout << keyValue.first << "=" << keyValue.second << "\n";
+ }
+ }
+}
+#endif
+
+void Parser::setFile(const fs::path& filePath)
+{
+ this->filePath = filePath;
+ std::fstream stream;
+ stream.open(filePath.string(), std::fstream::in);
+
+ if (!stream.is_open())
+ {
+ return;
+ }
+ // clear all the section data.
+ sections.clear();
+ parse(stream);
+ stream.close();
+}
+
+void Parser::parse(std::istream& in)
+{
+ static const std::regex commentRegex{R"x(\s*[;#])x"};
+ static const std::regex sectionRegex{R"x(\s*\[([^\]]+)\])x"};
+ static const std::regex valueRegex{R"x(\s*(\S[^ \t=]*)\s*=\s*(\S+)\s*$)x"};
+ std::string section;
+ std::smatch pieces;
+ for (std::string line; std::getline(in, line);)
+ {
+ if (line.empty() || std::regex_match(line, pieces, commentRegex))
+ {
+ // skip comment lines and blank lines
+ }
+ else if (std::regex_match(line, pieces, sectionRegex))
+ {
+ if (pieces.size() == 2)
+ {
+ section = pieces[1].str();
+ }
+ }
+ else if (std::regex_match(line, pieces, valueRegex))
+ {
+ if (pieces.size() == 3)
+ {
+ setValue(section, pieces[1].str(), pieces[2].str());
+ }
+ }
+ }
+}
+
+} // namespace config
+} // namespace network
+} // namespace phosphor
diff --git a/src/config_parser.hpp b/src/config_parser.hpp
new file mode 100644
index 0000000..8af4a34
--- /dev/null
+++ b/src/config_parser.hpp
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <filesystem>
+#include <map>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <vector>
+
+namespace phosphor
+{
+namespace network
+{
+namespace config
+{
+
+using Section = std::string;
+using KeyValueMap = std::multimap<std::string, std::string>;
+using ValueList = std::vector<std::string>;
+
+namespace fs = std::filesystem;
+
+enum class ReturnCode
+{
+ SUCCESS = 0x0,
+ SECTION_NOT_FOUND = 0x1,
+ KEY_NOT_FOUND = 0x2,
+};
+
+class Parser
+{
+ public:
+ Parser() = default;
+
+ /** @brief Constructor
+ * @param[in] fileName - Absolute path of the file which will be parsed.
+ */
+
+ Parser(const fs::path& fileName);
+
+ /** @brief Get the values of the given key and section.
+ * @param[in] section - section name.
+ * @param[in] key - key to look for.
+ * @returns the tuple of return code and the
+ * values associated with the key.
+ */
+
+ std::tuple<ReturnCode, ValueList> getValues(const std::string& section,
+ const std::string& key);
+
+ /** @brief Set the value of the given key and section.
+ * @param[in] section - section name.
+ * @param[in] key - key name.
+ * @param[in] value - value.
+ */
+
+ void setValue(const std::string& section, const std::string& key,
+ const std::string& value);
+
+ /** @brief Set the file name and parse it.
+ * @param[in] fileName - Absolute path of the file.
+ */
+
+ void setFile(const fs::path& fileName);
+
+ private:
+ /** @brief Parses the given file and fills the data.
+ * @param[in] stream - inputstream.
+ */
+
+ void parse(std::istream& stream);
+
+ /** @brief Get all the key values of the given section.
+ * @param[in] section - section name.
+ * @returns the tuple of return code and the map of (key,value).
+ */
+
+ std::tuple<ReturnCode, KeyValueMap> getSection(const std::string& section);
+
+ /** @brief checks that whether the value exist in the
+ * given section.
+ * @param[in] section - section name.
+ * @param[in] key - key name.
+ * @param[in] value - value.
+ * @returns true if exist otherwise false.
+ */
+
+ bool isValueExist(const std::string& section, const std::string& key,
+ const std::string& value);
+
+ std::unordered_map<Section, KeyValueMap> sections;
+ fs::path filePath;
+};
+
+} // namespace config
+} // namespace network
+} // namespace phosphor
diff --git a/src/dhcp_configuration.cpp b/src/dhcp_configuration.cpp
new file mode 100644
index 0000000..690ae47
--- /dev/null
+++ b/src/dhcp_configuration.cpp
@@ -0,0 +1,111 @@
+#include "config.h"
+
+#include "dhcp_configuration.hpp"
+
+#include "network_manager.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace dhcp
+{
+
+using namespace phosphor::network;
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+bool Configuration::sendHostNameEnabled(bool value)
+{
+ if (value == sendHostNameEnabled())
+ {
+ return value;
+ }
+
+ auto name = ConfigIntf::sendHostNameEnabled(value);
+ manager.writeToConfigurationFile();
+
+ return name;
+}
+
+bool Configuration::hostNameEnabled(bool value)
+{
+ if (value == hostNameEnabled())
+ {
+ return value;
+ }
+
+ auto name = ConfigIntf::hostNameEnabled(value);
+ manager.writeToConfigurationFile();
+ manager.restartSystemdUnit(phosphor::network::networkdService);
+
+ return name;
+}
+
+bool Configuration::ntpEnabled(bool value)
+{
+ if (value == ntpEnabled())
+ {
+ return value;
+ }
+
+ auto ntp = ConfigIntf::ntpEnabled(value);
+ manager.writeToConfigurationFile();
+ manager.restartSystemdUnit(phosphor::network::networkdService);
+ manager.restartSystemdUnit(phosphor::network::timeSynchdService);
+
+ return ntp;
+}
+
+bool Configuration::dnsEnabled(bool value)
+{
+ if (value == dnsEnabled())
+ {
+ return value;
+ }
+
+ auto dns = ConfigIntf::dnsEnabled(value);
+ manager.writeToConfigurationFile();
+ manager.restartSystemdUnit(phosphor::network::networkdService);
+
+ return dns;
+}
+
+bool Configuration::getDHCPPropFromConf(const std::string& prop)
+{
+ fs::path confPath = manager.getConfDir();
+ auto interfaceStrList = getInterfaces();
+ // get the first interface name, we need it to know config file name.
+ auto interface = *interfaceStrList.begin();
+ auto fileName = systemd::config::networkFilePrefix + interface +
+ systemd::config::networkFileSuffix;
+
+ confPath /= fileName;
+ // systemd default behaviour is all DHCP fields should be enabled by
+ // default.
+ auto propValue = true;
+ config::Parser parser(confPath);
+
+ auto rc = config::ReturnCode::SUCCESS;
+ config::ValueList values{};
+ std::tie(rc, values) = parser.getValues("DHCP", prop);
+
+ if (rc != config::ReturnCode::SUCCESS)
+ {
+ log<level::DEBUG>("Unable to get the value from section DHCP",
+ entry("PROP=%s", prop.c_str()), entry("RC=%d", rc));
+ return propValue;
+ }
+
+ if (values[0] == "false")
+ {
+ propValue = false;
+ }
+ return propValue;
+}
+} // namespace dhcp
+} // namespace network
+} // namespace phosphor
diff --git a/src/dhcp_configuration.hpp b/src/dhcp_configuration.hpp
new file mode 100644
index 0000000..441c7b2
--- /dev/null
+++ b/src/dhcp_configuration.hpp
@@ -0,0 +1,117 @@
+#pragma once
+
+#include "config_parser.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <xyz/openbmc_project/Network/DHCPConfiguration/server.hpp>
+
+#ifndef SDBUSPP_NEW_CAMELCASE
+#define dnsEnabled dNSEnabled
+#define ntpEnabled nTPEnabled
+#endif
+
+namespace phosphor
+{
+namespace network
+{
+
+class Manager; // forward declaration of network manager.
+
+namespace dhcp
+{
+
+using ConfigIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::DHCPConfiguration;
+
+using Iface = sdbusplus::server::object::object<ConfigIntf>;
+
+/** @class Configuration
+ * @brief DHCP configuration.
+ * @details A concrete implementation for the
+ * xyz.openbmc_project.Network.DHCP DBus interface.
+ */
+class Configuration : public Iface
+{
+ public:
+ Configuration() = default;
+ Configuration(const Configuration&) = delete;
+ Configuration& operator=(const Configuration&) = delete;
+ Configuration(Configuration&&) = delete;
+ Configuration& operator=(Configuration&&) = delete;
+ virtual ~Configuration() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] parent - Parent object.
+ */
+ Configuration(sdbusplus::bus::bus& bus, const std::string& objPath,
+ Manager& parent) :
+ Iface(bus, objPath.c_str(), true),
+ bus(bus), manager(parent)
+ {
+ ConfigIntf::dnsEnabled(getDHCPPropFromConf("UseDNS"));
+ ConfigIntf::ntpEnabled(getDHCPPropFromConf("UseNTP"));
+ ConfigIntf::hostNameEnabled(getDHCPPropFromConf("UseHostname"));
+ ConfigIntf::sendHostNameEnabled(getDHCPPropFromConf("SendHostname"));
+ emit_object_added();
+ }
+
+ /** @brief If true then DNS servers received from the DHCP server
+ * will be used and take precedence over any statically
+ * configured ones.
+ * @param[in] value - true if DNS server needed from DHCP server
+ * else false.
+ */
+ bool dnsEnabled(bool value) override;
+
+ /** @brief If true then NTP servers received from the DHCP server
+ will be used by systemd-timesyncd.
+ * @param[in] value - true if NTP server needed from DHCP server
+ * else false.
+ */
+ bool ntpEnabled(bool value) override;
+
+ /** @brief If true then Hostname received from the DHCP server will
+ * be set as the hostname of the system
+ * @param[in] value - true if hostname needed from the DHCP server
+ * else false.
+ *
+ */
+ bool hostNameEnabled(bool value) override;
+
+ /** @brief if true then it will cause an Option 12 field, i.e machine's
+ * hostname, will be included in the DHCP packet.
+ * @param[in] value - true if machine's host name needs to be included
+ * in the DHCP packet.
+ */
+ bool sendHostNameEnabled(bool value) override;
+
+ /** @brief read the DHCP Prop value from the configuration file
+ * @param[in] prop - DHCP Prop name.
+ */
+ bool getDHCPPropFromConf(const std::string& prop);
+
+ /* @brief Network Manager needed the below function to know the
+ * value of the properties (ntpEnabled,dnsEnabled,hostnameEnabled
+ sendHostNameEnabled).
+ *
+ */
+ using ConfigIntf::dnsEnabled;
+ using ConfigIntf::hostNameEnabled;
+ using ConfigIntf::ntpEnabled;
+ using ConfigIntf::sendHostNameEnabled;
+
+ private:
+ /** @brief sdbusplus DBus bus connection. */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief Network Manager object. */
+ phosphor::network::Manager& manager;
+};
+
+} // namespace dhcp
+} // namespace network
+} // namespace phosphor
diff --git a/src/dns_updater.cpp b/src/dns_updater.cpp
new file mode 100644
index 0000000..bf29fff
--- /dev/null
+++ b/src/dns_updater.cpp
@@ -0,0 +1,58 @@
+#include "config.h"
+
+#include "dns_updater.hpp"
+
+#include <fstream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace dns
+{
+namespace updater
+{
+
+void updateDNSEntries(const fs::path& inFile, const fs::path& outFile)
+{
+ using namespace phosphor::logging;
+ using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+ std::fstream outStream(outFile, std::fstream::out);
+ if (!outStream.is_open())
+ {
+ log<level::ERR>("Unable to open output file",
+ entry("FILE=%s", outFile.c_str()));
+ elog<InternalFailure>();
+ }
+
+ std::fstream inStream(inFile, std::fstream::in);
+ if (!inStream.is_open())
+ {
+ log<level::ERR>("Unable to open the input file",
+ entry("FILE=%s", inFile.c_str()));
+ elog<InternalFailure>();
+ }
+
+ outStream << "### Generated by phosphor-networkd ###\n";
+
+ for (std::string line; std::getline(inStream, line);)
+ {
+ auto index = line.find("DNS=");
+ if (index != std::string::npos)
+ {
+ auto dns = line.substr(index + 4);
+ outStream << "nameserver " << dns << "\n";
+ }
+ }
+ return;
+}
+
+} // namespace updater
+} // namespace dns
+} // namespace network
+} // namespace phosphor
diff --git a/src/dns_updater.hpp b/src/dns_updater.hpp
new file mode 100644
index 0000000..5d23b29
--- /dev/null
+++ b/src/dns_updater.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <filesystem>
+
+namespace phosphor
+{
+namespace network
+{
+namespace dns
+{
+namespace updater
+{
+
+namespace fs = std::filesystem;
+
+constexpr auto RESOLV_CONF = "/etc/resolv.conf";
+
+/** @brief Reads DNS entries supplied by DHCP and updates specified file
+ *
+ * @param[in] inFile - File having DNS entries supplied by DHCP
+ * @param[in] outFile - File to write the nameserver entries to
+ */
+void updateDNSEntries(const fs::path& inFile, const fs::path& outFile);
+
+/** @brief User callback handler invoked by inotify watcher
+ *
+ * Needed to enable production and test code so that the right
+ * callback functions could be implemented
+ *
+ * @param[in] inFile - File having DNS entries supplied by DHCP
+ */
+inline void processDNSEntries(const fs::path& inFile)
+{
+ return updateDNSEntries(inFile, RESOLV_CONF);
+}
+
+} // namespace updater
+} // namespace dns
+} // namespace network
+} // namespace phosphor
diff --git a/src/ethernet_interface.cpp b/src/ethernet_interface.cpp
new file mode 100644
index 0000000..ce424ff
--- /dev/null
+++ b/src/ethernet_interface.cpp
@@ -0,0 +1,1179 @@
+#include "config.h"
+
+#include "ethernet_interface.hpp"
+
+#include "config_parser.hpp"
+#include "neighbor.hpp"
+#include "network_manager.hpp"
+#include "routing_table.hpp"
+#include "vlan_interface.hpp"
+
+#include <arpa/inet.h>
+#include <linux/ethtool.h>
+#include <linux/rtnetlink.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sstream>
+#include <stdplus/raw.hpp>
+#include <string>
+#include <string_view>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+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
+{
+ EthernetIntfSocket(int domain, int type, int protocol)
+ {
+ if ((sock = socket(domain, type, protocol)) < 0)
+ {
+ log<level::ERR>("socket creation failed:",
+ entry("ERROR=%s", strerror(errno)));
+ }
+ }
+
+ ~EthernetIntfSocket()
+ {
+ if (sock >= 0)
+ {
+ close(sock);
+ }
+ }
+
+ int sock{-1};
+};
+
+std::map<EthernetInterface::DHCPConf, std::string> mapDHCPToSystemd = {
+ {EthernetInterface::DHCPConf::both, "true"},
+ {EthernetInterface::DHCPConf::v4, "ipv4"},
+ {EthernetInterface::DHCPConf::v6, "ipv6"},
+ {EthernetInterface::DHCPConf::none, "false"}};
+
+EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
+ const std::string& objPath,
+ DHCPConf dhcpEnabled, Manager& parent,
+ bool emitSignal) :
+ Ifaces(bus, objPath.c_str(), true),
+ bus(bus), manager(parent), objPath(objPath)
+{
+ auto intfName = objPath.substr(objPath.rfind("/") + 1);
+ std::replace(intfName.begin(), intfName.end(), '_', '.');
+ interfaceName(intfName);
+ EthernetInterfaceIntf::dhcpEnabled(dhcpEnabled);
+ EthernetInterfaceIntf::ipv6AcceptRA(getIPv6AcceptRAFromConf());
+ route::Table routingTable;
+ auto gatewayList = routingTable.getDefaultGateway();
+ auto gateway6List = routingTable.getDefaultGateway6();
+ std::string defaultGateway;
+ std::string defaultGateway6;
+
+ for (auto& gateway : gatewayList)
+ {
+ if (gateway.first == intfName)
+ {
+ defaultGateway = gateway.second;
+ break;
+ }
+ }
+
+ for (auto& gateway6 : gateway6List)
+ {
+ if (gateway6.first == intfName)
+ {
+ defaultGateway6 = gateway6.second;
+ break;
+ }
+ }
+
+ EthernetInterfaceIntf::defaultGateway(defaultGateway);
+ EthernetInterfaceIntf::defaultGateway6(defaultGateway6);
+ // Don't get the mac address from the system as the mac address
+ // would be same as parent interface.
+ if (intfName.find(".") == std::string::npos)
+ {
+ MacAddressIntf::macAddress(getMACAddress(intfName));
+ }
+ EthernetInterfaceIntf::ntpServers(getNTPServersFromConf());
+
+ EthernetInterfaceIntf::linkUp(linkUp());
+ EthernetInterfaceIntf::nicEnabled(nicEnabled());
+
+#ifdef NIC_SUPPORTS_ETHTOOL
+ InterfaceInfo ifInfo = EthernetInterface::getInterfaceInfo();
+
+ EthernetInterfaceIntf::autoNeg(std::get<2>(ifInfo));
+ EthernetInterfaceIntf::speed(std::get<0>(ifInfo));
+#endif
+
+ // Emit deferred signal.
+ if (emitSignal)
+ {
+ this->emit_object_added();
+ }
+}
+
+static IP::Protocol convertFamily(int family)
+{
+ switch (family)
+ {
+ case AF_INET:
+ return IP::Protocol::IPv4;
+ case AF_INET6:
+ return IP::Protocol::IPv6;
+ }
+
+ 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,
+ const std::string& nextDHCPState)
+{
+ return ((nextDHCPState == "true") ||
+ ((nextDHCPState == "ipv6") && (family == IP::Protocol::IPv6)) ||
+ ((nextDHCPState == "ipv4") && (family == IP::Protocol::IPv4)));
+}
+
+bool EthernetInterface::originIsManuallyAssigned(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()];
+
+ for (auto& addr : addrs)
+ {
+ IP::Protocol addressType = convertFamily(addr.addrType);
+ IP::AddressOrigin origin = IP::AddressOrigin::Static;
+ if (dhcpIsEnabled(addressType))
+ {
+ origin = IP::AddressOrigin::DHCP;
+ }
+ if (isLinkLocalIP(addr.ipaddress))
+ {
+ origin = IP::AddressOrigin::LinkLocal;
+ }
+ // Obsolete parameter
+ std::string gateway = "";
+
+ std::string ipAddressObjectPath = generateObjectPath(
+ addressType, addr.ipaddress, addr.prefix, gateway);
+
+ this->addrs.insert_or_assign(
+ addr.ipaddress,
+ std::make_shared<phosphor::network::IPAddress>(
+ bus, ipAddressObjectPath.c_str(), *this, addressType,
+ addr.ipaddress, origin, addr.prefix, gateway));
+ }
+}
+
+void EthernetInterface::createStaticNeighborObjects()
+{
+ staticNeighbors.clear();
+
+ NeighborFilter filter;
+ filter.interface = ifIndex();
+ filter.state = NUD_PERMANENT;
+ auto neighbors = getCurrentNeighbors(filter);
+ for (const auto& neighbor : neighbors)
+ {
+ if (!neighbor.mac)
+ {
+ continue;
+ }
+ std::string ip = toString(neighbor.address);
+ std::string mac = mac_address::toString(*neighbor.mac);
+ std::string objectPath = generateStaticNeighborObjectPath(ip, mac);
+ staticNeighbors.emplace(ip,
+ std::make_shared<phosphor::network::Neighbor>(
+ bus, objectPath.c_str(), *this, ip, mac,
+ Neighbor::State::Permanent));
+ }
+}
+
+unsigned EthernetInterface::ifIndex() const
+{
+ unsigned idx = if_nametoindex(interfaceName().c_str());
+ if (idx == 0)
+ {
+ throw std::system_error(errno, std::generic_category(),
+ "if_nametoindex");
+ }
+ return idx;
+}
+
+ObjectPath EthernetInterface::ip(IP::Protocol protType, std::string ipaddress,
+ uint8_t prefixLength, std::string gateway)
+{
+ if (dhcpIsEnabled(protType))
+ {
+ log<level::INFO>("DHCP enabled on the interface"),
+ entry("INTERFACE=%s", interfaceName().c_str());
+ disableDHCP(protType);
+ }
+
+ IP::AddressOrigin origin = IP::AddressOrigin::Static;
+
+ int addressFamily = (protType == IP::Protocol::IPv4) ? AF_INET : AF_INET6;
+
+ if (!isValidIP(addressFamily, ipaddress))
+ {
+ log<level::ERR>("Not a valid IP address"),
+ entry("ADDRESS=%s", ipaddress.c_str());
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("ipaddress"),
+ Argument::ARGUMENT_VALUE(ipaddress.c_str()));
+ }
+
+ // Gateway is an obsolete parameter
+ gateway = "";
+
+ if (!isValidPrefix(addressFamily, prefixLength))
+ {
+ log<level::ERR>("PrefixLength is not correct "),
+ entry("PREFIXLENGTH=%" PRIu8, prefixLength);
+ elog<InvalidArgument>(
+ Argument::ARGUMENT_NAME("prefixLength"),
+ Argument::ARGUMENT_VALUE(std::to_string(prefixLength).c_str()));
+ }
+
+ std::string objectPath =
+ generateObjectPath(protType, ipaddress, prefixLength, gateway);
+ this->addrs.insert_or_assign(ipaddress,
+ std::make_shared<phosphor::network::IPAddress>(
+ bus, objectPath.c_str(), *this, protType,
+ ipaddress, origin, prefixLength, gateway));
+
+ manager.writeToConfigurationFile();
+ return objectPath;
+}
+
+ObjectPath EthernetInterface::neighbor(std::string ipAddress,
+ std::string macAddress)
+{
+ if (!isValidIP(AF_INET, ipAddress) && !isValidIP(AF_INET6, ipAddress))
+ {
+ log<level::ERR>("Not a valid IP address",
+ entry("ADDRESS=%s", ipAddress.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("ipAddress"),
+ Argument::ARGUMENT_VALUE(ipAddress.c_str()));
+ }
+ if (!mac_address::isUnicast(mac_address::fromString(macAddress)))
+ {
+ log<level::ERR>("Not a valid MAC address",
+ entry("MACADDRESS=%s", ipAddress.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("macAddress"),
+ Argument::ARGUMENT_VALUE(macAddress.c_str()));
+ }
+
+ std::string objectPath =
+ generateStaticNeighborObjectPath(ipAddress, macAddress);
+ staticNeighbors.emplace(ipAddress,
+ std::make_shared<phosphor::network::Neighbor>(
+ bus, objectPath.c_str(), *this, ipAddress,
+ macAddress, Neighbor::State::Permanent));
+ manager.writeToConfigurationFile();
+ return objectPath;
+}
+
+#ifdef NIC_SUPPORTS_ETHTOOL
+/*
+ Enable this code if your NIC driver supports the ETHTOOL features.
+ Do this by adding the following to your phosphor-network*.bbappend file.
+ EXTRA_OECONF_append = " --enable-nic-ethtool=yes"
+ The default compile mode is to omit getInterfaceInfo()
+*/
+InterfaceInfo EthernetInterface::getInterfaceInfo() const
+{
+ ifreq ifr = {};
+ ethtool_cmd edata = {};
+ LinkSpeed speed = {};
+ Autoneg autoneg = {};
+ DuplexMode duplex = {};
+ LinkUp linkState = {};
+ NICEnabled enabled = {};
+ EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+
+ if (eifSocket.sock < 0)
+ {
+ return std::make_tuple(speed, duplex, autoneg, linkState, enabled);
+ }
+
+ std::strncpy(ifr.ifr_name, interfaceName().c_str(), IFNAMSIZ - 1);
+ ifr.ifr_data = reinterpret_cast<char*>(&edata);
+
+ edata.cmd = ETHTOOL_GSET;
+ if (ioctl(eifSocket.sock, SIOCETHTOOL, &ifr) >= 0)
+ {
+ speed = edata.speed;
+ duplex = edata.duplex;
+ autoneg = edata.autoneg;
+ }
+
+ enabled = nicEnabled();
+ linkState = linkUp();
+
+ return std::make_tuple(speed, duplex, autoneg, linkState, enabled);
+}
+#endif
+
+/** @brief get the mac address of the interface.
+ * @return macaddress on success
+ */
+
+std::string
+ EthernetInterface::getMACAddress(const std::string& interfaceName) const
+{
+ std::string activeMACAddr = MacAddressIntf::macAddress();
+ EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+
+ if (eifSocket.sock < 0)
+ {
+ return activeMACAddr;
+ }
+
+ ifreq ifr = {};
+ std::strncpy(ifr.ifr_name, interfaceName.c_str(), IFNAMSIZ - 1);
+ if (ioctl(eifSocket.sock, SIOCGIFHWADDR, &ifr) != 0)
+ {
+ log<level::ERR>("ioctl failed for SIOCGIFHWADDR:",
+ entry("ERROR=%s", strerror(errno)));
+ elog<InternalFailure>();
+ }
+
+ static_assert(sizeof(ifr.ifr_hwaddr.sa_data) >= sizeof(ether_addr));
+ std::string_view hwaddr(reinterpret_cast<char*>(ifr.ifr_hwaddr.sa_data),
+ sizeof(ifr.ifr_hwaddr.sa_data));
+ return mac_address::toString(stdplus::raw::copyFrom<ether_addr>(hwaddr));
+}
+
+std::string EthernetInterface::generateId(const std::string& ipaddress,
+ uint8_t prefixLength,
+ const std::string& gateway)
+{
+ std::stringstream hexId;
+ std::string hashString = ipaddress;
+ hashString += std::to_string(prefixLength);
+ hashString += gateway;
+
+ // Only want 8 hex digits.
+ hexId << std::hex << ((std::hash<std::string>{}(hashString)) & 0xFFFFFFFF);
+ return hexId.str();
+}
+
+std::string EthernetInterface::generateNeighborId(const std::string& ipAddress,
+ const std::string& macAddress)
+{
+ std::stringstream hexId;
+ std::string hashString = ipAddress + macAddress;
+
+ // Only want 8 hex digits.
+ hexId << std::hex << ((std::hash<std::string>{}(hashString)) & 0xFFFFFFFF);
+ return hexId.str();
+}
+
+void EthernetInterface::deleteObject(const std::string& ipaddress)
+{
+ auto it = addrs.find(ipaddress);
+ if (it == addrs.end())
+ {
+ log<level::ERR>("DeleteObject:Unable to find the object.");
+ return;
+ }
+ this->addrs.erase(it);
+ manager.writeToConfigurationFile();
+}
+
+void EthernetInterface::deleteStaticNeighborObject(const std::string& ipAddress)
+{
+ auto it = staticNeighbors.find(ipAddress);
+ if (it == staticNeighbors.end())
+ {
+ log<level::ERR>(
+ "DeleteStaticNeighborObject:Unable to find the object.");
+ return;
+ }
+ staticNeighbors.erase(it);
+ manager.writeToConfigurationFile();
+}
+
+void EthernetInterface::deleteVLANFromSystem(const std::string& interface)
+{
+ auto confDir = manager.getConfDir();
+ fs::path networkFile = confDir;
+ networkFile /= systemd::config::networkFilePrefix + interface +
+ systemd::config::networkFileSuffix;
+
+ fs::path deviceFile = confDir;
+ deviceFile /= interface + systemd::config::deviceFileSuffix;
+
+ // delete the vlan network file
+ if (fs::is_regular_file(networkFile))
+ {
+ fs::remove(networkFile);
+ }
+
+ // delete the vlan device file
+ if (fs::is_regular_file(deviceFile))
+ {
+ fs::remove(deviceFile);
+ }
+
+ // TODO systemd doesn't delete the virtual network interface
+ // even after deleting all the related configuartion.
+ // https://github.com/systemd/systemd/issues/6600
+ try
+ {
+ deleteInterface(interface);
+ }
+ catch (InternalFailure& e)
+ {
+ commit<InternalFailure>();
+ }
+}
+
+void EthernetInterface::deleteVLANObject(const std::string& interface)
+{
+ auto it = vlanInterfaces.find(interface);
+ if (it == vlanInterfaces.end())
+ {
+ log<level::ERR>("DeleteVLANObject:Unable to find the object",
+ entry("INTERFACE=%s", interface.c_str()));
+ return;
+ }
+
+ deleteVLANFromSystem(interface);
+ // delete the interface
+ vlanInterfaces.erase(it);
+
+ manager.writeToConfigurationFile();
+}
+
+std::string EthernetInterface::generateObjectPath(
+ IP::Protocol addressType, const std::string& ipaddress,
+ uint8_t prefixLength, const std::string& gateway) const
+{
+ std::string type = convertForMessage(addressType);
+ type = type.substr(type.rfind('.') + 1);
+ std::transform(type.begin(), type.end(), type.begin(), ::tolower);
+
+ std::filesystem::path objectPath;
+ objectPath /= objPath;
+ objectPath /= type;
+ objectPath /= generateId(ipaddress, prefixLength, gateway);
+ return objectPath.string();
+}
+
+std::string EthernetInterface::generateStaticNeighborObjectPath(
+ const std::string& ipAddress, const std::string& macAddress) const
+{
+ std::filesystem::path objectPath;
+ objectPath /= objPath;
+ objectPath /= "static_neighbor";
+ objectPath /= generateNeighborId(ipAddress, macAddress);
+ return objectPath.string();
+}
+
+bool EthernetInterface::ipv6AcceptRA(bool value)
+{
+ if (value == EthernetInterfaceIntf::ipv6AcceptRA())
+ {
+ return value;
+ }
+ EthernetInterfaceIntf::ipv6AcceptRA(value);
+ manager.writeToConfigurationFile();
+ return value;
+}
+
+EthernetInterface::DHCPConf EthernetInterface::dhcpEnabled(DHCPConf value)
+{
+ if (value == EthernetInterfaceIntf::dhcpEnabled())
+ {
+ return value;
+ }
+
+ EthernetInterfaceIntf::dhcpEnabled(value);
+ manager.writeToConfigurationFile();
+ return value;
+}
+
+bool EthernetInterface::linkUp() const
+{
+ EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ bool value = EthernetInterfaceIntf::linkUp();
+
+ if (eifSocket.sock < 0)
+ {
+ return value;
+ }
+
+ ifreq ifr = {};
+ std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1);
+ if (ioctl(eifSocket.sock, SIOCGIFFLAGS, &ifr) == 0)
+ {
+ value = static_cast<bool>(ifr.ifr_flags & IFF_RUNNING);
+ }
+ else
+ {
+ log<level::ERR>("ioctl failed for SIOCGIFFLAGS:",
+ entry("ERROR=%s", strerror(errno)));
+ }
+ return value;
+}
+
+bool EthernetInterface::nicEnabled() const
+{
+ EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ bool value = EthernetInterfaceIntf::nicEnabled();
+
+ if (eifSocket.sock < 0)
+ {
+ return value;
+ }
+
+ ifreq ifr = {};
+ std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1);
+ if (ioctl(eifSocket.sock, SIOCGIFFLAGS, &ifr) == 0)
+ {
+ value = static_cast<bool>(ifr.ifr_flags & IFF_UP);
+ }
+ else
+ {
+ log<level::ERR>("ioctl failed for SIOCGIFFLAGS:",
+ entry("ERROR=%s", strerror(errno)));
+ }
+ return value;
+}
+
+bool EthernetInterface::nicEnabled(bool value)
+{
+ if (value == EthernetInterfaceIntf::nicEnabled())
+ {
+ return value;
+ }
+
+ EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (eifSocket.sock < 0)
+ {
+ return EthernetInterfaceIntf::nicEnabled();
+ }
+
+ ifreq ifr = {};
+ std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1);
+ if (ioctl(eifSocket.sock, SIOCGIFFLAGS, &ifr) != 0)
+ {
+ log<level::ERR>("ioctl failed for SIOCGIFFLAGS:",
+ entry("ERROR=%s", strerror(errno)));
+ return EthernetInterfaceIntf::nicEnabled();
+ }
+
+ ifr.ifr_flags &= ~IFF_UP;
+ ifr.ifr_flags |= value ? IFF_UP : 0;
+
+ if (ioctl(eifSocket.sock, SIOCSIFFLAGS, &ifr) != 0)
+ {
+ log<level::ERR>("ioctl failed for SIOCSIFFLAGS:",
+ entry("ERROR=%s", strerror(errno)));
+ return EthernetInterfaceIntf::nicEnabled();
+ }
+ EthernetInterfaceIntf::nicEnabled(value);
+ writeConfigurationFile();
+
+ return value;
+}
+
+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) &&
+ !isValidIP(AF_INET6, nameserverip))
+ {
+ log<level::ERR>("Not a valid IP address"),
+ entry("ADDRESS=%s", nameserverip.c_str());
+ elog<InvalidArgument>(
+ Argument::ARGUMENT_NAME("StaticNameserver"),
+ Argument::ARGUMENT_VALUE(nameserverip.c_str()));
+ }
+ }
+ try
+ {
+ EthernetInterfaceIntf::staticNameServers(value);
+ writeConfigurationFile();
+ // resolved reads the DNS server configuration from the
+ // network file.
+ manager.restartSystemdUnit(networkdService);
+ }
+ catch (InternalFailure& e)
+ {
+ log<level::ERR>("Exception processing DNS entries");
+ }
+ return EthernetInterfaceIntf::staticNameServers();
+}
+
+void EthernetInterface::loadNameServers()
+{
+ EthernetInterfaceIntf::nameservers(getNameServerFromResolvd());
+ EthernetInterfaceIntf::staticNameServers(getstaticNameServerFromConf());
+}
+
+ServerList EthernetInterface::getstaticNameServerFromConf()
+{
+ fs::path confPath = manager.getConfDir();
+
+ std::string fileName = systemd::config::networkFilePrefix +
+ interfaceName() + systemd::config::networkFileSuffix;
+ confPath /= fileName;
+ ServerList servers;
+ config::Parser parser(confPath.string());
+ auto rc = config::ReturnCode::SUCCESS;
+
+ std::tie(rc, servers) = parser.getValues("Network", "DNS");
+ if (rc != config::ReturnCode::SUCCESS)
+ {
+ log<level::DEBUG>("Unable to get the value for network[DNS]",
+ entry("RC=%d", rc));
+ }
+ 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)
+ {
+ int addressFamily = std::get<0>(*i);
+ std::vector<uint8_t>& ipaddress = std::get<1>(*i);
+
+ switch (addressFamily)
+ {
+ case AF_INET:
+ if (ipaddress.size() == sizeof(struct in_addr))
+ {
+ servers.push_back(toString(
+ *reinterpret_cast<struct in_addr*>(ipaddress.data())));
+ }
+ else
+ {
+ log<level::ERR>(
+ "Invalid data recived from Systemd-Resolved");
+ }
+ break;
+
+ case AF_INET6:
+ if (ipaddress.size() == sizeof(struct in6_addr))
+ {
+ servers.push_back(toString(
+ *reinterpret_cast<struct in6_addr*>(ipaddress.data())));
+ }
+ else
+ {
+ log<level::ERR>(
+ "Invalid data recived from Systemd-Resolved");
+ }
+ break;
+
+ default:
+ log<level::ERR>(
+ "Unsupported address family in DNS from Systemd-Resolved");
+ break;
+ }
+ }
+ return servers;
+}
+
+void EthernetInterface::loadVLAN(VlanId id)
+{
+ std::string vlanInterfaceName = interfaceName() + "." + std::to_string(id);
+ std::string path = objPath;
+ path += "_" + std::to_string(id);
+
+ DHCPConf dhcpEnabled =
+ getDHCPValue(manager.getConfDir().string(), vlanInterfaceName);
+ auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
+ bus, path.c_str(), dhcpEnabled, EthernetInterfaceIntf::nicEnabled(), id,
+ *this, manager);
+
+ // Fetch the ip address from the system
+ // and create the dbus object.
+ vlanIntf->createIPAddressObjects();
+ vlanIntf->createStaticNeighborObjects();
+
+ this->vlanInterfaces.emplace(std::move(vlanInterfaceName),
+ std::move(vlanIntf));
+}
+
+ObjectPath EthernetInterface::createVLAN(VlanId id)
+{
+ std::string vlanInterfaceName = interfaceName() + "." + std::to_string(id);
+ std::string path = objPath;
+ path += "_" + std::to_string(id);
+
+ // Pass the parents nicEnabled property, so that the child
+ // VLAN interface can inherit.
+
+ auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
+ bus, path.c_str(), EthernetInterface::DHCPConf::none,
+ EthernetInterfaceIntf::nicEnabled(), id, *this, manager);
+
+ // write the device file for the vlan interface.
+ vlanIntf->writeDeviceFile();
+
+ this->vlanInterfaces.emplace(vlanInterfaceName, std::move(vlanIntf));
+ // write the new vlan device entry to the configuration(network) file.
+ manager.writeToConfigurationFile();
+
+ return path;
+}
+
+bool EthernetInterface::getIPv6AcceptRAFromConf()
+{
+ fs::path confPath = manager.getConfDir();
+
+ std::string fileName = systemd::config::networkFilePrefix +
+ interfaceName() + systemd::config::networkFileSuffix;
+ confPath /= fileName;
+ config::ValueList values;
+ config::Parser parser(confPath.string());
+ auto rc = config::ReturnCode::SUCCESS;
+ std::tie(rc, values) = parser.getValues("Network", "IPv6AcceptRA");
+ if (rc != config::ReturnCode::SUCCESS)
+ {
+ log<level::DEBUG>("Unable to get the value for Network[IPv6AcceptRA]",
+ entry("rc=%d", rc));
+ return false;
+ }
+ return (values[0] == "true");
+}
+
+ServerList EthernetInterface::getNTPServersFromConf()
+{
+ fs::path confPath = manager.getConfDir();
+
+ std::string fileName = systemd::config::networkFilePrefix +
+ interfaceName() + systemd::config::networkFileSuffix;
+ confPath /= fileName;
+
+ ServerList servers;
+ config::Parser parser(confPath.string());
+ auto rc = config::ReturnCode::SUCCESS;
+
+ std::tie(rc, servers) = parser.getValues("Network", "NTP");
+ if (rc != config::ReturnCode::SUCCESS)
+ {
+ log<level::DEBUG>("Unable to get the value for Network[NTP]",
+ entry("rc=%d", rc));
+ }
+
+ return servers;
+}
+
+ServerList EthernetInterface::ntpServers(ServerList servers)
+{
+ auto ntpServers = EthernetInterfaceIntf::ntpServers(servers);
+
+ writeConfigurationFile();
+ // timesynchd reads the NTP server configuration from the
+ // network file.
+ manager.restartSystemdUnit(networkdService);
+ return ntpServers;
+}
+// Need to merge the below function with the code which writes the
+// config file during factory reset.
+// TODO openbmc/openbmc#1751
+
+void EthernetInterface::writeConfigurationFile()
+{
+ // write all the static ip address in the systemd-network conf file
+
+ using namespace std::string_literals;
+ namespace fs = std::filesystem;
+
+ // if there is vlan interafce then write the configuration file
+ // for vlan also.
+
+ for (const auto& intf : vlanInterfaces)
+ {
+ intf.second->writeConfigurationFile();
+ }
+
+ fs::path confPath = manager.getConfDir();
+
+ std::string fileName = systemd::config::networkFilePrefix +
+ interfaceName() + systemd::config::networkFileSuffix;
+ confPath /= fileName;
+ std::fstream stream;
+
+ stream.open(confPath.c_str(), std::fstream::out);
+ if (!stream.is_open())
+ {
+ log<level::ERR>("Unable to open the file",
+ entry("FILE=%s", confPath.c_str()));
+ elog<InternalFailure>();
+ }
+
+ // Write the device
+ stream << "[Match]\n";
+ stream << "Name=" << interfaceName() << "\n";
+
+ auto addrs = getAddresses();
+
+ // Write the link section
+ stream << "[Link]\n";
+ auto mac = MacAddressIntf::macAddress();
+ if (!mac.empty())
+ {
+ stream << "MACAddress=" << mac << "\n";
+ }
+
+ if (!EthernetInterfaceIntf::nicEnabled())
+ {
+ stream << "Unmanaged=yes\n";
+ }
+
+ // write the network section
+ stream << "[Network]\n";
+#ifdef LINK_LOCAL_AUTOCONFIGURATION
+ stream << "LinkLocalAddressing=yes\n";
+#else
+ stream << "LinkLocalAddressing=no\n";
+#endif
+ stream << std::boolalpha
+ << "IPv6AcceptRA=" << EthernetInterfaceIntf::ipv6AcceptRA() << "\n";
+
+ // Add the VLAN entry
+ for (const auto& intf : vlanInterfaces)
+ {
+ stream << "VLAN=" << intf.second->EthernetInterface::interfaceName()
+ << "\n";
+ }
+ // Add the NTP server
+ for (const auto& ntp : EthernetInterfaceIntf::ntpServers())
+ {
+ stream << "NTP=" << ntp << "\n";
+ }
+
+ // Add the DNS entry
+ for (const auto& dns : EthernetInterfaceIntf::staticNameServers())
+ {
+ stream << "DNS=" << dns << "\n";
+ }
+
+ // Add the DHCP entry
+ stream << "DHCP="s +
+ mapDHCPToSystemd[EthernetInterfaceIntf::dhcpEnabled()] + "\n";
+
+ // Static IP addresses
+ for (const auto& addr : addrs)
+ {
+ if (originIsManuallyAssigned(addr.second->origin()) &&
+ !dhcpIsEnabled(addr.second->type()))
+ {
+ // Process all static addresses
+ 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";
+ }
+ }
+
+ auto gateway = EthernetInterfaceIntf::defaultGateway();
+ if (!gateway.empty())
+ {
+ stream << "[Route]\n";
+ stream << "Gateway=" << gateway << "\n";
+ }
+
+ auto gateway6 = EthernetInterfaceIntf::defaultGateway6();
+ if (!gateway6.empty())
+ {
+ stream << "[Route]\n";
+ stream << "Gateway=" << gateway6 << "\n";
+ }
+
+ if (manager.getSystemConf())
+ {
+ const auto& gateway = manager.getSystemConf()->defaultGateway();
+ if (!gateway.empty())
+ {
+ stream << "[Route]\n";
+ stream << "Gateway=" << gateway << "\n";
+ }
+ const auto& gateway6 = manager.getSystemConf()->defaultGateway6();
+ if (!gateway6.empty())
+ {
+ stream << "[Route]\n";
+ stream << "Gateway=" << gateway6 << "\n";
+ }
+ }
+
+ // Write the neighbor sections
+ for (const auto& neighbor : staticNeighbors)
+ {
+ stream << "[Neighbor]"
+ << "\n";
+ stream << "Address=" << neighbor.second->ipAddress() << "\n";
+ stream << "MACAddress=" << neighbor.second->macAddress() << "\n";
+ }
+
+ // Write the dhcp section irrespective of whether DHCP is enabled or not
+ writeDHCPSection(stream);
+
+ stream.close();
+}
+
+void EthernetInterface::writeDHCPSection(std::fstream& stream)
+{
+ using namespace std::string_literals;
+ // write the dhcp section
+ stream << "[DHCP]\n";
+
+ // Hardcoding the client identifier to mac, to address below issue
+ // https://github.com/openbmc/openbmc/issues/1280
+ stream << "ClientIdentifier=mac\n";
+ if (manager.getDHCPConf())
+ {
+ auto value = manager.getDHCPConf()->dnsEnabled() ? "true"s : "false"s;
+ stream << "UseDNS="s + value + "\n";
+
+ value = manager.getDHCPConf()->ntpEnabled() ? "true"s : "false"s;
+ stream << "UseNTP="s + value + "\n";
+
+ value = manager.getDHCPConf()->hostNameEnabled() ? "true"s : "false"s;
+ stream << "UseHostname="s + value + "\n";
+
+ value =
+ manager.getDHCPConf()->sendHostNameEnabled() ? "true"s : "false"s;
+ stream << "SendHostname="s + value + "\n";
+ }
+}
+
+std::string EthernetInterface::macAddress(std::string value)
+{
+ ether_addr newMAC;
+ try
+ {
+ newMAC = mac_address::fromString(value);
+ }
+ catch (std::invalid_argument&)
+ {
+ log<level::ERR>("MACAddress is not valid.",
+ entry("MAC=%s", value.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
+ Argument::ARGUMENT_VALUE(value.c_str()));
+ }
+ if (!mac_address::isUnicast(newMAC))
+ {
+ log<level::ERR>("MACAddress is not valid.",
+ entry("MAC=%s", value.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("MACAddress"),
+ Argument::ARGUMENT_VALUE(value.c_str()));
+ }
+
+ auto interface = interfaceName();
+ std::string validMAC = mac_address::toString(newMAC);
+
+ // We don't need to update the system if the address is unchanged
+ ether_addr oldMAC = mac_address::fromString(MacAddressIntf::macAddress());
+ if (!stdplus::raw::equal(newMAC, oldMAC))
+ {
+ // Update everything that depends on the MAC value
+ for (const auto& [name, intf] : vlanInterfaces)
+ {
+ intf->MacAddressIntf::macAddress(validMAC);
+ }
+ MacAddressIntf::macAddress(validMAC);
+
+ // TODO: would remove the call below and
+ // just restart systemd-netwokd
+ // through https://github.com/systemd/systemd/issues/6696
+ execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(),
+ "down");
+ manager.writeToConfigurationFile();
+ }
+
+#ifdef HAVE_UBOOT_ENV
+ // Ensure that the valid address is stored in the u-boot-env
+ auto envVar = interfaceToUbootEthAddr(interface.c_str());
+ if (envVar)
+ {
+ // Trimming MAC addresses that are out of range. eg: AA:FF:FF:FF:FF:100;
+ // and those having more than 6 bytes. eg: AA:AA:AA:AA:AA:AA:BB
+ execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
+ validMAC.c_str());
+ }
+#endif // HAVE_UBOOT_ENV
+
+ return value;
+}
+
+void EthernetInterface::deleteAll()
+{
+ if (dhcpIsEnabled(IP::Protocol::IPv4, true))
+ {
+ log<level::INFO>("DHCP enabled on the interface"),
+ entry("INTERFACE=%s", interfaceName().c_str());
+ }
+
+ // clear all the ip on the interface
+ addrs.clear();
+ manager.writeToConfigurationFile();
+}
+
+std::string EthernetInterface::defaultGateway(std::string gateway)
+{
+ auto gw = EthernetInterfaceIntf::defaultGateway();
+ if (gw == gateway)
+ {
+ return gw;
+ }
+
+ if (!isValidIP(AF_INET, gateway))
+ {
+ log<level::ERR>("Not a valid v4 Gateway",
+ entry("GATEWAY=%s", gateway.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
+ Argument::ARGUMENT_VALUE(gateway.c_str()));
+ }
+ gw = EthernetInterfaceIntf::defaultGateway(gateway);
+ manager.writeToConfigurationFile();
+ return gw;
+}
+
+std::string EthernetInterface::defaultGateway6(std::string gateway)
+{
+ auto gw = EthernetInterfaceIntf::defaultGateway6();
+ if (gw == gateway)
+ {
+ return gw;
+ }
+
+ if (!isValidIP(AF_INET6, gateway))
+ {
+ log<level::ERR>("Not a valid v6 Gateway",
+ entry("GATEWAY=%s", gateway.c_str()));
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("GATEWAY"),
+ Argument::ARGUMENT_VALUE(gateway.c_str()));
+ }
+ gw = EthernetInterfaceIntf::defaultGateway6(gateway);
+ manager.writeToConfigurationFile();
+ return gw;
+}
+} // namespace network
+} // namespace phosphor
diff --git a/src/ethernet_interface.hpp b/src/ethernet_interface.hpp
new file mode 100644
index 0000000..8e28b51
--- /dev/null
+++ b/src/ethernet_interface.hpp
@@ -0,0 +1,372 @@
+#pragma once
+
+#include "types.hpp"
+#include "util.hpp"
+#include "xyz/openbmc_project/Network/IP/Create/server.hpp"
+#include "xyz/openbmc_project/Network/Neighbor/CreateStatic/server.hpp"
+
+#include <filesystem>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
+#include <xyz/openbmc_project/Network/EthernetInterface/server.hpp>
+#include <xyz/openbmc_project/Network/MACAddress/server.hpp>
+
+#ifndef SDBUSPP_NEW_CAMELCASE
+#define dhcpEnabled dHCPEnabled
+#define ip iP
+#define ipAddress iPAddress
+#define ipv6AcceptRA iPv6AcceptRA
+#define macAddress mACAddress
+#define nicEnabled nICEnabled
+#define ntpServers nTPServers
+#endif
+
+namespace phosphor
+{
+namespace network
+{
+
+using Ifaces = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface,
+ 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>;
+
+using IP = sdbusplus::xyz::openbmc_project::Network::server::IP;
+
+using EthernetInterfaceIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::EthernetInterface;
+using MacAddressIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::MACAddress;
+
+using ServerList = std::vector<std::string>;
+using ObjectPath = sdbusplus::message::object_path;
+
+namespace fs = std::filesystem;
+
+class Manager; // forward declaration of network manager.
+
+class TestEthernetInterface;
+
+class VlanInterface;
+
+class IPAddress;
+
+class Neighbor;
+
+using LinkSpeed = uint16_t;
+using DuplexMode = uint8_t;
+using Autoneg = uint8_t;
+using LinkUp = bool;
+using NICEnabled = bool;
+using VlanId = uint32_t;
+using InterfaceName = std::string;
+using InterfaceInfo =
+ std::tuple<LinkSpeed, DuplexMode, Autoneg, LinkUp, NICEnabled>;
+using AddressMap = std::map<std::string, std::shared_ptr<IPAddress>>;
+using NeighborMap = std::map<std::string, std::shared_ptr<Neighbor>>;
+using VlanInterfaceMap =
+ std::map<InterfaceName, std::unique_ptr<VlanInterface>>;
+
+/** @class EthernetInterface
+ * @brief OpenBMC Ethernet Interface implementation.
+ * @details A concrete implementation for the
+ * xyz.openbmc_project.Network.EthernetInterface DBus API.
+ */
+class EthernetInterface : public Ifaces
+{
+ public:
+ EthernetInterface() = delete;
+ EthernetInterface(const EthernetInterface&) = delete;
+ EthernetInterface& operator=(const EthernetInterface&) = delete;
+ EthernetInterface(EthernetInterface&&) = delete;
+ EthernetInterface& operator=(EthernetInterface&&) = delete;
+ virtual ~EthernetInterface() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] dhcpEnabled - is dhcp enabled(true/false).
+ * @param[in] parent - parent object.
+ * @param[in] emitSignal - true if the object added signal needs to be
+ * send.
+ */
+ EthernetInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
+ DHCPConf 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.
+ * @param[in] prefixLength - Length of prefix.
+ * @param[in] gateway - Gateway ip address.
+ */
+
+ ObjectPath ip(IP::Protocol addressType, std::string ipAddress,
+ uint8_t prefixLength, std::string gateway) override;
+
+ /** @brief Function to create static neighbor dbus object.
+ * @param[in] ipAddress - IP address.
+ * @param[in] macAddress - Low level MAC address.
+ */
+ ObjectPath neighbor(std::string ipAddress, std::string macAddress) override;
+
+ /* @brief delete the dbus object of the given ipAddress.
+ * @param[in] ipAddress - IP address.
+ */
+ void deleteObject(const std::string& ipAddress);
+
+ /* @brief delete the dbus object of the given ipAddress.
+ * @param[in] ipAddress - IP address.
+ */
+ void deleteStaticNeighborObject(const std::string& ipAddress);
+
+ /* @brief delete the vlan dbus object of the given interface.
+ * Also deletes the device file and the network file.
+ * @param[in] interface - VLAN Interface.
+ */
+ void deleteVLANObject(const std::string& interface);
+
+ /* @brief creates the dbus object(IPaddres) given in the address list.
+ * @param[in] addrs - address list for which dbus objects needs
+ * to create.
+ */
+ void createIPAddressObjects();
+
+ /* @brief creates the dbus object(Neighbor) given in the neighbor list.
+ */
+ void createStaticNeighborObjects();
+
+ /* @brief Gets the index of the interface on the system
+ */
+ unsigned ifIndex() const;
+
+ /* @brief Gets all the ip addresses.
+ * @returns the list of ipAddress.
+ */
+ const AddressMap& getAddresses() const
+ {
+ return addrs;
+ }
+
+ /* @brief Gets all the static neighbor entries.
+ * @returns Static neighbor map.
+ */
+ const NeighborMap& getStaticNeighbors() const
+ {
+ return staticNeighbors;
+ }
+
+ /** Set value of DHCPEnabled */
+ DHCPConf dhcpEnabled(DHCPConf value) override;
+
+ /** @brief Selectively disables DHCP
+ * @param[in] protocol - The IPv4 or IPv6 protocol to return to static
+ * addressing mode
+ */
+ void disableDHCP(IP::Protocol protocol);
+
+ /** Retrieve Link State */
+ bool linkUp() const override;
+
+ /** Retrieve NIC State */
+ bool nicEnabled() const override;
+
+ /** Set value of NICEnabled */
+ bool nicEnabled(bool value) override;
+
+ /** @brief sets the MAC address.
+ * @param[in] value - MAC address which needs to be set on the system.
+ * @returns macAddress of the interface or throws an error.
+ */
+ std::string macAddress(std::string value) override;
+
+ /** @brief get the IPv6AcceptRA flag from the network configuration file
+ *
+ */
+ bool getIPv6AcceptRAFromConf();
+
+ /** @brief check conf file for Router Advertisements
+ *
+ */
+ bool ipv6AcceptRA(bool value) override;
+
+ /** @brief sets the NTP servers.
+ * @param[in] value - vector of NTP servers.
+ */
+ ServerList ntpServers(ServerList value) override;
+
+ /** @brief sets the DNS/nameservers.
+ * @param[in] value - vector of DNS servers.
+ */
+ 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.
+ */
+ ObjectPath createVLAN(VlanId id);
+
+ /** @brief load the vlan info from the system
+ * and creates the ip address dbus objects.
+ * @param[in] vlanID- VLAN identifier.
+ */
+ void loadVLAN(VlanId vlanID);
+
+ /** @brief write the network conf file with the in-memory objects.
+ */
+ void writeConfigurationFile();
+
+ /** @brief delete all dbus objects.
+ */
+ void deleteAll();
+
+ /** @brief set the default v4 gateway of the interface.
+ * @param[in] gateway - default v4 gateway of the interface.
+ */
+ std::string defaultGateway(std::string gateway) override;
+
+ /** @brief set the default v6 gateway of the interface.
+ * @param[in] gateway - default v6 gateway of the interface.
+ */
+ std::string defaultGateway6(std::string gateway) override;
+
+ using EthernetInterfaceIntf::dhcpEnabled;
+ using EthernetInterfaceIntf::interfaceName;
+ using EthernetInterfaceIntf::linkUp;
+ using EthernetInterfaceIntf::nicEnabled;
+ using MacAddressIntf::macAddress;
+
+ using EthernetInterfaceIntf::defaultGateway;
+ using EthernetInterfaceIntf::defaultGateway6;
+ /** @brief Absolute path of the resolv conf file */
+ static constexpr auto resolvConfFile = "/etc/resolv.conf";
+
+ protected:
+ /** @brief get the info of the ethernet interface.
+ * @return tuple having the link speed,autonegotiation,duplexmode .
+ */
+ InterfaceInfo getInterfaceInfo() const;
+
+ /* @brief delete the vlan interface from system.
+ * @param[in] interface - vlan Interface.
+ */
+ void deleteVLANFromSystem(const std::string& interface);
+
+ /** @brief get the mac address of the interface.
+ * @param[in] interfaceName - Network interface name.
+ * @return macaddress on success
+ */
+
+ std::string getMACAddress(const std::string& interfaceName) const;
+
+ /** @brief construct the ip address dbus object path.
+ * @param[in] addressType - Type of ip address.
+ * @param[in] ipAddress - IP address.
+ * @param[in] prefixLength - Length of prefix.
+ * @param[in] gateway - Gateway address.
+
+ * @return path of the address object.
+ */
+
+ std::string generateObjectPath(IP::Protocol addressType,
+ const std::string& ipAddress,
+ uint8_t prefixLength,
+ const std::string& gateway) const;
+
+ std::string
+ generateStaticNeighborObjectPath(const std::string& ipAddress,
+ const std::string& macAddress) const;
+
+ /** @brief generates the id by doing hash of ipAddress,
+ * prefixlength and the gateway.
+ * @param[in] ipAddress - IP address.
+ * @param[in] prefixLength - Length of prefix.
+ * @param[in] gateway - Gateway address.
+ * @return hash string.
+ */
+
+ static std::string generateId(const std::string& ipAddress,
+ uint8_t prefixLength,
+ const std::string& gateway);
+
+ /** @brief generates the id by doing hash of ipAddress
+ * and the mac address.
+ * @param[in] ipAddress - IP address.
+ * @param[in] macAddress - Gateway address.
+ * @return hash string.
+ */
+ static std::string generateNeighborId(const std::string& ipAddress,
+ const std::string& macAddress);
+
+ /** @brief write the dhcp section **/
+ void writeDHCPSection(std::fstream& stream);
+
+ /** @brief get the NTP server list from the network conf
+ *
+ */
+ ServerList getNTPServersFromConf();
+
+ /** @brief get the name server details from the network conf
+ *
+ */
+ virtual ServerList getNameServerFromResolvd();
+ ServerList getstaticNameServerFromConf();
+
+ /** @brief Persistent sdbusplus DBus bus connection. */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief Network Manager object. */
+ Manager& manager;
+
+ /** @brief Persistent map of IPAddress dbus objects and their names */
+ AddressMap addrs;
+
+ /** @brief Persistent map of Neighbor dbus objects and their names */
+ NeighborMap staticNeighbors;
+
+ /** @brief Persistent map of VLAN interface dbus objects and their names */
+ VlanInterfaceMap vlanInterfaces;
+
+ /** @brief Dbus object path */
+ std::string objPath;
+
+ friend class TestEthernetInterface;
+
+ private:
+ /** @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, const 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 originIsManuallyAssigned(IP::AddressOrigin origin);
+};
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/ipaddress.cpp b/src/ipaddress.cpp
new file mode 100644
index 0000000..f30cf34
--- /dev/null
+++ b/src/ipaddress.cpp
@@ -0,0 +1,73 @@
+#include "config.h"
+
+#include "ipaddress.hpp"
+
+#include "ethernet_interface.hpp"
+#include "util.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+namespace phosphor
+{
+namespace network
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
+using Reason = xyz::openbmc_project::Common::NotAllowed::REASON;
+
+IPAddress::IPAddress(sdbusplus::bus::bus& bus, const char* objPath,
+ EthernetInterface& parent, IP::Protocol type,
+ const std::string& ipaddress, IP::AddressOrigin origin,
+ uint8_t prefixLength, const std::string& gateway) :
+ IPIfaces(bus, objPath, true),
+ parent(parent)
+{
+
+ IP::address(ipaddress);
+ IP::prefixLength(prefixLength);
+ IP::gateway(gateway);
+ IP::type(type);
+ IP::origin(origin);
+
+ // Emit deferred signal.
+ emit_object_added();
+}
+std::string IPAddress::address(std::string /*ipAddress*/)
+{
+ elog<NotAllowed>(Reason("Property update is not allowed"));
+}
+uint8_t IPAddress::prefixLength(uint8_t /*value*/)
+{
+ elog<NotAllowed>(Reason("Property update is not allowed"));
+}
+std::string IPAddress::gateway(std::string /*gateway*/)
+{
+ elog<NotAllowed>(Reason("Property update is not allowed"));
+}
+IP::Protocol IPAddress::type(IP::Protocol /*type*/)
+{
+ elog<NotAllowed>(Reason("Property update is not allowed"));
+}
+IP::AddressOrigin IPAddress::origin(IP::AddressOrigin /*origin*/)
+{
+ elog<NotAllowed>(Reason("Property update is not allowed"));
+}
+void IPAddress::delete_()
+{
+ if (origin() != IP::AddressOrigin::Static)
+ {
+ log<level::ERR>("Tried to delete a non-static address"),
+ entry("ADDRESS=%s", address().c_str()),
+ entry("PREFIX=%" PRIu8, prefixLength()),
+ entry("INTERFACE=%s", parent.interfaceName().c_str());
+ elog<InternalFailure>();
+ }
+
+ parent.deleteObject(address());
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/ipaddress.hpp b/src/ipaddress.hpp
new file mode 100644
index 0000000..3ed551a
--- /dev/null
+++ b/src/ipaddress.hpp
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <xyz/openbmc_project/Network/IP/server.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+using IPIfaces = sdbusplus::server::object::object<
+ sdbusplus::xyz::openbmc_project::Network::server::IP,
+ sdbusplus::xyz::openbmc_project::Object::server::Delete>;
+
+using IP = sdbusplus::xyz::openbmc_project::Network::server::IP;
+
+class EthernetInterface;
+
+/** @class IPAddress
+ * @brief OpenBMC IPAddress implementation.
+ * @details A concrete implementation for the
+ * xyz.openbmc_project.Network.IPProtocol
+ * xyz.openbmc_project.Network.IP Dbus interfaces.
+ */
+class IPAddress : public IPIfaces
+{
+ public:
+ IPAddress() = delete;
+ IPAddress(const IPAddress&) = delete;
+ IPAddress& operator=(const IPAddress&) = delete;
+ IPAddress(IPAddress&&) = delete;
+ IPAddress& operator=(IPAddress&&) = delete;
+ virtual ~IPAddress() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] parent - Parent object.
+ * @param[in] type - ipaddress type(v4/v6).
+ * @param[in] ipAddress - ipadress.
+ * @param[in] origin - origin of ipaddress(dhcp/static/SLAAC/LinkLocal).
+ * @param[in] prefixLength - Length of prefix.
+ * @param[in] gateway - gateway address.
+ */
+ IPAddress(sdbusplus::bus::bus& bus, const char* objPath,
+ EthernetInterface& parent, IP::Protocol type,
+ const std::string& ipAddress, IP::AddressOrigin origin,
+ uint8_t prefixLength, const std::string& gateway);
+
+ std::string address(std::string ipAddress) override;
+ uint8_t prefixLength(uint8_t) override;
+ std::string gateway(std::string gateway) override;
+ IP::Protocol type(IP::Protocol type) override;
+ IP::AddressOrigin origin(IP::AddressOrigin origin) override;
+
+ /** @brief Delete this d-bus object.
+ */
+ void delete_() override;
+
+ using IP::address;
+ using IP::gateway;
+ using IP::origin;
+ using IP::prefixLength;
+ using IP::type;
+
+ private:
+ /** @brief Parent Object. */
+ EthernetInterface& parent;
+};
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..31371f9
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,94 @@
+phosphor_dbus_interfaces_dep = dependency('phosphor-dbus-interfaces')
+phosphor_logging_dep = dependency('phosphor-logging')
+
+src_includes = include_directories('.')
+
+executable(
+ 'ncsi-netlink',
+ 'argument.cpp',
+ 'ncsi_netlink_main.cpp',
+ 'ncsi_util.cpp',
+ implicit_include_directories: false,
+ include_directories: src_includes,
+ dependencies: [
+ dependency('libnl-3.0'),
+ dependency('libnl-genl-3.0'),
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ ],
+ install: true,
+ install_dir: get_option('bindir'))
+
+json_dep = declare_dependency()
+if get_option('sync-mac')
+ # nlohmann_json might not have a pkg-config. It is header only so just make
+ # sure we can access the needed symbols from the header.
+ has_json = meson.get_compiler('cpp').has_header_symbol(
+ 'nlohmann/json.hpp',
+ 'nlohmann::json::string_t',
+ required: false)
+ if not has_json
+ json_dep = dependency(
+ 'nlohmann_json',
+ fallback: ['nlohmann_json', 'nlohmann_json_dep'],
+ required: true)
+ endif
+endif
+
+networkd_deps = [
+ json_dep,
+ phosphor_dbus_interfaces_dep,
+ phosphor_logging_dep,
+ sdbusplus_dep,
+ dependency('sdeventplus', fallback: ['sdeventplus', 'sdeventplus_dep']),
+ dependency('stdplus', fallback: ['stdplus', 'stdplus_dep']),
+]
+
+conf_header = configure_file(
+ output: 'config.h',
+ configuration: conf_data)
+
+networkd_generated = [
+ conf_header,
+] + generated_sources
+
+networkd_includes = [
+ src_includes,
+ generated_includes,
+]
+
+networkd_lib = static_library(
+ 'networkd',
+ networkd_generated,
+ 'ethernet_interface.cpp',
+ 'neighbor.cpp',
+ 'ipaddress.cpp',
+ 'netlink.cpp',
+ 'network_config.cpp',
+ 'network_manager.cpp',
+ 'system_configuration.cpp',
+ 'util.cpp',
+ 'routing_table.cpp',
+ 'config_parser.cpp',
+ 'dhcp_configuration.cpp',
+ 'vlan_interface.cpp',
+ 'rtnetlink_server.cpp',
+ 'dns_updater.cpp',
+ 'watch.cpp',
+ implicit_include_directories: false,
+ include_directories: networkd_includes,
+ dependencies: networkd_deps)
+
+networkd_dep = declare_dependency(
+ sources: networkd_generated,
+ dependencies: networkd_deps,
+ include_directories: networkd_includes,
+ link_with: networkd_lib)
+
+executable(
+ 'phosphor-network-manager',
+ 'network_manager_main.cpp',
+ implicit_include_directories: false,
+ dependencies: networkd_dep,
+ install: true,
+ install_dir: get_option('bindir'))
diff --git a/src/ncsi_netlink_main.cpp b/src/ncsi_netlink_main.cpp
new file mode 100644
index 0000000..9db624f
--- /dev/null
+++ b/src/ncsi_netlink_main.cpp
@@ -0,0 +1,111 @@
+/**
+ * Copyright © 2018 IBM 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.
+ */
+#include "argument.hpp"
+#include "ncsi_util.hpp"
+
+#include <iostream>
+#include <string>
+
+static void exitWithError(const char* err, char** argv)
+{
+ phosphor::network::ncsi::ArgumentParser::usage(argv);
+ std::cerr << "ERROR: " << err << "\n";
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char** argv)
+{
+ using namespace phosphor::network;
+ using namespace phosphor::network::ncsi;
+ // Read arguments.
+ auto options = ArgumentParser(argc, argv);
+ int packageInt{};
+ int channelInt{};
+ int indexInt{};
+
+ // Parse out interface argument.
+ auto ifIndex = (options)["index"];
+ try
+ {
+ indexInt = stoi(ifIndex, nullptr);
+ }
+ catch (const std::exception& e)
+ {
+ exitWithError("Interface not specified.", argv);
+ }
+
+ if (indexInt < 0)
+ {
+ exitWithError("Interface value should be greater than equal to 0",
+ argv);
+ }
+
+ // Parse out package argument.
+ auto package = (options)["package"];
+ try
+ {
+ packageInt = stoi(package, nullptr);
+ }
+ catch (const std::exception& e)
+ {
+ packageInt = DEFAULT_VALUE;
+ }
+
+ if (packageInt < 0)
+ {
+ packageInt = DEFAULT_VALUE;
+ }
+
+ // Parse out channel argument.
+ auto channel = (options)["channel"];
+ try
+ {
+ channelInt = stoi(channel, nullptr);
+ }
+ catch (const std::exception& e)
+ {
+ channelInt = DEFAULT_VALUE;
+ }
+
+ if (channelInt < 0)
+ {
+ channelInt = DEFAULT_VALUE;
+ }
+
+ auto setCmd = (options)["set"];
+ if (setCmd == "true")
+ {
+ // Can not perform set operation without package.
+ if (packageInt == DEFAULT_VALUE)
+ {
+ exitWithError("Package not specified.", argv);
+ }
+ return ncsi::setChannel(indexInt, packageInt, channelInt);
+ }
+ else if ((options)["info"] == "true")
+ {
+ return ncsi::getInfo(indexInt, packageInt);
+ }
+ else if ((options)["clear"] == "true")
+ {
+ return ncsi::clearInterface(indexInt);
+ }
+ else
+ {
+ exitWithError("No Command specified", argv);
+ }
+ return 0;
+}
diff --git a/src/ncsi_util.cpp b/src/ncsi_util.cpp
new file mode 100644
index 0000000..902911d
--- /dev/null
+++ b/src/ncsi_util.cpp
@@ -0,0 +1,301 @@
+#include "ncsi_util.hpp"
+
+#include <linux/ncsi.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+#include <netlink/netlink.h>
+
+#include <iostream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace ncsi
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+using CallBack = int (*)(struct nl_msg* msg, void* arg);
+
+namespace internal
+{
+
+using nlMsgPtr = std::unique_ptr<nl_msg, decltype(&::nlmsg_free)>;
+using nlSocketPtr = std::unique_ptr<nl_sock, decltype(&::nl_socket_free)>;
+
+CallBack infoCallBack = [](struct nl_msg* msg, void* /*arg*/) {
+ using namespace phosphor::network::ncsi;
+ auto nlh = nlmsg_hdr(msg);
+
+ struct nlattr* tb[NCSI_ATTR_MAX + 1] = {nullptr};
+ struct nla_policy ncsiPolicy[NCSI_ATTR_MAX + 1] = {
+ {NLA_UNSPEC, 0, 0}, {NLA_U32, 0, 0}, {NLA_NESTED, 0, 0},
+ {NLA_U32, 0, 0}, {NLA_U32, 0, 0},
+ };
+
+ struct nlattr* packagetb[NCSI_PKG_ATTR_MAX + 1] = {nullptr};
+ struct nla_policy packagePolicy[NCSI_PKG_ATTR_MAX + 1] = {
+ {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
+ {NLA_FLAG, 0, 0}, {NLA_NESTED, 0, 0},
+ };
+
+ struct nlattr* channeltb[NCSI_CHANNEL_ATTR_MAX + 1] = {nullptr};
+ struct nla_policy channelPolicy[NCSI_CHANNEL_ATTR_MAX + 1] = {
+ {NLA_UNSPEC, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_U32, 0, 0},
+ {NLA_FLAG, 0, 0}, {NLA_NESTED, 0, 0}, {NLA_UNSPEC, 0, 0},
+ };
+
+ auto ret = genlmsg_parse(nlh, 0, tb, NCSI_ATTR_MAX, ncsiPolicy);
+ if (!tb[NCSI_ATTR_PACKAGE_LIST])
+ {
+ std::cerr << "No Packages" << std::endl;
+ return -1;
+ }
+
+ auto attrTgt = static_cast<nlattr*>(nla_data(tb[NCSI_ATTR_PACKAGE_LIST]));
+ if (!attrTgt)
+ {
+ std::cerr << "Package list attribute is null" << std::endl;
+ return -1;
+ }
+
+ auto rem = nla_len(tb[NCSI_ATTR_PACKAGE_LIST]);
+ nla_for_each_nested(attrTgt, tb[NCSI_ATTR_PACKAGE_LIST], rem)
+ {
+ ret = nla_parse_nested(packagetb, NCSI_PKG_ATTR_MAX, attrTgt,
+ packagePolicy);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to parse package nested" << std::endl;
+ return -1;
+ }
+
+ if (packagetb[NCSI_PKG_ATTR_ID])
+ {
+ auto attrID = nla_get_u32(packagetb[NCSI_PKG_ATTR_ID]);
+ std::cout << "Package has id : " << std::hex << attrID << std::endl;
+ }
+ else
+ {
+ std::cout << "Package with no id" << std::endl;
+ }
+
+ if (packagetb[NCSI_PKG_ATTR_FORCED])
+ {
+ std::cout << "This package is forced" << std::endl;
+ }
+
+ auto channelListTarget = static_cast<nlattr*>(
+ nla_data(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]));
+
+ auto channelrem = nla_len(packagetb[NCSI_PKG_ATTR_CHANNEL_LIST]);
+ nla_for_each_nested(channelListTarget,
+ packagetb[NCSI_PKG_ATTR_CHANNEL_LIST], channelrem)
+ {
+ ret = nla_parse_nested(channeltb, NCSI_CHANNEL_ATTR_MAX,
+ channelListTarget, channelPolicy);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to parse channel nested" << std::endl;
+ return -1;
+ }
+
+ if (channeltb[NCSI_CHANNEL_ATTR_ID])
+ {
+ auto channel = nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_ID]);
+ if (channeltb[NCSI_CHANNEL_ATTR_ACTIVE])
+ {
+ std::cout << "Channel Active : " << std::hex << channel
+ << std::endl;
+ }
+ else
+ {
+ std::cout << "Channel Not Active : " << std::hex << channel
+ << std::endl;
+ }
+
+ if (channeltb[NCSI_CHANNEL_ATTR_FORCED])
+ {
+ std::cout << "Channel is forced" << std::endl;
+ }
+ }
+ else
+ {
+ std::cout << "Channel with no ID" << std::endl;
+ }
+
+ if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR])
+ {
+ auto major =
+ nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MAJOR]);
+ std::cout << "Channel Major Version : " << std::hex << major
+ << std::endl;
+ }
+ if (channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR])
+ {
+ auto minor =
+ nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_VERSION_MINOR]);
+ std::cout << "Channel Minor Version : " << std::hex << minor
+ << std::endl;
+ }
+ if (channeltb[NCSI_CHANNEL_ATTR_VERSION_STR])
+ {
+ auto str =
+ nla_get_string(channeltb[NCSI_CHANNEL_ATTR_VERSION_STR]);
+ std::cout << "Channel Version Str :" << str << std::endl;
+ }
+ if (channeltb[NCSI_CHANNEL_ATTR_LINK_STATE])
+ {
+
+ auto link =
+ nla_get_u32(channeltb[NCSI_CHANNEL_ATTR_LINK_STATE]);
+ std::cout << "Channel Link State : " << std::hex << link
+ << std::endl;
+ }
+ if (channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST])
+ {
+ std::cout << "Active Vlan ids" << std::endl;
+ auto vids = channeltb[NCSI_CHANNEL_ATTR_VLAN_LIST];
+ auto vid = static_cast<nlattr*>(nla_data(vids));
+ auto len = nla_len(vids);
+ while (nla_ok(vid, len))
+ {
+ auto id = nla_get_u16(vid);
+ std::cout << "VID : " << id << std::endl;
+ vid = nla_next(vid, &len);
+ }
+ }
+ }
+ }
+ return (int)NL_SKIP;
+};
+
+int applyCmd(int ifindex, int cmd, int package = DEFAULT_VALUE,
+ int channel = DEFAULT_VALUE, int flags = NONE,
+ CallBack function = nullptr)
+{
+ nlSocketPtr socket(nl_socket_alloc(), &::nl_socket_free);
+ auto ret = genl_connect(socket.get());
+ if (ret < 0)
+ {
+ std::cerr << "Failed to open the socket , RC : " << ret << std::endl;
+ return ret;
+ }
+
+ auto driverID = genl_ctrl_resolve(socket.get(), "NCSI");
+ if (driverID < 0)
+ {
+ std::cerr << "Failed to resolve, RC : " << ret << std::endl;
+ return driverID;
+ }
+
+ nlMsgPtr msg(nlmsg_alloc(), &::nlmsg_free);
+
+ auto msgHdr = genlmsg_put(msg.get(), 0, 0, driverID, 0, flags, cmd, 0);
+ if (!msgHdr)
+ {
+ std::cerr << "Unable to add the netlink headers , COMMAND : " << cmd
+ << std::endl;
+ return -1;
+ }
+
+ if (package != DEFAULT_VALUE)
+ {
+ ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_PACKAGE_ID,
+ package);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to set the attribute , RC : " << ret
+ << "PACKAGE " << std::hex << package << std::endl;
+ return ret;
+ }
+ }
+
+ if (channel != DEFAULT_VALUE)
+ {
+ ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_CHANNEL_ID,
+ channel);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to set the attribute , RC : " << ret
+ << "CHANNEL : " << std::hex << channel << std::endl;
+ return ret;
+ }
+ }
+
+ ret = nla_put_u32(msg.get(), ncsi_nl_attrs::NCSI_ATTR_IFINDEX, ifindex);
+ if (ret < 0)
+ {
+ std::cerr << "Failed to set the attribute , RC : " << ret
+ << "INTERFACE : " << std::hex << ifindex << std::endl;
+ return ret;
+ }
+
+ if (function)
+ {
+ // Add a callback function to the socket
+ nl_socket_modify_cb(socket.get(), NL_CB_VALID, NL_CB_CUSTOM, function,
+ nullptr);
+ }
+
+ ret = nl_send_auto(socket.get(), msg.get());
+ if (ret < 0)
+ {
+ std::cerr << "Failed to send the message , RC : " << ret << std::endl;
+ return ret;
+ }
+
+ ret = nl_recvmsgs_default(socket.get());
+ if (ret < 0)
+ {
+ std::cerr << "Failed to receive the message , RC : " << ret
+ << std::endl;
+ }
+ return ret;
+}
+
+} // namespace internal
+
+int setChannel(int ifindex, int package, int channel)
+{
+ std::cout << "Set Channel : " << std::hex << channel
+ << ", PACKAGE : " << std::hex << package
+ << ", IFINDEX : " << std::hex << ifindex << std::endl;
+ return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_SET_INTERFACE,
+ package, channel);
+}
+
+int clearInterface(int ifindex)
+{
+ std::cout << "ClearInterface , IFINDEX :" << std::hex << ifindex
+ << std::endl;
+ return internal::applyCmd(ifindex,
+ ncsi_nl_commands::NCSI_CMD_CLEAR_INTERFACE);
+}
+
+int getInfo(int ifindex, int package)
+{
+ std::cout << "Get Info , PACKAGE : " << std::hex << package
+ << ", IFINDEX : " << std::hex << ifindex << std::endl;
+ if (package == DEFAULT_VALUE)
+ {
+ return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_PKG_INFO,
+ package, DEFAULT_VALUE, NLM_F_DUMP,
+ internal::infoCallBack);
+ }
+ else
+ {
+ return internal::applyCmd(ifindex, ncsi_nl_commands::NCSI_CMD_PKG_INFO,
+ package, DEFAULT_VALUE, NONE,
+ internal::infoCallBack);
+ }
+}
+
+} // namespace ncsi
+} // namespace network
+} // namespace phosphor
diff --git a/src/ncsi_util.hpp b/src/ncsi_util.hpp
new file mode 100644
index 0000000..db754fe
--- /dev/null
+++ b/src/ncsi_util.hpp
@@ -0,0 +1,44 @@
+namespace phosphor
+{
+namespace network
+{
+namespace ncsi
+{
+
+constexpr auto DEFAULT_VALUE = -1;
+constexpr auto NONE = 0;
+
+/* @brief This function will ask underlying NCSI driver
+ * to set a specific package or package/channel
+ * combination as the preferred choice.
+ * This function talks with the NCSI driver over
+ * netlink messages.
+ * @param[in] ifindex - Interface Index.
+ * @param[in] package - NCSI Package.
+ * @param[in] channel - Channel number with in the package.
+ * @returns 0 on success and negative value for failure.
+ */
+int setChannel(int ifindex, int package, int channel);
+
+/* @brief This function will ask underlying NCSI driver
+ * to clear any preferred setting from the given
+ * interface.
+ * This function talks with the NCSI driver over
+ * netlink messages.
+ * @param[in] ifindex - Interface Index.
+ * @returns 0 on success and negative value for failure.
+ */
+int clearInterface(int ifindex);
+
+/* @brief This function is used to dump all the info
+ * of the package and the channels underlying
+ * the package.
+ * @param[in] ifindex - Interface Index.
+ * @param[in] package - NCSI Package.
+ * @returns 0 on success and negative value for failure.
+ */
+int getInfo(int ifindex, int package);
+
+} // namespace ncsi
+} // namespace network
+} // namespace phosphor
diff --git a/src/neighbor.cpp b/src/neighbor.cpp
new file mode 100644
index 0000000..11b0666
--- /dev/null
+++ b/src/neighbor.cpp
@@ -0,0 +1,108 @@
+#include "config.h"
+
+#include "neighbor.hpp"
+
+#include "ethernet_interface.hpp"
+#include "netlink.hpp"
+#include "util.hpp"
+
+#include <linux/neighbour.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <stdexcept>
+#include <stdplus/raw.hpp>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace phosphor
+{
+namespace network
+{
+namespace detail
+{
+
+void parseNeighbor(const NeighborFilter& filter, const nlmsghdr& hdr,
+ std::string_view msg, std::vector<NeighborInfo>& neighbors)
+{
+ if (hdr.nlmsg_type != RTM_NEWNEIGH)
+ {
+ throw std::runtime_error("Not a neighbor msg");
+ }
+ auto ndm = stdplus::raw::extract<ndmsg>(msg);
+
+ // Filter out neighbors we don't care about
+ unsigned ifindex = ndm.ndm_ifindex;
+ if (filter.interface != 0 && filter.interface != ifindex)
+ {
+ return;
+ }
+ if ((ndm.ndm_state & filter.state) == 0)
+ {
+ return;
+ }
+
+ // Build the neighbor info for our valid neighbor
+ NeighborInfo neighbor;
+ neighbor.interface = ifindex;
+ neighbor.state = ndm.ndm_state;
+ bool set_addr = false;
+ while (!msg.empty())
+ {
+ auto [hdr, data] = netlink::extractRtAttr(msg);
+ if (hdr.rta_type == NDA_LLADDR)
+ {
+ neighbor.mac = stdplus::raw::copyFrom<ether_addr>(data);
+ }
+ else if (hdr.rta_type == NDA_DST)
+ {
+ neighbor.address = addrFromBuf(ndm.ndm_family, data);
+ set_addr = true;
+ }
+ }
+ if (!set_addr)
+ {
+ throw std::runtime_error("Missing address");
+ }
+ neighbors.push_back(std::move(neighbor));
+}
+
+} // namespace detail
+
+std::vector<NeighborInfo> getCurrentNeighbors(const NeighborFilter& filter)
+{
+ std::vector<NeighborInfo> neighbors;
+ auto cb = [&filter, &neighbors](const nlmsghdr& hdr, std::string_view msg) {
+ detail::parseNeighbor(filter, hdr, msg, neighbors);
+ };
+ ndmsg msg{};
+ msg.ndm_ifindex = filter.interface;
+ netlink::performRequest(NETLINK_ROUTE, RTM_GETNEIGH, NLM_F_DUMP, msg, cb);
+ return neighbors;
+}
+
+Neighbor::Neighbor(sdbusplus::bus::bus& bus, const char* objPath,
+ EthernetInterface& parent, const std::string& ipAddress,
+ const std::string& macAddress, State state) :
+ NeighborObj(bus, objPath, true),
+ parent(parent)
+{
+ this->ipAddress(ipAddress);
+ this->macAddress(macAddress);
+ this->state(state);
+
+ // Emit deferred signal.
+ emit_object_added();
+}
+
+void Neighbor::delete_()
+{
+ parent.deleteStaticNeighborObject(ipAddress());
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/neighbor.hpp b/src/neighbor.hpp
new file mode 100644
index 0000000..cca9c11
--- /dev/null
+++ b/src/neighbor.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include "types.hpp"
+
+#include <linux/netlink.h>
+#include <net/ethernet.h>
+
+#include <cstdint>
+#include <optional>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <string_view>
+#include <vector>
+#include <xyz/openbmc_project/Network/Neighbor/server.hpp>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+using NeighborIntf = sdbusplus::xyz::openbmc_project::Network::server::Neighbor;
+
+using NeighborObj = sdbusplus::server::object::object<
+ NeighborIntf, sdbusplus::xyz::openbmc_project::Object::server::Delete>;
+
+class EthernetInterface;
+
+/* @class NeighborFilter
+ */
+struct NeighborFilter
+{
+ unsigned interface;
+ uint16_t state;
+
+ /* @brief Creates an empty filter */
+ NeighborFilter() : interface(0), state(~UINT16_C(0))
+ {
+ }
+};
+
+/** @class NeighborInfo
+ * @brief Information about a neighbor from the kernel
+ */
+struct NeighborInfo
+{
+ unsigned interface;
+ InAddrAny address;
+ std::optional<ether_addr> mac;
+ uint16_t state;
+};
+
+/** @brief Returns a list of the current system neighbor table
+ */
+std::vector<NeighborInfo> getCurrentNeighbors(const NeighborFilter& filter);
+
+/** @class Neighbor
+ * @brief OpenBMC network neighbor implementation.
+ * @details A concrete implementation for the
+ * xyz.openbmc_project.Network.Neighbor dbus interface.
+ */
+class Neighbor : public NeighborObj
+{
+ public:
+ using State = NeighborIntf::State;
+
+ Neighbor() = delete;
+ Neighbor(const Neighbor&) = delete;
+ Neighbor& operator=(const Neighbor&) = delete;
+ Neighbor(Neighbor&&) = delete;
+ Neighbor& operator=(Neighbor&&) = delete;
+ virtual ~Neighbor() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] parent - Parent object.
+ * @param[in] ipAddress - IP address.
+ * @param[in] macAddress - Low level MAC address.
+ * @param[in] state - The state of the neighbor entry.
+ */
+ Neighbor(sdbusplus::bus::bus& bus, const char* objPath,
+ EthernetInterface& parent, const std::string& ipAddress,
+ const std::string& macAddress, State state);
+
+ /** @brief Delete this d-bus object.
+ */
+ void delete_() override;
+
+ private:
+ /** @brief Parent Object. */
+ EthernetInterface& parent;
+};
+
+namespace detail
+{
+
+void parseNeighbor(const NeighborFilter& filter, const nlmsghdr& hdr,
+ std::string_view msg, std::vector<NeighborInfo>& neighbors);
+
+} // namespace detail
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/netlink.cpp b/src/netlink.cpp
new file mode 100644
index 0000000..9039575
--- /dev/null
+++ b/src/netlink.cpp
@@ -0,0 +1,195 @@
+#include "netlink.hpp"
+
+#include "util.hpp"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <array>
+#include <stdexcept>
+#include <stdplus/raw.hpp>
+#include <system_error>
+
+namespace phosphor
+{
+namespace network
+{
+namespace netlink
+{
+namespace detail
+{
+
+void processMsg(std::string_view& msgs, bool& done, const ReceiveCallback& cb)
+{
+ // Parse and update the message buffer
+ auto hdr = stdplus::raw::copyFrom<nlmsghdr>(msgs);
+ if (hdr.nlmsg_len < sizeof(hdr))
+ {
+ throw std::runtime_error("Invalid nlmsg length");
+ }
+ if (msgs.size() < hdr.nlmsg_len)
+ {
+ throw std::runtime_error("Bad nlmsg payload");
+ }
+ auto msg = msgs.substr(NLMSG_HDRLEN, hdr.nlmsg_len - NLMSG_HDRLEN);
+ msgs.remove_prefix(NLMSG_ALIGN(hdr.nlmsg_len));
+
+ // Figure out how to handle the individual message
+ bool doCallback = true;
+ if (hdr.nlmsg_flags & NLM_F_MULTI)
+ {
+ done = false;
+ }
+ if (hdr.nlmsg_type == NLMSG_NOOP)
+ {
+ doCallback = false;
+ }
+ else if (hdr.nlmsg_type == NLMSG_DONE)
+ {
+ if (done)
+ {
+ throw std::runtime_error("Got done for non-multi msg");
+ }
+ done = true;
+ doCallback = false;
+ }
+ else if (hdr.nlmsg_type == NLMSG_ERROR)
+ {
+ auto err = stdplus::raw::copyFrom<nlmsgerr>(msg);
+ // This is just an ACK so don't do the callback
+ if (err.error <= 0)
+ {
+ doCallback = false;
+ }
+ }
+ // All multi-msg headers must have the multi flag
+ if (!done && !(hdr.nlmsg_flags & NLM_F_MULTI))
+ {
+ throw std::runtime_error("Got non-multi msg before done");
+ }
+ if (doCallback)
+ {
+ cb(hdr, msg);
+ }
+}
+
+static void receive(int sock, const ReceiveCallback& cb)
+{
+ // We need to make sure we have enough room for an entire packet otherwise
+ // it gets truncated. The netlink docs guarantee packets will not exceed 8K
+ std::array<char, 8192> buf;
+
+ iovec iov{};
+ iov.iov_base = buf.data();
+ iov.iov_len = buf.size();
+
+ sockaddr_nl from{};
+ from.nl_family = AF_NETLINK;
+
+ msghdr hdr{};
+ hdr.msg_name = &from;
+ hdr.msg_namelen = sizeof(from);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+
+ // We only do multiple recvs if we have a MULTI type message
+ bool done = true;
+ do
+ {
+ ssize_t recvd = recvmsg(sock, &hdr, 0);
+ if (recvd < 0)
+ {
+ throw std::system_error(errno, std::generic_category(),
+ "netlink recvmsg");
+ }
+ if (recvd == 0)
+ {
+ throw std::runtime_error("netlink recvmsg: Got empty payload");
+ }
+
+ std::string_view msgs(buf.data(), recvd);
+ do
+ {
+ processMsg(msgs, done, cb);
+ } while (!done && !msgs.empty());
+
+ if (done && !msgs.empty())
+ {
+ throw std::runtime_error("Extra unprocessed netlink messages");
+ }
+ } while (!done);
+}
+
+static void requestSend(int sock, void* data, size_t size)
+{
+ sockaddr_nl dst{};
+ dst.nl_family = AF_NETLINK;
+
+ iovec iov{};
+ iov.iov_base = data;
+ iov.iov_len = size;
+
+ msghdr hdr{};
+ hdr.msg_name = reinterpret_cast<sockaddr*>(&dst);
+ hdr.msg_namelen = sizeof(dst);
+ hdr.msg_iov = &iov;
+ hdr.msg_iovlen = 1;
+
+ if (sendmsg(sock, &hdr, 0) < 0)
+ {
+ throw std::system_error(errno, std::generic_category(),
+ "netlink sendmsg");
+ }
+}
+
+static int newRequestSocket(int protocol)
+{
+ int sock = socket(AF_NETLINK, SOCK_RAW, protocol);
+ if (sock < 0)
+ {
+ throw std::system_error(errno, std::generic_category(), "netlink open");
+ }
+
+ sockaddr_nl local{};
+ local.nl_family = AF_NETLINK;
+ int r = bind(sock, reinterpret_cast<sockaddr*>(&local), sizeof(local));
+ if (r < 0)
+ {
+ close(sock);
+ throw std::system_error(errno, std::generic_category(), "netlink bind");
+ }
+
+ return sock;
+}
+
+void performRequest(int protocol, void* data, size_t size,
+ const ReceiveCallback& cb)
+{
+ Descriptor sock(newRequestSocket(protocol));
+ requestSend(sock(), data, size);
+ receive(sock(), cb);
+}
+
+} // namespace detail
+
+std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data)
+{
+ auto hdr = stdplus::raw::copyFrom<rtattr>(data);
+ if (hdr.rta_len < RTA_LENGTH(0))
+ {
+ throw std::runtime_error("Invalid rtattr length");
+ }
+ if (data.size() < hdr.rta_len)
+ {
+ throw std::runtime_error("Not enough data for rtattr");
+ }
+ auto attr = data.substr(RTA_LENGTH(0), hdr.rta_len - RTA_LENGTH(0));
+ data.remove_prefix(RTA_ALIGN(hdr.rta_len));
+ return {hdr, attr};
+}
+
+} // namespace netlink
+} // namespace network
+} // namespace phosphor
diff --git a/src/netlink.hpp b/src/netlink.hpp
new file mode 100644
index 0000000..e1a8253
--- /dev/null
+++ b/src/netlink.hpp
@@ -0,0 +1,69 @@
+#pragma once
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <functional>
+#include <string_view>
+#include <tuple>
+#include <type_traits>
+
+namespace phosphor
+{
+namespace network
+{
+namespace netlink
+{
+
+/* @brief Called on each nlmsg received on the socket
+ */
+using ReceiveCallback = std::function<void(const nlmsghdr&, std::string_view)>;
+
+namespace detail
+{
+
+void processMsg(std::string_view& msgs, bool& done, const ReceiveCallback& cb);
+
+void performRequest(int protocol, void* data, size_t size,
+ const ReceiveCallback& cb);
+
+} // namespace detail
+
+/* @brief Call on a block of rtattrs to parse a single one out
+ * Updates the input to remove the attr parsed out.
+ *
+ * @param[in,out] attrs - The buffer holding rtattrs to parse
+ * @return A tuple of rtattr header + data buffer for the attr
+ */
+std::tuple<rtattr, std::string_view> extractRtAttr(std::string_view& data);
+
+/** @brief Performs a netlink request of the specified type with the given
+ * message Calls the callback upon receiving
+ *
+ * @param[in] protocol - The netlink protocol to use when opening the socket
+ * @param[in] type - The netlink message type
+ * @param[in] flags - Additional netlink flags for the request
+ * @param[in] msg - The message payload for the request
+ * @param[in] cb - Called for each response message payload
+ */
+template <typename T>
+void performRequest(int protocol, uint16_t type, uint16_t flags, const T& msg,
+ const ReceiveCallback& cb)
+{
+ static_assert(std::is_trivially_copyable_v<T>);
+
+ struct
+ {
+ nlmsghdr hdr;
+ T msg;
+ } data{};
+ data.hdr.nlmsg_len = sizeof(data);
+ data.hdr.nlmsg_type = type;
+ data.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
+ data.msg = msg;
+
+ detail::performRequest(protocol, &data, sizeof(data), cb);
+}
+
+} // namespace netlink
+} // namespace network
+} // namespace phosphor
diff --git a/src/network_config.cpp b/src/network_config.cpp
new file mode 100644
index 0000000..94c8eae
--- /dev/null
+++ b/src/network_config.cpp
@@ -0,0 +1,47 @@
+#include "config.h"
+
+#include "network_config.hpp"
+
+#include <fstream>
+#include <string>
+
+namespace phosphor
+{
+namespace network
+{
+
+namespace bmc
+{
+void writeDHCPDefault(const std::string& filename, const std::string& interface)
+{
+ std::ofstream filestream;
+
+ filestream.open(filename);
+ // Add the following line to your phosphor-network bbappend file
+ // to control IPV6_ACCEPT_RA
+ // EXTRA_OECONF_append = " --enable-ipv6-accept-ra=yes"
+ // If this switch is not present or set to 'no'
+ // ENABLE_IPV6_ACCEPT_RA will be undefined.
+ // The new value is only assigned on first boot, when the default
+ // file is not present, or after the default file has been
+ // manually removed.
+ filestream << "[Match]\nName=" << interface <<
+ "\n[Network]\nDHCP=true\n"
+#ifdef LINK_LOCAL_AUTOCONFIGURATION
+ "LinkLocalAddressing=yes\n"
+#else
+ "LinkLocalAddressing=no\n"
+#endif
+#ifdef ENABLE_IPV6_ACCEPT_RA
+ "IPv6AcceptRA=true\n"
+#else
+ "IPv6AcceptRA=false\n"
+
+#endif
+ "[DHCP]\nClientIdentifier=mac\n";
+ filestream.close();
+}
+} // namespace bmc
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/network_config.hpp b/src/network_config.hpp
new file mode 100644
index 0000000..7260ca2
--- /dev/null
+++ b/src/network_config.hpp
@@ -0,0 +1,15 @@
+#include <string>
+
+namespace phosphor
+{
+namespace network
+{
+
+namespace bmc
+{
+void writeDHCPDefault(const std::string& filename,
+ const std::string& interface);
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/network_manager.cpp b/src/network_manager.cpp
new file mode 100644
index 0000000..751d1a1
--- /dev/null
+++ b/src/network_manager.cpp
@@ -0,0 +1,289 @@
+#include "config.h"
+
+#include "network_manager.hpp"
+
+#include "ipaddress.hpp"
+#include "network_config.hpp"
+#include "types.hpp"
+#include "util.hpp"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <net/if.h>
+
+#include <algorithm>
+#include <bitset>
+#include <filesystem>
+#include <fstream>
+#include <map>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+constexpr char SYSTEMD_BUSNAME[] = "org.freedesktop.systemd1";
+constexpr char SYSTEMD_PATH[] = "/org/freedesktop/systemd1";
+constexpr char SYSTEMD_INTERFACE[] = "org.freedesktop.systemd1.Manager";
+constexpr auto FirstBootFile = "/var/lib/network/firstBoot_";
+
+namespace phosphor
+{
+namespace network
+{
+
+extern std::unique_ptr<Timer> refreshObjectTimer;
+extern std::unique_ptr<Timer> restartTimer;
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Manager::Manager(sdbusplus::bus::bus& bus, const char* objPath,
+ const std::string& path) :
+ details::VLANCreateIface(bus, objPath, true),
+ bus(bus), objectPath(objPath)
+{
+ fs::path confDir(path);
+ setConfDir(confDir);
+}
+
+bool Manager::createDefaultNetworkFiles(bool force)
+{
+ auto isCreated = false;
+ try
+ {
+ // Directory would have created before with
+ // setConfDir function.
+ if (force)
+ {
+ // Factory Reset case
+ // we need to forcefully write the files
+ // so delete the existing ones.
+ if (fs::is_directory(confDir))
+ {
+ for (const auto& file : fs::directory_iterator(confDir))
+ {
+ fs::remove(file.path());
+ }
+ }
+ }
+
+ auto interfaceStrList = getInterfaces();
+ for (const auto& interface : interfaceStrList)
+ {
+ // if the interface has '.' in the name, it means that this is a
+ // VLAN - don't create the network file.
+ if (interface.find(".") != std::string::npos)
+ {
+ continue;
+ }
+
+ auto fileName = systemd::config::networkFilePrefix + interface +
+ systemd::config::networkFileSuffix;
+
+ fs::path filePath = confDir;
+ filePath /= fileName;
+
+ // create the interface specific network file
+ // if not exist or we forcefully wants to write
+ // the network file.
+
+ if (force || !fs::is_regular_file(filePath.string()))
+ {
+ bmc::writeDHCPDefault(filePath.string(), interface);
+ log<level::INFO>("Created the default network file.",
+ entry("INTERFACE=%s", interface.c_str()));
+ isCreated = true;
+ }
+ }
+ }
+ catch (std::exception& e)
+ {
+ log<level::ERR>("Unable to create the default network file");
+ }
+ return isCreated;
+}
+
+void Manager::setConfDir(const fs::path& dir)
+{
+ confDir = dir;
+
+ if (!fs::exists(confDir))
+ {
+ if (!fs::create_directories(confDir))
+ {
+ log<level::ERR>("Unable to create the network conf dir",
+ entry("DIR=%s", confDir.c_str()));
+ elog<InternalFailure>();
+ }
+ }
+}
+
+void Manager::createInterfaces()
+{
+ // clear all the interfaces first
+ interfaces.clear();
+
+ auto interfaceStrList = getInterfaces();
+
+ for (auto& interface : interfaceStrList)
+ {
+ fs::path objPath = objectPath;
+ auto index = interface.find(".");
+
+ // interface can be of vlan type or normal ethernet interface.
+ // vlan interface looks like "interface.vlanid",so here by looking
+ // at the interface name we decide that we need
+ // to create the vlaninterface or normal physical interface.
+ if (index != std::string::npos)
+ {
+ // it is vlan interface
+ auto interfaceName = interface.substr(0, index);
+ auto vlanid = interface.substr(index + 1);
+ uint32_t vlanInt = std::stoul(vlanid);
+
+ interfaces[interfaceName]->loadVLAN(vlanInt);
+ continue;
+ }
+ // normal ethernet interface
+ objPath /= interface;
+
+ auto dhcp = getDHCPValue(confDir, interface);
+
+ auto intf = std::make_shared<phosphor::network::EthernetInterface>(
+ bus, objPath.string(), dhcp, *this);
+
+ intf->createIPAddressObjects();
+ intf->createStaticNeighborObjects();
+ intf->loadNameServers();
+
+ this->interfaces.emplace(
+ std::make_pair(std::move(interface), std::move(intf)));
+ }
+}
+
+void Manager::createChildObjects()
+{
+ // creates the ethernet interface dbus object.
+ createInterfaces();
+
+ systemConf.reset(nullptr);
+ dhcpConf.reset(nullptr);
+
+ fs::path objPath = objectPath;
+ objPath /= "config";
+
+ // create the system conf object.
+ systemConf = std::make_unique<phosphor::network::SystemConfiguration>(
+ bus, objPath.string(), *this);
+ // create the dhcp conf object.
+ objPath /= "dhcp";
+ dhcpConf = std::make_unique<phosphor::network::dhcp::Configuration>(
+ bus, objPath.string(), *this);
+}
+
+ObjectPath Manager::vlan(IntfName interfaceName, uint32_t id)
+{
+ return interfaces[interfaceName]->createVLAN(id);
+}
+
+void Manager::reset()
+{
+ if (!createDefaultNetworkFiles(true))
+ {
+ log<level::ERR>("Network Factory Reset failed.");
+ return;
+ // TODO: openbmc/openbmc#1721 - Log ResetFailed error here.
+ }
+
+ log<level::INFO>("Network Factory Reset done.");
+}
+
+// Need to merge the below function with the code which writes the
+// config file during factory reset.
+// TODO openbmc/openbmc#1751
+void Manager::writeToConfigurationFile()
+{
+ // write all the static ip address in the systemd-network conf file
+ for (const auto& intf : interfaces)
+ {
+ intf.second->writeConfigurationFile();
+ }
+ restartTimers();
+}
+
+#ifdef SYNC_MAC_FROM_INVENTORY
+void Manager::setFistBootMACOnInterface(
+ const std::pair<std::string, std::string>& inventoryEthPair)
+{
+ for (const auto& interface : interfaces)
+ {
+ if (interface.first == inventoryEthPair.first)
+ {
+ auto returnMAC =
+ interface.second->macAddress(inventoryEthPair.second);
+ if (returnMAC == inventoryEthPair.second)
+ {
+ log<level::INFO>("Set the MAC on "),
+ entry("interface : ", interface.first.c_str()),
+ entry("MAC : ", inventoryEthPair.second.c_str());
+ std::error_code ec;
+ if (std::filesystem::is_directory("/var/lib/network", ec))
+ {
+ std::ofstream persistentFile(FirstBootFile +
+ interface.first);
+ }
+ break;
+ }
+ else
+ {
+ log<level::INFO>("MAC is Not Set on ethernet Interface");
+ }
+ }
+ }
+}
+
+#endif
+
+void Manager::restartTimers()
+{
+ using namespace std::chrono;
+ if (refreshObjectTimer && restartTimer)
+ {
+ restartTimer->restartOnce(restartTimeout);
+ refreshObjectTimer->restartOnce(refreshTimeout);
+ }
+}
+
+void Manager::restartSystemdUnit(const std::string& unit)
+{
+ try
+ {
+ auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+ SYSTEMD_INTERFACE, "ResetFailedUnit");
+ method.append(unit);
+ bus.call_noreply(method);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>("Failed to reset failed unit",
+ entry("UNIT=%s", unit.c_str()),
+ entry("ERR=%s", ex.what()));
+ elog<InternalFailure>();
+ }
+
+ try
+ {
+ auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH,
+ SYSTEMD_INTERFACE, "RestartUnit");
+ method.append(unit.c_str(), "replace");
+ bus.call_noreply(method);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>("Failed to restart service", entry("ERR=%s", ex.what()),
+ entry("UNIT=%s", unit.c_str()));
+ elog<InternalFailure>();
+ }
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/network_manager.hpp b/src/network_manager.hpp
new file mode 100644
index 0000000..227955c
--- /dev/null
+++ b/src/network_manager.hpp
@@ -0,0 +1,183 @@
+#pragma once
+
+#include "dhcp_configuration.hpp"
+#include "ethernet_interface.hpp"
+#include "system_configuration.hpp"
+#include "vlan_interface.hpp"
+#include "xyz/openbmc_project/Network/VLAN/Create/server.hpp"
+
+#include <filesystem>
+#include <list>
+#include <memory>
+#include <sdbusplus/bus.hpp>
+#include <string>
+#include <vector>
+#include <xyz/openbmc_project/Common/FactoryReset/server.hpp>
+
+#ifndef SDBUSPP_NEW_CAMELCASE
+#define vlan vLAN
+#endif
+
+namespace phosphor
+{
+namespace network
+{
+
+using SystemConfPtr = std::unique_ptr<SystemConfiguration>;
+using DHCPConfPtr = std::unique_ptr<dhcp::Configuration>;
+
+namespace fs = std::filesystem;
+namespace details
+{
+
+template <typename T, typename U>
+using ServerObject = typename sdbusplus::server::object::object<T, U>;
+
+using VLANCreateIface = details::ServerObject<
+ sdbusplus::xyz::openbmc_project::Network::VLAN::server::Create,
+ sdbusplus::xyz::openbmc_project::Common::server::FactoryReset>;
+
+} // namespace details
+
+/** @class Manager
+ * @brief OpenBMC network manager implementation.
+ */
+class Manager : public details::VLANCreateIface
+{
+ public:
+ Manager() = delete;
+ Manager(const Manager&) = delete;
+ Manager& operator=(const Manager&) = delete;
+ Manager(Manager&&) = delete;
+ Manager& operator=(Manager&&) = delete;
+ virtual ~Manager() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] dir - Network Configuration directory path.
+ */
+ Manager(sdbusplus::bus::bus& bus, const char* objPath,
+ const std::string& dir);
+
+ ObjectPath vlan(IntfName interfaceName, uint32_t id) override;
+
+ /** @brief write the network conf file with the in-memory objects.
+ */
+ void writeToConfigurationFile();
+
+ /** @brief Fetch the interface and the ipaddress details
+ * from the system and create the ethernet interraces
+ * dbus object.
+ */
+ virtual void createInterfaces();
+
+ /** @brief create child interface object and the system conf object.
+ */
+ void createChildObjects();
+
+ /** @brief sets the network conf directory.
+ * @param[in] dirName - Absolute path of the directory.
+ */
+ void setConfDir(const fs::path& dir);
+
+ /** @brief gets the network conf directory.
+ */
+ fs::path getConfDir()
+ {
+ return confDir;
+ }
+
+ /** @brief gets the system conf object.
+ *
+ */
+ const SystemConfPtr& getSystemConf()
+ {
+ return systemConf;
+ }
+
+ /** @brief gets the dhcp conf object.
+ *
+ */
+ const DHCPConfPtr& getDHCPConf()
+ {
+ return dhcpConf;
+ }
+
+ /** @brief create the default network files for each interface
+ * @detail if force param is true then forcefully create the network
+ * files otherwise if network file doesn't exist then
+ * create it.
+ * @param[in] force - forcefully create the file
+ * @return true if network file created else false
+ */
+ bool createDefaultNetworkFiles(bool force);
+
+ /** @brief restart the network timers. */
+ void restartTimers();
+
+ /** @brief This function gets the MAC address from the VPD and
+ * sets it on the corresponding ethernet interface during first
+ * Boot, once it sets the MAC from VPD, it creates a file named
+ * firstBoot under /var/lib to make sure we dont run this function
+ * again.
+ *
+ * @param[in] ethPair - Its a pair of ethernet interface name & the
+ * corresponding MAC Address from the VPD
+ *
+ * return - NULL
+ */
+ void setFistBootMACOnInterface(
+ const std::pair<std::string, std::string>& ethPair);
+
+ /** @brief Restart the systemd unit
+ * @param[in] unit - systemd unit name which needs to be
+ * restarted.
+ */
+ virtual void restartSystemdUnit(const std::string& unit);
+
+ /** @brief Returns the number of interfaces under this manager.
+ *
+ * @return the number of interfaces managed by this manager.
+ */
+ int getInterfaceCount()
+ {
+ return interfaces.size();
+ }
+
+ /** @brief Does the requested interface exist under this manager?
+ *
+ * @param[in] intf - the interface name to check.
+ * @return true if found, false otherwise.
+ */
+ bool hasInterface(const std::string& intf)
+ {
+ return (interfaces.find(intf) != interfaces.end());
+ }
+
+ protected:
+ /** @brief Persistent sdbusplus DBus bus connection. */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief Persistent map of EthernetInterface dbus objects and their names
+ */
+ std::map<IntfName, std::shared_ptr<EthernetInterface>> interfaces;
+
+ /** @brief BMC network reset - resets network configuration for BMC. */
+ void reset() override;
+
+ /** @brief Path of Object. */
+ std::string objectPath;
+
+ /** @brief pointer to system conf object. */
+ SystemConfPtr systemConf = nullptr;
+
+ /** @brief pointer to dhcp conf object. */
+ DHCPConfPtr dhcpConf = nullptr;
+
+ /** @brief Network Configuration directory. */
+ fs::path confDir;
+};
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/network_manager_main.cpp b/src/network_manager_main.cpp
new file mode 100644
index 0000000..b0d6d2e
--- /dev/null
+++ b/src/network_manager_main.cpp
@@ -0,0 +1,342 @@
+#include "config.h"
+
+#include "network_manager.hpp"
+#include "rtnetlink_server.hpp"
+#include "types.hpp"
+#include "watch.hpp"
+
+#include <linux/netlink.h>
+
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <memory>
+#ifdef SYNC_MAC_FROM_INVENTORY
+#include <nlohmann/json.hpp>
+#endif
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/server/manager.hpp>
+#include <sdeventplus/event.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+using phosphor::logging::elog;
+using phosphor::logging::entry;
+using phosphor::logging::level;
+using phosphor::logging::log;
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using DbusObjectPath = std::string;
+using DbusInterface = std::string;
+using PropertyValue = std::string;
+
+constexpr char NETWORK_CONF_DIR[] = "/etc/systemd/network";
+
+constexpr char DEFAULT_OBJPATH[] = "/xyz/openbmc_project/network";
+
+constexpr auto firstBootPath = "/var/lib/network/firstBoot_";
+constexpr auto configFile = "/usr/share/network/config.json";
+
+constexpr auto invNetworkIntf =
+ "xyz.openbmc_project.Inventory.Item.NetworkInterface";
+
+namespace phosphor
+{
+namespace network
+{
+
+std::unique_ptr<phosphor::network::Manager> manager = nullptr;
+std::unique_ptr<Timer> refreshObjectTimer = nullptr;
+std::unique_ptr<Timer> restartTimer = nullptr;
+
+#ifdef SYNC_MAC_FROM_INVENTORY
+std::unique_ptr<sdbusplus::bus::match::match> EthInterfaceMatch = nullptr;
+std::vector<std::string> first_boot_status;
+
+bool setInventoryMACOnSystem(sdbusplus::bus::bus& bus,
+ const nlohmann::json& configJson,
+ const std::string& intfname)
+{
+ try
+ {
+ auto inventoryMAC = mac_address::getfromInventory(bus, intfname);
+ if (!mac_address::toString(inventoryMAC).empty())
+ {
+ log<level::INFO>("Mac Address in Inventory on "),
+ entry("Interface : ", intfname.c_str()),
+ entry("MAC Address :",
+ (mac_address::toString(inventoryMAC)).c_str());
+ manager->setFistBootMACOnInterface(std::make_pair(
+ intfname.c_str(), mac_address::toString(inventoryMAC)));
+ first_boot_status.push_back(intfname.c_str());
+ bool status = true;
+ for (const auto& keys : configJson.items())
+ {
+ if (!(std::find(first_boot_status.begin(),
+ first_boot_status.end(),
+ keys.key()) != first_boot_status.end()))
+ {
+ log<level::INFO>("Interface MAC is NOT set from VPD"),
+ entry("INTERFACE", keys.key().c_str());
+ status = false;
+ }
+ }
+ if (status)
+ {
+ log<level::INFO>("Removing the match for ethernet interfaces");
+ phosphor::network::EthInterfaceMatch = nullptr;
+ }
+ }
+ else
+ {
+ log<level::INFO>("Nothing is present in Inventory");
+ return false;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Exception occurred during getting of MAC "
+ "address from Inventory");
+ return false;
+ }
+ return true;
+}
+
+// register the macthes to be monitored from inventory manager
+void registerSignals(sdbusplus::bus::bus& bus, const nlohmann::json& configJson)
+{
+ log<level::INFO>("Registering the Inventory Signals Matcher");
+
+ static std::unique_ptr<sdbusplus::bus::match::match> MacAddressMatch;
+
+ auto callback = [&](sdbusplus::message::message& m) {
+ std::map<DbusObjectPath,
+ std::map<DbusInterface, std::variant<PropertyValue>>>
+ interfacesProperties;
+
+ sdbusplus::message::object_path objPath;
+ std::pair<std::string, std::string> ethPair;
+ m.read(objPath, interfacesProperties);
+
+ for (const auto& pattern : configJson.items())
+ {
+ if (objPath.str.find(pattern.value()) != std::string::npos)
+ {
+ for (auto& interface : interfacesProperties)
+ {
+ if (interface.first == invNetworkIntf)
+ {
+ for (const auto& property : interface.second)
+ {
+ if (property.first == "MACAddress")
+ {
+ ethPair = std::make_pair(
+ pattern.key(),
+ std::get<std::string>(property.second));
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (!(ethPair.first.empty() || ethPair.second.empty()))
+ {
+ manager->setFistBootMACOnInterface(ethPair);
+ }
+ }
+ }
+ };
+
+ MacAddressMatch = std::make_unique<sdbusplus::bus::match::match>(
+ bus,
+ "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
+ "member='InterfacesAdded',path='/xyz/openbmc_project/"
+ "inventory'",
+ callback);
+}
+
+void watchEthernetInterface(sdbusplus::bus::bus& bus,
+ const nlohmann::json& configJson)
+{
+ auto mycallback = [&](sdbusplus::message::message& m) {
+ std::map<DbusObjectPath,
+ std::map<DbusInterface, std::variant<PropertyValue>>>
+ interfacesProperties;
+
+ sdbusplus::message::object_path objPath;
+ std::pair<std::string, std::string> ethPair;
+ m.read(objPath, interfacesProperties);
+ for (const auto& interfaces : interfacesProperties)
+ {
+ if (interfaces.first ==
+ "xyz.openbmc_project.Network.EthernetInterface")
+ {
+ for (const auto& property : interfaces.second)
+ {
+ if (property.first == "InterfaceName")
+ {
+ std::string infname =
+ std::get<std::string>(property.second);
+
+ if (configJson.find(infname) == configJson.end())
+ {
+ // ethernet interface not found in configJSON
+ // check if it is not sit0 interface, as it is
+ // expected.
+ if (infname != "sit0")
+ {
+ log<level::ERR>(
+ "Wrong Interface Name in Config Json");
+ }
+ }
+ else
+ {
+ if (!phosphor::network::setInventoryMACOnSystem(
+ bus, configJson, infname))
+ {
+ phosphor::network::registerSignals(bus,
+ configJson);
+ phosphor::network::EthInterfaceMatch = nullptr;
+ }
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ };
+ // Incase if phosphor-inventory-manager started early and the VPD is already
+ // collected by the time network service has come up, better to check the
+ // VPD directly and set the MAC Address on the respective Interface.
+
+ bool registeredSignals = false;
+ for (const auto& interfaceString : configJson.items())
+ {
+ if (!std::filesystem::exists(firstBootPath + interfaceString.key()) &&
+ !registeredSignals)
+ {
+
+ log<level::INFO>(
+ "First boot file is not present, check VPD for MAC");
+ phosphor::network::EthInterfaceMatch = std::make_unique<
+ sdbusplus::bus::match::match>(
+ bus,
+ "interface='org.freedesktop.DBus.ObjectManager',type='signal',"
+ "member='InterfacesAdded',path='/xyz/openbmc_project/network'",
+ mycallback);
+ registeredSignals = true;
+ }
+ }
+}
+
+#endif
+
+/** @brief refresh the network objects. */
+void refreshObjects()
+{
+ if (manager)
+ {
+ log<level::INFO>("Refreshing the objects.");
+ manager->createChildObjects();
+ log<level::INFO>("Refreshing complete.");
+ }
+}
+
+/** @brief restart the systemd networkd. */
+void restartNetwork()
+{
+ if (manager)
+ {
+ manager->restartSystemdUnit("systemd-networkd.service");
+ }
+}
+
+void initializeTimers()
+{
+ auto event = sdeventplus::Event::get_default();
+ refreshObjectTimer =
+ std::make_unique<Timer>(event, std::bind(refreshObjects));
+ restartTimer = std::make_unique<Timer>(event, std::bind(restartNetwork));
+}
+
+} // namespace network
+} // namespace phosphor
+
+void createNetLinkSocket(phosphor::Descriptor& smartSock)
+{
+ // RtnetLink socket
+ auto fd = socket(PF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, NETLINK_ROUTE);
+ if (fd < 0)
+ {
+ log<level::ERR>("Unable to create the net link socket",
+ entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+ smartSock.set(fd);
+}
+
+int main(int /*argc*/, char** /*argv*/)
+{
+ phosphor::network::initializeTimers();
+
+ auto bus = sdbusplus::bus::new_default();
+
+ // Need sd_event to watch for OCC device errors
+ sd_event* event = nullptr;
+ auto r = sd_event_default(&event);
+ if (r < 0)
+ {
+ log<level::ERR>("Error creating a default sd_event handler");
+ return r;
+ }
+
+ phosphor::network::EventPtr eventPtr{event};
+ event = nullptr;
+
+ // Attach the bus to sd_event to service user requests
+ bus.attach_event(eventPtr.get(), SD_EVENT_PRIORITY_NORMAL);
+
+ // Add sdbusplus Object Manager for the 'root' path of the network manager.
+ sdbusplus::server::manager::manager objManager(bus, DEFAULT_OBJPATH);
+ bus.request_name(DEFAULT_BUSNAME);
+
+ phosphor::network::manager = std::make_unique<phosphor::network::Manager>(
+ bus, DEFAULT_OBJPATH, NETWORK_CONF_DIR);
+
+ // create the default network files if the network file
+ // is not there for any interface.
+ // Parameter false means don't create the network
+ // files forcefully.
+ if (phosphor::network::manager->createDefaultNetworkFiles(false))
+ {
+ // if files created restart the network.
+ // don't need to call the create child objects as eventhandler
+ // will create it.
+ phosphor::network::restartNetwork();
+ }
+ else
+ {
+ // this will add the additional fixes which is needed
+ // in the existing network file.
+ phosphor::network::manager->writeToConfigurationFile();
+ // whenever the configuration file gets written it restart
+ // the network which creates the network objects
+ }
+
+ // RtnetLink socket
+ phosphor::Descriptor smartSock;
+ createNetLinkSocket(smartSock);
+
+ // RTNETLINK event handler
+ phosphor::network::rtnetlink::Server svr(eventPtr, smartSock);
+
+#ifdef SYNC_MAC_FROM_INVENTORY
+ std::ifstream in(configFile);
+ nlohmann::json configJson;
+ in >> configJson;
+ phosphor::network::watchEthernetInterface(bus, configJson);
+#endif
+ sd_event_loop(eventPtr.get());
+}
diff --git a/src/routing_table.cpp b/src/routing_table.cpp
new file mode 100644
index 0000000..68a9ee3
--- /dev/null
+++ b/src/routing_table.cpp
@@ -0,0 +1,232 @@
+#include "routing_table.hpp"
+
+#include "util.hpp"
+
+#include <arpa/inet.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <optional>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <stdexcept>
+#include <string_view>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace route
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Table::Table()
+{
+ try
+ {
+ getRoutes();
+ }
+ catch (InternalFailure& e)
+ {
+ commit<InternalFailure>();
+ }
+}
+
+int Table::readNetLinkSock(int sockFd, std::array<char, BUFSIZE>& buf)
+{
+ struct nlmsghdr* nlHdr = nullptr;
+ int readLen{};
+ int msgLen{};
+ uint8_t seqNum = 1;
+ uint8_t pID = getpid();
+ char* bufPtr = buf.data();
+
+ do
+ {
+ // Receive response from the kernel
+ if ((readLen = recv(sockFd, bufPtr, BUFSIZE - msgLen, 0)) < 0)
+ {
+ auto error = errno;
+ log<level::ERR>("Socket recv failed:",
+ entry("ERROR=%s", strerror(error)));
+ elog<InternalFailure>();
+ }
+
+ nlHdr = reinterpret_cast<nlmsghdr*>(bufPtr);
+
+ // Check if the header is valid
+
+ if ((NLMSG_OK(nlHdr, readLen) == 0) ||
+ (nlHdr->nlmsg_type == NLMSG_ERROR))
+ {
+
+ auto error = errno;
+ log<level::ERR>("Error validating header",
+ entry("NLMSGTYPE=%d", nlHdr->nlmsg_type),
+ entry("ERROR=%s", strerror(error)));
+ elog<InternalFailure>();
+ }
+
+ // Check if the its the last message
+ if (nlHdr->nlmsg_type == NLMSG_DONE)
+ {
+ break;
+ }
+ else
+ {
+ // Else move the pointer to buffer appropriately
+ bufPtr += readLen;
+ msgLen += readLen;
+ }
+
+ // Check if its a multi part message
+ if ((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0)
+ {
+ break;
+ }
+ } while ((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pID));
+ return msgLen;
+}
+
+void Table::parseRoutes(const nlmsghdr* nlHdr)
+{
+ rtmsg* rtMsg = nullptr;
+ rtattr* rtAttr = nullptr;
+ int rtLen{};
+ std::optional<InAddrAny> dstAddr;
+ std::optional<InAddrAny> gateWayAddr;
+ char ifName[IF_NAMESIZE] = {};
+
+ rtMsg = reinterpret_cast<rtmsg*>(NLMSG_DATA(nlHdr));
+
+ // If the route is not for AF_INET{,6} or does not belong to main routing
+ // table then return.
+ if ((rtMsg->rtm_family != AF_INET && rtMsg->rtm_family != AF_INET6) ||
+ rtMsg->rtm_table != RT_TABLE_MAIN)
+ {
+ return;
+ }
+
+ // get the rtattr field
+ rtAttr = reinterpret_cast<rtattr*>(RTM_RTA(rtMsg));
+
+ rtLen = RTM_PAYLOAD(nlHdr);
+
+ for (; RTA_OK(rtAttr, rtLen); rtAttr = RTA_NEXT(rtAttr, rtLen))
+ {
+ std::string_view attrData(reinterpret_cast<char*>(RTA_DATA(rtAttr)),
+ RTA_PAYLOAD(rtAttr));
+ switch (rtAttr->rta_type)
+ {
+ case RTA_OIF:
+ if_indextoname(*reinterpret_cast<int*>(RTA_DATA(rtAttr)),
+ ifName);
+ break;
+ case RTA_GATEWAY:
+ gateWayAddr = addrFromBuf(rtMsg->rtm_family, attrData);
+ break;
+ case RTA_DST:
+ dstAddr = addrFromBuf(rtMsg->rtm_family, attrData);
+ break;
+ }
+ }
+
+ std::string dstStr;
+ if (dstAddr)
+ {
+ dstStr = toString(*dstAddr);
+ }
+ std::string gatewayStr;
+ if (gateWayAddr)
+ {
+ gatewayStr = toString(*gateWayAddr);
+ }
+ if (!dstAddr && gateWayAddr)
+ {
+ std::string ifNameStr(ifName);
+ if (rtMsg->rtm_family == AF_INET)
+ {
+ defaultGateway[ifNameStr] = gatewayStr;
+ }
+ else if (rtMsg->rtm_family == AF_INET6)
+ {
+ defaultGateway6[ifNameStr] = gatewayStr;
+ }
+ }
+ Entry route(dstStr, gatewayStr, ifName);
+ // if there is already existing route for this network
+ // then ignore the next one as it would not be used by the
+ // routing policy
+ // So don't update the route entry for the network for which
+ // there is already a route exist.
+ if (routeList.find(dstStr) == routeList.end())
+ {
+ routeList.emplace(std::make_pair(dstStr, std::move(route)));
+ }
+}
+
+Map Table::getRoutes()
+{
+ nlmsghdr* nlMsg = nullptr;
+ std::array<char, BUFSIZE> msgBuf = {0};
+
+ int sock = -1;
+ int len{0};
+
+ uint8_t msgSeq{0};
+
+ // Create Socket
+ if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during socket creation",
+ entry("ERRNO=%s", strerror(error)));
+ elog<InternalFailure>();
+ }
+
+ phosphor::Descriptor smartSock(sock);
+ sock = -1;
+
+ // point the header and the msg structure pointers into the buffer.
+ nlMsg = reinterpret_cast<nlmsghdr*>(msgBuf.data());
+ // Length of message
+ nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg));
+ // Get the routes from kernel routing table
+ nlMsg->nlmsg_type = RTM_GETROUTE;
+ // The message is a request for dump
+ nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+
+ nlMsg->nlmsg_seq = msgSeq;
+ nlMsg->nlmsg_pid = getpid();
+
+ // Send the request
+ if (send(smartSock(), nlMsg, nlMsg->nlmsg_len, 0) < 0)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during send on netlink socket",
+ entry("ERRNO=%s", strerror(error)));
+ elog<InternalFailure>();
+ }
+
+ // Read the response
+ len = readNetLinkSock(smartSock(), msgBuf);
+
+ // Parse and print the response
+ for (; NLMSG_OK(nlMsg, len); nlMsg = NLMSG_NEXT(nlMsg, len))
+ {
+ parseRoutes(nlMsg);
+ }
+ return routeList;
+}
+
+} // namespace route
+} // namespace network
+} // namespace phosphor
diff --git a/src/routing_table.hpp b/src/routing_table.hpp
new file mode 100644
index 0000000..8b634c6
--- /dev/null
+++ b/src/routing_table.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <sys/socket.h>
+
+#include <iostream>
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+namespace phosphor
+{
+namespace network
+{
+namespace route
+{
+constexpr auto BUFSIZE = 4096;
+
+struct Entry
+{
+ // destination network
+ std::string destination;
+ // gateway for this network.
+ std::string gateway;
+ // interface for this route
+ std::string interface;
+ Entry(const std::string& dest, const std::string& gtw,
+ const std::string& intf) :
+ destination(dest),
+ gateway(gtw), interface(intf)
+ {
+ }
+
+ bool operator==(const Entry& rhs)
+ {
+ return this->destination == rhs.destination &&
+ this->gateway == rhs.gateway && this->interface == rhs.interface;
+ }
+};
+
+// Map of network address and the route entry
+using Map = std::map<std::string, struct Entry>;
+
+class Table
+{
+ public:
+ Table();
+ ~Table() = default;
+ Table(const Table&) = default;
+ Table& operator=(const Table&) = default;
+ Table(Table&&) = default;
+ Table& operator=(Table&&) = default;
+
+ /**
+ * @brief gets the list of routes.
+ *
+ * @returns list of routes.
+ */
+ Map getRoutes();
+
+ /**
+ * @brief gets the default v4 gateway.
+ *
+ * @returns the default v4 gateway list.
+ */
+ std::map<std::string, std::string> getDefaultGateway() const
+ {
+ return defaultGateway;
+ };
+
+ /**
+ * @brief gets the default v6 gateway.
+ *
+ * @returns the default v6 gateway list.
+ */
+ std::map<std::string, std::string> getDefaultGateway6() const
+ {
+ return defaultGateway6;
+ };
+
+ private:
+ /**
+ * @brief read the routing data from the socket and fill the buffer.
+ *
+ * @param[in] bufPtr - unique pointer to confidentiality algorithm
+ * instance
+ */
+ int readNetLinkSock(int sockFd, std::array<char, BUFSIZE>& buff);
+ /**
+ * @brief Parse the route and add it to the route list.
+ *
+ * @param[in] nlHdr - net link message header.
+ */
+ void parseRoutes(const struct nlmsghdr* nlHdr);
+
+ std::map<std::string, std::string> defaultGateway; // default gateway list
+ std::map<std::string, std::string> defaultGateway6; // default gateway list
+ Map routeList; // List of routes
+};
+
+} // namespace route
+} // namespace network
+} // namespace phosphor
diff --git a/src/rtnetlink_server.cpp b/src/rtnetlink_server.cpp
new file mode 100644
index 0000000..07ca08c
--- /dev/null
+++ b/src/rtnetlink_server.cpp
@@ -0,0 +1,171 @@
+#include "rtnetlink_server.hpp"
+
+#include "types.hpp"
+#include "util.hpp"
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <systemd/sd-daemon.h>
+#include <unistd.h>
+
+#include <memory>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string_view>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+extern std::unique_ptr<Timer> refreshObjectTimer;
+
+namespace rtnetlink
+{
+
+static bool shouldRefresh(const struct nlmsghdr& hdr, std::string_view data)
+{
+ switch (hdr.nlmsg_type)
+ {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ case RTM_NEWROUTE:
+ case RTM_DELROUTE:
+ {
+ return true;
+ }
+ case RTM_NEWNEIGH:
+ case RTM_DELNEIGH:
+ {
+ struct ndmsg ndm;
+ if (data.size() < sizeof(ndm))
+ {
+ return false;
+ }
+ memcpy(&ndm, data.data(), sizeof(ndm));
+ // We only want to refresh for static neighbors
+ return ndm.ndm_state & NUD_PERMANENT;
+ }
+ }
+
+ return false;
+}
+
+/* Call Back for the sd event loop */
+static int eventHandler(sd_event_source* /*es*/, int fd, uint32_t /*revents*/,
+ void* /*userdata*/)
+{
+ char buffer[phosphor::network::rtnetlink::BUFSIZE]{};
+ int len{};
+
+ auto netLinkHeader = reinterpret_cast<struct nlmsghdr*>(buffer);
+ while ((len = recv(fd, netLinkHeader, phosphor::network::rtnetlink::BUFSIZE,
+ 0)) > 0)
+ {
+ for (; (NLMSG_OK(netLinkHeader, len)) &&
+ (netLinkHeader->nlmsg_type != NLMSG_DONE);
+ netLinkHeader = NLMSG_NEXT(netLinkHeader, len))
+ {
+ std::string_view data(
+ reinterpret_cast<const char*>(NLMSG_DATA(netLinkHeader)),
+ netLinkHeader->nlmsg_len - NLMSG_HDRLEN);
+ if (shouldRefresh(*netLinkHeader, data))
+ {
+ // starting the timer here to make sure that we don't want
+ // create the child objects multiple times.
+ if (!refreshObjectTimer->isEnabled())
+ {
+ // if start timer throws exception then let the application
+ // crash
+ refreshObjectTimer->restartOnce(refreshTimeout);
+ } // end if
+ } // end if
+
+ } // end for
+
+ } // end while
+
+ return 0;
+}
+
+Server::Server(EventPtr& eventPtr, const phosphor::Descriptor& smartSock)
+{
+ using namespace phosphor::logging;
+ using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+ struct sockaddr_nl addr
+ {
+ };
+ int r{};
+
+ sigset_t ss{};
+ // check that the given socket is valid or not.
+ if (smartSock() < 0)
+ {
+ r = -EBADF;
+ goto finish;
+ }
+
+ if (sigemptyset(&ss) < 0 || sigaddset(&ss, SIGTERM) < 0 ||
+ sigaddset(&ss, SIGINT) < 0)
+ {
+ r = -errno;
+ goto finish;
+ }
+ /* Block SIGTERM first, so that the event loop can handle it */
+ if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0)
+ {
+ r = -errno;
+ goto finish;
+ }
+
+ /* Let's make use of the default handler and "floating"
+ reference features of sd_event_add_signal() */
+
+ r = sd_event_add_signal(eventPtr.get(), NULL, SIGTERM, NULL, NULL);
+ if (r < 0)
+ {
+ goto finish;
+ }
+
+ r = sd_event_add_signal(eventPtr.get(), NULL, SIGINT, NULL, NULL);
+ if (r < 0)
+ {
+ goto finish;
+ }
+
+ std::memset(&addr, 0, sizeof(addr));
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR |
+ RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_ROUTE | RTMGRP_NEIGH;
+
+ if (bind(smartSock(), (struct sockaddr*)&addr, sizeof(addr)) < 0)
+ {
+ r = -errno;
+ goto finish;
+ }
+
+ r = sd_event_add_io(eventPtr.get(), nullptr, smartSock(), EPOLLIN,
+ eventHandler, nullptr);
+ if (r < 0)
+ {
+ goto finish;
+ }
+
+finish:
+
+ if (r < 0)
+ {
+ log<level::ERR>("Failure Occurred in starting of server:",
+ entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+}
+
+} // namespace rtnetlink
+} // namespace network
+} // namespace phosphor
diff --git a/src/rtnetlink_server.hpp b/src/rtnetlink_server.hpp
new file mode 100644
index 0000000..e62f51c
--- /dev/null
+++ b/src/rtnetlink_server.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "types.hpp"
+#include "util.hpp"
+
+#include <systemd/sd-event.h>
+
+namespace phosphor
+{
+namespace network
+{
+namespace rtnetlink
+{
+
+constexpr auto BUFSIZE = 4096;
+
+/** General rtnetlink server which waits for the POLLIN event
+ and calls the call back once it gets the event.
+ Usage would be create the server with the call back
+ and call the run method.
+ */
+
+class Server
+{
+
+ public:
+ /** @brief Constructor
+ *
+ * @details Sets up the server to handle incoming RTNETLINK events
+ *
+ * @param[in] eventPtr - Unique ptr reference to sd_event.
+ * @param[in] socket - netlink socket.
+ */
+ Server(EventPtr& eventPtr, const phosphor::Descriptor& socket);
+
+ Server() = delete;
+ ~Server() = default;
+ Server(const Server&) = delete;
+ Server& operator=(const Server&) = delete;
+ Server(Server&&) = default;
+ Server& operator=(Server&&) = default;
+};
+
+} // namespace rtnetlink
+} // namespace network
+} // namespace phosphor
diff --git a/src/system_configuration.cpp b/src/system_configuration.cpp
new file mode 100644
index 0000000..4fbf601
--- /dev/null
+++ b/src/system_configuration.cpp
@@ -0,0 +1,151 @@
+#include "config.h"
+
+#include "system_configuration.hpp"
+
+#include "network_manager.hpp"
+#include "routing_table.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+// systemd service to kick start a target.
+constexpr auto HOSTNAMED_SERVICE = "org.freedesktop.hostname1";
+constexpr auto HOSTNAMED_SERVICE_PATH = "/org/freedesktop/hostname1";
+constexpr auto HOSTNAMED_INTERFACE = "org.freedesktop.hostname1";
+constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
+constexpr auto METHOD_GET = "Get";
+constexpr auto METHOD_SET = "SetStaticHostname";
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+using InvalidArgumentMetadata = xyz::openbmc_project::Common::InvalidArgument;
+
+using SystemConfigIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::SystemConfiguration;
+
+SystemConfiguration::SystemConfiguration(sdbusplus::bus::bus& bus,
+ const std::string& objPath,
+ Manager& parent) :
+ Iface(bus, objPath.c_str(), true),
+ bus(bus), manager(parent)
+{
+ auto name = getHostNameFromSystem();
+ route::Table routingTable;
+
+ SystemConfigIntf::hostName(name);
+ auto gatewayList = routingTable.getDefaultGateway();
+ auto gateway6List = routingTable.getDefaultGateway6();
+ // Assign first entry of gateway list
+ std::string gateway;
+ std::string gateway6;
+ if (!gatewayList.empty())
+ {
+ gateway = gatewayList.begin()->second;
+ }
+ if (!gateway6List.empty())
+ {
+ gateway6 = gateway6List.begin()->second;
+ }
+
+ SystemConfigIntf::defaultGateway(gateway);
+ SystemConfigIntf::defaultGateway6(gateway6);
+
+ this->emit_object_added();
+}
+
+std::string SystemConfiguration::hostName(std::string name)
+{
+ if (SystemConfigIntf::hostName() == name)
+ {
+ return name;
+ }
+ auto method = bus.new_method_call(HOSTNAMED_SERVICE, HOSTNAMED_SERVICE_PATH,
+ HOSTNAMED_INTERFACE, METHOD_SET);
+
+ method.append(name, true);
+
+ if (!bus.call(method))
+ {
+ log<level::ERR>("Failed to set the hostname");
+ report<InternalFailure>();
+ return SystemConfigIntf::hostName();
+ }
+
+ return SystemConfigIntf::hostName(name);
+}
+
+std::string SystemConfiguration::getHostNameFromSystem() const
+{
+ try
+ {
+ std::variant<std::string> name;
+ auto method =
+ bus.new_method_call(HOSTNAMED_SERVICE, HOSTNAMED_SERVICE_PATH,
+ PROPERTY_INTERFACE, METHOD_GET);
+
+ method.append(HOSTNAMED_INTERFACE, "Hostname");
+
+ auto reply = bus.call(method);
+
+ reply.read(name);
+ return std::get<std::string>(name);
+ }
+ catch (const sdbusplus::exception::SdBusError& ex)
+ {
+ log<level::ERR>(
+ "Failed to get the hostname from systemd-hostnamed service",
+ entry("ERR=%s", ex.what()));
+ }
+ return "";
+}
+
+std::string SystemConfiguration::defaultGateway(std::string gateway)
+{
+ auto gw = SystemConfigIntf::defaultGateway();
+ if (gw == gateway)
+ {
+ return gw;
+ }
+
+ if (!isValidIP(AF_INET, gateway))
+ {
+ log<level::ERR>("Not a valid v4 Gateway",
+ entry("GATEWAY=%s", gateway.c_str()));
+ elog<InvalidArgument>(
+ InvalidArgumentMetadata::ARGUMENT_NAME("GATEWAY"),
+ InvalidArgumentMetadata::ARGUMENT_VALUE(gateway.c_str()));
+ }
+ gw = SystemConfigIntf::defaultGateway(gateway);
+ manager.writeToConfigurationFile();
+ return gw;
+}
+
+std::string SystemConfiguration::defaultGateway6(std::string gateway)
+{
+ auto gw = SystemConfigIntf::defaultGateway6();
+ if (gw == gateway)
+ {
+ return gw;
+ }
+
+ if (!isValidIP(AF_INET6, gateway))
+ {
+ log<level::ERR>("Not a valid v6 Gateway",
+ entry("GATEWAY=%s", gateway.c_str()));
+ elog<InvalidArgument>(
+ InvalidArgumentMetadata::ARGUMENT_NAME("GATEWAY"),
+ InvalidArgumentMetadata::ARGUMENT_VALUE(gateway.c_str()));
+ }
+ gw = SystemConfigIntf::defaultGateway6(gateway);
+ manager.writeToConfigurationFile();
+ return gw;
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/system_configuration.hpp b/src/system_configuration.hpp
new file mode 100644
index 0000000..a29309c
--- /dev/null
+++ b/src/system_configuration.hpp
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <xyz/openbmc_project/Network/SystemConfiguration/server.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+using SystemConfigIntf =
+ sdbusplus::xyz::openbmc_project::Network::server::SystemConfiguration;
+
+using Iface = sdbusplus::server::object::object<SystemConfigIntf>;
+
+class Manager; // forward declaration of network manager.
+
+/** @class SystemConfiguration
+ * @brief Network system configuration.
+ * @details A concrete implementation for the
+ * xyz.openbmc_project.Network.SystemConfiguration DBus API.
+ */
+class SystemConfiguration : public Iface
+{
+ public:
+ SystemConfiguration() = default;
+ SystemConfiguration(const SystemConfiguration&) = delete;
+ SystemConfiguration& operator=(const SystemConfiguration&) = delete;
+ SystemConfiguration(SystemConfiguration&&) = delete;
+ SystemConfiguration& operator=(SystemConfiguration&&) = delete;
+ virtual ~SystemConfiguration() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] parent - Parent object.
+ */
+ SystemConfiguration(sdbusplus::bus::bus& bus, const std::string& objPath,
+ Manager& parent);
+
+ /** @brief set the hostname of the system.
+ * @param[in] name - host name of the system.
+ */
+ std::string hostName(std::string name) override;
+
+ /** @brief set the default v4 gateway of the system.
+ * @param[in] gateway - default v4 gateway of the system.
+ */
+ std::string defaultGateway(std::string gateway) override;
+
+ using SystemConfigIntf::defaultGateway;
+
+ /** @brief set the default v6 gateway of the system.
+ * @param[in] gateway - default v6 gateway of the system.
+ */
+ std::string defaultGateway6(std::string gateway) override;
+
+ using SystemConfigIntf::defaultGateway6;
+
+ private:
+ /** @brief get the hostname from the system by doing
+ * dbus call to hostnamed service.
+ */
+ std::string getHostNameFromSystem() const;
+
+ /** @brief Persistent sdbusplus DBus bus connection. */
+ sdbusplus::bus::bus& bus;
+
+ /** @brief Network Manager object. */
+ Manager& manager;
+};
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/types.hpp b/src/types.hpp
new file mode 100644
index 0000000..082d588
--- /dev/null
+++ b/src/types.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "ipaddress.hpp"
+
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <systemd/sd-event.h>
+
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <sdeventplus/clock.hpp>
+#include <sdeventplus/utility/timer.hpp>
+#include <set>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace phosphor
+{
+namespace network
+{
+
+using namespace std::chrono_literals;
+
+// wait for three seconds before restarting the networkd
+constexpr auto restartTimeout = 3s;
+
+// refresh the objets after five seconds as network
+// configuration takes 3-4 sec after systemd-networkd restart.
+constexpr auto refreshTimeout = restartTimeout + 7s;
+
+namespace systemd
+{
+namespace config
+{
+
+constexpr auto networkFilePrefix = "00-bmc-";
+constexpr auto networkFileSuffix = ".network";
+constexpr auto deviceFileSuffix = ".netdev";
+
+} // namespace config
+} // namespace systemd
+
+using IntfName = std::string;
+
+struct AddrInfo
+{
+ uint8_t addrType;
+ std::string ipaddress;
+ uint16_t prefix;
+};
+
+using Addr_t = ifaddrs*;
+
+struct AddrDeleter
+{
+ void operator()(Addr_t ptr) const
+ {
+ freeifaddrs(ptr);
+ }
+};
+
+using AddrPtr = std::unique_ptr<ifaddrs, AddrDeleter>;
+
+/* Need a custom deleter for freeing up sd_event */
+struct EventDeleter
+{
+ void operator()(sd_event* event) const
+ {
+ sd_event_unref(event);
+ }
+};
+using EventPtr = std::unique_ptr<sd_event, EventDeleter>;
+
+template <typename T>
+using UniquePtr = std::unique_ptr<T, std::function<void(T*)>>;
+
+// Byte representations for common address types in network byte order
+using InAddrAny = std::variant<struct in_addr, struct in6_addr>;
+
+using AddrList = std::list<AddrInfo>;
+using IntfAddrMap = std::map<IntfName, AddrList>;
+using InterfaceList = std::set<IntfName>;
+
+using Timer = sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>;
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/util.cpp b/src/util.cpp
new file mode 100644
index 0000000..5c5e427
--- /dev/null
+++ b/src/util.cpp
@@ -0,0 +1,673 @@
+#include "util.hpp"
+
+#include "config_parser.hpp"
+#include "types.hpp"
+
+#include <arpa/inet.h>
+#include <dirent.h>
+#include <net/if.h>
+#include <sys/wait.h>
+
+#include <algorithm>
+#include <cctype>
+#include <cstdlib>
+#include <cstring>
+#include <filesystem>
+#include <fstream>
+#include <list>
+#ifdef SYNC_MAC_FROM_INVENTORY
+#include <nlohmann/json.hpp>
+#endif
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <stdexcept>
+#include <stdplus/raw.hpp>
+#include <string>
+#include <variant>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+namespace fs = std::filesystem;
+
+namespace internal
+{
+
+void executeCommandinChildProcess(const char* path, char** args)
+{
+ using namespace std::string_literals;
+ pid_t pid = fork();
+ int status{};
+
+ if (pid == 0)
+ {
+ execv(path, args);
+ auto error = errno;
+ // create the command from var args.
+ std::string command = path + " "s;
+
+ for (int i = 0; args[i]; i++)
+ {
+ command += args[i] + " "s;
+ }
+
+ log<level::ERR>("Couldn't exceute the command",
+ entry("ERRNO=%d", error),
+ entry("CMD=%s", command.c_str()));
+ elog<InternalFailure>();
+ }
+ else if (pid < 0)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
+ elog<InternalFailure>();
+ }
+ else if (pid > 0)
+ {
+ while (waitpid(pid, &status, 0) == -1)
+ {
+ if (errno != EINTR)
+ { // Error other than EINTR
+ status = -1;
+ break;
+ }
+ }
+
+ if (status < 0)
+ {
+ std::string command = path + " "s;
+ for (int i = 0; args[i]; i++)
+ {
+ command += args[i] + " "s;
+ }
+
+ log<level::ERR>("Unable to execute the command",
+ entry("CMD=%s", command.c_str()),
+ entry("STATUS=%d", status));
+ elog<InternalFailure>();
+ }
+ }
+}
+
+/** @brief Get ignored interfaces from environment */
+std::string_view getIgnoredInterfacesEnv()
+{
+ auto r = std::getenv("IGNORED_INTERFACES");
+ if (r == nullptr)
+ {
+ return "";
+ }
+ return r;
+}
+
+/** @brief Parse the comma separated interface names */
+std::set<std::string_view> parseInterfaces(std::string_view interfaces)
+{
+ std::set<std::string_view> result;
+ while (true)
+ {
+ auto sep = interfaces.find(',');
+ auto interface = interfaces.substr(0, sep);
+ while (!interface.empty() && std::isspace(interface.front()))
+ {
+ interface.remove_prefix(1);
+ }
+ while (!interface.empty() && std::isspace(interface.back()))
+ {
+ interface.remove_suffix(1);
+ }
+ if (!interface.empty())
+ {
+ result.insert(interface);
+ }
+ if (sep == interfaces.npos)
+ {
+ break;
+ }
+ interfaces = interfaces.substr(sep + 1);
+ }
+ return result;
+}
+
+/** @brief Get the ignored interfaces */
+const std::set<std::string_view>& getIgnoredInterfaces()
+{
+ static auto ignoredInterfaces = parseInterfaces(getIgnoredInterfacesEnv());
+ return ignoredInterfaces;
+}
+
+} // namespace internal
+
+uint8_t toCidr(int addressFamily, const std::string& subnetMask)
+{
+ uint32_t subnet[sizeof(in6_addr) / sizeof(uint32_t)];
+ if (inet_pton(addressFamily, subnetMask.c_str(), &subnet) != 1)
+ {
+ log<level::ERR>("inet_pton failed:",
+ entry("SUBNETMASK=%s", subnetMask.c_str()));
+ return 0;
+ }
+
+ static_assert(sizeof(in6_addr) % sizeof(uint32_t) == 0);
+ static_assert(sizeof(in_addr) % sizeof(uint32_t) == 0);
+ auto i = (addressFamily == AF_INET ? sizeof(in_addr) : sizeof(in6_addr)) /
+ sizeof(uint32_t);
+ while (i > 0)
+ {
+ if (subnet[--i] != 0)
+ {
+ auto v = be32toh(subnet[i]);
+ static_assert(sizeof(unsigned) == sizeof(uint32_t));
+ auto trailing = __builtin_ctz(v);
+ auto ret = (i + 1) * 32 - trailing;
+ bool valid = ~v == 0 || 32 == trailing + __builtin_clz(~v);
+ while (i > 0 && (valid = (~subnet[--i] == 0) && valid))
+ ;
+ if (!valid)
+ {
+ log<level::ERR>("Invalid netmask",
+ entry("SUBNETMASK=%s", subnetMask.c_str()));
+ return 0;
+ }
+ return ret;
+ }
+ }
+ return 0;
+}
+
+std::string toMask(int addressFamily, uint8_t prefix)
+{
+ if (addressFamily == AF_INET6)
+ {
+ // TODO:- conversion for v6
+ return "";
+ }
+
+ if (prefix < 1 || prefix > 30)
+ {
+ log<level::ERR>("Invalid Prefix", entry("PREFIX=%d", prefix));
+ return "";
+ }
+ /* Create the netmask from the number of bits */
+ unsigned long mask = 0;
+ for (auto i = 0; i < prefix; i++)
+ {
+ mask |= 1 << (31 - i);
+ }
+ struct in_addr netmask;
+ netmask.s_addr = htonl(mask);
+ return inet_ntoa(netmask);
+}
+
+InAddrAny addrFromBuf(int addressFamily, std::string_view buf)
+{
+ if (addressFamily == AF_INET)
+ {
+ struct in_addr ret;
+ if (buf.size() != sizeof(ret))
+ {
+ throw std::runtime_error("Buf not in_addr sized");
+ }
+ memcpy(&ret, buf.data(), sizeof(ret));
+ return ret;
+ }
+ else if (addressFamily == AF_INET6)
+ {
+ struct in6_addr ret;
+ if (buf.size() != sizeof(ret))
+ {
+ throw std::runtime_error("Buf not in6_addr sized");
+ }
+ memcpy(&ret, buf.data(), sizeof(ret));
+ return ret;
+ }
+
+ throw std::runtime_error("Unsupported address family");
+}
+
+std::string toString(const struct in_addr& addr)
+{
+ std::string ip(INET_ADDRSTRLEN, '\0');
+ if (inet_ntop(AF_INET, &addr, ip.data(), ip.size()) == nullptr)
+ {
+ throw std::runtime_error("Failed to convert IP4 to string");
+ }
+
+ ip.resize(strlen(ip.c_str()));
+ return ip;
+}
+
+std::string toString(const struct in6_addr& addr)
+{
+ std::string ip(INET6_ADDRSTRLEN, '\0');
+ if (inet_ntop(AF_INET6, &addr, ip.data(), ip.size()) == nullptr)
+ {
+ throw std::runtime_error("Failed to convert IP6 to string");
+ }
+
+ ip.resize(strlen(ip.c_str()));
+ return ip;
+}
+
+std::string toString(const InAddrAny& addr)
+{
+ if (std::holds_alternative<struct in_addr>(addr))
+ {
+ const auto& v = std::get<struct in_addr>(addr);
+ return toString(v);
+ }
+ else if (std::holds_alternative<struct in6_addr>(addr))
+ {
+ const auto& v = std::get<struct in6_addr>(addr);
+ return toString(v);
+ }
+
+ throw std::runtime_error("Invalid addr type");
+}
+
+bool isLinkLocalIP(const std::string& address)
+{
+ return address.find(IPV4_PREFIX) == 0 || address.find(IPV6_PREFIX) == 0;
+}
+
+bool isValidIP(int addressFamily, const std::string& address)
+{
+ unsigned char buf[sizeof(struct in6_addr)];
+
+ return inet_pton(addressFamily, address.c_str(), buf) > 0;
+}
+
+bool isValidPrefix(int addressFamily, uint8_t prefixLength)
+{
+ if (addressFamily == AF_INET)
+ {
+ if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
+ prefixLength > IPV4_MAX_PREFIX_LENGTH)
+ {
+ return false;
+ }
+ }
+
+ if (addressFamily == AF_INET6)
+ {
+ if (prefixLength < IPV4_MIN_PREFIX_LENGTH ||
+ prefixLength > IPV6_MAX_PREFIX_LENGTH)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+IntfAddrMap getInterfaceAddrs()
+{
+ IntfAddrMap intfMap{};
+ struct ifaddrs* ifaddr = nullptr;
+
+ // attempt to fill struct with ifaddrs
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during the getifaddrs call",
+ entry("ERRNO=%s", strerror(error)));
+ elog<InternalFailure>();
+ }
+
+ AddrPtr ifaddrPtr(ifaddr);
+ ifaddr = nullptr;
+
+ std::string intfName{};
+
+ for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
+ {
+ // walk interfaces
+ if (ifa->ifa_addr == nullptr)
+ {
+ continue;
+ }
+
+ // get only INET interfaces not ipv6
+ if (ifa->ifa_addr->sa_family == AF_INET ||
+ ifa->ifa_addr->sa_family == AF_INET6)
+ {
+ // if loopback, or not running ignore
+ if ((ifa->ifa_flags & IFF_LOOPBACK) ||
+ !(ifa->ifa_flags & IFF_RUNNING))
+ {
+ continue;
+ }
+ intfName = ifa->ifa_name;
+ AddrInfo info{};
+ char ip[INET6_ADDRSTRLEN] = {0};
+ char subnetMask[INET6_ADDRSTRLEN] = {0};
+
+ if (ifa->ifa_addr->sa_family == AF_INET)
+ {
+
+ inet_ntop(ifa->ifa_addr->sa_family,
+ &(((struct sockaddr_in*)(ifa->ifa_addr))->sin_addr),
+ ip, sizeof(ip));
+
+ inet_ntop(
+ ifa->ifa_addr->sa_family,
+ &(((struct sockaddr_in*)(ifa->ifa_netmask))->sin_addr),
+ subnetMask, sizeof(subnetMask));
+ }
+ else
+ {
+ inet_ntop(ifa->ifa_addr->sa_family,
+ &(((struct sockaddr_in6*)(ifa->ifa_addr))->sin6_addr),
+ ip, sizeof(ip));
+
+ inet_ntop(
+ ifa->ifa_addr->sa_family,
+ &(((struct sockaddr_in6*)(ifa->ifa_netmask))->sin6_addr),
+ subnetMask, sizeof(subnetMask));
+ }
+
+ info.addrType = ifa->ifa_addr->sa_family;
+ info.ipaddress = ip;
+ info.prefix = toCidr(info.addrType, std::string(subnetMask));
+ intfMap[intfName].push_back(info);
+ }
+ }
+ return intfMap;
+}
+
+InterfaceList getInterfaces()
+{
+ InterfaceList interfaces{};
+ struct ifaddrs* ifaddr = nullptr;
+
+ // attempt to fill struct with ifaddrs
+ if (getifaddrs(&ifaddr) == -1)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during the getifaddrs call",
+ entry("ERRNO=%d", error));
+ elog<InternalFailure>();
+ }
+
+ AddrPtr ifaddrPtr(ifaddr);
+ ifaddr = nullptr;
+ const auto& ignoredInterfaces = internal::getIgnoredInterfaces();
+
+ for (ifaddrs* ifa = ifaddrPtr.get(); ifa != nullptr; ifa = ifa->ifa_next)
+ {
+ // walk interfaces
+ // if loopback ignore
+ if (ifa->ifa_flags & IFF_LOOPBACK ||
+ ignoredInterfaces.find(ifa->ifa_name) != ignoredInterfaces.end())
+ {
+ continue;
+ }
+ interfaces.emplace(ifa->ifa_name);
+ }
+ return interfaces;
+}
+
+void deleteInterface(const std::string& intf)
+{
+ pid_t pid = fork();
+ int status{};
+
+ if (pid == 0)
+ {
+
+ execl("/sbin/ip", "ip", "link", "delete", "dev", intf.c_str(), nullptr);
+ auto error = errno;
+ log<level::ERR>("Couldn't delete the device", entry("ERRNO=%d", error),
+ entry("INTF=%s", intf.c_str()));
+ elog<InternalFailure>();
+ }
+ else if (pid < 0)
+ {
+ auto error = errno;
+ log<level::ERR>("Error occurred during fork", entry("ERRNO=%d", error));
+ elog<InternalFailure>();
+ }
+ else if (pid > 0)
+ {
+ while (waitpid(pid, &status, 0) == -1)
+ {
+ if (errno != EINTR)
+ { /* Error other than EINTR */
+ status = -1;
+ break;
+ }
+ }
+
+ if (status < 0)
+ {
+ log<level::ERR>("Unable to delete the interface",
+ entry("INTF=%s", intf.c_str()),
+ entry("STATUS=%d", status));
+ elog<InternalFailure>();
+ }
+ }
+}
+
+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";
+}
+
+EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
+ const std::string& intf)
+{
+ EthernetInterfaceIntf::DHCPConf dhcp =
+ EthernetInterfaceIntf::DHCPConf::none;
+ // Get the interface mode value from systemd conf
+ // using namespace std::string_literals;
+ fs::path confPath = confDir;
+ std::string fileName = systemd::config::networkFilePrefix + intf +
+ systemd::config::networkFileSuffix;
+ confPath /= fileName;
+
+ auto rc = config::ReturnCode::SUCCESS;
+ config::ValueList values;
+ config::Parser parser(confPath.string());
+
+ std::tie(rc, values) = parser.getValues("Network", "DHCP");
+ if (rc != config::ReturnCode::SUCCESS)
+ {
+ log<level::DEBUG>("Unable to get the value for Network[DHCP]",
+ entry("RC=%d", rc));
+ return dhcp;
+ }
+ // There will be only single value for DHCP key.
+ if (values[0] == "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;
+}
+
+namespace mac_address
+{
+
+constexpr auto mapperBus = "xyz.openbmc_project.ObjectMapper";
+constexpr auto mapperObj = "/xyz/openbmc_project/object_mapper";
+constexpr auto mapperIntf = "xyz.openbmc_project.ObjectMapper";
+constexpr auto propIntf = "org.freedesktop.DBus.Properties";
+constexpr auto methodGet = "Get";
+constexpr auto configFile = "/usr/share/network/config.json";
+
+using DbusObjectPath = std::string;
+using DbusService = std::string;
+using DbusInterface = std::string;
+using ObjectTree =
+ std::map<DbusObjectPath, std::map<DbusService, std::vector<DbusInterface>>>;
+
+constexpr auto invBus = "xyz.openbmc_project.Inventory.Manager";
+constexpr auto invNetworkIntf =
+ "xyz.openbmc_project.Inventory.Item.NetworkInterface";
+constexpr auto invRoot = "/xyz/openbmc_project/inventory";
+
+ether_addr getfromInventory(sdbusplus::bus::bus& bus,
+ const std::string& intfName)
+{
+
+ std::string interfaceName = intfName;
+
+#ifdef SYNC_MAC_FROM_INVENTORY
+ // load the config JSON from the Read Only Path
+ std::ifstream in(configFile);
+ nlohmann::json configJson;
+ in >> configJson;
+ interfaceName = configJson[intfName];
+#endif
+
+ std::vector<DbusInterface> interfaces;
+ interfaces.emplace_back(invNetworkIntf);
+
+ auto depth = 0;
+
+ auto mapperCall =
+ bus.new_method_call(mapperBus, mapperObj, mapperIntf, "GetSubTree");
+
+ mapperCall.append(invRoot, depth, interfaces);
+
+ auto mapperReply = bus.call(mapperCall);
+ if (mapperReply.is_method_error())
+ {
+ log<level::ERR>("Error in mapper call");
+ elog<InternalFailure>();
+ }
+
+ ObjectTree objectTree;
+ mapperReply.read(objectTree);
+
+ if (objectTree.empty())
+ {
+ log<level::ERR>("No Object has implemented the interface",
+ entry("INTERFACE=%s", invNetworkIntf));
+ elog<InternalFailure>();
+ }
+
+ DbusObjectPath objPath;
+ DbusService service;
+
+ if (1 == objectTree.size())
+ {
+ objPath = objectTree.begin()->first;
+ service = objectTree.begin()->second.begin()->first;
+ }
+ else
+ {
+ // If there are more than 2 objects, object path must contain the
+ // interface name
+ for (auto const& object : objectTree)
+ {
+ log<level::INFO>("interface",
+ entry("INT=%s", interfaceName.c_str()));
+ log<level::INFO>("object", entry("OBJ=%s", object.first.c_str()));
+
+ if (std::string::npos != object.first.find(interfaceName.c_str()))
+ {
+ objPath = object.first;
+ service = object.second.begin()->first;
+ break;
+ }
+ }
+
+ if (objPath.empty())
+ {
+ log<level::ERR>("Can't find the object for the interface",
+ entry("intfName=%s", interfaceName.c_str()));
+ elog<InternalFailure>();
+ }
+ }
+
+ auto method = bus.new_method_call(service.c_str(), objPath.c_str(),
+ propIntf, methodGet);
+
+ method.append(invNetworkIntf, "MACAddress");
+
+ auto reply = bus.call(method);
+ if (reply.is_method_error())
+ {
+ log<level::ERR>("Failed to get MACAddress",
+ entry("PATH=%s", objPath.c_str()),
+ entry("INTERFACE=%s", invNetworkIntf));
+ elog<InternalFailure>();
+ }
+
+ std::variant<std::string> value;
+ reply.read(value);
+ return fromString(std::get<std::string>(value));
+}
+
+ether_addr fromString(const char* str)
+{
+ struct ether_addr* mac = ether_aton(str);
+ if (mac == nullptr)
+ {
+ throw std::invalid_argument("Invalid MAC Address");
+ }
+ return *mac;
+}
+
+std::string toString(const ether_addr& mac)
+{
+ char buf[18] = {0};
+ snprintf(buf, 18, "%02x:%02x:%02x:%02x:%02x:%02x", mac.ether_addr_octet[0],
+ mac.ether_addr_octet[1], mac.ether_addr_octet[2],
+ mac.ether_addr_octet[3], mac.ether_addr_octet[4],
+ mac.ether_addr_octet[5]);
+ return buf;
+}
+
+bool isEmpty(const ether_addr& mac)
+{
+ return stdplus::raw::equal(mac, ether_addr{});
+}
+
+bool isMulticast(const ether_addr& mac)
+{
+ return mac.ether_addr_octet[0] & 0b1;
+}
+
+bool isUnicast(const ether_addr& mac)
+{
+ return !isEmpty(mac) && !isMulticast(mac);
+}
+
+} // namespace mac_address
+} // namespace network
+} // namespace phosphor
diff --git a/src/util.hpp b/src/util.hpp
new file mode 100644
index 0000000..804d492
--- /dev/null
+++ b/src/util.hpp
@@ -0,0 +1,250 @@
+#pragma once
+
+#include "config.h"
+
+#include "ethernet_interface.hpp"
+#include "types.hpp"
+
+#include <netinet/ether.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <optional>
+#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 = 128;
+constexpr auto IPV4_PREFIX = "169.254";
+constexpr auto IPV6_PREFIX = "fe80";
+
+namespace mac_address
+{
+
+/** @brief gets the MAC address from the Inventory.
+ * @param[in] bus - DBUS Bus Object.
+ * @param[in] intfName - Interface name
+ */
+ether_addr getfromInventory(sdbusplus::bus::bus& bus,
+ const std::string& intfName);
+
+/** @brief Converts the given mac address into byte form
+ * @param[in] str - The mac address in human readable form
+ * @returns A mac address in network byte order
+ * @throws std::runtime_error for bad mac
+ */
+ether_addr fromString(const char* str);
+inline ether_addr fromString(const std::string& str)
+{
+ return fromString(str.c_str());
+}
+
+/** @brief Converts the given mac address bytes into a string
+ * @param[in] mac - The mac address
+ * @returns A valid mac address string
+ */
+std::string toString(const ether_addr& mac);
+
+/** @brief Determines if the mac address is empty
+ * @param[in] mac - The mac address
+ * @return True if 00:00:00:00:00:00
+ */
+bool isEmpty(const ether_addr& mac);
+
+/** @brief Determines if the mac address is a multicast address
+ * @param[in] mac - The mac address
+ * @return True if multicast bit is set
+ */
+bool isMulticast(const ether_addr& mac);
+
+/** @brief Determines if the mac address is a unicast address
+ * @param[in] mac - The mac address
+ * @return True if not multicast or empty
+ */
+bool isUnicast(const ether_addr& mac);
+
+} // namespace mac_address
+
+constexpr auto networkdService = "systemd-networkd.service";
+constexpr auto timeSynchdService = "systemd-timesyncd.service";
+
+/* @brief converts the given subnet into prefix notation.
+ * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
+ * @param[in] mask - Subnet Mask.
+ * @returns prefix.
+ */
+uint8_t toCidr(int addressFamily, const std::string& mask);
+
+/* @brief converts a sockaddr for the specified address family into
+ * a type_safe InAddrAny.
+ * @param[in] addressFamily - The address family of the buf
+ * @param[in] buf - The network byte order address
+ */
+InAddrAny addrFromBuf(int addressFamily, std::string_view buf);
+
+/* @brief converts the ip bytes into a string representation
+ * @param[in] addr - input ip address to convert.
+ * @returns String representation of the ip.
+ */
+std::string toString(const InAddrAny& addr);
+std::string toString(const struct in_addr& addr);
+std::string toString(const struct in6_addr& addr);
+
+/* @brief converts the prefix into subnetmask.
+ * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
+ * @param[in] prefix - prefix length.
+ * @returns subnet mask.
+ */
+std::string toMask(int addressFamily, uint8_t prefix);
+
+/* @brief checks that the given ip address is link local or not.
+ * @param[in] address - IP address.
+ * @returns true if it is linklocal otherwise false.
+ */
+bool isLinkLocalIP(const std::string& address);
+
+/* @brief checks that the given ip address valid or not.
+ * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
+ * @param[in] address - IP address.
+ * @returns true if it is valid otherwise false.
+ */
+bool isValidIP(int addressFamily, const std::string& address);
+
+/* @brief checks that the given prefix is valid or not.
+ * @param[in] addressFamily - IP address family(AF_INET/AF_INET6).
+ * @param[in] prefix - prefix length.
+ * @returns true if it is valid otherwise false.
+ */
+bool isValidPrefix(int addressFamily, uint8_t prefixLength);
+
+/** @brief Gets the map of interface and the associated
+ * address.
+ * @returns map of interface and the address.
+ */
+IntfAddrMap getInterfaceAddrs();
+
+/** @brief Get all the interfaces from the system.
+ * @returns list of interface names.
+ */
+InterfaceList getInterfaces();
+
+/** @brief Delete the given interface.
+ * @param[in] intf - interface name.
+ */
+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.
+ */
+EthernetInterfaceIntf::DHCPConf getDHCPValue(const std::string& confDir,
+ const std::string& intf);
+
+namespace internal
+{
+
+/* @brief runs the given command in child process.
+ * @param[in] path - path of the binary file which needs to be execeuted.
+ * @param[in] args - arguments of the command.
+ */
+void executeCommandinChildProcess(const char* path, char** args);
+
+/** @brief Get ignored interfaces from environment */
+std::string_view getIgnoredInterfacesEnv();
+
+/** @brief Parse the comma separated interface names */
+std::set<std::string_view> parseInterfaces(std::string_view interfaces);
+
+/** @brief Get the ignored interfaces */
+const std::set<std::string_view>& getIgnoredInterfaces();
+
+} // namespace internal
+
+/* @brief runs the given command in child process.
+ * @param[in] path -path of the binary file which needs to be execeuted.
+ * @param[in] tArgs - arguments of the command.
+ */
+template <typename... ArgTypes>
+void execute(const char* path, ArgTypes&&... tArgs)
+{
+ using expandType = char*[];
+
+ expandType args = {const_cast<char*>(tArgs)..., nullptr};
+
+ internal::executeCommandinChildProcess(path, args);
+}
+
+} // namespace network
+
+class Descriptor
+{
+ private:
+ /** default value */
+ int fd = -1;
+
+ public:
+ Descriptor() = default;
+ Descriptor(const Descriptor&) = delete;
+ Descriptor& operator=(const Descriptor&) = delete;
+ Descriptor(Descriptor&&) = delete;
+ Descriptor& operator=(Descriptor&&) = delete;
+
+ explicit Descriptor(int fd) : fd(fd)
+ {
+ }
+
+ /* @brief sets the internal file descriptor with the given descriptor
+ * and closes the old descriptor.
+ * @param[in] descriptor - File/Socket descriptor.
+ */
+ void set(int descriptor)
+ {
+ // same descriptor given
+ if (fd == descriptor)
+ {
+ return;
+ }
+
+ // close the old descriptor
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+
+ fd = descriptor;
+ }
+
+ ~Descriptor()
+ {
+ if (fd >= 0)
+ {
+ close(fd);
+ }
+ }
+
+ int operator()() const
+ {
+ return fd;
+ }
+};
+
+} // namespace phosphor
diff --git a/src/vlan_interface.cpp b/src/vlan_interface.cpp
new file mode 100644
index 0000000..4920c77
--- /dev/null
+++ b/src/vlan_interface.cpp
@@ -0,0 +1,80 @@
+#include "config.h"
+
+#include "vlan_interface.hpp"
+
+#include "ethernet_interface.hpp"
+#include "network_manager.hpp"
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <string>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+VlanInterface::VlanInterface(sdbusplus::bus::bus& bus,
+ const std::string& objPath, DHCPConf dhcpEnabled,
+ bool nicEnabled, uint32_t vlanID,
+ EthernetInterface& intf, Manager& parent) :
+ VlanIface(bus, objPath.c_str()),
+ DeleteIface(bus, objPath.c_str()),
+ EthernetInterface(bus, objPath, dhcpEnabled, parent, false),
+ parentInterface(intf)
+{
+ id(vlanID);
+ EthernetInterfaceIntf::nicEnabled(nicEnabled);
+ VlanIface::interfaceName(EthernetInterface::interfaceName());
+ MacAddressIntf::macAddress(parentInterface.macAddress());
+
+ emit_object_added();
+}
+
+std::string VlanInterface::macAddress(std::string)
+{
+ log<level::ERR>("Tried to set MAC address on VLAN");
+ elog<InternalFailure>();
+}
+
+void VlanInterface::writeDeviceFile()
+{
+ using namespace std::string_literals;
+ fs::path confPath = manager.getConfDir();
+ std::string fileName = EthernetInterface::interfaceName() + ".netdev"s;
+ confPath /= fileName;
+ std::fstream stream;
+ try
+ {
+ stream.open(confPath.c_str(), std::fstream::out);
+ }
+ catch (std::ios_base::failure& e)
+ {
+ log<level::ERR>("Unable to open the VLAN device file",
+ entry("FILE=%s", confPath.c_str()),
+ entry("ERROR=%s", e.what()));
+ elog<InternalFailure>();
+ }
+
+ stream << "[NetDev]\n";
+ stream << "Name=" << EthernetInterface::interfaceName() << "\n";
+ stream << "Kind=vlan\n";
+ stream << "[VLAN]\n";
+ stream << "Id=" << id() << "\n";
+ stream.close();
+}
+
+void VlanInterface::delete_()
+{
+ parentInterface.deleteVLANObject(EthernetInterface::interfaceName());
+}
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/vlan_interface.hpp b/src/vlan_interface.hpp
new file mode 100644
index 0000000..c003056
--- /dev/null
+++ b/src/vlan_interface.hpp
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "ethernet_interface.hpp"
+#include "types.hpp"
+#include "xyz/openbmc_project/Network/VLAN/server.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <string>
+#include <xyz/openbmc_project/Object/Delete/server.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+
+class EthernetInterface;
+class Manager;
+
+using DeleteIface = sdbusplus::xyz::openbmc_project::Object::server::Delete;
+using VlanIface = sdbusplus::xyz::openbmc_project::Network::server::VLAN;
+
+/** @class VlanInterface
+ * @brief OpenBMC vlan Interface implementation.
+ * @details A concrete implementation for the vlan interface
+ */
+class VlanInterface : public VlanIface,
+ public DeleteIface,
+ public EthernetInterface
+{
+ public:
+ VlanInterface() = delete;
+ VlanInterface(const VlanInterface&) = delete;
+ VlanInterface& operator=(const VlanInterface&) = delete;
+ VlanInterface(VlanInterface&&) = delete;
+ VlanInterface& operator=(VlanInterface&&) = delete;
+ virtual ~VlanInterface() = default;
+
+ /** @brief Constructor to put object onto bus at a dbus path.
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Path to attach at.
+ * @param[in] dhcpEnabled - DHCP enable value.
+ * @param[in] vlanID - vlan identifier.
+ * @param[in] intf - ethernet interface object.
+ * @param[in] manager - network manager object.
+ *
+ * This constructor is called during loading the VLAN Interface
+ */
+ VlanInterface(sdbusplus::bus::bus& bus, const std::string& objPath,
+ DHCPConf dhcpEnabled, bool nicEnabled, uint32_t vlanID,
+ EthernetInterface& intf, Manager& parent);
+
+ /** @brief Delete this d-bus object.
+ */
+ void delete_() override;
+
+ /** @brief sets the MAC address.
+ * @param[in] value - MAC address which needs to be set on the system.
+ * @returns macAddress of the interface or throws an error.
+ */
+ std::string macAddress(std::string value) override;
+
+ /** @brief writes the device configuration.
+ systemd reads this configuration file
+ and creates the vlan interface.*/
+ void writeDeviceFile();
+
+ private:
+ /** @brief VLAN Identifier. */
+ using VlanIface::id;
+
+ EthernetInterface& parentInterface;
+
+ friend class TestVlanInterface;
+};
+
+} // namespace network
+} // namespace phosphor
diff --git a/src/watch.cpp b/src/watch.cpp
new file mode 100644
index 0000000..0efe300
--- /dev/null
+++ b/src/watch.cpp
@@ -0,0 +1,125 @@
+#include "watch.hpp"
+
+#include <errno.h>
+#include <sys/inotify.h>
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor
+{
+namespace network
+{
+namespace inotify
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+Watch::Watch(phosphor::network::EventPtr& eventPtr, fs::path path,
+ UserCallBack userFunc, int flags, uint32_t mask, uint32_t events) :
+ path(path),
+ userFunc(userFunc), flags(flags), mask(mask), events(events),
+ fd(inotifyInit())
+{
+ // Check if watch file exists
+ // This is supposed to be there always
+ if (!fs::is_regular_file(path))
+ {
+ log<level::ERR>("Watch file doesn't exist",
+ entry("FILE=%s", path.c_str()));
+ elog<InternalFailure>();
+ }
+
+ auto dirPath = path.parent_path();
+ wd = inotify_add_watch(fd(), dirPath.c_str(), mask);
+ if (wd == -1)
+ {
+ log<level::ERR>("Error from inotify_add_watch",
+ entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+
+ // Register the fd with sd_event infrastructure and setup a
+ // callback handler to be invoked on events
+ auto rc = sd_event_add_io(eventPtr.get(), nullptr, fd(), events,
+ Watch::processEvents, this);
+ if (rc < 0)
+ {
+ // Failed to add to event loop
+ log<level::ERR>("Error registering with sd_event_add_io",
+ entry("RC=%d", rc));
+ elog<InternalFailure>();
+ }
+}
+
+int Watch::inotifyInit()
+{
+ auto fd = inotify_init1(flags);
+ if (fd < 0)
+ {
+ log<level::ERR>("Error from inotify_init1", entry("ERRNO=%d", errno));
+ elog<InternalFailure>();
+ }
+ return fd;
+}
+
+int Watch::processEvents(sd_event_source* /*eventSource*/, int fd,
+ uint32_t retEvents, void* userData)
+{
+ auto watch = static_cast<Watch*>(userData);
+
+ // Not the ones we are interested in
+ if (!(retEvents & watch->events))
+ {
+ return 0;
+ }
+
+ // Buffer size to be used while reading events.
+ // per inotify(7), below number should be fine for reading
+ // at-least one event
+ constexpr auto maxBytes = sizeof(struct inotify_event) + NAME_MAX + 1;
+ uint8_t eventData[maxBytes]{};
+
+ auto bytes = read(fd, eventData, maxBytes);
+ if (bytes <= 0)
+ {
+ // Failed to read inotify event data
+ // Report error and return
+ log<level::ERR>("Error reading inotify event",
+ entry("ERRNO=%d", errno));
+ report<InternalFailure>();
+ return 0;
+ }
+
+ auto offset = 0;
+ auto stateFile = watch->path.filename();
+ while (offset < bytes)
+ {
+ auto event = reinterpret_cast<inotify_event*>(&eventData[offset]);
+
+ // Filter the interesting ones
+ auto mask = event->mask & watch->mask;
+ if (mask)
+ {
+ if ((event->len > 0) &&
+ (strstr(event->name, stateFile.string().c_str())))
+ {
+ if (watch->userFunc)
+ {
+ watch->userFunc(watch->path);
+ }
+ // Found the event of interest
+ break;
+ }
+ }
+ // Move past this entry
+ offset += offsetof(inotify_event, name) + event->len;
+ }
+ return 0;
+}
+
+} // namespace inotify
+} // namespace network
+} // namespace phosphor
diff --git a/src/watch.hpp b/src/watch.hpp
new file mode 100644
index 0000000..b45f53f
--- /dev/null
+++ b/src/watch.hpp
@@ -0,0 +1,109 @@
+#pragma once
+
+#include "dns_updater.hpp"
+#include "types.hpp"
+#include "util.hpp"
+
+#include <sys/inotify.h>
+#include <systemd/sd-event.h>
+
+#include <filesystem>
+#include <functional>
+#include <map>
+
+namespace phosphor
+{
+namespace network
+{
+namespace inotify
+{
+
+namespace fs = std::filesystem;
+
+// Auxiliary callback to be invoked on inotify events
+using UserCallBack = std::function<void(const std::string&)>;
+
+/** @class Watch
+ *
+ * @brief Adds inotify watch on directory
+ *
+ * @details Calls back user function on matching events
+ */
+class Watch
+{
+ public:
+ Watch() = delete;
+ Watch(const Watch&) = delete;
+ Watch& operator=(const Watch&) = delete;
+ Watch(Watch&&) = delete;
+ Watch& operator=(Watch&&) = delete;
+
+ /** @brief Hooks inotify watch with sd-event
+ *
+ * @param[in] eventPtr - Reference to sd_event wrapped in unique_ptr
+ * @param[in] path - File path to be watched
+ * @param[in] userFunc - User specific callback function on events
+ * @param[in] flags - Flags to be supplied to inotify
+ * @param[in] mask - Mask of events to be supplied to inotify
+ * @param[in] events - Events to be watched
+ */
+ Watch(phosphor::network::EventPtr& eventPtr, const fs::path path,
+ UserCallBack userFunc, int flags = IN_NONBLOCK,
+ uint32_t mask = IN_CLOSE_WRITE, uint32_t events = EPOLLIN);
+
+ /** @brief Remove inotify watch and close fd's */
+ ~Watch()
+ {
+ if ((fd() >= 0) && (wd >= 0))
+ {
+ inotify_rm_watch(fd(), wd);
+ }
+ }
+
+ private:
+ /** @brief Callback invoked when inotify event fires
+ *
+ * @details On a matching event, calls back into user supplied
+ * function if there is one registered
+ *
+ * @param[in] eventSource - Event source
+ * @param[in] fd - Inotify fd
+ * @param[in] retEvents - Events that matched for fd
+ * @param[in] userData - Pointer to Watch object
+ *
+ * @returns 0 on success, -1 on fail
+ */
+ static int processEvents(sd_event_source* eventSource, int fd,
+ uint32_t retEvents, void* userData);
+
+ /** @brief Initializes an inotify instance
+ *
+ * @return Descriptor on success, -1 on failure
+ */
+ int inotifyInit();
+
+ /** @brief File path to be watched */
+ const fs::path path;
+
+ /** @brief User callback function */
+ UserCallBack userFunc;
+
+ /** @brief Inotify flags */
+ int flags;
+
+ /** @brief Mask of events */
+ uint32_t mask;
+
+ /** @brief Events to be watched */
+ uint32_t events;
+
+ /** @brief Watch descriptor */
+ int wd = -1;
+
+ /** @brief File descriptor manager */
+ phosphor::Descriptor fd;
+};
+
+} // namespace inotify
+} // namespace network
+} // namespace phosphor