blob: d7e0baa0ee0fe986045e1c368399e9f125fd1240 [file] [log] [blame]
#include "config.h"
#include "ethernet_interface.hpp"
#include "config_parser.hpp"
#include "ipaddress.hpp"
#include "neighbor.hpp"
#include "network_manager.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};
};
EthernetInterface::EthernetInterface(sdbusplus::bus::bus& bus,
const std::string& objPath,
bool 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());
// 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());
#if 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::createIPAddressObjects()
{
addrs.clear();
auto addrs = getInterfaceAddrs()[interfaceName()];
for (auto& addr : addrs)
{
IP::Protocol addressType = convertFamily(addr.addrType);
IP::AddressOrigin origin = IP::AddressOrigin::Static;
if (dHCPEnabled())
{
origin = IP::AddressOrigin::DHCP;
}
if (isLinkLocalIP(addr.ipaddress))
{
origin = IP::AddressOrigin::LinkLocal;
}
// Obsolete parameter
std::string gateway = "";
std::string ipAddressObjectPath = generateObjectPath(
addressType, addr.ipaddress, addr.prefix, gateway);
this->addrs.emplace(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 (dHCPEnabled())
{
log<level::INFO>("DHCP enabled on the interface"),
entry("INTERFACE=%s", interfaceName().c_str());
dHCPEnabled(false);
}
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.emplace(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;
}
#if 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{0};
ethtool_cmd edata{0};
LinkSpeed speed{0};
Autoneg autoneg{0};
DuplexMode duplex{0};
LinkUp linkState{false};
NICEnabled nicEnabled{false};
EthernetIntfSocket eifSocket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
if (eifSocket.sock < 0)
{
return std::make_tuple(speed, duplex, autoneg, linkState, nicEnabled);
}
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;
}
nicEnabled = nICEnabled();
linkState = linkUp();
return std::make_tuple(speed, duplex, autoneg, linkState, nicEnabled);
}
#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{0};
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;
}
bool EthernetInterface::dHCPEnabled(bool 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{0};
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{0};
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{0};
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)
{
std::vector<uint8_t> ipaddress = std::get<1>(*i);
std::string address;
for (auto byte : ipaddress)
{
address += std::to_string(byte) + ".";
}
address.pop_back();
servers.push_back(address);
}
return servers;
}
void EthernetInterface::loadVLAN(VlanId id)
{
std::string vlanInterfaceName = interfaceName() + "." + std::to_string(id);
std::string path = objPath;
path += "_" + std::to_string(id);
auto dhcpEnabled =
getDHCPValue(manager.getConfDir().string(), vlanInterfaceName);
auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>(
bus, path.c_str(), dhcpEnabled, 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(), false, 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;
using AddressOrigin =
sdbusplus::xyz::openbmc_project::Network::server::IP::AddressOrigin;
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
auto value = dHCPEnabled() ? "true"s : "false"s;
stream << "DHCP="s + value + "\n";
// When the interface configured as dhcp, we don't need below given entries
// in config file.
if (dHCPEnabled() == false)
{
// Static
for (const auto& addr : addrs)
{
if (addr.second->origin() == AddressOrigin::Static
#ifndef LINK_LOCAL_AUTOCONFIGURATION
|| addr.second->origin() == AddressOrigin::LinkLocal
#endif
)
{
std::string address =
addr.second->address() + "/" +
std::to_string(addr.second->prefixLength());
stream << "Address=" << address << "\n";
}
}
if (manager.getSystemConf())
{
const auto& gateway = manager.getSystemConf()->defaultGateway();
if (!gateway.empty())
{
stream << "Gateway=" << gateway << "\n";
}
const auto& gateway6 = manager.getSystemConf()->defaultGateway6();
if (!gateway6.empty())
{
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 = mac_address::fromString(value);
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()));
}
// 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))
{
if (!mac_address::isLocalAdmin(newMAC))
{
try
{
auto inventoryMAC =
mac_address::getfromInventory(bus, interfaceName());
if (!stdplus::raw::equal(newMAC, inventoryMAC))
{
log<level::ERR>(
"Given MAC address is neither a local Admin "
"type nor is same as in inventory");
elog<InvalidArgument>(
Argument::ARGUMENT_NAME("MACAddress"),
Argument::ARGUMENT_VALUE(value.c_str()));
}
}
catch (const std::exception& e)
{
log<level::ERR>("Exception occurred during getting of MAC "
"address from Inventory");
elog<InternalFailure>();
}
}
// Update everything that depends on the MAC value
for (const auto& [name, intf] : vlanInterfaces)
{
intf->MacAddressIntf::mACAddress(value);
}
MacAddressIntf::mACAddress(value);
auto interface = interfaceName();
#ifdef HAVE_UBOOT_ENV
auto envVar = interfaceToUbootEthAddr(interface.c_str());
if (envVar)
{
execute("/sbin/fw_setenv", "fw_setenv", envVar->c_str(),
value.c_str());
}
#endif // HAVE_UBOOT_ENV
// 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();
}
return value;
}
void EthernetInterface::deleteAll()
{
if (EthernetInterfaceIntf::dHCPEnabled())
{
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();
}
} // namespace network
} // namespace phosphor