Browse Source
- 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 modulecongestion
5 changed files with 136 additions and 332 deletions
@ -1,204 +1,102 @@
|
||||
/**
|
||||
* getmyip.c - Get local IP for outgoing connections |
||||
* Определяет локальный IP адрес, который будет использоваться |
||||
* при отправке пакетов на заданный destination. |
||||
* getmyip.c - Определение локального IP-адреса для исходящих соединений |
||||
* |
||||
* Использует временный UDP-сокет с connect() для определения |
||||
* локального IP, который ОС выберет для заданного маршрута. |
||||
*/ |
||||
|
||||
#include "getmyip.h" |
||||
#include "platform_compat.h" |
||||
#include <stdio.h> |
||||
#include <stdlib.h> |
||||
#include <string.h> |
||||
#include "socket_compat.h" |
||||
#include "debug_config.h" |
||||
|
||||
#ifdef _WIN32 |
||||
# include <winsock2.h> |
||||
# include <ws2tcpip.h> |
||||
# pragma comment(lib, "ws2_32.lib") |
||||
typedef SOCKET socket_t; |
||||
# define SOCKET_INVALID INVALID_SOCKET |
||||
#include <winsock2.h> |
||||
#include <ws2tcpip.h> |
||||
#else |
||||
# include <unistd.h> |
||||
# include <ifaddrs.h> |
||||
# include <net/if.h> |
||||
typedef int socket_t; |
||||
# define SOCKET_INVALID (-1) |
||||
#include <unistd.h> |
||||
#include <arpa/inet.h> |
||||
#include <errno.h> |
||||
#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 (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) |
||||
if (!local_addr || !remote_addr || !result) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument"); |
||||
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; |
||||
} |
||||
|
||||
// Create UDP socket
|
||||
sock = socket(family, SOCK_DGRAM, IPPROTO_UDP); |
||||
if (sock == SOCKET_INVALID) |
||||
if (remote_addr->ss_family != family) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch"); |
||||
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; |
||||
socket_t sock = socket_create_udp(family); |
||||
if (sock == SOCKET_INVALID) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed"); |
||||
return -1; |
||||
} |
||||
|
||||
// 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)); |
||||
// Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
|
||||
struct sockaddr_storage bind_addr; |
||||
memcpy(&bind_addr, local_addr, sizeof(bind_addr)); |
||||
if (family == AF_INET) { |
||||
((struct sockaddr_in*)result)->sin_port = 0; |
||||
((struct sockaddr_in *)&bind_addr)->sin_port = 0; |
||||
} else { |
||||
((struct sockaddr_in6*)result)->sin6_port = 0; |
||||
((struct sockaddr_in6 *)&bind_addr)->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); |
||||
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) { |
||||
int err = socket_get_error(); |
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err)); |
||||
socket_close_wrapper(sock); |
||||
return -1; |
||||
} |
||||
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; |
||||
} |
||||
|
||||
memset(result, 0, sizeof(*result)); |
||||
|
||||
if (getifaddrs(&ifaddr) == -1) |
||||
// Получаем выбранный локальный адрес
|
||||
struct sockaddr_storage local; |
||||
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; |
||||
} |
||||
|
||||
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; |
||||
socket_close_wrapper(sock); |
||||
|
||||
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; |
||||
// Копируем только IP (без порта)
|
||||
memset(result, 0, sizeof(*result)); |
||||
if (family == AF_INET) { |
||||
struct sockaddr_in *sin = (struct sockaddr_in *)result; |
||||
struct sockaddr_in *src = (struct sockaddr_in *)&local; |
||||
sin->sin_family = AF_INET; |
||||
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 ret; |
||||
} |
||||
#else |
||||
int get_local_ip_by_index(uint32_t ifindex, struct sockaddr_storage* result) |
||||
{ |
||||
(void)ifindex; |
||||
(void)result; |
||||
return -1; |
||||
return 0; |
||||
} |
||||
#endif |
||||
Loading…
Reference in new issue