diff --git a/protocol/include/microsoft/net/remote/protocol/NetRemoteProtocol.hxx b/protocol/include/microsoft/net/remote/protocol/NetRemoteProtocol.hxx index 85b3c189..f054f30e 100644 --- a/protocol/include/microsoft/net/remote/protocol/NetRemoteProtocol.hxx +++ b/protocol/include/microsoft/net/remote/protocol/NetRemoteProtocol.hxx @@ -19,11 +19,43 @@ struct NetRemoteProtocol #define xstr(s) str(s) #define str(s) #s + /** + * @brief Primary service related definitions. + */ + + /** + * @brief Default port if none specified. + */ static constexpr uint32_t PortDefault{ PORT_DEFAULT }; + + /** + * @brief Port separator for address parsing. + */ static constexpr std::string_view PortSeparator{ PORT_SEPARATOR }; + + /** + * @brief Default IP address if none specified. + */ static constexpr std::string_view IpDefault{ IP_DEFAULT }; + + /** + * @brief Default address if none specified; includes default port. + */ static constexpr std::string_view AddressDefault{ IP_DEFAULT PORT_SEPARATOR xstr(PORT_DEFAULT) }; + /** + * @brief Discovery service related definitions. + */ + + static constexpr auto DnssdServiceName = "netremote"; + + /** + * @brief Note that all non-TCP protocols must use "udp" as the protocol value, even if the protocol is not "udp". + * See RFC 6763 "DNS-Based Service Discovery", section 4.1.2 "Service Names" and and section 7 "Service Names" for + * additional details and reasoning. + */ + static constexpr auto DnssdServiceProtocol = "udp"; + #undef IP_DEFAULT #undef PORT_DEFAULT #undef PORT_SEPARATOR diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 1da88a44..cad51d35 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(client) add_subdirectory(dotnet) +add_subdirectory(net) add_subdirectory(server) add_subdirectory(service) add_subdirectory(shared) diff --git a/src/common/net/CMakeLists.txt b/src/common/net/CMakeLists.txt new file mode 100644 index 00000000..9d6b054b --- /dev/null +++ b/src/common/net/CMakeLists.txt @@ -0,0 +1,23 @@ + +add_library(${PROJECT_NAME}-net INTERFACE "") + +set(NET_REMOTE_NET_PUBLIC_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include) +set(NET_REMOTE_NET_PUBLIC_INCLUDE_SUFFIX microsoft/net) +set(NET_REMOTE_NET_PUBLIC_INCLUDE_PREFIX ${NET_REMOTE_NET_PUBLIC_INCLUDE}/${NET_REMOTE_NET_PUBLIC_INCLUDE_SUFFIX}) + +target_sources(${PROJECT_NAME}-net + PUBLIC + FILE_SET HEADERS + BASE_DIRS ${NET_REMOTE_NET_PUBLIC_INCLUDE} + FILES + ${NET_REMOTE_NET_PUBLIC_INCLUDE_PREFIX}/INetworkOperations.hxx + ${NET_REMOTE_NET_PUBLIC_INCLUDE_PREFIX}/IpAddressInformation.hxx +) + +install( + TARGETS ${PROJECT_NAME}-net + EXPORT ${PROJECT_NAME} + COMPONENT dev + FILE_SET HEADERS + PUBLIC_HEADER DESTINATION "${NETREMOTE_DIR_INSTALL_PUBLIC_HEADER_BASE}/${NET_REMOTE_NET_PUBLIC_INCLUDE_SUFFIX}" +) diff --git a/src/common/net/include/microsoft/net/INetworkOperations.hxx b/src/common/net/include/microsoft/net/INetworkOperations.hxx new file mode 100644 index 00000000..c136068f --- /dev/null +++ b/src/common/net/include/microsoft/net/INetworkOperations.hxx @@ -0,0 +1,34 @@ + +#ifndef NETWORK_OPERATIONS_HXX +#define NETWORK_OPERATIONS_HXX + +#include +#include +#include +#include + +#include + +namespace Microsoft::Net +{ +/** + * @brief Interface for network operations needed by the server. + */ +struct INetworkOperations +{ + virtual ~INetworkOperations() = default; + + /** + * @brief Obtain information about the specified IP address. The returned map will contain the IP address as the key + * and the information as the value. In the case of a fixed address, the returned map will have a single entry. In + * the case of any "any" address (eg. 0.0.0.0, ::, [::]), the returned map will contain all available addresses. + * + * @param ipAddress The ip address to obtain information for. + * @return std::unordered_map + */ + virtual std::unordered_map + GetLocalIpAddressInformation(std::string_view ipAddress) const noexcept = 0; +}; +} // namespace Microsoft::Net + +#endif // NETWORK_OPERATIONS_HXX diff --git a/src/common/net/include/microsoft/net/IpAddressInformation.hxx b/src/common/net/include/microsoft/net/IpAddressInformation.hxx new file mode 100644 index 00000000..2816c2ed --- /dev/null +++ b/src/common/net/include/microsoft/net/IpAddressInformation.hxx @@ -0,0 +1,73 @@ + +#ifndef IP_ADDRESS_INFORMATION_HXX +#define IP_ADDRESS_INFORMATION_HXX + +#include +#include +#include + +namespace Microsoft::Net +{ +/** + * @brief Determine if a string contains an IPv4 or IPv6 "any" address. The port is optional for both forms, and square + * brackets are optional for the IPv6 form. + * + * @param ipAddressView The view of the string to check. + * @return true If the string contains an IPv4 or IPv6 "any" address. + * @return false If the string does not contain an IPv4 or IPv6 "any" address. + */ +inline bool +IsAnyAddress(std::string_view ipAddressView) noexcept +{ + const std::regex RegexIpv4AnyAddressWithPort{ "^0.0.0.0(:\\d+)?$" }; + const std::regex RegexIpv6AnyAddressWithPort{ "^\\[?::\\]?(:\\d+)?$" }; + const std::string ipAddress(ipAddressView); + + return std::regex_match(ipAddress, RegexIpv4AnyAddressWithPort) || std::regex_match(ipAddress, RegexIpv6AnyAddressWithPort); +} + +/** + * @brief The type of network interface. + */ +enum class NetworkInterfaceType { + Unknown, + Wifi, + Other, +}; + +/** + * @brief The IP family of an IP address. + */ +enum class IpFamily { + Unknown, + Ipv4, + Ipv6, +}; + +/** + * @brief Information about an IP address needed by the server. + */ +struct IpAddressInformation +{ + IpFamily Family{ IpFamily::Unknown }; + NetworkInterfaceType InterfaceType{ NetworkInterfaceType::Unknown }; + + auto + operator<=>(const IpAddressInformation&) const = default; +}; +} // namespace Microsoft::Net + +namespace std +{ +template <> +struct hash +{ + std::size_t + operator()(const Microsoft::Net::IpAddressInformation& ipInfo) const noexcept + { + return std::hash{}(static_cast(ipInfo.Family)) ^ std::hash{}(static_cast(ipInfo.InterfaceType)); + } +}; +} // namespace std + +#endif // IP_ADDRESS_INFORMATION_HXX diff --git a/src/common/server/CMakeLists.txt b/src/common/server/CMakeLists.txt index 9894b574..b46b3969 100644 --- a/src/common/server/CMakeLists.txt +++ b/src/common/server/CMakeLists.txt @@ -7,18 +7,23 @@ set(NET_REMOTE_SERVER_PUBLIC_INCLUDE_PREFIX ${NET_REMOTE_SERVER_PUBLIC_INCLUDE}/ target_sources(${PROJECT_NAME}-server PRIVATE + NetRemoteDiscoveryDnssd.cxx + NetRemoteDiscoveryService.cxx NetRemoteServer.cxx NetRemoteServerConfiguration.cxx PUBLIC FILE_SET HEADERS BASE_DIRS ${NET_REMOTE_SERVER_PUBLIC_INCLUDE} FILES + ${NET_REMOTE_SERVER_PUBLIC_INCLUDE_PREFIX}/NetRemoteDiscoveryDnssd.hxx + ${NET_REMOTE_SERVER_PUBLIC_INCLUDE_PREFIX}/NetRemoteDiscoveryService.hxx ${NET_REMOTE_SERVER_PUBLIC_INCLUDE_PREFIX}/NetRemoteServer.hxx ${NET_REMOTE_SERVER_PUBLIC_INCLUDE_PREFIX}/NetRemoteServerConfiguration.hxx ) target_link_libraries(${PROJECT_NAME}-server PUBLIC + ${PROJECT_NAME}-net ${PROJECT_NAME}-service PRIVATE CLI11::CLI11 diff --git a/src/common/server/NetRemoteDiscoveryDnssd.cxx b/src/common/server/NetRemoteDiscoveryDnssd.cxx new file mode 100644 index 00000000..e8efdd88 --- /dev/null +++ b/src/common/server/NetRemoteDiscoveryDnssd.cxx @@ -0,0 +1,15 @@ + +#ifndef NET_REMOTE_DISCOVERY_DNSSD_HXX +#define NET_REMOTE_DISCOVERY_DNSSD_HXX + +#include + +#include + +namespace Microsoft::Net::Remote +{ +std::string +BuildDnssdTxtRecord(); +} // namespace Microsoft::Net::Remote + +#endif // NET_REMOTE_DISCOVERY_DNSSD_HXX diff --git a/src/common/server/NetRemoteDiscoveryService.cxx b/src/common/server/NetRemoteDiscoveryService.cxx new file mode 100644 index 00000000..b83b8b1d --- /dev/null +++ b/src/common/server/NetRemoteDiscoveryService.cxx @@ -0,0 +1,80 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Microsoft::Net::Remote; +using Microsoft::Net::INetworkOperations; +using Microsoft::Net::IpAddressInformation; + +NetRemoteDiscoveryService::NetRemoteDiscoveryService(std::string hostname, uint32_t port, std::unordered_map ipAddresses) : + m_hostname(std::move(hostname)), + m_port(port), + m_ipAddresses(std::move(ipAddresses)) +{} + +std::string_view +NetRemoteDiscoveryService::GetHostname() const noexcept +{ + return m_hostname; +} + +uint32_t +NetRemoteDiscoveryService::GetPort() const noexcept +{ + return m_port; +} + +const std::unordered_map& +NetRemoteDiscoveryService::GetIpAddresses() const noexcept +{ + return m_ipAddresses; +} + +NetRemoteDiscoveryServiceBuilder::NetRemoteDiscoveryServiceBuilder(std::unique_ptr discoveryServiceFactory, std::unique_ptr networkOperations) : + m_discoveryServiceFactory(std::move(discoveryServiceFactory)), + m_networkOperations(std::move(networkOperations)) +{} + +NetRemoteDiscoveryServiceBuilder& +NetRemoteDiscoveryServiceBuilder::SetHostname(std::string hostname) +{ + m_hostname = std::move(hostname); + return *this; +} + +NetRemoteDiscoveryServiceBuilder& +NetRemoteDiscoveryServiceBuilder::SetPort(uint32_t port) +{ + m_port = port; + return *this; +} + +NetRemoteDiscoveryServiceBuilder& +NetRemoteDiscoveryServiceBuilder::AddIpAddress(std::string ipAddress) +{ + auto ipAddressInfo = m_networkOperations->GetLocalIpAddressInformation(ipAddress); + if (std::empty(ipAddressInfo)) { + throw std::invalid_argument(std::format("Invalid IP address: {}", ipAddress)); + } + + m_ipAddresses.merge(ipAddressInfo); + + return *this; +} + +std::shared_ptr +NetRemoteDiscoveryServiceBuilder::Build() +{ + return m_discoveryServiceFactory->Create(m_hostname, m_port, m_ipAddresses); +} diff --git a/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryDnssd.hxx b/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryDnssd.hxx new file mode 100644 index 00000000..a6d93c4d --- /dev/null +++ b/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryDnssd.hxx @@ -0,0 +1,14 @@ + +#ifndef NET_REMOTE_DISCOVERY_DNSSD_HXX +#define NET_REMOTE_DISCOVERY_DNSSD_HXX + +#include + +namespace Microsoft::Net::Remote +{ +struct DnssdServiceBuilder +{ +}; +} // namespace Microsoft::Net::Remote + +#endif // NET_REMOTE_DISCOVERY_DNSSD_HXX diff --git a/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryService.hxx b/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryService.hxx new file mode 100644 index 00000000..28036506 --- /dev/null +++ b/src/common/server/include/microsoft/net/remote/NetRemoteDiscoveryService.hxx @@ -0,0 +1,180 @@ + +#ifndef NET_REMOTE_DISCOVERY_SERVICE_HXX +#define NET_REMOTE_DISCOVERY_SERVICE_HXX + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Microsoft::Net::Remote +{ +/** + * @brief Network service enabling clients to discover netremote servers on the network. + */ +struct NetRemoteDiscoveryService +{ + /** + * @brief Construct a new NetRemoteDiscoveryService object with the specified hostname, port, and IP addresses. + * + * @param hostname The hostname of the service. + * @param port The IP port the service listens on. + * @param ipAddresses The IP addresses the service listens on. The map key specifies the IP address, and the value + * specifies information about the IP address needed by discovery clients, such as the IP family and type of + * interface. + */ + NetRemoteDiscoveryService(std::string hostname, uint32_t port, std::unordered_map ipAddresses); + + virtual ~NetRemoteDiscoveryService() = default; + + NetRemoteDiscoveryService(const NetRemoteDiscoveryService&) = delete; + + NetRemoteDiscoveryService(NetRemoteDiscoveryService&&) = delete; + + NetRemoteDiscoveryService& + operator=(const NetRemoteDiscoveryService&) = delete; + + NetRemoteDiscoveryService& + operator=(NetRemoteDiscoveryService&&) = delete; + + /** + * @brief Start the discovery service. This will begin advertising the service on the network. + */ + virtual void + Start() = 0; + + /** + * @brief Stop the discovery service. This will stop advertising the service on the network, preventing it from + * being discoverable by clients. + */ + virtual void + Stop() = 0; + +protected: + /** + * @brief Get the host name of the service. + * + * @return std::string_view + */ + std::string_view + GetHostname() const noexcept; + + /** + * @brief Get the port of the service. + * + * @return uint32_t + */ + uint32_t + GetPort() const noexcept; + + /** + * @brief Get the IP addresses the service listens on. + * + * @return const std::unordered_map& + */ + const std::unordered_map& + GetIpAddresses() const noexcept; + +private: + std::string m_hostname; + uint32_t m_port; + std::unordered_map m_ipAddresses; +}; + +/** + * @brief Factory to create a NetRemoteDiscoveryService. + * + * This facilitates multiple implementations of the service, including across operating systems and mock implementations + * for testing. + */ +struct INetRemoteDiscoveryServiceFactory +{ + virtual ~INetRemoteDiscoveryServiceFactory() = default; + + /** + * @brief Create a new NetRemoteDiscoveryService object. + * + * @param hostname The hostname of the service. + * @param port The IP port the service listens on. + * @param ipAddresses The IP addresses the service listens on. The map key specifies the IP address, and the value + * specifies information about the IP address needed by discovery clients, such as the IP family and type of + * interface. + * @return std::shared_ptr + */ + virtual std::shared_ptr + Create(std::string hostname, uint32_t port, std::unordered_map ipAddresses) = 0; +}; + +/** + * @brief Helper to build a NetRemoteDiscoveryService. + * + * Note that this is not thread-safe. + */ +struct NetRemoteDiscoveryServiceBuilder +{ + /** + * @brief Construct a new NetRemoteDiscoveryServiceBuilder object. + * + * @param discoveryServiceFactory The factory to use to create the service instance. + * @param networkOperations The object to use when performing network operations. + */ + NetRemoteDiscoveryServiceBuilder(std::unique_ptr discoveryServiceFactory, std::unique_ptr networkOperations); + + /** + * @brief Destroy the NetRemoteDiscoveryServiceBuilder object. + */ + virtual ~NetRemoteDiscoveryServiceBuilder() = default; + + /** + * @brief Set the hostname of the service. + * + * @param hostname The hostname to set. + * @return NetRemoteDiscoveryServiceBuilder& + */ + NetRemoteDiscoveryServiceBuilder& + SetHostname(std::string hostname); + + /** + * @brief Set the port of the service. + * + * If not specified, the default port is used. + * + * @param port The port the service listens on. + * @return NetRemoteDiscoveryServiceBuilder& + */ + NetRemoteDiscoveryServiceBuilder& + SetPort(uint32_t port); + + /** + * @brief Add an IP address the service listens on. + * + * @param ipAddress The ip address the service listens on. + * @return NetRemoteDiscoveryServiceBuilder& + */ + NetRemoteDiscoveryServiceBuilder& + AddIpAddress(std::string ipAddress); + + /** + * @brief Create a NetRemoteDiscoveryService object with the stored configuration. + * + * @return std::shared_ptr + */ + std::shared_ptr + Build(); + +private: + std::unique_ptr m_discoveryServiceFactory; + std::unique_ptr m_networkOperations; + std::string m_hostname; + uint32_t m_port{ Protocol::NetRemoteProtocol::PortDefault }; + std::unordered_map m_ipAddresses{}; +}; +} // namespace Microsoft::Net::Remote + +#endif // NET_REMOTE_DISCOVERY_SERVICE_HXX diff --git a/src/common/server/include/microsoft/net/remote/NetRemoteServer.hxx b/src/common/server/include/microsoft/net/remote/NetRemoteServer.hxx index 02491845..2b35b386 100644 --- a/src/common/server/include/microsoft/net/remote/NetRemoteServer.hxx +++ b/src/common/server/include/microsoft/net/remote/NetRemoteServer.hxx @@ -32,9 +32,12 @@ struct NetRemoteServer * Prevent copying and moving of this object. */ NetRemoteServer(const NetRemoteServer&) = delete; + + NetRemoteServer(NetRemoteServer&&) = delete; + NetRemoteServer& operator=(const NetRemoteServer&) = delete; - NetRemoteServer(NetRemoteServer&&) = delete; + NetRemoteServer& operator=(NetRemoteServer&&) = delete; diff --git a/src/common/server/include/microsoft/net/remote/NetRemoteServerConfiguration.hxx b/src/common/server/include/microsoft/net/remote/NetRemoteServerConfiguration.hxx index 64fdc75f..7ac3fd50 100644 --- a/src/common/server/include/microsoft/net/remote/NetRemoteServerConfiguration.hxx +++ b/src/common/server/include/microsoft/net/remote/NetRemoteServerConfiguration.hxx @@ -7,6 +7,9 @@ #include #include +#include +#include + namespace Microsoft::Net::Wifi { class AccessPointManager; @@ -64,7 +67,7 @@ struct NetRemoteServerConfiguration * and a level of 3 or above will show all verbose messages. */ uint32_t LogVerbosity{ 0 }; - + /** * @brief Whether to enable logging to file or not. */ @@ -74,6 +77,16 @@ struct NetRemoteServerConfiguration * @brief Access point manager instance. */ std::shared_ptr AccessPointManager; + + /** + * @brief Object to use when performing network operations. + */ + std::unique_ptr NetworkOperations; + + /** + * @brief Factory to use to create the discovery service. + */ + std::unique_ptr DiscoveryServiceFactory; }; } // namespace Microsoft::Net::Remote diff --git a/src/linux/CMakeLists.txt b/src/linux/CMakeLists.txt index f4a47a37..0b485765 100644 --- a/src/linux/CMakeLists.txt +++ b/src/linux/CMakeLists.txt @@ -1,8 +1,9 @@ -find_package(sdbus-c++ REQUIRED) +find_package(sdbus-c++ CONFIG REQUIRED) add_subdirectory(external) add_subdirectory(libnl-helpers) +add_subdirectory(net) add_subdirectory(wifi) add_subdirectory(wpa-controller) diff --git a/src/linux/libnl-helpers/NetlinkRoute.cxx b/src/linux/libnl-helpers/NetlinkRoute.cxx index 72a4293f..1dc34578 100644 --- a/src/linux/libnl-helpers/NetlinkRoute.cxx +++ b/src/linux/libnl-helpers/NetlinkRoute.cxx @@ -1,9 +1,13 @@ +#include +#include #include #include #include #include +#include #include +#include #include #include @@ -11,19 +15,102 @@ #include #include #include +#include #include #include #include -#include #include #include #include +#include #include #include #include namespace Microsoft::Net::Netlink { +namespace detail +{ +// The maximum length of an ipv4 address is INET_ADDRSTRLEN (16) + 3 (for the subnet mask), eg. xxx.xxx.xxx.xxx/yy. +constexpr auto Ipv4AddressAsciiLengthMax{ INET_ADDRSTRLEN + 3 }; +constexpr auto Ipv6AddressAsciiLengthMax{ INET6_ADDRSTRLEN + 5 }; + +/** + * @brief Get the (maximum) length of an address in ascii format. + * + * @param addressFamily The address family to get the length for. This must be either AF_INET or AF_INET6. + * @return std::size_t The maximum length of an address string corresponding to the specified IP family. + */ +std::size_t +GetAddressAsciiLength(int addressFamily) noexcept +{ + switch (addressFamily) { + case AF_INET: + return Ipv4AddressAsciiLengthMax; + case AF_INET6: + [[fallthrough]]; + default: + return Ipv6AddressAsciiLengthMax; + } +} + +/** + * @brief Get a string representation of the specified IP address family. + * + * @param addressFamily The address family to get the name for. This must be either AF_INET or AF_INET6. + * @return constexpr std::string_view The name of the address family or "Unknown" if the address family is not + * recognized. + */ +constexpr std::string_view +GetAddressFamilyName(int addressFamily) noexcept +{ + switch (addressFamily) { + case AF_INET: + return "IPv4"; + case AF_INET6: + return "IPv6"; + default: + return "Unknown"; + } +} + +/** + * @brief Helper function to use with nl_cache_foreach when processing an rtnl link cache. This will parse the netlink + * object and if it represents a valid netlink link, add an entry to the map specified by the context. + * + * @param nlObjectLink The netlink object representing a potential netlink link. + * @param context The context to populate with the result. This must be of type std::unordered_set. + */ +void +OnLink(struct nl_object *nlObjectLink, void *context) +{ + auto *rtnlLink = reinterpret_cast(nlObjectLink); // NOLINT + auto netlinkLink = NetlinkLink::FromRtnlLink(rtnlLink); + + // Populate output variable (context) with result. + auto &netlinkLinks = *static_cast *>(context); + netlinkLinks.emplace(std::move(netlinkLink)); +} + +/** + * @brief Helper function to use with nl_cache_foreach when processing an rtnl address cache. This parses the specified + * netlink object and if it represents a valid netlink address, add an entry to the map specified by the context. + * + * @param nlObjectAddress The netlink object representing a potential netlink address. + * @param context The context to populate with the result. This must be of type std::unordered_set. + */ +void +OnAddress(struct nl_object *nlObjectAddress, void *context) +{ + auto *rtnlAddress = reinterpret_cast(nlObjectAddress); // NOLINT + auto netlinkAddress = NetlinkIpAddress::FromRtnlAddr(rtnlAddress); + + // Populate output variable (context) with result. + auto &netlinkAddresses = *static_cast *>(context); + netlinkAddresses.emplace(std::move(netlinkAddress)); +} +} // namespace detail + NetlinkSocket CreateNlRouteSocket() { @@ -42,13 +129,37 @@ CreateNlRouteSocket() return socket; } -std::vector -NetlinkEnumerateIpv4Addresses() +std::unordered_set +NetlinkEnumerateLinks() +{ + auto nlRouteSocket{ CreateNlRouteSocket() }; + + struct nl_cache *linkCache{ nullptr }; + const int ret = rtnl_link_alloc_cache(nlRouteSocket, AF_UNSPEC, &linkCache); + if (ret != 0) { + const auto errorCode = MakeNetlinkErrorCode(-ret); + const auto message = std::format("Failed to allocate link cache with error {}", errorCode.value()); + LOGE << message; + throw std::system_error(errorCode, message); + } + + auto freeLinkCache = notstd::scope_exit([&linkCache] { + nl_cache_free(linkCache); + }); + + std::unordered_set links{}; + nl_cache_foreach(linkCache, detail::OnLink, &links); + + return links; +} + +std::unordered_set +NetlinkEnumerateIpAddresses() { auto nlRouteSocket{ CreateNlRouteSocket() }; struct nl_cache *ipAddressCache{ nullptr }; - int ret = rtnl_addr_alloc_cache(nlRouteSocket, &ipAddressCache); + const int ret = rtnl_addr_alloc_cache(nlRouteSocket, &ipAddressCache); if (ret != 0) { const auto errorCode = MakeNetlinkErrorCode(-ret); const auto message = std::format("Failed to allocate address cache with error {}", errorCode.value()); @@ -56,34 +167,98 @@ NetlinkEnumerateIpv4Addresses() throw std::system_error(errorCode, message); } - auto freeNlCache = notstd::scope_exit([&ipAddressCache] { + auto freeIpAddressCache = notstd::scope_exit([&ipAddressCache] { nl_cache_free(ipAddressCache); }); - std::vector ipv4Addresses{}; - struct nl_object *nlObjectIpAddress{ nullptr }; - for (nlObjectIpAddress = nl_cache_get_first(ipAddressCache); nlObjectIpAddress != nullptr; nlObjectIpAddress = nl_cache_get_next(nlObjectIpAddress)) { - auto *nlIpAddress = reinterpret_cast(nlObjectIpAddress); // NOLINT + std::unordered_set ipAddresses{}; + nl_cache_foreach(ipAddressCache, detail::OnAddress, &ipAddresses); + + return ipAddresses; +} + +/* static */ +NetlinkIpAddress +NetlinkIpAddress::FromRtnlAddr(struct rtnl_addr *rtnlAddress) +{ + const auto index = rtnl_addr_get_ifindex(rtnlAddress); + const auto family = rtnl_addr_get_family(rtnlAddress); + const auto prefixLength = rtnl_addr_get_prefixlen(rtnlAddress); + const auto *local = rtnl_addr_get_local(rtnlAddress); + + // Convert the address to a string then resize to actual null-terminated length. + std::string addressLocalAscii(detail::GetAddressAsciiLength(family), '\0'); + nl_addr2str(local, std::data(addressLocalAscii), std::size(addressLocalAscii)); + addressLocalAscii.resize(std::strlen(std::data(addressLocalAscii))); - // Process ipv4 addresses only. - const auto family = rtnl_addr_get_family(nlIpAddress); - if (family != AF_INET) { - continue; - } + return NetlinkIpAddress{ index, family, prefixLength, addressLocalAscii }; +} + +/* static */ +NetlinkLink +NetlinkLink::FromRtnlLink(struct rtnl_link *rtnlLink) +{ + const auto index = rtnl_link_get_ifindex(rtnlLink); + const auto *name = rtnl_link_get_name(rtnlLink); + const auto *type = rtnl_link_get_type(rtnlLink); + const auto *macAddress = rtnl_link_get_addr(rtnlLink); + + // TODO: implement independent function to get proper type when this is nullptr. + if (type == nullptr) { + type = "none"; + } + + // Convert the address to a string then resize to actual null-terminated length. + std::string macAddressAscii(detail::Ipv6AddressAsciiLengthMax, '\0'); + nl_addr2str(macAddress, std::data(macAddressAscii), std::size(macAddressAscii)); + macAddressAscii.resize(std::strlen(std::data(macAddressAscii))); + + return NetlinkLink{ index, name, type, std::move(macAddressAscii) }; +} - // The maximum length of an ipv4 address is INET_ADDRSTRLEN (16) + 3 (for the subnet mask), eg. xxx.xxx.xxx.xxx/yy. - constexpr auto Ipv4AddressLengthMax{ INET_ADDRSTRLEN + 3 }; +/* static */ +std::optional +NetlinkLink::FromInterfaceIndex(int interfaceIndex) +{ + // Obtain interface/link name from interface index. + std::array interfaceName{ '\0' }; + if_indextoname(static_cast(interfaceIndex), std::data(interfaceName)); - // Convert the ipv4 address to a string. - auto *ipv4Address = rtnl_addr_get_local(nlIpAddress); - std::string ipv4AddressAscii(Ipv4AddressLengthMax, '\0'); - nl_addr2str(ipv4Address, std::data(ipv4AddressAscii), std::size(ipv4AddressAscii)); + // Allocate a netlink socket to make the kernel request with. + auto nlRouteSocket{ CreateNlRouteSocket() }; - // Resize to the actual null-terminated string length. - ipv4AddressAscii.resize(std::strlen(std::data(ipv4AddressAscii))); - ipv4Addresses.push_back(std::move(ipv4AddressAscii)); + // Send a kernel request to get the link information. + struct rtnl_link *rtnlLink{ nullptr }; + const int ret = rtnl_link_get_kernel(nlRouteSocket, interfaceIndex, std::data(interfaceName), &rtnlLink); + if (ret < 0) { + const auto errorCode = MakeNetlinkErrorCode(-ret); + const auto message = std::format("Failed to get link with error {}", errorCode.value()); + LOGE << message; + return std::nullopt; } - return ipv4Addresses; + auto freeLink = notstd::scope_exit([&rtnlLink] { + nl_object_free(OBJ_CAST(rtnlLink)); + rtnlLink = nullptr; + }); + + return FromRtnlLink(rtnlLink); } + +std::string +NetlinkLink::ToString() const +{ + return std::format("[{}] {} {} {}", InterfaceIndex, Name, Type, MacAddress); +} + +std::string +NetlinkIpAddress::ToString(bool showInterfaceIndex) const +{ + auto addressFamilyName = detail::GetAddressFamilyName(Family); + + return showInterfaceIndex + ? std::format("[{}] {} {}/{}", InterfaceIndex, addressFamilyName, Address, PrefixLength) + : std::format("{} {}/{}", addressFamilyName, Address, PrefixLength); +} + } // namespace Microsoft::Net::Netlink diff --git a/src/linux/libnl-helpers/include/microsoft/net/netlink/route/NetlinkRoute.hxx b/src/linux/libnl-helpers/include/microsoft/net/netlink/route/NetlinkRoute.hxx index 01317df5..615cbbc2 100644 --- a/src/linux/libnl-helpers/include/microsoft/net/netlink/route/NetlinkRoute.hxx +++ b/src/linux/libnl-helpers/include/microsoft/net/netlink/route/NetlinkRoute.hxx @@ -4,12 +4,89 @@ #include #include +#include #include #include +#include +#include +#include namespace Microsoft::Net::Netlink { +/** + * @brief Represents IP address information obtained from a netlink address (struct rtnl_addr) object. + */ +struct NetlinkIpAddress +{ + int InterfaceIndex; + int Family; + int PrefixLength; + std::string Address; + + auto + operator<=>(const NetlinkIpAddress &) const = default; + + /** + * @brief Parse a netlink address object and create a NetlinkIpAddress object from it. + * + * @param rtnlAddress The netlink address object to parse. + * @return NetlinkIpAddress + */ + static NetlinkIpAddress + FromRtnlAddr(struct rtnl_addr *rtnlAddress); + + /** + * @brief Get a string representation of the IP address. + * + * @param showInterfaceIndex Whether to include the interface index in the string representation. + * @return std::string + */ + std::string + ToString(bool showInterfaceIndex = true) const; +}; + +/** + * @brief Represents link information obtained from a netlink link (struct rtnl_link) object. A link is mostly + * synonymous with a network interface. + */ +struct NetlinkLink +{ + int InterfaceIndex; + std::string Name; + std::string Type; + std::string MacAddress; + + auto + operator<=>(const NetlinkLink &) const = default; + + /** + * @brief Parse a netlink link object and create a NetlinkLink object from it. + * + * @param link The netlink link object to parse. + * @return NetlinkLink + */ + static NetlinkLink + FromRtnlLink(struct rtnl_link *link); + + /** + * @brief Get a NetlinkLink object from an interface index. + * + * @param interfaceIndex + * @return std::optional + */ + static std::optional + FromInterfaceIndex(int interfaceIndex); + + /** + * @brief Get a string representation of the link. + * + * @return std::string + */ + std::string + ToString() const; +}; + /** * @brief Create a netlink socket for use with the "route" family (NETLINK_ROUTE). * @@ -21,13 +98,44 @@ Microsoft::Net::Netlink::NetlinkSocket CreateNlRouteSocket(); /** - * @brief Enumerate all ipv4 addresses on the system. - * - * @return std::vector + * @brief Enumerate all ip addresses on the system. + * + * @return std::unordered_set + */ +std::unordered_set +NetlinkEnumerateIpAddresses(); + +/** + * @brief Enumerate all links (network interfaces) on the system. + * + * @return std::unordered_set */ -std::vector -NetlinkEnumerateIpv4Addresses(); +std::unordered_set +NetlinkEnumerateLinks(); } // namespace Microsoft::Net::Netlink +namespace std +{ +template <> +struct hash +{ + std::size_t + operator()(const Microsoft::Net::Netlink::NetlinkIpAddress &address) const + { + return std::hash{}(address.InterfaceIndex) ^ std::hash{}(address.Family) ^ std::hash{}(address.PrefixLength) ^ std::hash{}(address.Address); + } +}; + +template <> +struct hash +{ + std::size_t + operator()(const Microsoft::Net::Netlink::NetlinkLink &link) const + { + return std::hash{}(link.InterfaceIndex) ^ std::hash{}(link.Name) ^ std::hash{}(link.Type) ^ std::hash{}(link.MacAddress); + } +}; +} // namespace std + #endif // NETLINK_ROUTE_HXX diff --git a/src/linux/net/CMakeLists.txt b/src/linux/net/CMakeLists.txt new file mode 100644 index 00000000..84db0870 --- /dev/null +++ b/src/linux/net/CMakeLists.txt @@ -0,0 +1,23 @@ + +add_library(${PROJECT_NAME}-net-linux STATIC "") + +set(NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE ${CMAKE_CURRENT_LIST_DIR}/include) +set(NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE_SUFFIX microsoft/net) +set(NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE_PREFIX ${NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE}/${NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE_SUFFIX}) + +target_sources(${PROJECT_NAME}-net-linux + PRIVATE + NetworkOperationsLinux.cxx + PUBLIC + FILE_SET HEADERS + BASE_DIRS ${NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE} + FILES + ${NET_REMOTE_NET_LINUX_PUBLIC_INCLUDE_PREFIX}/NetworkOperationsLinux.hxx +) + +target_link_libraries(${PROJECT_NAME}-net-linux + PUBLIC + ${PROJECT_NAME}-net + PRIVATE + libnl-helpers +) diff --git a/src/linux/net/NetworkOperationsLinux.cxx b/src/linux/net/NetworkOperationsLinux.cxx new file mode 100644 index 00000000..da269404 --- /dev/null +++ b/src/linux/net/NetworkOperationsLinux.cxx @@ -0,0 +1,88 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Microsoft::Net +{ +namespace detail +{ +NetworkInterfaceType +GetNetworkInterfaceType(const Netlink::NetlinkLink& netlinkLink) noexcept +{ + if (netlinkLink.Type == "wireless") { + return NetworkInterfaceType::Wifi; + } + if (netlinkLink.Type == "ethernet" || netlinkLink.Type == "loopback" || netlinkLink.Type == "bridge") { + return NetworkInterfaceType::Other; + } + return NetworkInterfaceType::Unknown; +} + +IpFamily +GetIpFamily(const Netlink::NetlinkIpAddress& netlinkIpAddress) noexcept +{ + switch (netlinkIpAddress.Family) { + case AF_INET: + return IpFamily::Ipv4; + case AF_INET6: + return IpFamily::Ipv6; + default: + return IpFamily::Unknown; + } +} + +std::pair +NetlinkToIpAddressInformation(const Netlink::NetlinkIpAddress& netlinkIpAddress, const Netlink::NetlinkLink& netlinkLink) noexcept +{ + std::string ipAddress{ netlinkIpAddress.Address }; + const IpAddressInformation ipAddressInformation{ + .Family = GetIpFamily(netlinkIpAddress), + .InterfaceType = GetNetworkInterfaceType(netlinkLink), + }; + + return { std::move(ipAddress), ipAddressInformation }; +} + +} // namespace detail + +std::unordered_map +NetworkOperationsLinux::GetLocalIpAddressInformation(std::string_view ipAddress) const noexcept +{ + std::unordered_map results{}; + const auto netlinkIpAddresses = Netlink::NetlinkEnumerateIpAddresses(); + + if (IsAnyAddress(ipAddress)) { + for (const auto& netlinkIpAddress : netlinkIpAddresses) { + auto netlinkLink = Netlink::NetlinkLink::FromInterfaceIndex(netlinkIpAddress.InterfaceIndex); + if (netlinkLink.has_value()) { + auto [ipAddressCopy, ipAddressInformation] = detail::NetlinkToIpAddressInformation(netlinkIpAddress, *netlinkLink); + results.emplace(std::move(ipAddressCopy), ipAddressInformation); + } + } + } else { + const auto matchesIpAnyPort = [ipAddress](const Netlink::NetlinkIpAddress& netlinkIpAddress) { + return ipAddress.starts_with(netlinkIpAddress.Address); + }; + + const auto netlinkIpAddress = std::ranges::find_if(netlinkIpAddresses, matchesIpAnyPort); + if (netlinkIpAddress != std::ranges::end(netlinkIpAddresses)) { + auto netlinkLink = Netlink::NetlinkLink::FromInterfaceIndex(netlinkIpAddress->InterfaceIndex); + if (netlinkLink.has_value()) { + auto [ipAddressCopy, ipAddressInformation] = detail::NetlinkToIpAddressInformation(*netlinkIpAddress, *netlinkLink); + results.emplace(std::move(ipAddressCopy), ipAddressInformation); + } + } + } + + return results; +} +} // namespace Microsoft::Net diff --git a/src/linux/net/include/microsoft/net/NetworkOperationsLinux.hxx b/src/linux/net/include/microsoft/net/NetworkOperationsLinux.hxx new file mode 100644 index 00000000..8a937550 --- /dev/null +++ b/src/linux/net/include/microsoft/net/NetworkOperationsLinux.hxx @@ -0,0 +1,50 @@ + +#ifndef NETWORK_OPERATIONS_LINUX_HXX +#define NETWORK_OPERATIONS_LINUX_HXX + +#include +#include +#include +#include + +#include + +namespace Microsoft::Net +{ +/** + * @brief Linux implementation of network operations. + */ +struct NetworkOperationsLinux : + public INetworkOperations +{ + ~NetworkOperationsLinux() override = default; + + /** + * @brief Construct a new NetworkOperationsLinux object. + */ + NetworkOperationsLinux() = default; + + NetworkOperationsLinux(const NetworkOperationsLinux&) = delete; + + NetworkOperationsLinux(NetworkOperationsLinux&&) = delete; + + NetworkOperationsLinux& + operator=(const NetworkOperationsLinux&) = delete; + + NetworkOperationsLinux& + operator=(NetworkOperationsLinux&&) = delete; + + /** + * @brief Obtain information about the specified IP address. The returned map will contain the IP address as the key + * and the information as the value. In the case of a fixed address, the returned map will have a single entry. In + * the case of any "any" address (eg. 0.0.0.0, ::), the returned map will contain all available addresses. + * + * @param ipAddress The ip address to obtain information for. + * @return std::unordered_map + */ + std::unordered_map + GetLocalIpAddressInformation(std::string_view ipAddress) const noexcept override; +}; +} // namespace Microsoft::Net + +#endif // NETWORK_OPERATIONS_LINUX_HXX diff --git a/src/linux/server/CMakeLists.txt b/src/linux/server/CMakeLists.txt index e55f735c..5f372862 100644 --- a/src/linux/server/CMakeLists.txt +++ b/src/linux/server/CMakeLists.txt @@ -4,10 +4,13 @@ add_executable(${PROJECT_NAME}-server-linux "") target_sources(${PROJECT_NAME}-server-linux PRIVATE Main.cxx + NetRemoteDiscoveryServiceLinuxDnssd.cxx + NetRemoteDiscoveryServiceLinuxDnssd.hxx ) target_link_libraries(${PROJECT_NAME}-server-linux PRIVATE + ${PROJECT_NAME}-net-linux ${PROJECT_NAME}-server logging-utils notstd diff --git a/src/linux/server/Main.cxx b/src/linux/server/Main.cxx index e9b28776..25929d4f 100644 --- a/src/linux/server/Main.cxx +++ b/src/linux/server/Main.cxx @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,9 @@ #include #include +#include "NetRemoteDiscoveryServiceLinuxDnssd.hxx" + +using namespace Microsoft::Net; using namespace Microsoft::Net::Remote; using namespace Microsoft::Net::Wifi; @@ -63,6 +67,12 @@ main(int argc, char *argv[]) accessPointManager->AddDiscoveryAgent(std::move(accessPointDiscoveryAgent)); } + // Configure service discovery to use DNS-SD. + { + configuration.NetworkOperations = std::make_unique(); + configuration.DiscoveryServiceFactory = std::make_unique(); + } + // Create the server. NetRemoteServer server{ configuration }; diff --git a/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.cxx b/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.cxx new file mode 100644 index 00000000..3fc43a5d --- /dev/null +++ b/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.cxx @@ -0,0 +1,22 @@ + +#include "NetRemoteDiscoveryServiceLinuxDnssd.hxx" + +using namespace Microsoft::Net::Remote; + +void +NetRemoteDiscoveryServiceLinuxDnssd::Start() +{ + // TODO +} + +void +NetRemoteDiscoveryServiceLinuxDnssd::Stop() +{ + // TODO +} + +std::shared_ptr +NetRemoteDiscoveryServiceLinuxDnssdFactory::Create(std::string hostname, uint32_t port, std::unordered_map ipAddresses) +{ + return std::make_unique(std::move(hostname), port, std::move(ipAddresses)); +} diff --git a/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.hxx b/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.hxx new file mode 100644 index 00000000..4bf5d219 --- /dev/null +++ b/src/linux/server/NetRemoteDiscoveryServiceLinuxDnssd.hxx @@ -0,0 +1,92 @@ + +#ifndef NET_REMOTE_DISCOVERY_SERVICE_LINUX_DNSSD_HXX +#define NET_REMOTE_DISCOVERY_SERVICE_LINUX_DNSSD_HXX + +#include +#include +#include + +#include +#include + +namespace Microsoft::Net::Remote +{ +/** + * @brief Linux DNS-SD based service discovery implementation. + */ +struct NetRemoteDiscoveryServiceLinuxDnssd : + public NetRemoteDiscoveryService +{ + using NetRemoteDiscoveryService::NetRemoteDiscoveryService; + + ~NetRemoteDiscoveryServiceLinuxDnssd() override = default; + + /** + * Prevent copying and moving of this object. + */ + NetRemoteDiscoveryServiceLinuxDnssd(const NetRemoteDiscoveryServiceLinuxDnssd&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssd(NetRemoteDiscoveryServiceLinuxDnssd&&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssd& + operator=(const NetRemoteDiscoveryServiceLinuxDnssd&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssd& + operator=(NetRemoteDiscoveryServiceLinuxDnssd&&) = delete; + + /** + * @brief Start the discovery service. + */ + void + Start() override; + + /** + * @brief Stop the discovery service. + */ + void + Stop() override; +}; + +/** + * @brief Factory to create Linux DNS-SD based discovery service. Creates an instance of NetRemoteDiscoveryServiceLinuxDnssd. + */ +struct NetRemoteDiscoveryServiceLinuxDnssdFactory : + public INetRemoteDiscoveryServiceFactory +{ + /** + * @brief Destroy the NetRemoteDiscoveryServiceLinuxDnssdFactory object. + */ + ~NetRemoteDiscoveryServiceLinuxDnssdFactory() override = default; + + /** + * @brief Construct a new NetRemoteDiscoveryServiceLinuxDnssdFactory object. + */ + NetRemoteDiscoveryServiceLinuxDnssdFactory() = default; + + /** + * Prevent copying and moving of this object. + */ + NetRemoteDiscoveryServiceLinuxDnssdFactory(const NetRemoteDiscoveryServiceLinuxDnssdFactory&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssdFactory(NetRemoteDiscoveryServiceLinuxDnssdFactory&&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssdFactory& + operator=(const NetRemoteDiscoveryServiceLinuxDnssdFactory&) = delete; + + NetRemoteDiscoveryServiceLinuxDnssdFactory& + operator=(NetRemoteDiscoveryServiceLinuxDnssdFactory&&) = delete; + + /** + * @brief Create a new instance of a NetRemoteDiscoveryServiceLinuxDnssd. + * + * @param hostname The hostname of the service. + * @param port The port the service runs on. + * @param ipAddresses The IP addresses of the service. + * @return std::shared_ptr + */ + std::shared_ptr + Create(std::string hostname, uint32_t port, std::unordered_map ipAddresses) override; +}; +} // namespace Microsoft::Net::Remote + +#endif // NET_REMOTE_DISCOVERY_SERVICE_LINUX_DNSSD_HXX diff --git a/tests/unit/linux/CMakeLists.txt b/tests/unit/linux/CMakeLists.txt index 2271c149..1a07904f 100644 --- a/tests/unit/linux/CMakeLists.txt +++ b/tests/unit/linux/CMakeLists.txt @@ -1,4 +1,5 @@ add_subdirectory(libnl-helpers) +add_subdirectory(net) add_subdirectory(wifi) add_subdirectory(wpa-controller) diff --git a/tests/unit/linux/libnl-helpers/TestNetlinkRoute.cxx b/tests/unit/linux/libnl-helpers/TestNetlinkRoute.cxx index 141bd1e5..859b8cfe 100644 --- a/tests/unit/linux/libnl-helpers/TestNetlinkRoute.cxx +++ b/tests/unit/linux/libnl-helpers/TestNetlinkRoute.cxx @@ -2,21 +2,11 @@ #include #include -TEST_CASE("NetlinkEnumerateIpv4Addresses", "[linux][libnl-helpers]") +TEST_CASE("NetlinkEnumerateIpAddresses", "[linux][libnl-helpers]") { - using Microsoft::Net::Netlink::NetlinkEnumerateIpv4Addresses; - - SECTION("Doesn't cause a crash") - { - REQUIRE_NOTHROW(NetlinkEnumerateIpv4Addresses()); - } - - SECTION("Returned values are non-empty") - { - const auto addresses = NetlinkEnumerateIpv4Addresses(); +} - for (const auto& address : addresses) { - REQUIRE_FALSE(std::empty(address)); - } - } +TEST_CASE("NetlinkEnumerateLinks", "[linux][libnl-helpers]") +{ } + diff --git a/tests/unit/linux/net/CMakeLists.txt b/tests/unit/linux/net/CMakeLists.txt new file mode 100644 index 00000000..1a1ee31d --- /dev/null +++ b/tests/unit/linux/net/CMakeLists.txt @@ -0,0 +1,22 @@ + +add_executable(${PROJECT_NAME}-net-linux-test-unit) + +target_sources(${PROJECT_NAME}-net-linux-test-unit + PRIVATE + Main.cxx + TestNetworkOperationsLinux.cxx +) + +target_include_directories(${PROJECT_NAME}-net-linux-test-unit + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(${PROJECT_NAME}-net-linux-test-unit + PRIVATE + Catch2::Catch2WithMain + ${PROJECT_NAME}-net-linux +) + +catch_discover_tests(${PROJECT_NAME}-net-linux-test-unit) + diff --git a/tests/unit/linux/net/Main.cxx b/tests/unit/linux/net/Main.cxx new file mode 100644 index 00000000..0952e177 --- /dev/null +++ b/tests/unit/linux/net/Main.cxx @@ -0,0 +1,16 @@ + +#include +#include +#include +#include +#include + +int +main(int argc, char* argv[]) +{ + static plog::ColorConsoleAppender colorConsoleAppender{}; + + plog::init(plog::debug, &colorConsoleAppender); + + return Catch::Session().run(argc, argv); +} diff --git a/tests/unit/linux/net/TestNetworkOperationsLinux.cxx b/tests/unit/linux/net/TestNetworkOperationsLinux.cxx new file mode 100644 index 00000000..4af0e024 --- /dev/null +++ b/tests/unit/linux/net/TestNetworkOperationsLinux.cxx @@ -0,0 +1,90 @@ + +#include +#include + +#include +#include + +TEST_CASE("GetLocalIpAddressInformation IPv4 Addresses", "[linux][server]") +{ + using namespace Microsoft::Net; + + constexpr auto Ipv4FixedWithoutPort{ "127.0.0.1" }; + constexpr auto Ipv4FixedWithPort{ "127.0.0.1:1234" }; + constexpr auto Ipv4AnyAddressWithoutPort{ "0.0.0.0" }; + constexpr auto Ipv4AnyAddressWithPort{ "0.0.0.0:1234" }; + + SECTION("Ipv4 fixed address without port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv4FixedWithoutPort)); + } + + SECTION("Ipv4 fixed address with port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv4FixedWithPort)); + } + + SECTION("Ipv4 any address without port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv4AnyAddressWithoutPort)); + } + + SECTION("Ipv4 any address with port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv4AnyAddressWithPort)); + } + + SECTION("Ipv4 any address without port resolves all addresses") + { + const auto ipInfo = NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv4AnyAddressWithoutPort); + REQUIRE(!std::empty(ipInfo)); + } +} + +TEST_CASE("GetLocalIpAddressInformation IPv6 Addresses", "[linux][server]") +{ + using namespace Microsoft::Net; + + constexpr auto Ipv6FixedWithoutPort{ "::1" }; + constexpr auto Ipv6FixedWithPort{ "[::1]:1234" }; + constexpr auto Ipv6AnyNoBracketsNoPort{ "::" }; + constexpr auto Ipv6AnyNoBracketsWithPort{ "::1234" }; + constexpr auto Ipv6AnyBracketsNoPort{ "[::]" }; + constexpr auto Ipv6AnyBracketsWithPort{ "[::]:1234" }; + + SECTION("Ipv6 fixed address without port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6FixedWithoutPort)); + } + + SECTION("Ipv6 fixed address with port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6FixedWithPort)); + } + + SECTION("Ipv6 any address without brackets and no port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6AnyNoBracketsNoPort)); + } + + SECTION("Ipv6 any address without brackets and with port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6AnyNoBracketsWithPort)); + } + + SECTION("Ipv6 any address with brackets and no port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6AnyBracketsNoPort)); + } + + SECTION("Ipv6 any address with brackets and with port doesn't cause a crash") + { + REQUIRE_NOTHROW(NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6AnyBracketsWithPort)); + } + + SECTION("Ipv6 any address without brackets and no port resolves all addresses") + { + const auto ipInfo = NetworkOperationsLinux{}.GetLocalIpAddressInformation(Ipv6AnyNoBracketsNoPort); + REQUIRE(!std::empty(ipInfo)); + } +}