Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConnectEx requires the socket to be "initially bound", but to what?

The ConnectEx function requires an "unconnected, previously bound socket". Indeed, if I omit the bind step in my example (see below), ConnectEx fails with WSAEINVAL.

Here's my current understanding: before calling ConnectEx, bind the socket to INADDR_ANY and port 0 (unless it is already bound):

struct sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... bind failed; call WSAGetLastError to see why ... }

Or for an IPv6 socket:

struct sockaddr_in6 addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... bind failed; call WSAGetLastError to see why ... }

This lets the operating system assign a local address to our socket (as opposed to the remote address we are connecting to). connect does this step automatically, but ConnectEx does not.

My questions are:

  1. Is my assessment correct?

  2. Is there a way to do this automatic bind that is agnostic to the address family, or will I have to handle each of AF_INET, AF_INET6, AF_BTH (Bluetooth), etc. manually?

Working ConnectEx example (also on Gist: https://gist.github.com/4158972):

#include <stdio.h>
#include <WinSock2.h>
#include <MSWSock.h>
#include <WS2tcpip.h>

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

struct mswsock_s {
    LPFN_CONNECTEX ConnectEx;
} mswsock;

static BOOL load_mswsock(void)
{
    SOCKET sock;
    DWORD dwBytes;
    int rc;

    /* Dummy socket needed for WSAIoctl */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET)
        return FALSE;

    {
        GUID guid = WSAID_CONNECTEX;
        rc = WSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
                      &guid, sizeof(guid),
                      &mswsock.ConnectEx, sizeof(mswsock.ConnectEx),
                      &dwBytes, NULL, NULL);
        if (rc != 0)
            return FALSE;
    }

    rc = closesocket(sock);
    if (rc != 0)
        return FALSE;

    return TRUE;
}

int main(int argc, char *argv[])
{
    int rc;
    BOOL ok;
    WSADATA wsaData;
    SOCKET sock;

    rc = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (rc != 0) {
        printf("WSAStartup failed: %d\n", rc);
        return 1;
    }
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
        printf("Your computer is from the wrong millenium.\n");
        WSACleanup();
        return 1;
    }

    if (!load_mswsock()) {
        printf("Error loading mswsock functions: %d\n", WSAGetLastError());
        return 1;
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        printf("socket: %d\n", WSAGetLastError());
        return 1;
    }

    /* ConnectEx requires the socket to be initially bound. */
    {
        struct sockaddr_in addr;
        ZeroMemory(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = 0;
        rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
        if (rc != 0) {
            printf("bind failed: %d\n", WSAGetLastError());
            return 1;
        }
    }

    /* Issue ConnectEx and wait for the operation to complete. */
    {
        OVERLAPPED ol;
        ZeroMemory(&ol, sizeof(ol));

        sockaddr_in addr;
        ZeroMemory(&addr, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr("173.194.37.36"); // google.com
        addr.sin_port = htons(80);

        ok = mswsock.ConnectEx(sock, (SOCKADDR*) &addr, sizeof(addr), NULL, 0, NULL, &ol);
        if (ok) {
            printf("ConnectEx succeeded immediately\n");
        } else if (WSAGetLastError() == ERROR_IO_PENDING) {
            printf("ConnectEx pending\n");

            DWORD numBytes;
            ok = GetOverlappedResult((HANDLE) sock, &ol, &numBytes, TRUE);
            if (ok)
                printf("ConnectEx succeeded\n");
            else
                printf("ConnectEx failed: %d\n", WSAGetLastError());
        } else {
            printf("ConnectEx failed: %d\n", WSAGetLastError());
            return 1;
        }
    }

    /* Make the socket more well-behaved. */
    rc = setsockopt(sock, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, NULL, 0);
    if (rc != 0) {
        printf("SO_UPDATE_CONNECT_CONTEXT failed: %d\n", WSAGetLastError());
        return 1;
    }

    /* This will fail if SO_UPDATE_CONNECT_CONTEXT was not performed. */
    rc = shutdown(sock, SD_BOTH);
    if (rc != 0) {
        printf("shutdown failed: %d\n", WSAGetLastError());
        return 1;
    }

    printf("Done\n");
    return 0;
}
like image 886
Joey Adams Avatar asked Nov 28 '12 05:11

Joey Adams


2 Answers

connect does this step automatically, but ConnectEx does not.

Correct.

Is my assessment correct?

Yes.

Is there a way to do this automatic bind that is agnostic to the address family, or will I have to handle each of AF_INET, AF_INET6, AF_BTH (Bluetooth), etc. manually?

I believe that INADDR_ANY is a bunch of zeros in all address families, so you could just try using the memset() and omitting the assignment to addr.sin_addr.s_addr completely. Whether this is kosher, portable, politically correct etc. is another question into which I will not enter.

It seems pretty curious that Microsoft didn't manage to have ConnectEx() call bind() internally, considering that saving system calls is the motivation for its existence, and also considering that most programs never bind an outbound socket at all.

like image 78
user207421 Avatar answered Sep 25 '22 14:09

user207421


It is possible to get the bind address for ConnectEx in an address family independent way.

Solution 1

Call getaddrinfo with the following options:

pServiceName = "0"

hints.ai_flags = AI_PASSIVE
hints.ai_family = address family of the socket

Then use the first result of the returned address list.

To get the address family of the socket you can use getsockopt with SO_PROTOCOL_INFOW.

Solution 2

Use SOCKADDR_STORAGE for the address structure and call INETADDR_SETANY which is defined in MSTcpIP.h. It supports AF_INET and AF_INET6.

like image 28
pindumb Avatar answered Sep 22 '22 14:09

pindumb