/* 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 #include #include #include /* ===================== Платформенные заголовки ===================== */ #ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # endif # include # include # include # 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 # include # include # include # include # include # include # include 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; } */