// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #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.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 std::vector GetAvailableNetworkInterfaces() { ULONG buf_size = 0; if (GetAdaptersAddresses( AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, nullptr, nullptr, &buf_size) != ERROR_BUFFER_OVERFLOW) { LOG_ERROR(Network, "GetAdaptersAddresses(overrun probe) failed"); return {}; } std::vector buffer(buf_size, 0); auto* addrs = reinterpret_cast(buffer.data()); if (GetAdaptersAddresses( AF_INET, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_INCLUDE_GATEWAYS, nullptr, addrs, &buf_size) != NO_ERROR) { LOG_ERROR(Network, "GetAdaptersAddresses(data) failed"); return {}; } std::vector result; for (auto* a = addrs; a; a = a->Next) { if (a->OperStatus != IfOperStatusUp || !a->FirstUnicastAddress || !a->FirstUnicastAddress->Address.lpSockaddr) continue; const in_addr ip = reinterpret_cast(a->FirstUnicastAddress->Address.lpSockaddr)->sin_addr; ULONG mask_raw = 0; if (ConvertLengthToIpv4Mask(a->FirstUnicastAddress->OnLinkPrefixLength, &mask_raw) != NO_ERROR) continue; in_addr mask{.S_un{.S_addr{mask_raw}}}; in_addr gw{.S_un{.S_addr{0}}}; if (a->FirstGatewayAddress && a->FirstGatewayAddress->Address.lpSockaddr) gw = reinterpret_cast(a->FirstGatewayAddress->Address.lpSockaddr) ->sin_addr; result.emplace_back(Network::NetworkInterface{ .name = Common::UTF16ToUTF8(std::wstring{a->FriendlyName}), .ip_address = ip, .subnet_mask = mask, .gateway = gw, .kind = (a->IfType == IF_TYPE_IEEE80211 ? HostAdapterKind::Wifi : HostAdapterKind::Ethernet) }); } return result; } #else std::vector GetAvailableNetworkInterfaces() { #if defined(__ANDROID__) || defined(__linux__) struct ifaddrs* ifaddr = nullptr; if (getifaddrs(&ifaddr) != 0) { LOG_ERROR(Network, "getifaddrs: {}", std::strerror(errno)); return {}; } // 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 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}; RoutingEntry info{}; iss >> info.iface_name >> std::hex >> info.dest >> info.gateway >> info.flags; routes.emplace_back(info); } } 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 = gw }); } freeifaddrs(ifaddr); 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 {}; } 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 {}; } 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); } } ::close(fd); return ifaces; #else return {}; #endif } #endif // _WIN32 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_WARNING(Network, "Couldn't find interface \"{}\"", sel_if); print_error = false; } return std::nullopt; } LOG_WARNING(Network, "No interfaces"); return std::nullopt; } void SelectFirstNetworkInterface() { if (auto const ifaces = Network::GetAvailableNetworkInterfaces(); ifaces.size() > 0) Settings::values.network_interface.SetValue(ifaces[0].name); } } // namespace Network