Thursday, May 6, 2010

Debian, IPv6 and ... Java ?

Today is the first time for me to talk about Debian. It has been my favorite Linux flavor for quite some time now. I would say at least ten years, when compiling stuff became less fun and more painful, or my spare time ran out. I don't know exactly.

But whatever the cause, I ditched compiling everything in favor of the flexibility (and frustrating nature at times) of a packaging system. At that time, RPM did not prove better than Slackware's plain .tar.gz packaging and although dselect could be stubborn at times, it soon got better with apt-get and friends.

Let's add IPv6 to this landscape.

I discovered the details of this tentative IPv4 replacement protocol at around the same time I got acquainted with Debian. I will not try to cover the details of why IPv6 is better (or not) and why it takes so long to deploy (or not) but just make sure that you know the following important thing : IPv6 is not designed to be a drop-in replacement for IPv4, but is made so that it closely resembles.

And this is probably a very important point in today's mess. Being close enough, it might make people think that it takes a one-liner to update everything. Or just a new board/firmware for the router.

Let's focus on applications on Linux.

IPv6 API uses sockets, much like IPv4, and even uses the same calls for the basic socket creation, binding, listening and so on. Of course, the upper protocols are not modified, which means that UDP, TCP and friends still work.

When binding a socket with the AF_INET6 family, it is specified that the socket can be bound to both IPv4 and IPv6 address spaces. Really ? Yes, probably as a means to make it easier for people to port applications, the system will start listening on both families and you only have to create one socket.

This is made possible by adding a specific format for the IPv4 address which is called IPv6-mapped-IPv4 address. Basically it means that the 32 bits of the IPv4 address are mingled with a special meaning IPv6 prefix. Then you just check on those bits and figure if the incoming connection is IPv4 or IPv6, if you ever care. Which you should.

So how do we enable (or disable) this behavior ? That is the very reason of this post : the default behavior is undetermined. In fact, the default behavior depends on a sysctl value. So imagine you are a software developer, you made a software assuming that dual-stacked sockets were the default. Now imagine that the Linux distribution you (or your user) runs has a different setting. Or even better, changes the setting at some point. This is what happened for Debian.

This is exactly what happened. The variable is called net.ipv6.bindv6only and used to be 0, i.e. a AF_INET6 socket is bound to both v4 and v6. When it changed, it took people a little bit of unpleasant time to find out why the Java VM provided by Sun decided to stop working and report a network connection error. The thing is, the vm listens on some port on localhost and while using IPv4 as a default mean of connection, the socket was probably created with AF_INET6 family to help support both IPv4 and IPv6 (just guessing).

To sum it all up, man is your friend in the coding world : man ipv6 should provide you with all the necessary information. Now if you want to stick to the old style, you have two solutions.

The first solution is to update the sysctl config file in /etc/sysctl.d folder. You will find the file under the name bindv6only.conf

But this is an ugly workaround. As we all learned when writing our first C code program dealing with int arrays, there should be no hard-coded value for anything (at that time, I believe the teacher was referring to the define directive that decided how large the array was).

To fix it properly, we need to stop expecting other people's system to work like ours. Since the default behavior is undefined because of a sysctl value, we can force the behavior we want with a simple setsockopt call. Indeed !

This brings us to the morale of this story :

  int arg = 1;
  setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, &arg, sizeof(arg));

which makes sure that the socket listens only on IPv6. Which prevents you from receiving a bind error in case another program listens on the same port in the IPv4 address space, and at the same time allows to receive IPv4 and IPv6 traffic on two separate sockets if you so wish.

No comments: