Browse Source

Replace buggy get_default_route_ip() with get_outgoing_local_ip() for reliable local IP detection

- Add lib/getmyip.c/h with get_outgoing_local_ip() using UDP connect+getsockname
- Update etcp_socket_add() to use new function for 0.0.0.0 and :: bindings
- Support both IPv4 (8.8.8.8) and IPv6 (2001:4860:4860::8888) auto-detection
- Remove broken get_default_route_ip() from platform_compat (it returned gateway IP, not local IP)
- Keep get_interface_ip_by_index() for explicit interface bindings
- Update Makefile.am to include new getmyip module
congestion
Evgeny 5 months ago
parent
commit
6aedf5fe91
  1. 240
      lib/getmyip.c
  2. 58
      lib/getmyip.h
  3. 90
      lib/platform_compat.c
  4. 4
      lib/platform_compat.h
  5. 76
      src/etcp_connections.c

240
lib/getmyip.c

@ -1,204 +1,102 @@
/** /**
* getmyip.c - Get local IP for outgoing connections * getmyip.c - Определение локального IP-адреса для исходящих соединений
* Определяет локальный IP адрес, который будет использоваться *
* при отправке пакетов на заданный destination. * Использует временный UDP-сокет с connect() для определения
* локального IP, который ОС выберет для заданного маршрута.
*/ */
#include "getmyip.h" #include "getmyip.h"
#include "platform_compat.h" #include "socket_compat.h"
#include <stdio.h> #include "debug_config.h"
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32 #ifdef _WIN32
# include <winsock2.h> #include <winsock2.h>
# include <ws2tcpip.h> #include <ws2tcpip.h>
# pragma comment(lib, "ws2_32.lib")
typedef SOCKET socket_t;
# define SOCKET_INVALID INVALID_SOCKET
#else #else
# include <unistd.h> #include <unistd.h>
# include <ifaddrs.h> #include <arpa/inet.h>
# include <net/if.h> #include <errno.h>
typedef int socket_t;
# define SOCKET_INVALID (-1)
#endif #endif
static void socket_close(socket_t s) int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
struct sockaddr_storage *result)
{ {
#ifdef _WIN32 if (!local_addr || !remote_addr || !result) {
if (s != SOCKET_INVALID) DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument");
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; return -1;
}
if (family != AF_INET && family != AF_INET6) int family = local_addr->ss_family;
if (family != AF_INET && family != AF_INET6) {
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: unsupported family %d", family);
return -1; return -1;
}
// Create UDP socket if (remote_addr->ss_family != family) {
sock = socket(family, SOCK_DGRAM, IPPROTO_UDP); DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch");
if (sock == SOCKET_INVALID)
return -1; 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) socket_t sock = socket_create_udp(family);
if (family == AF_INET) { if (sock == SOCKET_INVALID) {
struct sockaddr_in dst; DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed");
memset(&dst, 0, sizeof(dst)); return -1;
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 // Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
if (getsockname(sock, (struct sockaddr*)&src, &src_len) != 0) struct sockaddr_storage bind_addr;
goto end; memcpy(&bind_addr, local_addr, sizeof(bind_addr));
// Copy result (only IP, port = 0)
memcpy(result, &src, sizeof(src));
if (family == AF_INET) { if (family == AF_INET) {
((struct sockaddr_in*)result)->sin_port = 0; ((struct sockaddr_in *)&bind_addr)->sin_port = 0;
} else { } else {
((struct sockaddr_in6*)result)->sin6_port = 0; ((struct sockaddr_in6 *)&bind_addr)->sin6_port = 0;
} }
ret = 0; socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (bind(sock, (struct sockaddr *)&bind_addr, bind_len) != 0) {
end: int err = socket_get_error();
socket_close(sock); DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err));
return ret; socket_close_wrapper(sock);
} return -1;
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) // Connect к remote_addr — ядро вычислит маршрут и выберет source IP
socklen_t remote_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
if (connect(sock, (struct sockaddr *)remote_addr, remote_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1; return -1;
}
memset(result, 0, sizeof(*result)); // Получаем выбранный локальный адрес
struct sockaddr_storage local;
if (getifaddrs(&ifaddr) == -1) socklen_t local_len = sizeof(local);
if (getsockname(sock, (struct sockaddr *)&local, &local_len) != 0) {
int err = socket_get_error();
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err));
socket_close_wrapper(sock);
return -1; return -1;
}
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { socket_close_wrapper(sock);
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; // Копируем только IP (без порта)
struct sockaddr_in *res = (struct sockaddr_in*)result; memset(result, 0, sizeof(*result));
res->sin_family = AF_INET; if (family == AF_INET) {
res->sin_addr.s_addr = addr->sin_addr.s_addr; struct sockaddr_in *sin = (struct sockaddr_in *)result;
res->sin_port = 0; struct sockaddr_in *src = (struct sockaddr_in *)&local;
ret = 0; sin->sin_family = AF_INET;
break; sin->sin_addr = src->sin_addr;
sin->sin_port = 0;
} else {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)result;
struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&local;
sin6->sin6_family = AF_INET6;
sin6->sin6_addr = src6->sin6_addr;
sin6->sin6_port = 0;
} }
freeifaddrs(ifaddr); return 0;
return ret;
}
#else
int get_local_ip_by_index(uint32_t ifindex, struct sockaddr_storage* result)
{
(void)ifindex;
(void)result;
return -1;
} }
#endif

