| #include "config.h" |
| |
| #include "ethernet_interface.hpp" |
| |
| #include "config_parser.hpp" |
| #include "network_manager.hpp" |
| #include "util.hpp" |
| |
| #include <arpa/inet.h> |
| #include <fmt/compile.h> |
| #include <fmt/format.h> |
| #include <linux/ethtool.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/sockios.h> |
| #include <net/if.h> |
| |
| #include <algorithm> |
| #include <filesystem> |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <stdplus/fd/create.hpp> |
| #include <stdplus/raw.hpp> |
| #include <stdplus/zstring.hpp> |
| #include <string> |
| #include <unordered_map> |
| #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; |
| using NotAllowed = sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed; |
| using NotAllowedArgument = xyz::openbmc_project::Common::NotAllowed; |
| using Argument = xyz::openbmc_project::Common::InvalidArgument; |
| using std::literals::string_view_literals::operator""sv; |
| 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 TIMESYNCD_SERVICE = "org.freedesktop.timesync1"; |
| constexpr auto TIMESYNCD_INTERFACE = "org.freedesktop.timesync1.Manager"; |
| constexpr auto TIMESYNCD_SERVICE_PATH = "/org/freedesktop/timesync1"; |
| |
| constexpr auto METHOD_GET = "Get"; |
| |
| static stdplus::Fd& getIFSock() |
| { |
| using namespace stdplus::fd; |
| static auto fd = |
| socket(SocketDomain::INet, SocketType::Datagram, SocketProto::IP); |
| return fd; |
| } |
| |
| struct LinkInfo |
| { |
| bool autoneg; |
| uint16_t speed; |
| }; |
| |
| static LinkInfo getLinkInfo(stdplus::zstring_view ifname) |
| { |
| LinkInfo ret; |
| try |
| { |
| ethtool_cmd edata = {}; |
| edata.cmd = ETHTOOL_GSET; |
| |
| ifreq ifr = {}; |
| const auto copied = std::min<std::size_t>(ifname.size(), IFNAMSIZ - 1); |
| std::copy_n(ifname.begin(), copied, ifr.ifr_name); |
| ifr.ifr_data = reinterpret_cast<char*>(&edata); |
| |
| getIFSock().ioctl(SIOCETHTOOL, &ifr); |
| |
| ret.speed = edata.speed; |
| ret.autoneg = edata.autoneg; |
| } |
| catch (const std::system_error& e) |
| { |
| if (e.code() == std::errc::operation_not_supported) |
| { |
| auto msg = fmt::format("ETHTOOL not supported on {}", ifname); |
| log<level::NOTICE>(msg.c_str(), |
| entry("INTERFACE=%s", ifname.c_str())); |
| } |
| else |
| { |
| auto msg = |
| fmt::format("ETHTOOL failed on {}: {}", ifname, e.what()); |
| log<level::ERR>(msg.c_str(), entry("INTERFACE=%s", ifname.c_str())); |
| } |
| } |
| return ret; |
| } |
| |
| EthernetInterface::EthernetInterface(sdbusplus::bus_t& bus, |
| stdplus::zstring_view objPath, |
| const config::Parser& config, |
| Manager& parent, bool emitSignal, |
| std::optional<bool> enabled) : |
| Ifaces(bus, objPath.c_str(), |
| emitSignal ? Ifaces::action::defer_emit |
| : Ifaces::action::emit_no_signals), |
| bus(bus), manager(parent), objPath(objPath.c_str()) |
| { |
| auto intfName = std::string(objPath.substr(objPath.rfind('/') + 1)); |
| std::replace(intfName.begin(), intfName.end(), '_', '.'); |
| interfaceName(intfName); |
| auto dhcpVal = getDHCPValue(config); |
| EthernetInterfaceIntf::dhcp4(dhcpVal.v4); |
| EthernetInterfaceIntf::dhcp6(dhcpVal.v6); |
| EthernetInterfaceIntf::ipv6AcceptRA(getIPv6AcceptRA(config)); |
| EthernetInterfaceIntf::nicEnabled(enabled ? *enabled : queryNicEnabled()); |
| const auto& gatewayList = manager.getRouteTable().getDefaultGateway(); |
| const auto& gateway6List = manager.getRouteTable().getDefaultGateway6(); |
| std::string defaultGateway; |
| std::string defaultGateway6; |
| |
| for (const auto& gateway : gatewayList) |
| { |
| if (gateway.first == intfName) |
| { |
| defaultGateway = gateway.second; |
| break; |
| } |
| } |
| |
| for (const 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) |
| { |
| try |
| { |
| MacAddressIntf::macAddress(getMACAddress(intfName)); |
| } |
| catch (const std::exception& e) |
| { |
| auto msg = |
| fmt::format("Failed to get MAC for {}: {}", intfName, e.what()); |
| log<level::ERR>(msg.c_str(), |
| entry("INTERFACE=%s", intfName.c_str())); |
| } |
| } |
| EthernetInterfaceIntf::ntpServers( |
| config.map.getValueStrings("Network", "NTP")); |
| |
| EthernetInterfaceIntf::linkUp(linkUp()); |
| EthernetInterfaceIntf::mtu(mtu()); |
| |
| auto info = getLinkInfo(intfName); |
| EthernetInterfaceIntf::autoNeg(info.autoneg); |
| EthernetInterfaceIntf::speed(info.speed); |
| |
| // Emit deferred signal. |
| if (emitSignal) |
| { |
| this->emit_object_added(); |
| } |
| } |
| |
| static IP::Protocol getProtocol(const InAddrAny& addr) |
| { |
| if (std::holds_alternative<in_addr>(addr)) |
| { |
| return IP::Protocol::IPv4; |
| } |
| else if (std::holds_alternative<in6_addr>(addr)) |
| { |
| return IP::Protocol::IPv6; |
| } |
| |
| throw std::runtime_error("Invalid addr type"); |
| } |
| |
| bool EthernetInterface::dhcpIsEnabled(IP::Protocol family) |
| { |
| switch (family) |
| { |
| case IP::Protocol::IPv6: |
| return dhcp6(); |
| case IP::Protocol::IPv4: |
| return dhcp4(); |
| } |
| throw std::logic_error("Unreachable"); |
| } |
| |
| 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(); |
| |
| AddressFilter filter; |
| filter.interface = ifIndex(); |
| auto currentAddrs = getCurrentAddresses(filter); |
| for (const auto& addr : currentAddrs) |
| { |
| if (addr.flags & IFA_F_DEPRECATED) |
| { |
| continue; |
| } |
| auto address = toString(addr.address); |
| IP::Protocol addressType = getProtocol(addr.address); |
| IP::AddressOrigin origin = IP::AddressOrigin::Static; |
| if (dhcpIsEnabled(addressType)) |
| { |
| origin = IP::AddressOrigin::DHCP; |
| } |
| #ifdef LINK_LOCAL_AUTOCONFIGURATION |
| if (addr.scope == RT_SCOPE_LINK) |
| { |
| origin = IP::AddressOrigin::LinkLocal; |
| } |
| #endif |
| |
| auto ipAddressObjectPath = |
| generateObjectPath(addressType, address, addr.prefix, origin); |
| |
| this->addrs.insert_or_assign( |
| address, std::make_unique<IPAddress>(bus, ipAddressObjectPath, |
| *this, addressType, address, |
| origin, addr.prefix)); |
| } |
| } |
| |
| 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; |
| } |
| auto ip = toString(neighbor.address); |
| auto mac = mac_address::toString(*neighbor.mac); |
| auto objectPath = generateStaticNeighborObjectPath(ip, mac); |
| staticNeighbors.emplace( |
| ip, std::make_unique<Neighbor>(bus, objectPath, *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) |
| { |
| if (dhcpIsEnabled(protType)) |
| { |
| log<level::INFO>("DHCP enabled on the interface, disabling"), |
| entry("INTERFACE=%s", interfaceName().c_str()); |
| switch (protType) |
| { |
| case IP::Protocol::IPv4: |
| dhcp4(false); |
| break; |
| case IP::Protocol::IPv6: |
| dhcp6(false); |
| break; |
| } |
| // Delete the IP address object and that reloads the networkd |
| // to allow the same IP address to be set as Static IP |
| deleteObject(ipaddress); |
| } |
| |
| 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 (!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())); |
| } |
| |
| auto objectPath = |
| generateObjectPath(protType, ipaddress, prefixLength, origin); |
| this->addrs.insert_or_assign( |
| ipaddress, |
| std::make_unique<IPAddress>(bus, objectPath, *this, protType, ipaddress, |
| origin, prefixLength)); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| |
| return objectPath; |
| } |
| |
| ObjectPath EthernetInterface::neighbor(std::string ipAddress, |
| std::string macAddress) |
| { |
| if (!isValidIP(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())); |
| } |
| |
| auto objectPath = generateStaticNeighborObjectPath(ipAddress, macAddress); |
| staticNeighbors.emplace( |
| ipAddress, |
| std::make_unique<Neighbor>(bus, objectPath, *this, ipAddress, |
| macAddress, Neighbor::State::Permanent)); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| |
| return objectPath; |
| } |
| |
| /** @brief get the mac address of the interface. |
| * @return macaddress on success |
| */ |
| |
| std::string |
| EthernetInterface::getMACAddress(stdplus::const_zstring interfaceName) const |
| { |
| std::string activeMACAddr = MacAddressIntf::macAddress(); |
| |
| ifreq ifr = {}; |
| std::strncpy(ifr.ifr_name, interfaceName.c_str(), IFNAMSIZ - 1); |
| try |
| { |
| getIFSock().ioctl(SIOCGIFHWADDR, &ifr); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>("ioctl failed for SIOCGIFHWADDR:", |
| entry("ERROR=%s", e.what())); |
| elog<InternalFailure>(); |
| } |
| return mac_address::toString( |
| stdplus::raw::refFrom<ether_addr>(ifr.ifr_hwaddr.sa_data)); |
| } |
| |
| void EthernetInterface::deleteObject(std::string_view ipaddress) |
| { |
| auto it = addrs.find(ipaddress); |
| if (it == addrs.end()) |
| { |
| log<level::ERR>("DeleteObject:Unable to find the object."); |
| return; |
| } |
| this->addrs.erase(it); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| |
| void EthernetInterface::deleteStaticNeighborObject(std::string_view ipAddress) |
| { |
| auto it = staticNeighbors.find(ipAddress); |
| if (it == staticNeighbors.end()) |
| { |
| log<level::ERR>( |
| "DeleteStaticNeighborObject:Unable to find the object."); |
| return; |
| } |
| staticNeighbors.erase(it); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| |
| void EthernetInterface::deleteVLANFromSystem(stdplus::zstring_view interface) |
| { |
| const auto& confDir = manager.getConfDir(); |
| auto networkFile = config::pathForIntfConf(confDir, interface); |
| auto deviceFile = config::pathForIntfDev(confDir, interface); |
| |
| // delete the vlan network file |
| std::error_code ec; |
| std::filesystem::remove(networkFile, ec); |
| std::filesystem::remove(deviceFile, ec); |
| |
| // 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 (const InternalFailure& e) |
| { |
| commit<InternalFailure>(); |
| } |
| } |
| |
| void EthernetInterface::deleteVLANObject(stdplus::zstring_view 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); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| |
| std::string EthernetInterface::generateObjectPath( |
| IP::Protocol addressType, std::string_view ipAddress, uint8_t prefixLength, |
| IP::AddressOrigin origin) const |
| { |
| std::string_view type; |
| switch (addressType) |
| { |
| case IP::Protocol::IPv4: |
| type = "ipv4"sv; |
| break; |
| case IP::Protocol::IPv6: |
| type = "ipv6"sv; |
| break; |
| } |
| return fmt::format( |
| FMT_COMPILE("{}/{}/{:08x}"), objPath, type, |
| static_cast<uint32_t>(hash_multi( |
| ipAddress, prefixLength, |
| static_cast<std::underlying_type_t<IP::AddressOrigin>>(origin)))); |
| } |
| |
| std::string EthernetInterface::generateStaticNeighborObjectPath( |
| std::string_view ipAddress, std::string_view macAddress) const |
| { |
| return fmt::format( |
| FMT_COMPILE("{}/static_neighbor/{:08x}"), objPath, |
| static_cast<uint32_t>(hash_multi(ipAddress, macAddress))); |
| } |
| |
| bool EthernetInterface::ipv6AcceptRA(bool value) |
| { |
| if (ipv6AcceptRA() != EthernetInterfaceIntf::ipv6AcceptRA(value)) |
| { |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| return value; |
| } |
| |
| bool EthernetInterface::dhcp4(bool value) |
| { |
| if (dhcp4() != EthernetInterfaceIntf::dhcp4(value)) |
| { |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| return value; |
| } |
| |
| bool EthernetInterface::dhcp6(bool value) |
| { |
| if (dhcp6() != EthernetInterfaceIntf::dhcp6(value)) |
| { |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| return value; |
| } |
| |
| EthernetInterface::DHCPConf EthernetInterface::dhcpEnabled(DHCPConf value) |
| { |
| auto old4 = EthernetInterfaceIntf::dhcp4(); |
| auto new4 = EthernetInterfaceIntf::dhcp4(value == DHCPConf::v4 || |
| value == DHCPConf::v4v6stateless || |
| value == DHCPConf::both); |
| auto old6 = EthernetInterfaceIntf::dhcp6(); |
| auto new6 = EthernetInterfaceIntf::dhcp6(value == DHCPConf::v6 || |
| value == DHCPConf::both); |
| auto oldra = EthernetInterfaceIntf::ipv6AcceptRA(); |
| auto newra = EthernetInterfaceIntf::ipv6AcceptRA( |
| value == DHCPConf::v6stateless || value == DHCPConf::v4v6stateless || |
| value == DHCPConf::v6 || value == DHCPConf::both); |
| |
| if (old4 != new4 || old6 != new6 || oldra != newra) |
| { |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| return value; |
| } |
| |
| EthernetInterface::DHCPConf EthernetInterface::dhcpEnabled() const |
| { |
| if (dhcp6()) |
| { |
| return dhcp4() ? DHCPConf::both : DHCPConf::v6; |
| } |
| else if (dhcp4()) |
| { |
| return ipv6AcceptRA() ? DHCPConf::v4v6stateless : DHCPConf::v4; |
| } |
| return ipv6AcceptRA() ? DHCPConf::v6stateless : DHCPConf::none; |
| } |
| |
| bool EthernetInterface::linkUp() const |
| { |
| bool value = EthernetInterfaceIntf::linkUp(); |
| |
| ifreq ifr = {}; |
| std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1); |
| try |
| { |
| getIFSock().ioctl(SIOCGIFFLAGS, &ifr); |
| value = static_cast<bool>(ifr.ifr_flags & IFF_RUNNING); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>("ioctl failed for SIOCGIFFLAGS:", |
| entry("ERROR=%s", e.what())); |
| } |
| return value; |
| } |
| |
| size_t EthernetInterface::mtu() const |
| { |
| size_t value = EthernetInterfaceIntf::mtu(); |
| |
| ifreq ifr = {}; |
| std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1); |
| try |
| { |
| getIFSock().ioctl(SIOCGIFMTU, &ifr); |
| value = ifr.ifr_mtu; |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>("ioctl failed for SIOCGIFMTU:", |
| entry("ERROR=%s", e.what())); |
| } |
| return value; |
| } |
| |
| size_t EthernetInterface::mtu(size_t value) |
| { |
| if (value == EthernetInterfaceIntf::mtu()) |
| { |
| return value; |
| } |
| else if (value == 0) |
| { |
| return EthernetInterfaceIntf::mtu(); |
| } |
| |
| ifreq ifr = {}; |
| std::strncpy(ifr.ifr_name, interfaceName().c_str(), IF_NAMESIZE - 1); |
| ifr.ifr_mtu = value; |
| |
| try |
| { |
| getIFSock().ioctl(SIOCSIFMTU, &ifr); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>("ioctl failed for SIOCSIFMTU:", |
| entry("ERROR=%s", strerror(errno))); |
| return EthernetInterfaceIntf::mtu(); |
| } |
| |
| EthernetInterfaceIntf::mtu(value); |
| return value; |
| } |
| |
| bool EthernetInterface::queryNicEnabled() const |
| { |
| constexpr auto svc = "org.freedesktop.network1"; |
| constexpr auto intf = "org.freedesktop.network1.Link"; |
| constexpr auto prop = "AdministrativeState"; |
| char* rpath; |
| sd_bus_path_encode("/org/freedesktop/network1/link", |
| std::to_string(ifIndex()).c_str(), &rpath); |
| std::string path(rpath); |
| free(rpath); |
| |
| // Store / Parser for the AdministrativeState return value |
| std::optional<bool> ret; |
| auto cb = [&](std::string_view state) { |
| if (state != "initialized") |
| { |
| ret = state != "unmanaged"; |
| } |
| }; |
| |
| // Build a matcher before making the property call to ensure we |
| // can eventually get the value. |
| sdbusplus::bus::match_t match( |
| bus, |
| fmt::format("type='signal',sender='{}',path='{}',interface='{}',member=" |
| "'PropertiesChanged',arg0='{}',", |
| svc, path, PROPERTY_INTERFACE, intf) |
| .c_str(), |
| [&](sdbusplus::message_t& m) { |
| std::string intf; |
| std::unordered_map<std::string, std::variant<std::string>> values; |
| try |
| { |
| m.read(intf, values); |
| auto it = values.find(prop); |
| // Ignore properties that aren't AdministrativeState |
| if (it != values.end()) |
| { |
| cb(std::get<std::string>(it->second)); |
| } |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>( |
| fmt::format( |
| "AdministrativeState match parsing failed on {}: {}", |
| interfaceName(), e.what()) |
| .c_str(), |
| entry("INTERFACE=%s", interfaceName().c_str()), |
| entry("ERROR=%s", e.what())); |
| } |
| }); |
| |
| // Actively call for the value in case the interface is already configured |
| auto method = |
| bus.new_method_call(svc, path.c_str(), PROPERTY_INTERFACE, METHOD_GET); |
| method.append(intf, prop); |
| try |
| { |
| auto reply = bus.call(method); |
| std::variant<std::string> state; |
| reply.read(state); |
| cb(std::get<std::string>(state)); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::ERR>( |
| fmt::format("Failed to get AdministrativeState on {}: {}", |
| interfaceName(), e.what()) |
| .c_str(), |
| entry("INTERFACE=%s", interfaceName().c_str()), |
| entry("ERROR=%s", e.what())); |
| } |
| |
| // The interface is not yet configured by systemd-networkd, wait until it |
| // signals us a valid state. |
| while (!ret) |
| { |
| bus.wait(); |
| bus.process_discard(); |
| } |
| |
| return *ret; |
| } |
| |
| static void setNICAdminState(stdplus::const_zstring intf, bool up) |
| { |
| ifreq ifr = {}; |
| std::strncpy(ifr.ifr_name, intf.data(), IF_NAMESIZE - 1); |
| getIFSock().ioctl(SIOCGIFFLAGS, &ifr); |
| |
| ifr.ifr_flags &= ~IFF_UP; |
| ifr.ifr_flags |= up ? IFF_UP : 0; |
| getIFSock().ioctl(SIOCSIFFLAGS, &ifr); |
| } |
| |
| bool EthernetInterface::nicEnabled(bool value) |
| { |
| if (value == EthernetInterfaceIntf::nicEnabled()) |
| { |
| return value; |
| } |
| |
| EthernetInterfaceIntf::nicEnabled(value); |
| writeConfigurationFile(); |
| if (!value) |
| { |
| // We only need to bring down the interface, networkd will always bring |
| // up managed interfaces |
| manager.addReloadPreHook( |
| [ifname = interfaceName()]() { setNICAdminState(ifname, false); }); |
| } |
| manager.reloadConfigs(); |
| |
| return value; |
| } |
| |
| ServerList EthernetInterface::staticNameServers(ServerList value) |
| { |
| for (const auto& nameserverip : value) |
| { |
| if (!isValidIP(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(); |
| manager.reloadConfigs(); |
| } |
| catch (const InternalFailure& e) |
| { |
| log<level::ERR>("Exception processing DNS entries"); |
| } |
| return EthernetInterfaceIntf::staticNameServers(); |
| } |
| |
| void EthernetInterface::loadNTPServers(const config::Parser& config) |
| { |
| EthernetInterfaceIntf::ntpServers(getNTPServerFromTimeSyncd()); |
| EthernetInterfaceIntf::staticNTPServers( |
| config.map.getValueStrings("Network", "NTP")); |
| } |
| |
| void EthernetInterface::loadNameServers(const config::Parser& config) |
| { |
| EthernetInterfaceIntf::nameservers(getNameServerFromResolvd()); |
| EthernetInterfaceIntf::staticNameServers( |
| config.map.getValueStrings("Network", "DNS")); |
| } |
| |
| ServerList EthernetInterface::getNTPServerFromTimeSyncd() |
| { |
| ServerList servers; // Variable to capture the NTP Server IPs |
| auto method = bus.new_method_call(TIMESYNCD_SERVICE, TIMESYNCD_SERVICE_PATH, |
| PROPERTY_INTERFACE, METHOD_GET); |
| |
| method.append(TIMESYNCD_INTERFACE, "LinkNTPServers"); |
| |
| try |
| { |
| auto reply = bus.call(method); |
| std::variant<ServerList> response; |
| reply.read(response); |
| servers = std::get<ServerList>(response); |
| } |
| catch (const sdbusplus::exception::SdBusError& e) |
| { |
| log<level::ERR>( |
| "Failed to get NTP server information from Systemd-Timesyncd"); |
| } |
| |
| 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"); |
| |
| try |
| { |
| auto reply = bus.call(method); |
| reply.read(name); |
| } |
| catch (const sdbusplus::exception_t& 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; |
| } |
| |
| std::string EthernetInterface::vlanIntfName(VlanId id) const |
| { |
| return fmt::format(FMT_COMPILE("{}.{}"), interfaceName(), id); |
| } |
| |
| std::string EthernetInterface::vlanObjPath(VlanId id) const |
| { |
| return fmt::format(FMT_COMPILE("{}_{}"), objPath, id); |
| } |
| |
| void EthernetInterface::loadVLAN(VlanId id) |
| { |
| auto vlanInterfaceName = vlanIntfName(id); |
| auto path = vlanObjPath(id); |
| |
| config::Parser config( |
| config::pathForIntfConf(manager.getConfDir(), vlanInterfaceName)); |
| |
| auto vlanIntf = std::make_unique<phosphor::network::VlanInterface>( |
| bus, path.c_str(), config, EthernetInterfaceIntf::nicEnabled(), id, |
| *this, manager); |
| |
| // Fetch the ip address from the system |
| // and create the dbus object. |
| vlanIntf->createIPAddressObjects(); |
| vlanIntf->createStaticNeighborObjects(); |
| vlanIntf->loadNameServers(config); |
| vlanIntf->loadNTPServers(config); |
| |
| this->vlanInterfaces.emplace(std::move(vlanInterfaceName), |
| std::move(vlanIntf)); |
| } |
| |
| ObjectPath EthernetInterface::createVLAN(VlanId id) |
| { |
| auto vlanInterfaceName = vlanIntfName(id); |
| if (this->vlanInterfaces.find(vlanInterfaceName) != |
| this->vlanInterfaces.end()) |
| { |
| log<level::ERR>("VLAN already exists", entry("VLANID=%u", id)); |
| elog<InvalidArgument>( |
| Argument::ARGUMENT_NAME("VLANId"), |
| Argument::ARGUMENT_VALUE(std::to_string(id).c_str())); |
| } |
| |
| auto path = vlanObjPath(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(), config::Parser(), |
| EthernetInterfaceIntf::nicEnabled(), id, *this, manager); |
| |
| // write the device file for the vlan interface. |
| vlanIntf->writeDeviceFile(); |
| |
| this->vlanInterfaces.emplace(vlanInterfaceName, std::move(vlanIntf)); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| |
| return path; |
| } |
| |
| ServerList EthernetInterface::staticNTPServers(ServerList value) |
| { |
| try |
| { |
| EthernetInterfaceIntf::staticNTPServers(value); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| catch (InternalFailure& e) |
| { |
| log<level::ERR>("Exception processing NTP entries"); |
| } |
| return EthernetInterfaceIntf::staticNTPServers(); |
| } |
| |
| ServerList EthernetInterface::ntpServers(ServerList /*servers*/) |
| { |
| elog<NotAllowed>(NotAllowedArgument::REASON("ReadOnly Property")); |
| } |
| // Need to merge the below function with the code which writes the |
| // config file during factory reset. |
| // TODO openbmc/openbmc#1751 |
| |
| void EthernetInterface::writeConfigurationFile() |
| { |
| for (const auto& intf : vlanInterfaces) |
| { |
| intf.second->writeConfigurationFile(); |
| } |
| |
| config::Parser config; |
| config.map["Match"].emplace_back()["Name"].emplace_back(interfaceName()); |
| { |
| auto& link = config.map["Link"].emplace_back(); |
| #ifdef PERSIST_MAC |
| auto mac = MacAddressIntf::macAddress(); |
| if (!mac.empty()) |
| { |
| link["MACAddress"].emplace_back(mac); |
| } |
| #endif |
| if (!EthernetInterfaceIntf::nicEnabled()) |
| { |
| link["Unmanaged"].emplace_back("yes"); |
| } |
| } |
| { |
| auto& network = config.map["Network"].emplace_back(); |
| auto& lla = network["LinkLocalAddressing"]; |
| #ifdef LINK_LOCAL_AUTOCONFIGURATION |
| lla.emplace_back("yes"); |
| #else |
| lla.emplace_back("no"); |
| #endif |
| network["IPv6AcceptRA"].emplace_back(ipv6AcceptRA() ? "true" : "false"); |
| network["DHCP"].emplace_back(dhcp4() ? (dhcp6() ? "true" : "ipv4") |
| : (dhcp6() ? "ipv6" : "false")); |
| { |
| auto& vlans = network["VLAN"]; |
| for (const auto& intf : vlanInterfaces) |
| { |
| vlans.emplace_back( |
| intf.second->EthernetInterface::interfaceName()); |
| } |
| } |
| { |
| auto& ntps = network["NTP"]; |
| for (const auto& ntp : EthernetInterfaceIntf::staticNTPServers()) |
| { |
| ntps.emplace_back(ntp); |
| } |
| } |
| { |
| auto& dnss = network["DNS"]; |
| for (const auto& dns : EthernetInterfaceIntf::staticNameServers()) |
| { |
| dnss.emplace_back(dns); |
| } |
| } |
| { |
| auto& address = network["Address"]; |
| for (const auto& addr : getAddresses()) |
| { |
| if (originIsManuallyAssigned(addr.second->origin()) && |
| !dhcpIsEnabled(addr.second->type())) |
| { |
| address.emplace_back( |
| fmt::format("{}/{}", addr.second->address(), |
| addr.second->prefixLength())); |
| } |
| } |
| } |
| { |
| auto& gateways = network["Gateway"]; |
| if (!dhcp4()) |
| { |
| auto gateway = EthernetInterfaceIntf::defaultGateway(); |
| if (!gateway.empty()) |
| { |
| gateways.emplace_back(gateway); |
| } |
| } |
| |
| if (!dhcp6()) |
| { |
| auto gateway6 = EthernetInterfaceIntf::defaultGateway6(); |
| if (!gateway6.empty()) |
| { |
| gateways.emplace_back(gateway6); |
| } |
| } |
| } |
| } |
| config.map["IPv6AcceptRA"].emplace_back()["DHCPv6Client"].emplace_back( |
| dhcp6() ? "true" : "false"); |
| { |
| auto& neighbors = config.map["Neighbor"]; |
| for (const auto& sneighbor : staticNeighbors) |
| { |
| auto& neighbor = neighbors.emplace_back(); |
| neighbor["Address"].emplace_back(sneighbor.second->ipAddress()); |
| neighbor["MACAddress"].emplace_back(sneighbor.second->macAddress()); |
| } |
| } |
| { |
| auto& dhcp = config.map["DHCP"].emplace_back(); |
| dhcp["ClientIdentifier"].emplace_back("mac"); |
| if (manager.getDHCPConf()) |
| { |
| const auto& conf = *manager.getDHCPConf(); |
| auto dns_enabled = conf.dnsEnabled() ? "true" : "false"; |
| dhcp["UseDNS"].emplace_back(dns_enabled); |
| dhcp["UseDomains"].emplace_back(dns_enabled); |
| dhcp["UseNTP"].emplace_back(conf.ntpEnabled() ? "true" : "false"); |
| dhcp["UseHostname"].emplace_back(conf.hostNameEnabled() ? "true" |
| : "false"); |
| dhcp["SendHostname"].emplace_back( |
| conf.sendHostNameEnabled() ? "true" : "false"); |
| } |
| } |
| auto path = config::pathForIntfConf(manager.getConfDir(), interfaceName()); |
| config.writeFile(path); |
| auto msg = fmt::format("Wrote networkd file: {}", path.native()); |
| log<level::INFO>(msg.c_str(), entry("FILE=%s", path.c_str())); |
| } |
| |
| std::string EthernetInterface::macAddress([[maybe_unused]] std::string value) |
| { |
| #ifdef PERSIST_MAC |
| ether_addr newMAC; |
| try |
| { |
| newMAC = mac_address::fromString(value); |
| } |
| catch (const 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); |
| |
| writeConfigurationFile(); |
| manager.addReloadPreHook([interface]() { |
| // The MAC and LLADDRs will only update if the NIC is already down |
| setNICAdminState(interface, false); |
| }); |
| manager.reloadConfigs(); |
| } |
| |
| #ifdef HAVE_UBOOT_ENV |
| // Ensure that the valid address is stored in the u-boot-env |
| auto envVar = interfaceToUbootEthAddr(interface); |
| 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; |
| #else |
| elog<NotAllowed>( |
| NotAllowedArgument::REASON("Writing MAC address is not allowed")); |
| #endif // PERSIST_MAC |
| } |
| |
| void EthernetInterface::deleteAll() |
| { |
| // clear all the ip on the interface |
| addrs.clear(); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| } |
| |
| std::string EthernetInterface::defaultGateway(std::string gateway) |
| { |
| auto gw = EthernetInterfaceIntf::defaultGateway(); |
| if (gw == gateway) |
| { |
| return gw; |
| } |
| |
| if (!isValidIP(AF_INET, gateway) && !gateway.empty()) |
| { |
| 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); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| |
| return gw; |
| } |
| |
| std::string EthernetInterface::defaultGateway6(std::string gateway) |
| { |
| auto gw = EthernetInterfaceIntf::defaultGateway6(); |
| if (gw == gateway) |
| { |
| return gw; |
| } |
| |
| if (!isValidIP(AF_INET6, gateway) && !gateway.empty()) |
| { |
| 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); |
| |
| writeConfigurationFile(); |
| manager.reloadConfigs(); |
| |
| return gw; |
| } |
| } // namespace network |
| } // namespace phosphor |