From 724a9ff5c780b26c9592f444be844c9bb870b51f Mon Sep 17 00:00:00 2001 From: Evgeny Date: Fri, 24 Apr 2026 13:58:24 +0300 Subject: [PATCH] Add getmyip module for reliable local IP detection - New lib/getmyip.c/h with get_outgoing_ip(), get_default_local_ip(), get_local_ip_by_index() - Uses UDP connect + getsockname instead of /proc/net/route parsing - Supports both IPv4 and IPv6 - Updated etcp_socket_add() to use new functions for interface_addr detection --- lib/Makefile.am | 2 + lib/getmyip.c | 204 +++++++++++++++++++++++++++++++++++++++++ lib/getmyip.h | 65 +++++++++++++ src/etcp_connections.c | 37 +++++--- 4 files changed, 295 insertions(+), 13 deletions(-) create mode 100644 lib/getmyip.c create mode 100644 lib/getmyip.h diff --git a/lib/Makefile.am b/lib/Makefile.am index f001f4e0..ad2431de 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -17,6 +17,8 @@ libuasync_a_SOURCES = \ socket_compat.h \ platform_compat.c \ platform_compat.h \ + getmyip.c \ + getmyip.h \ swm_min.c \ swm_min.h \ mem.c \ diff --git a/lib/getmyip.c b/lib/getmyip.c new file mode 100644 index 00000000..9e73bac8 --- /dev/null +++ b/lib/getmyip.c @@ -0,0 +1,204 @@ +/** + * getmyip.c - Get local IP for outgoing connections + * Определяет локальный IP адрес, который будет использоваться + * при отправке пакетов на заданный destination. + */ + +#include "getmyip.h" +#include "platform_compat.h" +#include +#include +#include + +#ifdef _WIN32 +# include +# include +# pragma comment(lib, "ws2_32.lib") + typedef SOCKET socket_t; +# define SOCKET_INVALID INVALID_SOCKET +#else +# include +# include +# include + typedef int socket_t; +# define SOCKET_INVALID (-1) +#endif + +static void socket_close(socket_t s) +{ +#ifdef _WIN32 + if (s != SOCKET_INVALID) + closesocket(s); +#else + if (s >= 0) + close(s); +#endif +} + +static int net_init(void) +{ +#ifdef _WIN32 + static int done = 0; + if (!done) { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) + return -1; + done = 1; + } +#endif + return 0; +} + +int get_outgoing_ip(const char* dst_ip, int dst_port, int family, + const struct sockaddr_storage* local_ip, + struct sockaddr_storage* result) +{ + socket_t sock = SOCKET_INVALID; + int ret = -1; + struct sockaddr_storage src; + socklen_t src_len = sizeof(src); + + if (!dst_ip || !result) + return -1; + + memset(result, 0, sizeof(*result)); + memset(&src, 0, sizeof(src)); + + if (net_init() != 0) + return -1; + + if (family != AF_INET && family != AF_INET6) + return -1; + + // Create UDP socket + sock = socket(family, SOCK_DGRAM, IPPROTO_UDP); + if (sock == SOCKET_INVALID) + return -1; + + // Bind to local address if specified + if (local_ip && local_ip->ss_family != 0) { + socklen_t local_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + if (bind(sock, (struct sockaddr*)local_ip, local_len) != 0) { + socket_close(sock); + return -1; + } + } else { + // Bind to INADDR_ANY + struct sockaddr_in any4; + struct sockaddr_in6 any6; + if (family == AF_INET) { + memset(&any4, 0, sizeof(any4)); + any4.sin_family = AF_INET; + any4.sin_addr.s_addr = INADDR_ANY; + any4.sin_port = 0; + if (bind(sock, (struct sockaddr*)&any4, sizeof(any4)) != 0) { + socket_close(sock); + return -1; + } + } else { + memset(&any6, 0, sizeof(any6)); + any6.sin6_family = AF_INET6; + any6.sin6_addr = in6addr_any; + any6.sin6_port = 0; + if (bind(sock, (struct sockaddr*)&any6, sizeof(any6)) != 0) { + socket_close(sock); + return -1; + } + } + } + + // Connect to destination (kernel calculates route) + if (family == AF_INET) { + struct sockaddr_in dst; + memset(&dst, 0, sizeof(dst)); + dst.sin_family = AF_INET; + dst.sin_port = htons((uint16_t)dst_port); + if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1) + goto end; + if (connect(sock, (struct sockaddr*)&dst, sizeof(dst)) != 0) + goto end; + } else { + struct sockaddr_in6 dst; + memset(&dst, 0, sizeof(dst)); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons((uint16_t)dst_port); + if (inet_pton(AF_INET6, dst_ip, &dst.sin6_addr) != 1) + goto end; + if (connect(sock, (struct sockaddr*)&dst, sizeof(dst)) != 0) + goto end; + } + + // Get local address + if (getsockname(sock, (struct sockaddr*)&src, &src_len) != 0) + goto end; + + // Copy result (only IP, port = 0) + memcpy(result, &src, sizeof(src)); + if (family == AF_INET) { + ((struct sockaddr_in*)result)->sin_port = 0; + } else { + ((struct sockaddr_in6*)result)->sin6_port = 0; + } + + ret = 0; + +end: + socket_close(sock); + return ret; +} + +int get_default_local_ip(int family, struct sockaddr_storage* result) +{ + if (family == AF_INET) { + return get_outgoing_ip("8.8.8.8", 53, family, NULL, result); + } else if (family == AF_INET6) { + return get_outgoing_ip("2001:4860:4860::8888", 53, family, NULL, result); + } + return -1; +} + +#ifndef _WIN32 +int get_local_ip_by_index(uint32_t ifindex, struct sockaddr_storage* result) +{ + struct ifaddrs *ifaddr, *ifa; + int ret = -1; + + if (!result) + return -1; + + memset(result, 0, sizeof(*result)); + + if (getifaddrs(&ifaddr) == -1) + return -1; + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + if (ifa->ifa_name == NULL) + continue; + unsigned int idx = if_nametoindex(ifa->ifa_name); + if (idx != ifindex) + continue; + if (ifa->ifa_addr->sa_family != AF_INET) + continue; + + struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; + struct sockaddr_in *res = (struct sockaddr_in*)result; + res->sin_family = AF_INET; + res->sin_addr.s_addr = addr->sin_addr.s_addr; + res->sin_port = 0; + ret = 0; + break; + } + + freeifaddrs(ifaddr); + return ret; +} +#else +int get_local_ip_by_index(uint32_t ifindex, struct sockaddr_storage* result) +{ + (void)ifindex; + (void)result; + return -1; +} +#endif \ No newline at end of file diff --git a/lib/getmyip.h b/lib/getmyip.h new file mode 100644 index 00000000..9f5e19b7 --- /dev/null +++ b/lib/getmyip.h @@ -0,0 +1,65 @@ +/** + * getmyip.h - Get local IP for outgoing connections + * Определяет локальный IP адрес, который будет использоваться + * при отправке пакетов на заданный destination. + */ + +#ifndef GETMYIP_H +#define GETMYIP_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Get local IPv4 or IPv6 address for outgoing connection + * + * @param dst_ip - Target IP (e.g., "8.8.8.8" or "2001:4860:4860::8888") + * @param dst_port - Target port (any open port, e.g., 53) + * @param family - AF_INET or AF_INET6 + * @param local_ip - If not NULL and ss_family != 0, bind to this local address first + * @param result - Output: local IP address (port will be 0) + * + * Returns 0 on success, -1 on error + * + * Algorithm: + * 1. Create UDP socket + * 2. If local_ip specified, bind to it (for binding to specific interface) + * 3. Connect to dst_ip:dst_port (kernel calculates route, no packets sent) + * 4. Use getsockname() to get actual local IP + * 5. Return result (only IP, port = 0) + */ +int get_outgoing_ip(const char* dst_ip, int dst_port, int family, + const struct sockaddr_storage* local_ip, + struct sockaddr_storage* result); + +/** + * Simplified version - get local IP for any destination (default route) + * + * @param family - AF_INET or AF_INET6 + * @param result - Output: local IP address + * + * Returns 0 on success, -1 on error + */ +int get_default_local_ip(int family, struct sockaddr_storage* result); + +/** + * Get local IPv4 address by interface index + * + * @param ifindex - Interface index (from if_nametoindex) + * @param result - Output: local IPv4 address + * + * Returns 0 on success, -1 on error + */ +int get_local_ip_by_index(uint32_t ifindex, struct sockaddr_storage* result); + +#ifdef __cplusplus +} +#endif + +#endif /* GETMYIP_H */ \ No newline at end of file diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 38395b39..508b4439 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -1,6 +1,7 @@ #include "etcp_connections.h" #include "../lib/socket_compat.h" #include "../lib/platform_compat.h" +#include "../lib/getmyip.h" #ifndef _WIN32 #include #include @@ -563,24 +564,28 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S // Определяем interface_addr: IP интерфейса или из конфига memset(&e_sock->interface_addr, 0, sizeof(e_sock->interface_addr)); if (netif_index > 0) { - uint32_t if_ip = get_interface_ip_by_index(netif_index); - if (if_ip != 0) { - struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->interface_addr; - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = if_ip; + struct sockaddr_storage if_addr; + if (get_local_ip_by_index(netif_index, &if_addr) == 0) { + memcpy(&e_sock->interface_addr, &if_addr, sizeof(struct sockaddr_storage)); } } else if (ip && ip->ss_family == AF_INET) { struct sockaddr_in* sin = (struct sockaddr_in*)ip; if (sin->sin_addr.s_addr == 0) { - uint32_t def_ip = get_default_route_ip(); - if (def_ip != 0) { - struct sockaddr_in* isin = (struct sockaddr_in*)&e_sock->interface_addr; - isin->sin_family = AF_INET; - isin->sin_addr.s_addr = def_ip; + struct sockaddr_storage def_addr; + if (get_default_local_ip(AF_INET, &def_addr) == 0) { + memcpy(&e_sock->interface_addr, &def_addr, sizeof(struct sockaddr_storage)); + } + } + } else if (ip && ip->ss_family == AF_INET6) { + struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ip; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + struct sockaddr_storage def_addr; + if (get_default_local_ip(AF_INET6, &def_addr) == 0) { + memcpy(&e_sock->interface_addr, &def_addr, sizeof(struct sockaddr_storage)); } } } - if (((struct sockaddr_in*)&e_sock->interface_addr)->sin_addr.s_addr == 0) { + if (e_sock->interface_addr.ss_family == 0) { if (ip) memcpy(&e_sock->interface_addr, ip, sizeof(struct sockaddr_storage)); } else { // скопируем порт из конфига в interface_addr @@ -588,6 +593,10 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S struct sockaddr_in* sin_cfg = (struct sockaddr_in*)ip; struct sockaddr_in* sin_if = (struct sockaddr_in*)&e_sock->interface_addr; sin_if->sin_port = sin_cfg->sin_port; + } else if (ip && ip->ss_family == AF_INET6) { + struct sockaddr_in6* sin6_cfg = (struct sockaddr_in6*)ip; + struct sockaddr_in6* sin6_if = (struct sockaddr_in6*)&e_sock->interface_addr; + sin6_if->sin6_port = sin6_cfg->sin6_port; } } @@ -1713,8 +1722,10 @@ int init_sockets(struct UTUN_INSTANCE* instance) { if (server->type == CFG_SERVER_TYPE_PUBLIC) { struct sockaddr_in* sin = (struct sockaddr_in*)&server->ip; if (sin->sin_addr.s_addr == 0) { - default_ip = get_default_route_ip(); - if (default_ip == 0) { + struct sockaddr_storage def_addr; + if (get_default_local_ip(AF_INET, &def_addr) == 0) { + default_ip = ((struct sockaddr_in*)&def_addr)->sin_addr.s_addr; + } else { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Failed to detect default route IP for server %s", server->name); } }