Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create dual-stack socket on all loopback interfaces on Windows

I an trying to create a dual-stack socket on Windows 7, for listening on both 127.0.0.1 and ::1 interfaces. I do not want to listen on all interfaces (0.0.0.0), just the loopback ones.

For dual-stack sockets, I have found that I need to disable the IPV6_V6ONLY option. I created a sample app which does just that (see below). When the app is running, netstat -an gives me the following output:

TCP    0.0.0.0:27015          0.0.0.0:0              LISTENING
TCP    [::1]:27015            [::]:0                 LISTENING

When connecting with putty on ::1, everything works. However, when I try to connect to 127.0.0.1, I get "Connection refused".

When creating a socket on the "::" IPv6 address with the V6ONLY option disabled, I am able to connect on both IPv4 and IPv6, as expected.

So, how do I create a socket that listens on both IPv4 and IPv6 loopback interfaces?

Sample app I used for testing, adapted from here:

#include "stdafx.h"

#undef UNICODE

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")

#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"

int __cdecl main(void) 
{
    WSADATA wsaData;
    int iResult;

    SOCKET ListenSocket = INVALID_SOCKET;
    SOCKET ClientSocket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    int iSendResult;
    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    //hints.ai_flags = AI_PASSIVE;

    // Resolve the server address and port
    iResult = getaddrinfo("localhost", DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Create a SOCKET for connecting to server
    ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Disable V6ONLY, so we get IPv4 
    int disable = 0;
    iResult = setsockopt(ListenSocket, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&disable, sizeof(disable));
    if (iResult == SOCKET_ERROR) {
        printf("setsockopt failed with error: %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }

    // Setup the TCP listening socket
    iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(result);

    iResult = listen(ListenSocket, SOMAXCONN);
    if (iResult == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // Accept a client socket
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        closesocket(ListenSocket);
        WSACleanup();
        return 1;
    }

    // No longer need server socket
    closesocket(ListenSocket);

    // Receive until the peer shuts down the connection
    do {

        iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
        if (iResult > 0) {
            printf("Bytes received: %d\n", iResult);

        // Echo the buffer back to the sender
            iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
            if (iSendResult == SOCKET_ERROR) {
                printf("send failed with error: %d\n", WSAGetLastError());
                closesocket(ClientSocket);
                WSACleanup();
                return 1;
            }
            printf("Bytes sent: %d\n", iSendResult);
        }
        else if (iResult == 0)
            printf("Connection closing...\n");
        else  {
            printf("recv failed with error: %d\n", WSAGetLastError());
            closesocket(ClientSocket);
            WSACleanup();
            return 1;
        }

    } while (iResult > 0);

    // shutdown the connection since we're done
    iResult = shutdown(ClientSocket, SD_SEND);
    if (iResult == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        closesocket(ClientSocket);
        WSACleanup();
        return 1;
    }

    // cleanup
    closesocket(ClientSocket);
    WSACleanup();

    return 0;
}
like image 663
cvlad Avatar asked Oct 24 '25 14:10

cvlad


1 Answers

You have to create two sockets for this case. Creating a separate socket for each protocol/address is the normal way of doing this. There is the ::/0 with V6ONLY=0 shortcut if you want to listen on any address on both IPv4 and IPv6, but for anything other than that you'll have to create multiple sockets.

Technically you can bind a server socket with V6ONLY=0 to an IPv4 mapped address, but there is not really any point in doing so. IN6ADDR_ANY is the only useful case. It makes the socket listen on any IPv6 address, including IPv4 mapped addresses (which are really IPv4 addresses in disguise).

Once you bind a socket to an address that binding acts like a filter, and only packets destined for that address will be received on the socket. And ::1 is a different address than ::ffff:127.0.0.1, so a socket bound to one will not receive messages for the other. You seem to assume that there is some implicit mapping between IPv4 and IPv6, but there isn't except for the IPv4 mapped addresses. Each address is unique.

Once you bind a socket it will only work for one address. For other addresses you'd need extra sockets, so in those cases you might as well just create a real IPv4 socket.

like image 97
Sander Steffann Avatar answered Oct 28 '25 05:10

Sander Steffann



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!