2

I am interested in retrieving the destination address that an inbound packet is sent to. For example on Linux you can utilize recvmsg:

res = recvmsg(socket, &msghdr, 0);
get_cmsg = CMSG_FIRSTHDR(msghdr);   
struct in_pktinfo *get_pktinfo = (struct in_pktinfo *)CMSG_DATA(get_cmsg);
printf(" - Header destination address (get_pktinfo.ipi_addr)=%s\n", inet_ntoa(pktinfo.ipi_addr));

Steps have been skipped to save many many lines

The key here is that recvmsg is easy to use. Similar functions are implemented for Windows XP like recvfrom but Windows does not seem to implement the recvmsg function.

Similarly named functions exist, like the WSARevcMsg function, but according to the linked documentation it is only included in Windows Vista and above.

Is there a way I can get the header destination address from a packet in Windows XP?


I know that using XP is bad and old, unfortunately we are attempting to fix a bug on a legacy product, so at this time we are unable to simply upgrade.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Fantastic Mr Fox
  • 32,495
  • 27
  • 95
  • 175

1 Answers1

3

windows does not seem to implement the recvmsg function.

Actually, it does (well, an equivalent, anyway):

WSARecvMsg function

Similar functions have been revamped like the WSArevcMsg but these are only included in windows vista and above.

You can't always trust MSDN documentation. When Microsoft officially drops support for a Windows version (like XP), it tends to remove references to that version from MSDN, including "Minimum supported ..." requirements of API functions (which annoys many programmers). The key point is supported. Microsoft does not support older Windows versions.

WSARecvMsg() was first introduced in XP, which was EOL'ed in 2014 and many references to it were stricken from MSDN documentation (here is proof from 2013 when the WSARecvMsg() documentation stated XP and not Vista was the "Minimum supported client").


As stated in the WSARecvFrom() documentation:

Note The function pointer for the WSARecvMsg function must be obtained at run time by making a call to the WSAIoctl() function with the SIO_GET_EXTENSION_FUNCTION_POINTER opcode specified. The input buffer passed to the WSAIoctl function must contain WSAID_WSARECVMSG, a globally unique identifier (GUID) whose value identifies the WSARecvMsg extension function. On success, the output returned by the WSAIoctl function contains a pointer to the WSARecvMsg function. The WSAID_WSARECVMSG GUID is defined in the Mswsock.h header file.

WSAID_WSARECVMSG is defined as:

#define WSAID_WSARECVMSG \
    {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}}

aka {F689D7C8-6F1F-436B-8A53-E54FE351C322} in text format.

For example:

SOCKET sckt = ...;
LPFN_WSARECVMSG lpWSARecvMsg = NULL;

GUID g = WSAID_WSARECVMSG;
DWORD dwBytesReturned = 0;
if (WSAIoctl(sckt, SIO_GET_EXTENSION_FUNCTION_POINTER, &g, sizeof(g), &lpWSARecvMsg, sizeof(lpWSARecvMsg), &dwBytesReturned, NULL, NULL) != 0)
{
    // WSARecvMsg is not available...
}

Then, to use it:

BYTE buffer[...];
DWORD dwBytesRecv;

WSABUF msgbuf;
memset(&msgbuf, 0, sizeof(msgbuf));
msgbuf.len = sizeof(buffer);
msgbuf.buf = (char *) buffer;

// call WSA_CMSG_SPACE() once for each option you enable
// on the socket that can return data in WSARecvMsg()...
int size = 0;
if (... IP_PKTINFO is enabled ...)
    size += WSA_CMSG_SPACE(sizeof(struct in_pktinfo));
if (... IPV6_PKTINFO is enabled ...)
    size += WSA_CMSG_SPACE(sizeof(struct in6_pktinfo));
// other packet options as needed...

BYTE *controlbuf = (BYTE *) malloc(size);

SOCKADDR_STORAGE *addrbuf = (SOCKADDR_STORAGE *) malloc(sizeof(SOCKADDR_STORAGE));

WSAMSG msg;
memset(&msg, 0, sizeof(msg));
msg.name = (struct sockaddr *) addrbuf;
msg.namelen = sizeof(SOCKADDR_STORAGE);
msg.lpBuffers = &msgbuf;
msg.dwBufferCount = 1;
msg.Control.len = size;
msg.Control.buf = (char *) controlbuf;

if (lpWSARecvMsg(sckt, &msg, &dwBytesRecv, NULL, NULL) == 0)
{
    // addrbuf contains the sender's IP and port...
    switch (addrbuf->ss_family)
    {
        case AF_INET:
        {
            struct sockaddr_in *addr = (struct sockaddr_in*) addrbuf;
            // use addr as needed...
            break;
        }

        case AF_INET6:
        {
            struct sockaddr_in6 *addr = (struct sockaddr_in6*) addrbuf;
            // use addr as needed...
            break;
        }
    }

    WSACMSGHDR *msghdr = WSA_CMSG_FIRSTHDR(&msg);
    while (msghdr)
    {
        switch (msghdr->cmsg_type)
        {
            case IP_PKTINFO: // also IPV6_PKTINF
            {
                // must call setsockopt(sckt, IPPROTO_IP, IP_PKTINFO, TRUE) beforehand to receive this for IPv4
                // must call setsockopt(sckt, IPPROTO_IPV6, IPV6_PKTINFO, TRUE) beforehand to receive this for IPv6

                switch (addrbuf->ss_family)
                {
                    case AF_INET:
                    {
                        struct in_pktinfo *pktinfo = (struct in_pktinfo *) WSA_CMSG_DATA(msghdr);
                        // use pktinfo as needed...
                        break;
                    }

                    case AF_INET6:
                    {
                        struct in6_pktinfo *pktinfo = (struct in6_pktinfo *) WSA_CMSG_DATA(msghdr);
                        // use pktinfo as needed...
                        break;
                    }
                }

                break;
            }

            // other packet options as needed...
        }

        msghdr = WSA_CMSG_NXTHDR(&msg, msghdr);
    }
}

free(addrbuf);
free(controlbuf);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Any idea why we are getting `error C3861: 'WSARecvMsg': identifier not found` when using `WSARecvMsg` with `#define WINVER 0x501` (Win XP)? `Mswsock.h` is included, as per the documentation. We cannot see that in the source file? – Fantastic Mr Fox May 19 '16 at 22:41
  • @Ben: Read the `WSARecvMsg()` documentation more carefully. I have added the relavant quote to my answer. The `WSARecvMsg()` function itself is not declared in any header files. You have to make your code declare a pointer variable of type `LPFN_WSARECVMSG` (which is defined in Mswsock.h) and then use `WSAIoctl()` to assign a function pointer to `WSARecvMsg()` to that variable, and then you can use that pointer to call `WSARecvMsg()` as needed. – Remy Lebeau May 19 '16 at 22:46
  • Yep after reading it is clear, should have just read from start to finish. – Fantastic Mr Fox May 19 '16 at 23:39
  • 1
    Also yeah, many references to xp seem to have been removed but not all, which is even more annoying ... – Fantastic Mr Fox May 19 '16 at 23:44
  • Do we need to get the function pointer for `WSARecvMsg` every time we read? Is there any reason this function would change? Can we just get it once? – Fantastic Mr Fox May 20 '16 at 18:51
  • 1
    No, you do not need to retrieve the pointer every time you want to call it. You can indeed retrieve it once and reuse it multiple times. – Remy Lebeau May 20 '16 at 19:22
  • Since it wasnt easy to find this answerr i added the question [here](http://stackoverflow.com/questions/37355397/why-is-the-wsarecvmsg-function-implemented-as-a-function-pointer-and-can-this-po) so it will be well documented. – Fantastic Mr Fox May 20 '16 at 20:36
  • Is `WSA_CMSG_LEN` being used correctly in the above example? It seems like you're taking the size of the entire message buffer plus 2 layers of `WSABUF` headers and allocating that whole size for the size of the Control buffer. It should work, presumably, in most cases, but only because the packet buffer should be way bigger than Control data. It seems like to me (for example) if you're receiving `in_pktinfo` you should size your buffer to `WSA_CMSG_LEN(sizeof(in_pktinfo))` and if there's another option you should add `WSA_CMSG_LEN(sizeof(whatever))` to that. Am I mistaken? – Michael Powell Feb 21 '22 at 20:55
  • @MichaelPowell "*Am I mistaken?*" - probably not. The above was taken from a socket library I work with, and that is how they allocate the control buffer. After reading the documentation again, I think what you describe is probably correct, except that it should be using `WSA_CMSG_SPACE()` instead of `WSA_CMSG_LEN()`. – Remy Lebeau Feb 21 '22 at 21:37
  • @RemyLebeau Thanks, I've continued working on this since asking the question, came to the same conclusion, and was about to come back and say this very thing. Thanks for your help, and for the otherwise invaluable example! This is a really tricky piece of API. Say hi to Rogue for me! – Michael Powell Feb 21 '22 at 22:08