From 386c2faa30377a60ee12bd72733e0592342cb899 Mon Sep 17 00:00:00 2001 From: DacoTaco Date: Sat, 21 Sep 2024 14:39:02 +0200 Subject: [PATCH] IOS/Net: implement ioctlv that retrieves the network routing table that libogc now uses on network init --- Source/Core/Core/IOS/Network/IP/Top.cpp | 278 ++++++++++++++++++++---- 1 file changed, 238 insertions(+), 40 deletions(-) diff --git a/Source/Core/Core/IOS/Network/IP/Top.cpp b/Source/Core/Core/IOS/Network/IP/Top.cpp index 2c078779b9..45ac955924 100644 --- a/Source/Core/Core/IOS/Network/IP/Top.cpp +++ b/Source/Core/Core/IOS/Network/IP/Top.cpp @@ -57,6 +57,11 @@ #include "jni/AndroidCommon/AndroidCommon.h" #endif +#ifdef __linux__ +#include +#include +#endif + namespace IOS::HLE { enum SOResultCode : s32 @@ -162,25 +167,189 @@ static s32 MapWiiSockOptNameToNative(u32 optname) return optname; } -// u32 values are in little endian (i.e. 0x0100007f means 127.0.0.1) -struct DefaultInterface +struct InterfaceRouting { - u32 inet; // IPv4 address - u32 netmask; // IPv4 subnet mask - u32 broadcast; // IPv4 broadcast address + u32 index; + in_addr destination; + in_addr netmask; + in_addr gateway; }; -static std::optional GetSystemDefaultInterface() +struct DefaultInterface { + in_addr inet; // IPv4 address + in_addr netmask; // IPv4 subnet mask + in_addr broadcast; // IPv4 broadcast address + std::vector routing_table; // IPv4 routing table +}; + +static std::vector GetSystemInterfaceRouting() +{ + std::vector routing_table; + #ifdef _WIN32 - std::unique_ptr forward_table; DWORD forward_table_size = 0; + std::unique_ptr forward_table; if (GetIpForwardTable(nullptr, &forward_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER) { forward_table = std::unique_ptr((PMIB_IPFORWARDTABLE) operator new(forward_table_size)); } + DWORD result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE); + // can return ERROR_MORE_DATA on XP even after the first call + while (result == NO_ERROR || result == ERROR_MORE_DATA) + { + const std::span table(forward_table->table, forward_table->dwNumEntries); + for (const auto& entry : table) + { + routing_table.emplace_back(entry.dwForwardIfIndex, + std::bit_cast(entry.dwForwardDest), + std::bit_cast(entry.dwForwardMask), + std::bit_cast(entry.dwForwardNextHop)); + } + + if (result == NO_ERROR) + break; + + result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE); + } + +#elif defined(__linux__) + constexpr int BUFF_SIZE = 8192; + constexpr timeval socket_timeout{.tv_sec = 2, .tv_usec = 0}; + unsigned int msg_seq = 0; + const int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + if (sock < 0) + { + ERROR_LOG_FMT(IOS_NET, "Failed to open netlink socket, error {}", Common::StrNetworkError()); + return {}; + } + + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, static_cast(&socket_timeout), + sizeof(socket_timeout)) < 0) + { + ERROR_LOG_FMT(IOS_NET, "Failed to set netlink socket recv timeout: {}", + Common::StrNetworkError()); + return {}; + } + + Common::ScopeGuard socket_guard{[sock] { close(sock); }}; + std::array msg_buffer{}; + auto nl_msg = reinterpret_cast(msg_buffer.data()); + auto rt_msg = reinterpret_cast(NLMSG_DATA(nl_msg)); + const unsigned int pid = getpid(); + + // prepare command/netlink packet + nl_msg->nlmsg_len = NLMSG_LENGTH(sizeof(rtmsg)); // Length of message. + nl_msg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table . + + nl_msg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump. + nl_msg->nlmsg_seq = msg_seq++; // Sequence of the message packet. + nl_msg->nlmsg_pid = pid; // PID of process sending the request. + + // ship it + if (send(sock, nl_msg, nl_msg->nlmsg_len, 0) < 0) + { + ERROR_LOG_FMT(IOS_NET, "Failed to send netlink request ({})", Common::StrNetworkError()); + return {}; + } + + // read response + int msg_len = 0; + msg_buffer.fill(0); + + do + { + auto buf_ptr = msg_buffer.data() + msg_len; + const int read_len = recv(sock, buf_ptr, BUFF_SIZE - msg_len, 0); + if (read_len < 0) + { + ERROR_LOG_FMT(IOS_NET, "Failed to receive netlink response ({})", Common::StrNetworkError()); + return {}; + } + + nl_msg = reinterpret_cast(buf_ptr); + if (NLMSG_OK(nl_msg, read_len) == 0) + { + ERROR_LOG_FMT(IOS_NET, "Received netlink error response ({})", NLMSG_OK(nl_msg, read_len)); + return {}; + } + + if (nl_msg->nlmsg_type == NLMSG_ERROR) + { + auto err = reinterpret_cast(NLMSG_DATA(nl_msg)); + if (nl_msg->nlmsg_len < NLMSG_LENGTH(sizeof(nlmsgerr))) + { + ERROR_LOG_FMT(IOS_NET, "Netlink message was truncated"); + } + else + { + ERROR_LOG_FMT(IOS_NET, "Received netlink error {}: {}", err->error, + Common::DecodeNetworkError(-err->error)); + } + return {}; + } + + // if its the last msg, we are done + if (nl_msg->nlmsg_type == NLMSG_DONE) + { + break; + } + + msg_len += read_len; + if ((nl_msg->nlmsg_flags & NLM_F_MULTI) == 0) + { + break; + } + + } while (msg_len <= BUFF_SIZE && (nl_msg->nlmsg_seq != msg_seq || nl_msg->nlmsg_pid != pid)); + + // parse the response + nl_msg = reinterpret_cast(msg_buffer.data()); + for (; NLMSG_OK(nl_msg, msg_len); nl_msg = NLMSG_NEXT(nl_msg, msg_len)) + { + rt_msg = reinterpret_cast(NLMSG_DATA(nl_msg)); + + // only parse AF_INET, as the wii is ipv4 only + if (rt_msg->rtm_family != AF_INET) + continue; + + auto rt_attr = reinterpret_cast(RTM_RTA(rt_msg)); + auto rt_len = RTM_PAYLOAD(nl_msg); + InterfaceRouting route = {}; + + // get netmask from the destination ip length and the rest from the response + for (; RTA_OK(rt_attr, rt_len); rt_attr = RTA_NEXT(rt_attr, rt_len)) + { + switch (rt_attr->rta_type) + { + case RTA_GATEWAY: + route.gateway.s_addr = *reinterpret_cast(RTA_DATA(rt_attr)); + break; + case RTA_DST: + route.destination.s_addr = *reinterpret_cast(RTA_DATA(rt_attr)); + break; + case RTA_OIF: + default: + continue; + } + } + const auto mask = (route.destination.s_addr == 0 && rt_msg->rtm_dst_len == 0) ? + 0 : + (1 << rt_msg->rtm_dst_len) - 1; + route.netmask.s_addr = mask; + routing_table.push_back(route); + } +#endif + + return routing_table; +} + +static std::optional GetSystemDefaultInterface() +{ + auto routing_table = GetSystemInterfaceRouting(); +#ifdef _WIN32 std::unique_ptr ip_table; DWORD ip_table_size = 0; if (GetIpAddrTable(nullptr, &ip_table_size, FALSE) == ERROR_INSUFFICIENT_BUFFER) @@ -189,34 +358,28 @@ static std::optional GetSystemDefaultInterface() } // find the interface IP used for the default route and use that - NET_IFINDEX ifIndex = NET_IFINDEX_UNSPECIFIED; - DWORD result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE); - // can return ERROR_MORE_DATA on XP even after the first call - while (result == NO_ERROR || result == ERROR_MORE_DATA) + NET_IFINDEX if_index = NET_IFINDEX_UNSPECIFIED; + for (InterfaceRouting route : routing_table) { - for (DWORD i = 0; i < forward_table->dwNumEntries; ++i) - { - if (forward_table->table[i].dwForwardDest == 0) - { - ifIndex = forward_table->table[i].dwForwardIfIndex; - break; - } - } + if (route.destination.s_addr != 0) + continue; - if (result == NO_ERROR || ifIndex != NET_IFINDEX_UNSPECIFIED) - break; - - result = GetIpForwardTable(forward_table.get(), &forward_table_size, FALSE); + if_index = route.index; + break; } - if (ifIndex != NET_IFINDEX_UNSPECIFIED && + if (if_index != NET_IFINDEX_UNSPECIFIED && GetIpAddrTable(ip_table.get(), &ip_table_size, FALSE) == NO_ERROR) { for (DWORD i = 0; i < ip_table->dwNumEntries; ++i) { const auto& entry = ip_table->table[i]; - if (entry.dwIndex == ifIndex) - return DefaultInterface{entry.dwAddr, entry.dwMask, entry.dwBCastAddr}; + if (entry.dwIndex == if_index) + { + return DefaultInterface{std::bit_cast(entry.dwAddr), + std::bit_cast(entry.dwMask), + std::bit_cast(entry.dwBCastAddr), routing_table}; + } } } #elif defined(__ANDROID__) @@ -224,8 +387,12 @@ static std::optional GetSystemDefaultInterface() const u32 prefix_length = GetNetworkPrefixLength(); const u32 netmask = (1 << prefix_length) - 1; const u32 gateway = GetNetworkGateway(); + // this isnt fully correct, but this will make calls to get the routing table at least return the + // gateway + if (routing_table.empty()) + routing_table = {{0, 0, 0, gateway}}; if (addr || netmask || gateway) - return DefaultInterface{addr, netmask, gateway}; + return DefaultInterface{addr, netmask, gateway, routing_table}; #else // Assume that the address that is used to access the Internet corresponds // to the default interface. @@ -265,8 +432,13 @@ static std::optional GetSystemDefaultInterface() if (iface->ifa_addr && iface->ifa_addr->sa_family == AF_INET && get_addr(iface->ifa_addr) == default_interface_address->s_addr) { + // this isnt fully correct, but this will make calls to get the routing table at least return + // the gateway + if (routing_table.empty()) + routing_table = {{0, 0, 0, get_addr(iface->ifa_dstaddr)}}; + return DefaultInterface{get_addr(iface->ifa_addr), get_addr(iface->ifa_netmask), - get_addr(iface->ifa_broadaddr)}; + get_addr(iface->ifa_broadaddr), routing_table}; } } #endif @@ -275,10 +447,13 @@ static std::optional GetSystemDefaultInterface() static DefaultInterface GetSystemDefaultInterfaceOrFallback() { - static const u32 FALLBACK_IP = inet_addr("10.0.1.30"); - static const u32 FALLBACK_NETMASK = inet_addr("255.255.255.0"); - static const u32 FALLBACK_GATEWAY = inet_addr("10.0.255.255"); - static const DefaultInterface FALLBACK_VALUES{FALLBACK_IP, FALLBACK_NETMASK, FALLBACK_GATEWAY}; + static const in_addr FALLBACK_IP = std::bit_cast(inet_addr("10.0.1.30")); + static const in_addr FALLBACK_NETMASK = std::bit_cast(inet_addr("255.255.255.0")); + static const in_addr FALLBACK_BROADCAST = std::bit_cast(inet_addr("10.0.1.255")); + static const in_addr FALLBACK_GATEWAY = std::bit_cast(inet_addr("10.0.1.1")); + static const InterfaceRouting FALLBACK_ROUTING = {.gateway = FALLBACK_GATEWAY}; + static const DefaultInterface FALLBACK_VALUES = { + FALLBACK_IP, FALLBACK_NETMASK, FALLBACK_BROADCAST, {FALLBACK_ROUTING}}; return GetSystemDefaultInterface().value_or(FALLBACK_VALUES); } @@ -611,7 +786,7 @@ IPCReply NetIPTopDevice::HandleGetPeerNameRequest(const IOCtlRequest& request) IPCReply NetIPTopDevice::HandleGetHostIDRequest(const IOCtlRequest& request) { const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback(); - const u32 host_ip = Common::swap32(interface.inet); + const u32 host_ip = ntohl(interface.inet.s_addr); INFO_LOG_FMT(IOS_NET, "IOCTL_SO_GETHOSTID = {}.{}.{}.{}", host_ip >> 24, (host_ip >> 16) & 0xFF, (host_ip >> 8) & 0xFF, host_ip & 0xFF); return IPCReply(host_ip); @@ -830,6 +1005,7 @@ IPCReply NetIPTopDevice::HandleGetInterfaceOptRequest(const IOCtlVRequest& reque auto& system = GetSystem(); auto& memory = system.GetMemory(); + const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback(); const u32 param = memory.Read_U32(request.in_vectors[0].address); const u32 param2 = memory.Read_U32(request.in_vectors[0].address + 4); const u32 param3 = memory.Read_U32(request.io_vectors[0].address); @@ -980,20 +1156,42 @@ IPCReply NetIPTopDevice::HandleGetInterfaceOptRequest(const IOCtlVRequest& reque case 0x4003: // ip addr table { - // XXX: this isn't exactly right; the buffer can be larger than 12 bytes, in which case - // SO can write 12 more bytes. + // XXX: this isn't exactly right; the buffer can be larger than 12 bytes, + // in which case, depending on some interface settings, SO can write 12 more bytes memory.Write_U32(0xC, request.io_vectors[1].address); - const DefaultInterface interface = GetSystemDefaultInterfaceOrFallback(); - memory.Write_U32(Common::swap32(interface.inet), request.io_vectors[0].address); - memory.Write_U32(Common::swap32(interface.netmask), request.io_vectors[0].address + 4); - memory.Write_U32(Common::swap32(interface.broadcast), request.io_vectors[0].address + 8); + memory.Write_U32(ntohl(interface.inet.s_addr), request.io_vectors[0].address); + memory.Write_U32(ntohl(interface.netmask.s_addr), request.io_vectors[0].address + 4); + memory.Write_U32(ntohl(interface.broadcast.s_addr), request.io_vectors[0].address + 8); break; } - case 0x4005: // hardcoded value + case 0x4005: + // get routing table size, which is almost always hardcoded to be 0x20 in IOS + // on pc its often around 0x20 too so... meh memory.Write_U32(0x20, request.io_vectors[0].address); break; + case 0x4006: // get routing table + for (InterfaceRouting route : interface.routing_table) + { + memory.Write_U32(ntohl(route.destination.s_addr), request.io_vectors[0].address + param5); + memory.Write_U32(ntohl(route.netmask.s_addr), request.io_vectors[0].address + param5 + 4); + memory.Write_U32(ntohl(route.gateway.s_addr), request.io_vectors[0].address + param5 + 8); + + // write flags. unknown what they do but when gateway was 0 bit 0 was always 0 + memory.Write_U32(route.gateway.s_addr == 0 ? 0 : 1, + request.io_vectors[0].address + param5 + 12); + + // some unknown + memory.Write_U64(0x00, request.io_vectors[0].address + param5 + 16); + param5 += 24; + if (param5 >= param4) + break; + } + + memory.Write_U32(param5, request.io_vectors[1].address); + break; + case 0x6003: // hardcoded value memory.Write_U32(0x80, request.io_vectors[0].address); break;