获取本机收到的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 
#include 
#include 
#include 
#include 

#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;
}

运行效果如下,可以审计到各个本机地址,甚至环回地址也能精确捕获:

获取本机收到的UDP数据包的目标地址_第1张图片

WSARecvMsg从Windows2000后都可用,与此对应的WSASendMsg函数却是从vista平台才可以提供的。linux平台中有个recvmsg函数应该可以达到类似的目的。本文中的程序在vs2008中编译通过。


你可能感兴趣的:(C++,WinSock)