Windows 系統下怎麼獲取 UDP 本機地址

2023-03-21 18:01:36

Windows 系統下怎麼獲取 UDP 本機地址

我們知道 UDP 獲取遠端地址非常簡單,通常介面 recvfrom 就可以直接獲取到遠端的地址和埠;如果獲取 UDP 的本機地址就需要點特殊處理了,特別是本機有多網路卡的情況下,我們想知道是那個 IP 接收的 UDP 包。對於 linux 我們知道,現在有了對應的解決方法,就是利用通訊端選項 IP_PKTINFO 和 recvmsg 介面,就能輕鬆完成這個動作。

const int on = 1;
// 開啟獲取包資訊 , 結果存放在輔助資料當中
setsockopt(sock,IPPROTO_IP,IP_PKTINFO,&on,sizeof(on));
...
// 接收封包
if ((retvalue=recvmsg(sock,&msg,0)) < 0){
    break;
}
//開始獲取輔助資料,由於輔助資料可以是一個也可以是一個陣列,因此迴圈;
for ( pcmsg = CMSG_FIRSTHDR(&msg) ; pcmsg != NULL ; pcmsg = CMSG_NXTHDR(&msg,pcmsg) ) {
    //判斷是否是包資訊
    if ( pcmsg->cmsg_level == IPPROTO_IP &&
        pcmsg->cmsg_type == IP_PKTINFO ) {
        //獲取我們的自定義資料 struct in_pktinfo ;
        unsigned char * pData = CMSG_DATA(pcmsg);
        struct in_pktinfo * pInfo = (struct in_pktinfo *)pData;
        //轉換
        inet_ntop(AF_INET,&pInfo->ipi_addr,dst_ip_buf,sizeof(dst_ip_buf));
        inet_ntop(AF_INET,&pInfo->ipi_spec_dst,route_ip_buf,sizeof(route_ip_buf));
        //下面都是列印資訊
        printf("client_addr:%s,port:%d\n",inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
        printf("route ip :%s, dst ip:%s , ifindex:%d\n" , route_ip_buf,dst_ip_buf, pInfo->ipi_ifindex);
        recvbuf[retvalue] = 0;
        printf("recv bytes:%d , recvbuf:%s \n", retvalue, recvbuf);
    }
}

Windows 系統下該怎麼處理?

其實 Windows 系統下也是類似的操作,通訊端選項也是需要開啟 IP_PKTINFO 選項,但接收函數 recvmsg 是 linux 系統的函數,windows 系統的對應函數是 WSARecvMsg,利用此介面,我們也能輕鬆實現獲取 UDP 包本機地址的需求

啥都沒程式碼有說服力 ( 程式碼有點爛,湊合看吧 )

#include <stdio.h>
#include <WinSock2.h>
#include <mswsock.h>
#include <ws2ipdef.h>
#include <WS2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

typedef unsigned char uint8_t;
LPFN_WSARECVMSG WSARecvMsg = nullptr;

void get_wsarecvmsg_fptr(void)
{
    DWORD dwBytesRecvd = 0;
    GUID guidWSARecvMsg = WSAID_WSARECVMSG;
    SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
    WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
        &guidWSARecvMsg, sizeof(guidWSARecvMsg),
        &WSARecvMsg, sizeof(WSARecvMsg),
        &dwBytesRecvd, NULL, NULL);
    closesocket(sock);
}

int recv_localaddr(SOCKET s, uint8_t* buf, size_t buf_sz,
    struct sockaddr_in* remote_addr,
    struct sockaddr_in* local_addr)
{
    DWORD bytes_received;
    WSAMSG msg = { 0 };
    WSABUF sbuf = { 0 };
    uint8_t cmdbuf[512];
    WSACMSGHDR* cmsg;
    PIN_PKTINFO pi;

    sbuf.buf = (char FAR*)buf;
    sbuf.len = (u_long)buf_sz;
    msg.lpBuffers = &sbuf;
    msg.dwBufferCount = 1;
    msg.name = (LPSOCKADDR)remote_addr;
    msg.namelen = sizeof(*remote_addr);
    msg.Control.buf = (char FAR*)cmdbuf;
    msg.Control.len = (u_long)sizeof(cmdbuf);

    /* Receive a packet */
    (WSARecvMsg)(s, &msg, &bytes_received, NULL, NULL);

    /* Parse the header info, look for the local address */
    cmsg = WSA_CMSG_FIRSTHDR(&msg);
    for ( ; cmsg != NULL; cmsg = WSA_CMSG_NXTHDR(&msg, cmsg) ) {
        if ((cmsg->cmsg_level == IPPROTO_IP) &&
            (cmsg->cmsg_type == IP_PKTINFO)) {
            char ipbuf[128] = { 0 };
            size_t iplen = 128;
            pi = (PIN_PKTINFO)WSA_CMSG_DATA(cmsg);
            local_addr->sin_family = AF_INET;
            local_addr->sin_addr = pi->ipi_addr;
            printf("local ip: %s, local port: %d\n", 
                inet_ntop(AF_INET, &(local_addr->sin_addr), ipbuf, iplen), ntohs(local_addr->sin_port));
            printf("recv msg: %s", buf);
            break;
        }
    }

    return (int)bytes_received;
}

int main(int argc, char* argv[])
{
    WSADATA wsaData = {};
    if ( WSAStartup(MAKEWORD(2, 1), &wsaData) == -1 ) {
        return -1;
    }
    get_wsarecvmsg_fptr();

    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in serv_addr, cli_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    memset(&cli_addr, 0, sizeof(cli_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8090);
    serv_addr.sin_addr.s_addr = 0;
    if (bind(sock, (sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        closesocket(sock);
        WSACleanup();
        return -1;
    }

    int sockopt = 1;
    setsockopt(sock, IPPROTO_IP, IP_PKTINFO, (char*)&sockopt, sizeof(sockopt));

    size_t length = 2048;
    char buffer[2048] = { 0 };
    recv_localaddr(sock, (uint8_t*)buffer, length, &cli_addr, &serv_addr);
    closesocket(sock);
    WSACleanup();
    return 0;
}