| #include "config.h" |
| #include "config_parser.hpp" |
| #include "ethernet_interface.hpp" |
| #include "ipaddress.hpp" |
| #include "network_manager.hpp" |
| #include "routing_table.hpp" |
| #include "vlan_interface.hpp" |
| #include "xyz/openbmc_project/Common/error.hpp" |
| |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <arpa/inet.h> |
| #include <linux/ethtool.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 <experimental/filesystem> |
| #include <fstream> |
| #include <sstream> |
| #include <string> |
| |
| namespace phosphor |
| { |
| namespace network |
| { |
| |
| using namespace phosphor::logging; |
| using namespace sdbusplus::xyz::openbmc_project::Common::Error; |
| using Argument = xyz::openbmc_project::Common::InvalidArgument; |
| |
| 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); |
| MacAddressIntf::mACAddress(getMACAddress(intfName)); |
| EthernetInterfaceIntf::nTPServers(getNTPServersFromConf()); |
| EthernetInterfaceIntf::nameservers(getNameServerFromConf()); |
| |
| // Emit deferred signal. |
| if (emitSignal) |
| { |
| this->emit_object_added(); |
| } |
| } |
| |
| void EthernetInterface::createIPAddressObjects() |
| { |
| std::string gateway; |
| addrs.clear(); |
| |
| auto addrs = getInterfaceAddrs()[interfaceName()]; |
| |
| IP::Protocol addressType = IP::Protocol::IPv4; |
| IP::AddressOrigin origin = IP::AddressOrigin::Static; |
| route::Table routingTable; |
| |
| for (auto& addr : addrs) |
| { |
| if (addr.addrType == AF_INET6) |
| { |
| addressType = IP::Protocol::IPv6; |
| } |
| if (dHCPEnabled()) |
| { |
| origin = IP::AddressOrigin::DHCP; |
| } |
| else if (isLinkLocalIP(addr.ipaddress)) |
| { |
| origin = IP::AddressOrigin::LinkLocal; |
| } |
| gateway = routingTable.getGateway(addr.addrType, addr.ipaddress, addr.prefix); |
| |
| std::string ipAddressObjectPath = generateObjectPath(addressType, |
| addr.ipaddress, |
| addr.prefix, |
| gateway); |
| |
| this->addrs.emplace( |
| std::move(addr.ipaddress), |
| std::make_shared<phosphor::network::IPAddress>( |
| bus, |
| ipAddressObjectPath.c_str(), |
| *this, |
| addressType, |
| addr.ipaddress, |
| origin, |
| addr.prefix, |
| gateway)); |
| |
| origin = IP::AddressOrigin::Static; |
| } |
| |
| } |
| |
| void 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())); |
| } |
| |
| if (!gateway.empty() && (!isValidIP(addressFamily, gateway))) |
| { |
| log<level::ERR>("Not a valid Gateway"), |
| entry("GATEWAY=%s", gateway.c_str()); |
| elog<InvalidArgument>(Argument::ARGUMENT_NAME("gateway"), |
| Argument::ARGUMENT_VALUE(gateway.c_str())); |
| } |
| |
| if (!isValidPrefix(addressFamily, prefixLength)) |
| { |
| log<level::ERR>("PrefixLength is not correct "), |
| entry("PREFIXLENGTH=%d", gateway.c_str()); |
| 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( |
| std::move(ipaddress), |
| std::make_shared<phosphor::network::IPAddress>( |
| bus, |
| objectPath.c_str(), |
| *this, |
| protType, |
| ipaddress, |
| origin, |
| prefixLength, |
| gateway)); |
| |
| manager.writeToConfigurationFile(); |
| } |
| |
| |
| /* |
| Note: We don't have support for ethtool now |
| will enable this code once we bring the ethtool |
| in the image. |
| TODO: https://github.com/openbmc/openbmc/issues/1484 |
| */ |
| |
| InterfaceInfo EthernetInterface::getInterfaceInfo() const |
| { |
| int sock{-1}; |
| struct ifreq ifr{0}; |
| struct ethtool_cmd edata{0}; |
| LinkSpeed speed {0}; |
| Autoneg autoneg {0}; |
| DuplexMode duplex {0}; |
| do |
| { |
| sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); |
| if (sock < 0) |
| { |
| log<level::ERR>("socket creation failed:", |
| entry("ERROR=%s", strerror(errno))); |
| break; |
| } |
| |
| strncpy(ifr.ifr_name, interfaceName().c_str(), sizeof(ifr.ifr_name)); |
| ifr.ifr_data = reinterpret_cast<char*>(&edata); |
| |
| edata.cmd = ETHTOOL_GSET; |
| |
| if (ioctl(sock, SIOCETHTOOL, &ifr) < 0) |
| { |
| log<level::ERR>("ioctl failed for SIOCETHTOOL:", |
| entry("ERROR=%s", strerror(errno))); |
| break; |
| |
| } |
| speed = edata.speed; |
| duplex = edata.duplex; |
| autoneg = edata.autoneg; |
| } |
| while (0); |
| |
| if (sock) |
| { |
| close(sock); |
| } |
| return std::make_tuple(speed, duplex, autoneg); |
| } |
| |
| /** @brief get the mac address of the interface. |
| * @return macaddress on success |
| */ |
| |
| std::string EthernetInterface::getMACAddress( |
| const std::string& interfaceName) const |
| { |
| struct ifreq ifr{}; |
| char macAddress[mac_address::size] {}; |
| |
| int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); |
| if (sock < 0) |
| { |
| log<level::ERR>("socket creation failed:", |
| entry("ERROR=%s", strerror(errno))); |
| return macAddress; |
| } |
| |
| strcpy(ifr.ifr_name, interfaceName.c_str()); |
| if (ioctl(sock, SIOCGIFHWADDR, &ifr) != 0) |
| { |
| log<level::ERR>("ioctl failed for SIOCGIFHWADDR:", |
| entry("ERROR=%s", strerror(errno))); |
| return macAddress; |
| } |
| |
| snprintf(macAddress, mac_address::size, mac_address::format, |
| ifr.ifr_hwaddr.sa_data[0], ifr.ifr_hwaddr.sa_data[1], |
| ifr.ifr_hwaddr.sa_data[2], ifr.ifr_hwaddr.sa_data[3], |
| ifr.ifr_hwaddr.sa_data[4], ifr.ifr_hwaddr.sa_data[5]); |
| |
| return macAddress; |
| } |
| |
| 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(); |
| } |
| |
| 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::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::experimental::filesystem::path objectPath; |
| objectPath /= objPath; |
| objectPath /= type; |
| objectPath /= generateId(ipaddress, prefixLength, gateway); |
| return objectPath.string(); |
| } |
| |
| bool EthernetInterface::dHCPEnabled(bool value) |
| { |
| if (value == EthernetInterfaceIntf::dHCPEnabled()) |
| { |
| return value; |
| } |
| |
| EthernetInterfaceIntf::dHCPEnabled(value); |
| manager.writeToConfigurationFile(); |
| return value; |
| } |
| |
| ServerList EthernetInterface::nameservers(ServerList value) |
| { |
| try |
| { |
| EthernetInterfaceIntf::nameservers(value); |
| |
| writeConfigurationFile(); |
| |
| // Currently we don't have systemd-resolved enabled |
| // in the openbmc. Once we update the network conf file, |
| // it should be read by systemd-resolved.service. |
| |
| // The other reason to write the resolv conf is, |
| // we don't want to restart the networkd for nameserver change. |
| // as restarting of systemd-networkd takes more then 2 secs |
| writeDNSEntries(value, resolvConfFile); |
| } |
| catch (InternalFailure& e) |
| { |
| log<level::ERR>("Exception processing DNS entries"); |
| } |
| return EthernetInterfaceIntf::nameservers(); |
| } |
| |
| ServerList EthernetInterface::getNameServerFromConf() |
| { |
| 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; |
| } |
| |
| void EthernetInterface::writeDNSEntries(const ServerList& dnsList, |
| const std::string& file) |
| { |
| std::fstream outStream(file, std::fstream::out); |
| if (!outStream.is_open()) |
| { |
| log<level::ERR>("Unable to open the file", |
| entry("FILE=%s", file.c_str())); |
| elog<InternalFailure>(); |
| } |
| |
| outStream << "### Generated manually via dbus settings ###\n"; |
| for(const auto& server : dnsList) |
| { |
| outStream << "nameserver " << server << "\n"; |
| } |
| } |
| |
| 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(); |
| |
| this->vlanInterfaces.emplace(std::move(vlanInterfaceName), |
| std::move(vlanIntf)); |
| } |
| |
| void EthernetInterface::createVLAN(VlanId id) |
| { |
| std::string vlanInterfaceName = interfaceName() + "." + |
| std::to_string(id); |
| std::string path = objPath; |
| path += "_" + std::to_string(id); |
| |
| |
| auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>( |
| bus, |
| path.c_str(), |
| false, |
| 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(); |
| } |
| |
| 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. |
| 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::experimental::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 network section |
| stream << "[" << "Network" << "]\n"; |
| stream << "LinkLocalAddressing=yes\n"; |
| stream << "IPv6AcceptRA=false\n"; |
| |
| // Add the VLAN entry |
| for (const auto& intf: vlanInterfaces) |
| { |
| stream << "VLAN=" << intf.second->EthernetInterface::interfaceName() |
| << "\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) |
| { |
| //Add the NTP server |
| for (const auto& ntp : EthernetInterfaceIntf::nTPServers()) |
| { |
| stream << "NTP=" << ntp << "\n"; |
| } |
| |
| //Add the DNS entry |
| for (const auto& dns : EthernetInterfaceIntf::nameservers()) |
| { |
| stream << "DNS=" << dns << "\n"; |
| } |
| |
| // Static |
| for (const auto& addr : addrs) |
| { |
| if (addr.second->origin() == AddressOrigin::Static) |
| { |
| std::string address = addr.second->address() + "/" + |
| std::to_string(addr.second->prefixLength()); |
| |
| stream << "Address=" << address << "\n"; |
| } |
| } |
| |
| if (manager.getSystemConf()) |
| { |
| stream << "Gateway=" << manager.getSystemConf()->defaultGateway() |
| << "\n"; |
| } |
| |
| // write the route section |
| stream << "[" << "Route" << "]\n"; |
| for (const auto& addr : addrs) |
| { |
| if (addr.second->origin() == AddressOrigin::Static) |
| { |
| int addressFamily = addr.second->type() == IP::Protocol::IPv4 ? |
| AF_INET : AF_INET6; |
| |
| std::string destination = getNetworkID( |
| addressFamily, |
| addr.second->address(), |
| addr.second->prefixLength()); |
| |
| if (addr.second->gateway() != "0.0.0.0" && |
| addr.second->gateway() != "" && |
| destination != "0.0.0.0" && |
| destination != "") |
| { |
| stream << "Gateway=" << addr.second->gateway() << "\n"; |
| stream << "Destination=" << destination << "\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) |
| { |
| if (!mac_address::validate(value)) |
| { |
| log<level::DEBUG>("MACAddress is not valid.", |
| entry("MAC=%s", value.c_str())); |
| return MacAddressIntf::mACAddress(); |
| } |
| |
| // check whether MAC is broadcast mac. |
| auto intMac = mac_address::internal::convertToInt(value); |
| |
| if (!(intMac ^ mac_address::broadcastMac)) |
| { |
| log<level::DEBUG>("MACAddress is a broadcast mac.", |
| entry("MAC=%s", value.c_str())); |
| return MacAddressIntf::mACAddress(); |
| } |
| |
| // Check if the MAC changed. |
| auto pmac = MacAddressIntf::mACAddress(); |
| if (strcasecmp(pmac.c_str(), value.c_str()) == 0) |
| { |
| return MacAddressIntf::mACAddress(); |
| } |
| |
| // Allow the mac to be set if one of the condition is true. |
| // 1) Incoming Mac is of local admin type. |
| // or |
| // 2) Incoming mac is same as eeprom Mac. |
| |
| if (!(intMac & mac_address::localAdminMask)) |
| { |
| try |
| { |
| auto inventoryMac = mac_address::getfromInventory(bus); |
| auto intInventoryMac = mac_address::internal::convertToInt(inventoryMac); |
| |
| if (intInventoryMac != intMac) |
| { |
| log<level::DEBUG>("Given MAC address is neither a local Admin " |
| "type nor is same as in inventory"); |
| return MacAddressIntf::mACAddress(); |
| } |
| } |
| catch(InternalFailure& e) |
| { |
| log<level::ERR>("Exception occurred during getting of MAC " |
| "address from Inventory"); |
| return MacAddressIntf::mACAddress(); |
| } |
| } |
| auto interface = interfaceName(); |
| execute("/sbin/fw_setenv", "fw_setenv", "ethaddr", value.c_str()); |
| //TODO: would replace below three calls |
| // with restarting of systemd-netwokd |
| // through https://github.com/systemd/systemd/issues/6696 |
| execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "down"); |
| execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "address", |
| value.c_str()); |
| |
| execute("/sbin/ip", "ip", "link", "set", "dev", interface.c_str(), "up"); |
| |
| auto mac = MacAddressIntf::mACAddress(std::move(value)); |
| //update all the vlan interfaces |
| for(const auto& intf: vlanInterfaces) |
| { |
| intf.second->updateMacAddress(); |
| } |
| |
| // restart the systemd networkd so that dhcp client gets the |
| // ip for the changed mac address. |
| if (dHCPEnabled()) |
| { |
| restartSystemdUnit(networkdService); |
| } |
| return mac; |
| |
| } |
| |
| 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 |