From 6b3f8e1b62f17650ed60a83dd30bedbe86268169 Mon Sep 17 00:00:00 2001 From: lizzie Date: Tue, 10 Feb 2026 22:17:24 +0100 Subject: [PATCH] [core/internal_network] cleanup network interface code a bit (#2905) Signed-off-by: lizzie Co-authored-by: Caio Oliveira Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2905 Reviewed-by: Maufeat Reviewed-by: DraVee Co-authored-by: lizzie Co-committed-by: lizzie --- src/core/CMakeLists.txt | 10 +- .../internal_network/network_interface.cpp | 278 +++++++++--------- src/core/internal_network/wifi_scanner.cpp | 109 +++---- .../internal_network/wifi_scanner_dummy.cpp | 11 + 4 files changed, 203 insertions(+), 205 deletions(-) create mode 100644 src/core/internal_network/wifi_scanner_dummy.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 14eb331d24..8bcb66ac35 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1131,7 +1131,6 @@ add_library(core STATIC internal_network/socket_proxy.cpp internal_network/socket_proxy.h internal_network/sockets.h - internal_network/wifi_scanner.cpp internal_network/wifi_scanner.h launch_timestamp_cache.cpp launch_timestamp_cache.h @@ -1170,9 +1169,12 @@ add_library(core STATIC tools/renderdoc.h) if (ENABLE_WIFI_SCAN) - # find_package(libiw REQUIRED) - target_compile_definitions(core PRIVATE ENABLE_WIFI_SCAN) - target_link_libraries(core PRIVATE iw) + target_sources(core PRIVATE internal_network/wifi_scanner.cpp) + if (PLATFORM_LINUX) + target_link_libraries(core PRIVATE iw) + endif() +else() + target_sources(core PRIVATE internal_network/wifi_scanner_dummy.cpp) endif() if (WIN32) diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 8e8f099be8..48be1e2dc4 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -9,21 +9,39 @@ #include #include -#include "common/common_types.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "common/string_util.h" -#include "core/internal_network/emu_net_state.h" -#include "core/internal_network/network_interface.h" - #ifdef _WIN32 +#include +#include #include -#else +#elif defined(__linux__) || defined(__ANDROID__) #include #include #include +#elif defined(__FreeBSD__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #endif +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/internal_network/emu_net_state.h" +#include "core/internal_network/network_interface.h" + namespace Network { #ifdef _WIN32 @@ -71,22 +89,12 @@ std::vector GetAvailableNetworkInterfaces() { gw = reinterpret_cast(a->FirstGatewayAddress->Address.lpSockaddr) ->sin_addr; - HostAdapterKind kind = HostAdapterKind::Ethernet; - switch (a->IfType) { - case IF_TYPE_IEEE80211: // 802.11 Wi-Fi - kind = HostAdapterKind::Wifi; - break; - default: - kind = HostAdapterKind::Ethernet; - break; - } - result.emplace_back(Network::NetworkInterface{ .name = Common::UTF16ToUTF8(std::wstring{a->FriendlyName}), .ip_address = ip, .subnet_mask = mask, .gateway = gw, - .kind = kind + .kind = (a->IfType == IF_TYPE_IEEE80211 ? HostAdapterKind::Wifi : HostAdapterKind::Ethernet) }); } @@ -96,158 +104,146 @@ std::vector GetAvailableNetworkInterfaces() { #else std::vector GetAvailableNetworkInterfaces() { +#if defined(__ANDROID__) || defined(__linux__) struct ifaddrs* ifaddr = nullptr; - if (getifaddrs(&ifaddr) != 0) { - LOG_ERROR(Network, "Failed to get network interfaces with getifaddrs: {}", - std::strerror(errno)); + LOG_ERROR(Network, "getifaddrs: {}", std::strerror(errno)); return {}; } - std::vector result; - - for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { - if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr) { - continue; - } - - if (ifa->ifa_addr->sa_family != AF_INET) { - continue; - } - - if ((ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) { - continue; - } - -#ifdef ANDROID - // On Android, we can't reliably get gateway info from /proc/net/route - // Just use 0 as the gateway address - result.emplace_back(Network::NetworkInterface{ - .name{ifa->ifa_name}, - .ip_address{std::bit_cast(*ifa->ifa_addr).sin_addr}, - .subnet_mask{std::bit_cast(*ifa->ifa_netmask).sin_addr}, - .gateway{in_addr{.s_addr = 0}} - }); + // TODO: This is still horrible, it was worse before (somehow) + struct RoutingEntry { + std::string iface_name; + u32 dest; + u32 gateway; + u32 flags; + }; + std::vector routes{}; +#ifdef __ANDROID__ + // Even through Linux based, we can't reliably obtain routing information from there :( #else - u32 gateway{}; - - std::ifstream file{"/proc/net/route"}; - if (!file.is_open()) { - LOG_ERROR(Network, "Failed to open \"/proc/net/route\""); - - // Solaris defines s_addr as a macro, can't use special C++ shenanigans here - in_addr gateway_0; - gateway_0.s_addr = gateway; - result.emplace_back(Network::NetworkInterface{ - .name = ifa->ifa_name, - .ip_address = std::bit_cast(*ifa->ifa_addr).sin_addr, - .subnet_mask = std::bit_cast(*ifa->ifa_netmask).sin_addr, - .gateway = gateway_0 - }); - continue; - } - - // ignore header - file.ignore((std::numeric_limits::max)(), '\n'); - - bool gateway_found = false; - + if (std::ifstream file("/proc/net/route"); file.is_open()) { + file.ignore((std::numeric_limits::max)(), '\n'); //ignore header for (std::string line; std::getline(file, line);) { std::istringstream iss{line}; - - std::string iface_name; - iss >> iface_name; - if (iface_name != ifa->ifa_name) { - continue; - } - - iss >> std::hex; - - u32 dest{}; - iss >> dest; - if (dest != 0) { - // not the default route - continue; - } - - iss >> gateway; - - u16 flags{}; - iss >> flags; - - // flag RTF_GATEWAY (defined in ) - if ((flags & 0x2) == 0) { - continue; - } - - gateway_found = true; - break; - } - - if (!gateway_found) { - gateway = 0; + RoutingEntry info{}; + iss >> info.iface_name >> std::hex + >> info.dest >> info.gateway >> info.flags; + routes.emplace_back(info); } - - in_addr gateway_0; - gateway_0.s_addr = gateway; - result.emplace_back(Network::NetworkInterface{ + } else { + LOG_WARNING(Network, "\"/proc/net/route\" not found - using gateway 0"); + } +#endif + std::vector ifaces; + for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr || ifa->ifa_netmask == nullptr /* Have a netmask and address */ + || ifa->ifa_addr->sa_family != AF_INET /* Must be of kind AF_INET */ + || (ifa->ifa_flags & IFF_UP) == 0 || (ifa->ifa_flags & IFF_LOOPBACK) != 0) /* Not loopback */ + continue; + // Just use 0 as the gateway address if not found OR routes are empty :) + auto const it = std::ranges::find_if(routes, [&ifa](auto const& e) { + return e.iface_name == ifa->ifa_name + && e.dest == 0 // not the default route + && (e.flags & 0x02) != 0; // flag RTF_GATEWAY (defined in ) + }); + in_addr gw; // Solaris defines s_addr as a macro, can't use special C++ shenanigans here + gw.s_addr = it != routes.end() ? it->gateway : 0; + ifaces.emplace_back(Network::NetworkInterface{ .name = ifa->ifa_name, .ip_address = std::bit_cast(*ifa->ifa_addr).sin_addr, .subnet_mask = std::bit_cast(*ifa->ifa_netmask).sin_addr, - .gateway = gateway_0 + .gateway = gw }); -#endif // ANDROID } - freeifaddrs(ifaddr); - return result; -} - -#endif // _WIN32 - -std::optional GetSelectedNetworkInterface() { + return ifaces; +#elif defined(__FreeBSD__) + std::vector ifaces; + int fd = ::socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); + if (fd < 0) { + LOG_ERROR(Network, "socket: {}", std::strerror(errno)); + return {}; + } - const auto& selected_network_interface = Settings::values.network_interface.GetValue(); - const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); - if (network_interfaces.empty()) { - LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); - return std::nullopt; + size_t bufsz = 0; + int mib[6] = { + CTL_NET, PF_ROUTE, 0, + AF_UNSPEC, NET_RT_IFLIST, 0 + }; + if (::sysctl(mib, sizeof(mib) / sizeof(mib[0]), nullptr, &bufsz, nullptr, 0) < 0) { + LOG_ERROR(Network, "sysctl.1: {}", std::strerror(errno)); + ::close(fd); + return {}; + } + std::vector buf(bufsz); + if (::sysctl(mib, sizeof(mib) / sizeof(mib[0]), buf.data(), &bufsz, nullptr, 0) < 0) { + LOG_ERROR(Network, "sysctl.2: {}", std::strerror(errno)); + ::close(fd); + return {}; } - #ifdef __ANDROID__ - if (selected_network_interface.empty()) { - return network_interfaces[0]; + struct rt_msghdr const *rtm = NULL; + for (char *next = buf.data(); next < buf.data() + bufsz; next += rtm->rtm_msglen) { + rtm = (struct rt_msghdr const *)next; + if (rtm->rtm_type == RTM_IFINFO) { + struct if_msghdr const* ifm = (struct if_msghdr const *)rtm; + size_t msglen = rtm->rtm_msglen - sizeof(*ifm); + char const* p = (char const*)(ifm + 1); + + Network::NetworkInterface iface{}; + for (size_t i = 0; i < RTAX_MAX; i++) + if ((ifm->ifm_addrs & (1 << i)) != 0) { + struct sockaddr const* sa = reinterpret_cast(p); + if (msglen == 0 || msglen < SA_SIZE(sa)) + break; + if (i == RTA_NETMASK && sa->sa_family == AF_LINK) { + size_t namelen = 0; + struct sockaddr_dl const* sdl = reinterpret_cast(sa); + ::link_ntoa_r(sdl, nullptr, &namelen); + iface.name = std::string(namelen, ' '); + ::link_ntoa_r(sdl, iface.name.data(), &namelen); + std::memcpy(&iface.ip_address, sa, sizeof(struct sockaddr_in)); + } + msglen -= SA_SIZE(sa); + p += SA_SIZE(sa); + } + ifaces.push_back(iface); } - #endif + } + ::close(fd); + return ifaces; +#else + return {}; +#endif +} - const auto res = - std::ranges::find_if(network_interfaces, [&selected_network_interface](const auto& iface) { - return iface.name == selected_network_interface; - }); +#endif // _WIN32 - if (res == network_interfaces.end()) { +std::optional GetSelectedNetworkInterface() { + auto const& sel_if = Settings::values.network_interface.GetValue(); + if (auto const ifaces = Network::GetAvailableNetworkInterfaces(); ifaces.size() > 0) { + if (sel_if.empty()) + return ifaces[0]; + if (auto const res = std::ranges::find_if(ifaces, [&sel_if](const auto& iface) { + return iface.name == sel_if; + }); res != ifaces.end()) + return *res; // Only print the error once to avoid log spam static bool print_error = true; if (print_error) { - LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", - selected_network_interface); + LOG_WARNING(Network, "Couldn't find interface \"{}\"", sel_if); print_error = false; } - return std::nullopt; } - - return *res; + LOG_WARNING(Network, "No interfaces"); + return std::nullopt; } void SelectFirstNetworkInterface() { - const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); - - if (network_interfaces.empty()) { - return; - } - - Settings::values.network_interface.SetValue(network_interfaces[0].name); + if (auto const ifaces = Network::GetAvailableNetworkInterfaces(); ifaces.size() > 0) + Settings::values.network_interface.SetValue(ifaces[0].name); } } // namespace Network diff --git a/src/core/internal_network/wifi_scanner.cpp b/src/core/internal_network/wifi_scanner.cpp index 127221099f..1b97f69177 100644 --- a/src/core/internal_network/wifi_scanner.cpp +++ b/src/core/internal_network/wifi_scanner.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include @@ -6,11 +6,6 @@ #include #include -#include "common/logging/log.h" -#include "core/internal_network/wifi_scanner.h" - -using namespace std::chrono_literals; - #ifdef _WIN32 #define NOMINMAX #include @@ -18,16 +13,30 @@ using namespace std::chrono_literals; #ifdef _MSC_VER #pragma comment(lib, "wlanapi.lib") #endif +#elif defined(__linux__) && !defined(__ANDROID__) +#include +#elif defined(__FreeBSD__) +#include +#include +#include +#include +#include +#include #endif +#include "common/logging/log.h" +#include "core/internal_network/network_interface.h" +#include "core/internal_network/wifi_scanner.h" + +using namespace std::chrono_literals; + namespace Network { -#ifdef ENABLE_WIFI_SCAN #ifdef _WIN32 static u8 QualityToPercent(DWORD q) { - return static_cast(q); + return u8(q); } -static std::vector ScanWifiWin(std::chrono::milliseconds deadline) { +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { std::vector out; HANDLE hClient{}; @@ -85,38 +94,16 @@ static std::vector ScanWifiWin(std::chrono::milliseconds dead WlanCloseHandle(hClient, nullptr); return out; } -#endif /* _WIN32 */ - -#if defined(__linux__) && !defined(_WIN32) && !defined(ANDROID) -#include - +#elif defined(__linux__) && !defined(__ANDROID__) static u8 QualityToPercent(const iwrange& r, const wireless_scan* ws) { const iw_quality qual = ws->stats.qual; const int lvl = qual.level; const int max = r.max_qual.level ? r.max_qual.level : 100; - return static_cast(std::clamp(100 * lvl / max, 0, 100)); -} - -static int wifi_callback(int skfd, char* ifname, char* args[], int count) -{ - iwrange range; - - int res = iw_get_range_info(skfd, ifname, &range); - - LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res); - - if (res >= 0) { - strncpy(args[0], ifname, IFNAMSIZ - 1); - args[0][IFNAMSIZ - 1] = 0; - - return 1; - } - - return 0; + return u8(std::clamp(100 * lvl / max, 0, 100)); } // TODO(crueter, Maufeat): Check if driver supports wireless extensions, fallback to nl80211 if not -static std::vector ScanWifiLinux(std::chrono::milliseconds deadline) { +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { std::vector out; int sock = iw_sockets_open(); if (sock < 0) { @@ -127,7 +114,17 @@ static std::vector ScanWifiLinux(std::chrono::milliseconds de char ifname[IFNAMSIZ] = {0}; char *args[1] = {ifname}; - iw_enum_devices(sock, &wifi_callback, args, 0); + iw_enum_devices(sock, [](int skfd, char* ifname, char* args[], int count) -> int { + iwrange range; + int res = iw_get_range_info(skfd, ifname, &range); + LOG_INFO(Network, "ifname {} returned {} on iw_get_range_info", ifname, res); + if (res >= 0) { + strncpy(args[0], ifname, IFNAMSIZ - 1); + args[0][IFNAMSIZ - 1] = 0; + return 1; + } + return 0; + }, args, 0); if (strlen(ifname) == 0) { LOG_WARNING(Network, "No wireless interface found"); @@ -153,20 +150,19 @@ static std::vector ScanWifiLinux(std::chrono::milliseconds de out.clear(); for (auto* ws = head.result; ws; ws = ws->next) { - if (!ws->b.has_essid) - continue; + if (ws->b.has_essid) { + Network::ScanData sd{}; + sd.ssid_len = static_cast(std::min(ws->b.essid_len, 0x20)); + std::memcpy(sd.ssid, ws->b.essid, sd.ssid_len); + sd.quality = QualityToPercent(range, ws); + sd.flags |= 1; + if (ws->b.has_key) + sd.flags |= 2; - Network::ScanData sd{}; - sd.ssid_len = static_cast(std::min(ws->b.essid_len, 0x20)); - std::memcpy(sd.ssid, ws->b.essid, sd.ssid_len); - sd.quality = QualityToPercent(range, ws); - sd.flags |= 1; - if (ws->b.has_key) - sd.flags |= 2; - - out.emplace_back(sd); - char tmp[0x22]{}; - std::memcpy(tmp, sd.ssid, sd.ssid_len); + out.emplace_back(sd); + char tmp[0x22]{}; + std::memcpy(tmp, sd.ssid, sd.ssid_len); + } } have = !out.empty(); } @@ -174,21 +170,14 @@ static std::vector ScanWifiLinux(std::chrono::milliseconds de iw_sockets_close(sock); return out; } -#endif /* linux */ -#endif - +#elif defined(__FreeBSD__) std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { -#ifdef ENABLE_WIFI_SCAN -#if defined(_WIN32) - return ScanWifiWin(deadline); -#elif defined(__linux__) && !defined(ANDROID) - return ScanWifiLinux(deadline); -#else - return {}; // unsupported host, pretend no results -#endif + return {}; // disabled, pretend no results +} #else +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { return {}; // disabled, pretend no results -#endif } +#endif } // namespace Network diff --git a/src/core/internal_network/wifi_scanner_dummy.cpp b/src/core/internal_network/wifi_scanner_dummy.cpp new file mode 100644 index 0000000000..ebc66b2e41 --- /dev/null +++ b/src/core/internal_network/wifi_scanner_dummy.cpp @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include "core/internal_network/wifi_scanner.h" + +namespace Network { +std::vector ScanWifiNetworks(std::chrono::milliseconds deadline) { + return {}; // disabled, pretend no results +} +} // namespace Network