本机收到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中编译通过。