You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
7.3 KiB
236 lines
7.3 KiB
/* outgoing_iface.c |
|
* |
|
* Определяет IPv4-адрес и индекс/имя исходящего интерфейса, |
|
* который ОС выберет при отправке на заданный destination |
|
* после bind на 0.0.0.0 (INADDR_ANY). |
|
* |
|
* Сборка: |
|
* Linux / BSD: gcc -o out out.c |
|
* Windows (MSVC): cl out.c /link ws2_32.lib iphlpapi.lib |
|
* Windows (MinGW): gcc -o out.exe out.c -lws2_32 -liphlpapi |
|
*/ |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdint.h> |
|
|
|
/* ===================== Платформенные заголовки ===================== */ |
|
#ifdef _WIN32 |
|
# ifndef WIN32_LEAN_AND_MEAN |
|
# define WIN32_LEAN_AND_MEAN |
|
# endif |
|
# include <winsock2.h> |
|
# include <ws2tcpip.h> |
|
# include <iphlpapi.h> |
|
# pragma comment(lib, "ws2_32.lib") |
|
# pragma comment(lib, "iphlpapi.lib") |
|
|
|
typedef SOCKET socket_t; |
|
# define SOCKET_INVALID INVALID_SOCKET |
|
# define SOCKET_ERRNO WSAGetLastError() |
|
|
|
/* На Windows нет IFNAMSIZ; берём с запасом */ |
|
# ifndef IF_NAMESIZE |
|
# define IF_NAMESIZE 256 |
|
# endif |
|
|
|
#else /* Linux, FreeBSD, OpenBSD, NetBSD, macOS, … */ |
|
# include <unistd.h> |
|
# include <sys/types.h> |
|
# include <sys/socket.h> |
|
# include <netinet/in.h> |
|
# include <arpa/inet.h> |
|
# include <ifaddrs.h> |
|
# include <net/if.h> |
|
# include <errno.h> |
|
|
|
typedef int socket_t; |
|
# define SOCKET_INVALID (-1) |
|
# define SOCKET_ERRNO errno |
|
|
|
# ifndef IF_NAMESIZE |
|
# define IF_NAMESIZE IFNAMSIZ /* обычно 16 */ |
|
# endif |
|
#endif |
|
|
|
/* ===================== Структура результата ===================== */ |
|
typedef struct { |
|
char ip[INET_ADDRSTRLEN]; /* IP строкой, например "192.168.1.5" */ |
|
char name[IF_NAMESIZE]; /* имя интерфейса: "eth0", "em0", "Wi-Fi" … */ |
|
uint32_t ifindex; /* числовой ifindex (if_nametoindex) */ |
|
} iface_info_t; |
|
|
|
/* ===================== Утилиты ===================== */ |
|
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; |
|
} |
|
|
|
static void socket_close(socket_t s) |
|
{ |
|
#ifdef _WIN32 |
|
if (s != SOCKET_INVALID) |
|
closesocket(s); |
|
#else |
|
if (s >= 0) |
|
close(s); |
|
#endif |
|
} |
|
|
|
/* ===================== Основная функция ===================== */ |
|
/* |
|
* dst_ip — адрес назначения (например, "8.8.8.8" или IP вашего VPN-сервера) |
|
* dst_port — любой открытый порт (например, 53 или 123), реальных пакетов не уйдёт |
|
* info — заполняемая структура |
|
* |
|
* Возвращает 0 при успехе, -1 при ошибке. |
|
*/ |
|
int get_outgoing_iface(const char *dst_ip, int dst_port, iface_info_t *info) |
|
{ |
|
socket_t sock = SOCKET_INVALID; |
|
int ret = -1; |
|
struct sockaddr_in dst, src; |
|
socklen_t src_len = sizeof(src); |
|
|
|
memset(info, 0, sizeof(*info)); |
|
if (net_init() != 0) |
|
return -1; |
|
|
|
/* 1. UDP-сокет */ |
|
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
|
if (sock == SOCKET_INVALID) |
|
return -1; |
|
|
|
/* 2. Bind на 0.0.0.0 — именно так, как в вопросе */ |
|
memset(&dst, 0, sizeof(dst)); |
|
dst.sin_family = AF_INET; |
|
dst.sin_addr.s_addr = INADDR_ANY; /* 0.0.0.0 */ |
|
dst.sin_port = 0; /* система выберет порт */ |
|
|
|
if (bind(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0) |
|
goto end; |
|
|
|
/* 3. "Соединяемся" с destination. |
|
* Для UDP это НЕ шлёт реальных пакетов, но ядро просчитывает |
|
* маршрут и фиксирует выбранный source IP в сокете. */ |
|
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; |
|
|
|
/* 4. Забираем выбранный локальный адрес */ |
|
if (getsockname(sock, (struct sockaddr *)&src, &src_len) != 0) |
|
goto end; |
|
if (!inet_ntop(AF_INET, &src.sin_addr, info->ip, sizeof(info->ip))) |
|
goto end; |
|
|
|
/* 5. По source IP ищем интерфейс в системном списке */ |
|
#ifdef _WIN32 |
|
{ |
|
ULONG size = 0; |
|
DWORD rv; |
|
PIP_ADAPTER_ADDRESSES adapters = NULL, a; |
|
PIP_ADAPTER_UNICAST_ADDRESS u; |
|
|
|
/* первый вызов — узнать размер буфера */ |
|
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, |
|
NULL, NULL, &size); |
|
if (rv != ERROR_BUFFER_OVERFLOW) |
|
goto end; |
|
|
|
adapters = (PIP_ADAPTER_ADDRESSES)malloc(size); |
|
if (!adapters) |
|
goto end; |
|
|
|
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, |
|
NULL, adapters, &size); |
|
if (rv != NO_ERROR) { |
|
free(adapters); |
|
goto end; |
|
} |
|
|
|
for (a = adapters; a != NULL; a = a->Next) { |
|
for (u = a->FirstUnicastAddress; u; u = u->Next) { |
|
struct sockaddr_in *ua = |
|
(struct sockaddr_in *)u->Address.lpSockaddr; |
|
if (ua->sin_addr.s_addr == src.sin_addr.s_addr) { |
|
info->ifindex = (uint32_t)a->IfIndex; |
|
/* FriendlyName — WCHAR, приводим к UTF-8 */ |
|
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, |
|
info->name, IF_NAMESIZE, |
|
NULL, NULL); |
|
ret = 0; |
|
goto win_done; |
|
} |
|
} |
|
} |
|
win_done: |
|
free(adapters); |
|
} |
|
|
|
#else /* Linux / BSD / macOS */ |
|
{ |
|
struct ifaddrs *ifap, *ifa; |
|
|
|
if (getifaddrs(&ifap) != 0) |
|
goto end; |
|
|
|
for (ifa = ifap; ifa; ifa = ifa->ifa_next) { |
|
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) |
|
continue; |
|
|
|
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; |
|
if (addr->sin_addr.s_addr == src.sin_addr.s_addr) { |
|
strncpy(info->name, ifa->ifa_name, IF_NAMESIZE - 1); |
|
info->ifindex = if_nametoindex(ifa->ifa_name); |
|
ret = 0; |
|
break; |
|
} |
|
} |
|
freeifaddrs(ifap); |
|
} |
|
#endif |
|
|
|
end: |
|
socket_close(sock); |
|
return ret; |
|
} |
|
|
|
/* ===================== Пример использования ===================== |
|
int main(int argc, char **argv) |
|
{ |
|
iface_info_t info; |
|
const char *dst = (argc > 1) ? argv[1] : "8.8.8.8"; |
|
int port = (argc > 2) ? atoi(argv[2]) : 53; |
|
|
|
if (get_outgoing_iface(dst, port, &info) == 0) { |
|
printf("Destination : %s:%d\n", dst, port); |
|
printf("Source IP : %s\n", info.ip); |
|
printf("Iface name : %s\n", info.name); |
|
printf("Iface index : %u\n", info.ifindex); |
|
return 0; |
|
} |
|
|
|
fprintf(stderr, "Failed to determine outgoing interface (errno=%d)\n", |
|
#ifdef _WIN32 |
|
(int)WSAGetLastError() |
|
#else |
|
errno |
|
#endif |
|
); |
|
return 1; |
|
} |
|
*/ |