58
lib/getmyip.h

@ -1,14 +1,14 @@
/** /**
* getmyip.h - Get local IP for outgoing connections * getmyip.h - Определение локального IP-адреса для исходящих соединений
* Определяет локальный IP адрес, который будет использоваться *
* при отправке пакетов на заданный destination. * Определяет локальный IP, который ОС выберет для отправки
* на заданный удаленный адрес через заданный локальный адрес.
*/ */
#ifndef GETMYIP_H #ifndef GETMYIP_H
#define GETMYIP_H #define GETMYIP_H
#include <stdint.h> #include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
@ -17,49 +17,23 @@ extern "C" {
#endif #endif
/** /**
* Get local IPv4 or IPv6 address for outgoing connection * Определяет локальный IP-адрес, который ОС выберет для отправки
* * на заданный удаленный адрес через заданный локальный адрес.
* @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 * Создает временный UDP-сокет, выполняет bind на local_addr,
* @param result - Output: local IP address * connect к remote_addr и получает выбранный локальный IP через getsockname.
* *
* Returns 0 on success, -1 on error * @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения)
* @param remote_addr Удаленный адрес (для определения маршрута)
* @param result Заполняемый локальный IP (порт = 0)
* @return 0 при успехе, -1 при ошибке
*/ */
int get_default_local_ip(int family, struct sockaddr_storage* result); int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
const struct sockaddr_storage *remote_addr,
/** 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 #ifdef __cplusplus
} }
#endif #endif
#endif /* GETMYIP_H */ #endif

90
lib/platform_compat.c

@ -47,96 +47,6 @@ int random_bytes(uint8_t *buffer, size_t len) {
#endif #endif
} }
#ifndef _WIN32
static uint32_t get_default_route_ip_linux(void) {
FILE *f = fopen("/proc/net/route", "r");
if (!f) return 0;
char line[256];
char *ifname = NULL;
unsigned int dest, gw;
if (!fgets(line, sizeof(line), f)) {
fclose(f);
return 0;
}
while (fgets(line, sizeof(line), f)) {
int items = sscanf(line, "%ms %x %x", &ifname, &dest, &gw);
if (items != 3) {
free(ifname);
continue;
}
if (dest == 0) {
free(ifname);
break;
}
free(ifname);
ifname = NULL;
}
fclose(f);
if (!ifname) return 0;
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr) == -1) {
free(ifname);
return 0;
}
uint32_t result = 0;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr == NULL) continue;
if (ifa->ifa_name == NULL) continue;
if (strcmp(ifa->ifa_name, ifname) != 0) continue;
if (ifa->ifa_addr->sa_family != AF_INET) continue;
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
result = addr->sin_addr.s_addr;
break;
}
freeifaddrs(ifaddr);
free(ifname);
return result;
}
#else
static uint32_t get_default_route_ip_windows(void) {
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == INVALID_SOCKET) return 0;
struct sockaddr_in remote;
remote.sin_family = AF_INET;
remote.sin_port = htons(53);
remote.sin_addr.s_addr = inet_addr("8.8.8.8");
if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
struct sockaddr_in local;
int len = sizeof(local);
if (getsockname(s, (struct sockaddr *)&local, &len) == SOCKET_ERROR) {
closesocket(s);
return 0;
}
closesocket(s);
return local.sin_addr.s_addr;
}
#endif
uint32_t get_default_route_ip(void) {
#ifdef _WIN32
return get_default_route_ip_windows();
#else
return get_default_route_ip_linux();
#endif
}
#ifndef _WIN32 #ifndef _WIN32
uint32_t get_interface_ip_by_index(uint32_t netif_index) { uint32_t get_interface_ip_by_index(uint32_t netif_index) {
struct ifaddrs *ifaddr, *ifa; struct ifaddrs *ifaddr, *ifa;

4
lib/platform_compat.h

@ -155,10 +155,6 @@
// Returns 0 on success, -1 on error // Returns 0 on success, -1 on error
int random_bytes(uint8_t *buffer, size_t len); int random_bytes(uint8_t *buffer, size_t len);
// Get IPv4 address of interface with default route (0.0.0.0 destination)
// Returns IPv4 address in network byte order, 0 on error
uint32_t get_default_route_ip(void);
// Get IPv4 address of interface by its index (ifindex) // Get IPv4 address of interface by its index (ifindex)
// Returns IPv4 address in network byte order, 0 on error // Returns IPv4 address in network byte order, 0 on error
uint32_t get_interface_ip_by_index(uint32_t netif_index); uint32_t get_interface_ip_by_index(uint32_t netif_index);

76
src/etcp_connections.c

@ -563,37 +563,55 @@ struct ETCP_SOCKET* etcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_S
} }
// Определяем interface_addr: IP интерфейса или из конфига // Определяем interface_addr: IP интерфейса или из конфига
memset(&e_sock->interface_addr, 0, sizeof(e_sock->interface_addr)); memset(&e_sock->interface_addr, 0, sizeof(e_sock->interface_addr));
if (netif_index > 0) { if (netif_index > 0 && ip && ip->ss_family == AF_INET) {
struct sockaddr_storage if_addr; uint32_t if_ip = get_interface_ip_by_index(netif_index);
if (get_local_ip_by_index(netif_index, &if_addr) == 0) { if (if_ip != 0) {
memcpy(&e_sock->interface_addr, &if_addr, sizeof(struct sockaddr_storage)); struct sockaddr_in* sin = (struct sockaddr_in*)&e_sock->interface_addr;
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = if_ip;
} }
} else if (ip && ip->ss_family == AF_INET) { } else if (ip) {
struct sockaddr_in* sin = (struct sockaddr_in*)ip; if (ip->ss_family == AF_INET) {
if (sin->sin_addr.s_addr == 0) { struct sockaddr_in* sin = (struct sockaddr_in*)ip;
struct sockaddr_storage def_addr; if (sin->sin_addr.s_addr == 0) {
if (get_default_local_ip(AF_INET, &def_addr) == 0) { // 0.0.0.0 — определяем через default route
memcpy(&e_sock->interface_addr, &def_addr, sizeof(struct sockaddr_storage)); struct sockaddr_storage remote;
memset(&remote, 0, sizeof(remote));
struct sockaddr_in* rem_sin = (struct sockaddr_in*)&remote;
rem_sin->sin_family = AF_INET;
rem_sin->sin_port = htons(53);
inet_pton(AF_INET, "8.8.8.8", &rem_sin->sin_addr);
if (get_outgoing_local_ip(ip, &remote, &e_sock->interface_addr) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "Failed to detect outgoing local IP for socket %s", e_sock->name);
}
} }
} } else if (ip->ss_family == AF_INET6) {
} else if (ip && ip->ss_family == AF_INET6) { struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ip;
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ip; if (memcmp(&sin6->sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0) {
if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { // :: — определяем через default route
struct sockaddr_storage def_addr; struct sockaddr_storage remote;
if (get_default_local_ip(AF_INET6, &def_addr) == 0) { memset(&remote, 0, sizeof(remote));
memcpy(&e_sock->interface_addr, &def_addr, sizeof(struct sockaddr_storage)); struct sockaddr_in6* rem_sin6 = (struct sockaddr_in6*)&remote;
rem_sin6->sin6_family = AF_INET6;
rem_sin6->sin6_port = htons(53);
inet_pton(AF_INET6, "2001:4860:4860::8888", &rem_sin6->sin6_addr);
if (get_outgoing_local_ip(ip, &remote, &e_sock->interface_addr) != 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONNECTION, "Failed to detect outgoing local IPv6 for socket %s", e_sock->name);
}
} }
} }
} }
if (e_sock->interface_addr.ss_family == 0) { if (e_sock->interface_addr.ss_family == 0) {
// Не удалось определить автоматически — используем адрес из конфига
if (ip) memcpy(&e_sock->interface_addr, ip, sizeof(struct sockaddr_storage)); if (ip) memcpy(&e_sock->interface_addr, ip, sizeof(struct sockaddr_storage));
} else { }
// скопируем порт из конфига в interface_addr // Копируем порт из конфига в interface_addr
if (ip && ip->ss_family == AF_INET) { if (ip && e_sock->interface_addr.ss_family == ip->ss_family) {
if (ip->ss_family == AF_INET) {
struct sockaddr_in* sin_cfg = (struct sockaddr_in*)ip; struct sockaddr_in* sin_cfg = (struct sockaddr_in*)ip;
struct sockaddr_in* sin_if = (struct sockaddr_in*)&e_sock->interface_addr; struct sockaddr_in* sin_if = (struct sockaddr_in*)&e_sock->interface_addr;
sin_if->sin_port = sin_cfg->sin_port; sin_if->sin_port = sin_cfg->sin_port;
} else if (ip && ip->ss_family == AF_INET6) { } else if (ip->ss_family == AF_INET6) {
struct sockaddr_in6* sin6_cfg = (struct sockaddr_in6*)ip; struct sockaddr_in6* sin6_cfg = (struct sockaddr_in6*)ip;
struct sockaddr_in6* sin6_if = (struct sockaddr_in6*)&e_sock->interface_addr; struct sockaddr_in6* sin6_if = (struct sockaddr_in6*)&e_sock->interface_addr;
sin6_if->sin6_port = sin6_cfg->sin6_port; sin6_if->sin6_port = sin6_cfg->sin6_port;
@ -1722,10 +1740,18 @@ int init_sockets(struct UTUN_INSTANCE* instance) {
if (server->type == CFG_SERVER_TYPE_PUBLIC) { if (server->type == CFG_SERVER_TYPE_PUBLIC) {
struct sockaddr_in* sin = (struct sockaddr_in*)&server->ip; struct sockaddr_in* sin = (struct sockaddr_in*)&server->ip;
if (sin->sin_addr.s_addr == 0) { if (sin->sin_addr.s_addr == 0) {
struct sockaddr_storage def_addr; struct sockaddr_storage remote;
if (get_default_local_ip(AF_INET, &def_addr) == 0) { memset(&remote, 0, sizeof(remote));
default_ip = ((struct sockaddr_in*)&def_addr)->sin_addr.s_addr; struct sockaddr_in* rem_sin = (struct sockaddr_in*)&remote;
} else { rem_sin->sin_family = AF_INET;
rem_sin->sin_port = htons(53);
inet_pton(AF_INET, "8.8.8.8", &rem_sin->sin_addr);
struct sockaddr_storage local;
if (get_outgoing_local_ip(&server->ip, &remote, &local) == 0) {
struct sockaddr_in* local_sin = (struct sockaddr_in*)&local;
default_ip = local_sin->sin_addr.s_addr;
}
if (default_ip == 0) {
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Failed to detect default route IP for server %s", server->name); DEBUG_WARN(DEBUG_CATEGORY_ETCP, "Failed to detect default route IP for server %s", server->name);
} }
} }

Loading…
Cancel
Save