1. 程式人生 > >獲取本機收到的UDP資料包的目標地址

獲取本機收到的UDP資料包的目標地址

本機收到UDP資料時,通過recvfrom函式可以直接獲取傳送者的地址:

int recvfrom(
  __in          SOCKET s,
  __out         char* buf,
  __in          int len,
  __in          int flags,
  __out         struct sockaddr* from,
  __in_out      int* fromlen
);

但recvfrom沒有提供獲取包的目標地址的方法,不久前遇到一個需要獲取收到包的目標地址的情況,並找到了解決辦法。WinSock提供了WSARecvMsg函式來解決類似問題:

int WSARecvMsg(
  __in          SOCKET s,
  __in_out      LPWSAMSG lpMsg,
  __out         LPDWORD lpdwNumberOfBytesRecvd,
  __in          LPWSAOVERLAPPED lpOverlapped,
  __in          LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);

示例程式碼如下:
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>
#include <Windows.h>
#include <stdio.h>

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

int main()
{
    WSADATA wd;

    WSAStartup(MAKEWORD(2, 2), &wd);

    SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    sockaddr_in sin;

    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(3333);

    if (SOCKET_ERROR == bind(sock, (sockaddr *)&sin, sizeof(sin)))
    {
        closesocket(sock);
        return 0;
    }

    int optval = 1;
    if (SOCKET_ERROR == setsockopt(sock, IPPROTO_IP, IP_PKTINFO, (char*)&optval, sizeof(int)))
    {
        closesocket(sock);
        return 0;
    }

    GUID guidWSARecvMsg = WSAID_WSARECVMSG;
    LPFN_WSARECVMSG lpfnWSARecvMsg = NULL;
    DWORD dwOutSize = 0;

    WSAIoctl(
        sock,
        SIO_GET_EXTENSION_FUNCTION_POINTER,
        &guidWSARecvMsg,
        sizeof(guidWSARecvMsg),
        &lpfnWSARecvMsg,
        sizeof(lpfnWSARecvMsg),
        &dwOutSize,
        NULL,
        NULL
        );

    if (lpfnWSARecvMsg == NULL)
    {
        closesocket(sock);
        return 0;
    }

    while (TRUE)
    {
        char szControlBuffer[1024] = "";
        char szDataBuffer[1024] = "";
        sockaddr_in sin_local;
        WSABUF wsaBufData;
        WSAMSG wsaMsg;
        DWORD dwBytesRecved = 0;

        wsaBufData.buf = szDataBuffer;
        wsaBufData.len = sizeof(szDataBuffer);
        wsaMsg.name = (sockaddr *)&sin_local;
        wsaMsg.namelen = sizeof(sin_local);
        wsaMsg.lpBuffers = &wsaBufData;
        wsaMsg.dwBufferCount = 1;
        wsaMsg.Control.buf = szControlBuffer;
        wsaMsg.Control.len = sizeof(szControlBuffer);
        wsaMsg.dwFlags = 0;

        if (0 == lpfnWSARecvMsg(sock, &wsaMsg, &dwBytesRecved, NULL, NULL))
        {
            WSACMSGHDR *pCMsgHdr = WSA_CMSG_FIRSTHDR(&wsaMsg);

            if (pCMsgHdr != NULL)
            {
                if (pCMsgHdr->cmsg_type == IP_PKTINFO)
                {
                    IN_PKTINFO *pPktInfo = (IN_PKTINFO *)WSA_CMSG_DATA(pCMsgHdr);

                    if (pPktInfo != NULL)
                    {
                        printf(
                            "%d.%d.%d.%d:%d->%d.%d.%d.%d:%d %s\n",
                            sin_local.sin_addr.S_un.S_un_b.s_b1,
                            sin_local.sin_addr.S_un.S_un_b.s_b2,
                            sin_local.sin_addr.S_un.S_un_b.s_b3,
                            sin_local.sin_addr.S_un.S_un_b.s_b4,
                            ntohs(sin_local.sin_port),
                            pPktInfo->ipi_addr.S_un.S_un_b.s_b1,
                            pPktInfo->ipi_addr.S_un.S_un_b.s_b2,
                            pPktInfo->ipi_addr.S_un.S_un_b.s_b3,
                            pPktInfo->ipi_addr.S_un.S_un_b.s_b4,
                            3333,
                            szDataBuffer
                            );
                    }
                }
            }
        }
        else
        {
            break;
        }
    }

    closesocket(sock);

    return 0;
}

執行效果如下,可以審計到各個本機地址,甚至環回地址也能精確捕獲:


WSARecvMsg從Windows2000後都可用,與此對應的WSASendMsg函式卻是從vista平臺才可以提供的。linux平臺中有個recvmsg函式應該可以達到類似的目的。本文中的程式在vs2008中編譯通過。