I maintain GPSD, a widely-deployed open-source service daemon that monitors GPSes and other geodetic sensors. It listens for client-application connections on port 2947 on both IPv4 and IPv6. For security and privacy it normally listens only on the loopback address, but there is a -G option to the daemon that is intended to cause it to listen on any address.
The problem: the -G option works in IPv4, but I can't figure out how to make it work with IPv6. The method that should work based on various tutorial examples does not, producing instead an error suggesting the address is already in use. I'm seeking help to fix this from people experienced with IPv6 network programming.
Relevant code is at http://git.berlios.de/cgi-bin/gitweb.cgi?p=gpsd;a=blob;f=gpsd.c;h=ee2156caf03ca23405f57f3e04e9ef306a75686f;hb=HEAD
This code operates correctly in both the -G and non -G cases under IPv4, as is easily verified with netstat -l.
Now look around line 398 after "case AF_INET6:". The listen_global option is set by -G; when false, the code succeeds. There is presently a following comment, inherited from an unknown contributor, that reads like this:
/* else */
/* BAD: sat.sa_in6.sin6_addr = in6addr_any;
* the simple assignment will not work (except as an initializer)
* because sin6_addr is an array not a simple type
* we could do something like this:
* memcpy(sat.sa_in6.sin6_addr, in6addr_any, sizeof(sin6_addr));
* BUT, all zeros is IPv6 wildcard, and we just zeroed the array
* so really nothing to do here
*/
According to various tutorial examples I have looked up, the assignment "sat.sa_in6.sin6_addr = in6addr_any;" is (despite the comment) correct, and it does compile. However, startup with -G fails claiming the listen address is already in use.
Is the assignment "sat.sa_in6.sin6_addr = in6addr_any;" nominally correct here? What else, if anything, am I missing?
An IPv6 address is represented as eight groups of four hexadecimal digits, each group representing 16 bits The groups are separated by colons (:). An example of an IPv6 address is: 2001:0db8:85a3:0000:0000:8a2e:0370:7334.
A bind() API supplies a unique name for the socket. In this example, the programmer sets the address to in6addr_any, which (by default) allows connections to be established from any IPv4 or IPv6 client that specifies port 3005 (that is, the bind is done to both the IPv4 and IPv6 port spaces).
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.
The square brackets are required to tell curl that it's an IPv6 address and not a host:port pair. The quotes are required to stop the shell from treating the square brackets as a glob. The backslash is required to stop curl from treating the square brackets as a range specification.
The reason the address is already in use is because on many IPv6 networking stacks, by default an IPv6 socket will listen to both IPv4 and IPv6 at the same time. IPv4 connections will be handled transparently and mapped to a subset of the IPv6 space. However, this means you cannot bind to an IPv6 socket on the same port as an IPv4 socket without changing the settings on the IPv6 socket. Make sense?
Just do this before your call to bind
(this is taken from one of my projects):
int on = 1;
if (addr->sa_family == AF_INET6) {
r = setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
if (r)
/* error */
}
Unfortunately, there is no default value across platforms for IPV6_V6ONLY
-- which basically means you always need to turn it either on or off explicitly if you care, unless you don't care about other platforms. Linux leaves it off by default, Windows leaves it on by default...
From a look in a random Linux system's include files, in6addr_any
is declared like so:
extern const struct in6_addr in6addr_any; /* :: */
extern const struct in6_addr in6addr_loopback; /* ::1 */
#define IN6ADDR_ANY_INIT { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }
So, perhaps the nearness to the INIT
array confused whoever left that comment in GPSD's sources. The actual type is clearly struct in6_addr
, which is assignable.
I did look around, and found some hints that suggested that if IPv4 is already listening to the "any" address, IPv6 can't also. Perhaps that's what's biting you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With