7

I am experimenting with IPv6 sockets, particularly the "dual stack" capability offered on Windows Vista and later, and apparently on Unix by default. I am finding that when I bind my server to a specific IP address, or to the hostname resolution of my local machine, I cannot accept a connection from an IPv4 client. When I bind to INADDR_ANY however, I can.

Please consider the following code for my server. You can see that I follow Microsoft's advice of creating an IPv6 socket, then setting the IPV6_V6ONLY flag to zero:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // We intend to use the addrinfo in a call to connect().  (I know it is ignored if we specify a server to connect to...)

int nRet = getaddrinfo("powerhouse", "82", &hints, &result);

SOCKET sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

int no = 0;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&no, sizeof(no)) != 0)
    return -1;

if (bind(sock, result->ai_addr, result->ai_addrlen) ==  SOCKET_ERROR)
    return -1;

if (listen(sock, SOMAXCONN) == SOCKET_ERROR)
    return -1;

SOCKET sockClient = accept(sock, NULL, NULL);
                                 

Here is the code for my client. You can see I create an IPv4 socket and attempt to connect to my server:

addrinfo* result, *pCurrent, hints;

memset(&hints, 0, sizeof hints);    // Must do this!
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;

if (getaddrinfo("powerhouse", "82", &hints, &result) != 0)
    return -1;

SOCKET sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
int nRet = connect(sock, result->ai_addr, result->ai_addrlen);
                                  

The result from my connect call is always 10061: connection refused.

If I change my server code to bind to :: (or pass a NULL host to getaddrinfo() (same thing)), and change my client code to specify a NULL host in the getaddrinfo() call, then the V4 client can connect fine.

Can anyone explain why please? I have not read anything that we must specify a NULL host (hence use INADDR_ANY) if we want dual-socket behaviour. This can't be a requirement, because what I have a multihomed host and I want to accept IPv4 on only some of the available IPs?

EDIT 15/05/2013:

This is the relevant documentation which has gotten me confused as to why my code fails:

From Dual-Stack Sockets for IPv6 Winsock Applications

"Windows Vista and later offer the ability to create a single IPv6 socket which can handle both IPv6 and IPv4 traffic. For example, a TCP listening socket for IPv6 is created, put into dual stack mode, and bound to port 5001. This dual-stack socket can accept connections from IPv6 TCP clients connecting to port 5001 and from IPv4 TCP clients connecting to port 5001."

"By default, an IPv6 socket created on Windows Vista and later only operates over the IPv6 protocol. In order to make an IPv6 socket into a dual-stack socket, the setsockopt function must be called with the IPV6_V6ONLY socket option to set this value to zero before the socket is bound to an IP address. When the IPV6_V6ONLY socket option is set to zero, a socket created for the AF_INET6 address family can be used to send and receive packets to and from an IPv6 address or an IPv4 mapped address. (emphasis mine)"

j6t
  • 9,150
  • 1
  • 15
  • 35
Wad
  • 1,454
  • 1
  • 16
  • 33
  • You need an IPv4 endpoint for IPv4 connections, the `bind` is explicitly an IPv6 endpoint and not a wildcard. – Steve-o May 10 '13 at 12:51
  • @Steve-o I don't see what you mean, sorry. Can you clarify? – Wad May 10 '13 at 12:57
  • Binding implements a filter to only accept data with destination address matching the bound address. IPv4 packets will have a IPv4 destination address thus will be rejected. – Steve-o May 10 '13 at 17:43
  • @Steve-o Thanks, I understand the principle now, but by calling setsockopt with IPPROTO_IPV6 I - according to documentation - enable that socket to also accept incoming IPv4 connections. Please also see the comment I have posted below on Sander's comment... – Wad May 13 '13 at 11:43

2 Answers2

8

IPv4 and IPv6 are two separate protocols. Packets of one protocol cannot be handled using the other protocol. That is why the concept of Dual Stack exists: your system runs both the IPv4 and IPv6 protocol stacks, has both IPv4 and IPv6 addresses, etc.

Operating systems have a trick where you can have an IPv6 socket that listens on all IPv4 and IPv6 addresses. You still need to have both address families on the host, and it only works when you bind to the wildcard address. Once you bind that socket to a fixed address that doesn't work anymore and it will only work for the address that you have bound to.

So if you want to listen on all available addresses then setting IPV6_V6ONLY to 0 and listening on the wildcard address work. The IPv4 clients will be shown as using IPv6 addresses starting with ::ffff: with the last 32 bits containing the IPv4 address.

When you want to bind to specific addresses you will need sockets bound to each of the addresses you want to listen on. Then you need to use i.e. select(...) to monitor those sockets and to respond to those that become active because someone connects to them.

Sander Steffann
  • 9,509
  • 35
  • 40
  • Thank you for commenting Sander, your comment makes sense. Can you provide a link to where this is documented though - I have been unable to find one. For example, http://msdn.microsoft.com/en-us/library/windows/desktop/bb513665%28v=vs.85%29.aspx seems to state that the only thing we need to do is set IPV6_V6ONLY to zero... – Wad May 10 '13 at 12:43
  • The official documentation is RFC 3493: http://tools.ietf.org/html/rfc3493.html. But this scenario is not described there because it is technically impossible. If you bind a server to an IPv6 address then the client can only connect when using that exact address as the destination address. An IPv4-only client can never connect to an IPv6 address, just like an IPv6-only client can never connect to an IPv4 address. Source and destination must always be of the same protocol. IPv6 is not backwards compatible with IPv4 on the wire... – Sander Steffann May 10 '13 at 17:06
  • Thanks Sander. I am binding to IPv6 only addresses, yes, but as noted above to Steve-o by setting teh IPPROTO_IPV6 the documentation claims that my IPv6 socket can also accept an IPv4 client: the IPv4 address will be mapped to an IPv6 one. Indeed, when I run my server and look at TCPView, after the setsockopt() call I see *two* bound sockets on powerhouse, one of each IPv4 and IPv6 protocol. Which is why I cannot understand why this is still failing... – Wad May 13 '13 at 11:43
  • 2
    This indeed works if you bind to the catch-all address. You bind the server to a specific real IPv6 address. But the client can't, because an IPv4-only client needs an IPv4 destination address to connect to, and that doesn't exist because you bound the server only to an IPv6 address. If you want a server to accept IPv4 connections on an IPv6 socket then you would need to bind the server to the IPv6-mapped IPv4 address of the server in addition to the global IPv6 address that you bind to. You *need* to provide an IPv4 address for the client to connect to. – Sander Steffann May 13 '13 at 13:41
  • Arghhh! Sorry Sander...so why is TCPView showing me that I have a listening IPv4 TCP socket as well as a listening IPv6 TCP socket (just by setting IPPROTO_IPV6 to 0) if I cannot connect to the IPv4 TCP socket? Is setting IPPROTO_IPV6 to 0 only useful when binding to the catch-all address, otherwise it is useless?? – Wad May 14 '13 at 12:48
  • No need to apologise! Nitpicking: You're talking about IPV6_V6ONLY :) You specify an IPv6 address to listen on. On what IPv4 address is your application listening then? Setting IPV6_V6ONLY to 0 can be useful *if* you also bind the server to an IPv6-mapped IPv4 address. – Sander Steffann May 14 '13 at 22:09
  • Sander, I have edited my question to include the MSDN text that led me to ask this question. Everything we have discussed here seems to contradict that. Unless of course I am reading it wrong. Can you please cast your eye over my edit/the link and clarify please; what seemed so simple in that article is turning out to be a real problem! – Wad May 15 '13 at 10:03
  • 1
    This part: "used to send and receive packets to and from an IPv6 address or an IPv4 mapped address" is confusing. Looking at the software layer that socket can deal with IPv6 and IPv6-mapped-IPv4 addresses. It doesn't say anything else and on the wire IPv4 and IPv6 are not compatible. So an IPv4 host *needs* an IPv4 destination address to connect to. IPv6-mapped-IPv4 addresses are actually IPv4 addresses represented as IPv6 addresses. Binding an IPv6 socket to such an address actually binds to an IPv4 address. Binding to only an IPv6 address will make it impossible for IPv4 clients to connect. – Sander Steffann May 17 '13 at 10:38
  • Sander thanks for the comment; I have been away from an IPv6 machine so I cannot run through my code and apply what you have said, I will get back to you ASAP. – Wad May 23 '13 at 09:10
  • Sander, I have asked enough on this question, and have moved onto new related queries at http://stackoverflow.com/questions/16762939/use-of-in6addr-setv4mapped-and-dual-stack-sockets. Thank you so much for your help on this question. Please be so kind as to cast your eye over my new question. – Wad May 26 '13 at 19:31
  • I find this answer very confusing ( 30 year professional network programmer here ) with the initial statement: "IPv4 and IPv6 are two separate protocols. Packets of one protocol cannot be handled using the other protocol. " At the IP layer, this is true, and all shipping OSes have IPv4 and IPv6 support today - but the TCP or UDP layers can run easily on both. Opening a socket at the TCP layer can be done in an agnostic fashion, by NOT setting IPV6_V6ONLY . Just know which you want. – Brian Bulkowski Dec 18 '15 at 18:38
3

This link http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch12lev1sec2.html gives more information about the IPv4 and IPv6 connection,

Most dual-stack hosts should use the following rules in dealing with listening sockets:

  • A listening IPv4 socket can accept incoming connections from only IPv4 clients.
  • If a server has a listening IPv6 socket that has bound the wildcard address and the IPV6_V6ONLY socket option (Section 7.8) is not set, that socket can accept incoming connections from either IPv4 clients or IPv6 clients. For a connection from an IPv4 client, the server's local address for the connection will be the corresponding IPv4-mapped IPv6 address.
  • If a server has a listening IPv6 socket that has bound an IPv6 address other than an IPv4-mapped IPv6 address, or has bound the wildcard address but has set the IPv6_V6ONLY socket option (Section 7.8), that socket can accept incoming connections from IPv6 clients only.
Michael Hampton
  • 9,737
  • 4
  • 55
  • 96
Sathish
  • 4,975
  • 3
  • 18
  • 23