quicktun

I have just been playing with quicktun, which is a program for setting up an encrypted VPN connection between two machines using the Linux tun device. It does exactly what I needed something to do without any additional bloat or complication from including 101 other things I don't. It should be very simple. It wasn't. I came across quite a few things. Most of them turned out to be things other people had also come across and asked about on the internet, usually without getting any useful answer. Fortunately I was eventually able to come up with my own.

This page refers to the Debian package of quicktun-2.2.6-2, which is current at the time of writing.

TUNSETIFF ioctl failed: Invalid argument

When you try and start it up, it will probably fail with TUNSETIFF ioctl failed: Invalid argument. You will fume and swear and modify the code to print out the exact values of the arguments to the ioctl and double check them with gdb. You will find that they are perfectly valid. Yet they do not work.

In the Linux kernel source there is a file Documentation/networking/tuntap.txt which has remained unchanged for years. It contains a snippet of sample code which demonstrates the use of that ioctl. The quicktun code is basically the same, which isn't surprising since that file seems to be all the documentation there is anywhere about those devices. There are also people on the internet who have tried to run that sample code snippet asking why it doesn't work and not being told anything useful. So I knocked up a quick wrapper to let me run the sample code, passing it various things or nothing at all for the device name, and see what it did.

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <unistd.h> #include <linux/if.h> #include <linux/if_tun.h> void errex(char *s, int e) { fprintf(stderr, "%s (%d) : %s (%d)\n", s, e, strerror(errno), errno); exit(1); } int tun_alloc(char *dev) /* see Documentation/networking/tuntap.txt */ { struct ifreq ifr; int fd, err; if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) errex("Cannot open tun", fd); memset(&ifr, 0, sizeof(ifr)); /* Flags: IFF_TUN - TUN device (no Ethernet headers) * IFF_TAP - TAP device * IFF_NO_PI - Do not provide packet information */ ifr.ifr_flags = IFF_TUN; if( *dev ) strncpy(ifr.ifr_name, dev, IFNAMSIZ); printf("%d %x %s\n", fd, ifr.ifr_flags, ifr.ifr_name); if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) errex("Cannot set TUNSETIFF", err); strcpy(dev, ifr.ifr_name); printf("Success: %d %s\n", fd, ifr.ifr_name); return fd; } int main(int argc, char *argv[]) { char buf[256]; memset(buf, 0, 256); if (argc > 1) strncpy(buf, argv[1], 256); close(tun_alloc(buf)); }

What I concluded from running this with a variety of device names and under various conditions was that basically the kernel's tun/tap implementation is flaky as fuck. The code does indeed fail with invalid argument more often than not and there seems to be no reason whatsoever why it should. Of course it still remains possible that this is actually expected behaviour, but until the kernel code authors WRITE SOME FUCKING DOCUMENTATION (THIS IS A HINT) then I don't know what they think we should expect.

The basic pattern seems to be that the more "untouched" the tun device is, the more likely the code is to succeed. It is better to allow the utility you use to create the device in the first place to auto-modprobe the tun module than to modprobe it by hand yourself first, even if you don't touch it in any other way in between. (If you don't have it as a module, you're probably fucked.) Once it has failed once, it will probably always fail on every subsequent attempt, unless you ifdown --force the device and rmmod -f the module and then start again from scratch. And it makes a difference which of the various possibilities for the aforementioned utility you choose, because if you use the wrong one you'll get the invalid argument error every time.

Included in the debian package is an if-pre-up.d file containing the following code:

if [ -x /usr/sbin/openvpn ]; then /usr/sbin/openvpn --mktun --dev "${IFACE}" --dev-type "${DEVTYPE}" --user quicktun elif [ -x /sbin/ip ] && /sbin/ip tuntap 2>&1 >/dev/null; then /sbin/ip tuntap add dev "${IFACE}" mode "${DEVTYPE}" user quicktun elif [ -x /usr/bin/tunctl ]; then /usr/bin/tunctl -u quicktun -t "${IFACE}" else echo "Unable to pre-create tun/tap interface. Run QuickTun as root by setting QT_NO_PRECREATE." fi

This gives us three suggestions for what can be used to initially create the device. I didn't bother to try openvpn since it seems somewhat daft to install one large and many-featured VPN package merely in order to start up some other VPN which it could just as well provide itself. So the choice was between ip and tunctl, which brings us to:

Object "tuntap" is unknown, try "ip help".

This is what you get when you try and use ip that makes you go "oh, fucking arseholes" and try tunctl instead. Don't. It doesn't work. The problem is that not all versions of ip work either but you don't know that until you try it. You can fix that by installing a more recent version of iproute. Version 20080725-2 from lenny doesn't have it, but version 20100519-3 from squeeze does.

Return from subroutine now and back to the matter of initially creating the tun device. You can't use tunctl because although it does create the device, it then also does something else which fucks it up and guarantees that when you try and set the device up - either with the standalone experimental implementation of the kernel documentation sample code or with the pretty well identical version incorporated in quicktun - the TUNSETIFF ioctl will always fail with invalid argument. You have to use ip, which does not do this extra thing and does leave it in a sufficiently untouched state that the ioctl will still work.

So basically I found that if you initially create the device using ip and not anything else; do not modprobe the tun module yourself but let it auto-modprobe it when it needs it; don't touch the device in any way between creating it and quicktun getting its hands on it; and don't try and reuse a device which has already thrown an error, but instead delete it and delete the module and start again from a blank slate; then it will probably work. If you do any of it differently from that, it almost certainly won't work. While I was trying to look up anything useful there might be to find on this matter, I came across people being equally baffled by the same invalid argument error when trying to use some completely different tunnel-creating package, and as it seems to be a kernel-level problem in the first place, it may well be that the same kind of procedure would help in those cases too.

It doesn't work on Debian

In fact it does, it's just that the way you pass it command-line parameters is not well described in the first place, and then the Debianised version fucks it up completely in a way that pretty much isn't described at all.

On Debian you basically don't give it actual command line parameters at all in the normal way. Instead you do it by setting environment variables which have the same names as the parameters only all in capitals and with IF_QT_ stuck on the front. This is because the Debianised form is designed to be run automatically from /etc/network/interfaces, where you specify the parameters in that file with qt_ stuck on the front so the names don't clash with anything already existing, and then the environment that quicktun is automatically run in reads the parameters from that file, turns the names into capitals, sticks IF_ on the front, and exports them as environment variables. So the Debian version gets its parameters by looking for environment variables with names like that. For some reason it does that instead of looking for those parameters on the command line instead of doing both. Fuck knows why, and since you have to inspect the source code to find this out, it makes it a pain in the arse trying to run it by hand to test it. (Actually the source code does still look as if it ought to accept them if you enter them in a strange format, but it still didn't work when I tried it.)

Not enough information: "dev" argument is required.

Included in the package is a very helpful sample /etc/network/interfaces entry, in the README.Debian file, which looks like this:

quicktun for Debian ------------------- Below you can find some sample use case for quicktun. Scenario: Host1 behind NAT, not publicly reachable. Host2 having public reachable address. Configuration on Host1: auto tun0 iface tun0 inet static address 10.0.0.1 # local address of Host1 pointopoint 10.0.0.2 # this will be "local" address of Host2 netmask 255.255.255.255 # self explanatory qt_remote_address <some.real.ip> # put here real address of machine that will act as server qt_local_address 0.0.0.0 # you can specify real address of Host1 or just use 0.0.0.0 to bind to whatever qt_tun_mode 1 # we're using TUN mode qt_protocol nacltai # and NACLTAI encryption qt_private_key bunchofrandomchars # SECRET part of keypair output from Host1 qt_public_key bunchofrandomchars # PUBLIC part of keypair output from Host2 Configuration on Host2: auto tun0 iface tun0 inet static address 10.0.0.2 # local address of Host2 pointopoint 10.0.0.1 # local address of Host1 netmask 255.255.255.255 # self explanatory qt_remote_address 0.0.0.0 # you can specify real address of Host1 but when it's behind the NAT then just use 0.0.0.0 qt_local_address 185.115.155.253 # local public address of Host2 qt_tun_mode 1 # we're using TUN mode qt_protocol nacltai # and NACLTAI encryption qt_private_key bunchofrandomchars # SECRET part of keypair output from Host2 qt_public_key bunchofrandomchars # PUBLIC part of keypair output from Host1 After launching such configuration you should be able to reach Host1 from Host2 by connecting to 10.0.0.1.

(Note the parameters with qt_ stuck on the front. So for example the command line parameter protocol becomes qt_protocol in /etc/network/interfaces, which in turn becomes IF_QT_PROTOCOL as a variable in the /etc/network/interfaces environment, and that is the form in which the Debian version of quicktun understands parameters.)

It's quite likely that the "Scenario" is a perfect match for what you're actually trying to do with it. It was for me. So the obvious thing to do is to copy and paste the above code verbatim into your /etc/network/interfaces, and then edit the IPs and keys to suit your actual setup. And it doesn't work. From the somewhat strange bloody stupid and completely unhelpful error messages (Not enough information: "dev" argument is required. It's tun0, you stupid bastard, what the fuck are you on about "dev" argument is required?) it appears that it's not taking any notice of the parameters you know you've just given it. Why the fuck not? They're there all right, look at the file.

The parameters in /etc/network/interfaces are being ignored because /etc/network/interfaces is a cunt.

We all know about comments in Linux scripts and config files. They are all full of them, and they all work the same. The # sign and everything after it is ignored. The above pattern configuration looks as if it's as usefully valid as all the other ones like it you've ever come across.

For some fucking stupid reason /etc/network/interfaces doesn't work like everything else. It only treats things as comments if the # is at the beginning of the line. If it's anywhere else the bastard thing treats the whole line as an error. And it doesn't tell you about it, it just lets you wait until you get some totally weird error from something else further down the line. So you can't do the usual helpful and convenient thing of writing half a line of code and then putting a # and using the rest of the line to explain what it does, like you can everywhere else.

Therefore, to make the above sample config into something that works, you have to delete the comments. It is said that you learn something new every day. That may be true, but it is somewhat depressing when the new thing you have just learned is so fucking stupid.

The "salty" encryption mode doesn't work: Encryption key calculation failed

The "salty" encryption mode doesn't work with libsodium version 1.0.9 or later. With earlier versions it does work.

When it first starts up, it obviously doesn't yet have a value for the remote key to use in its first round of making scrambled eggs with the keys. So it uses all zeroes instead. That worked fine for the libsodium version 0.4.5 it was apparently originally designed around. But in version 1.0.9, they changed one of the functions quicktun uses to throw an error if you pass it one of a small number of known pathological sets of values which weaken the resulting cipher. All zeroes is, not too unexpectedly, one of them. So quicktun won't work any more.

The simple way around this is to go here, for example, and get a version of libsodium which is old enough to work. Or you could edit a more recent version and take out the check. And in practical terms this may well be adequate, since the kind of vulnerability involved is described in http://eprint.iacr.org/2017/806.pdf (copy here) and it doesn't look as if it would be possible to actually exploit it in most circumstances. But I'm not really all that happy with deliberately weakening some encryption scheme just to make things easier when I do actually want to encrypt something with it (although I am fine with doing that when some wanker is trying to force encryption down my throat that I never wanted and specifically asked not to have: if I enter an http: URL I do NOT want it forcibly redirected to https:, especially when your fucking certificate is broken so the https: version doesn't fucking work), so I decided to experiment.

It occurred to me that since that initial startup value is inherently bollocks no matter what you put in it, maybe it doesn't have to be all zeroes; maybe it doesn't actually matter what you put in it. So I edited the code to use 32 bytes from /dev/urandom instead. Somewhat amazingly, it worked fine. It didn't get the error any more, of course, but it also didn't fail to understand its own encryption, which I had been slightly afraid it might.

Watching its output with debug turned on, I noticed that when it receives the first packet from the other end after it has started up, it does two more lots of scrambled eggs using its own keys, and when this is the first thing it does after it's done the bit that it was breaking on, it has all zeroes for one of its own key values in the first lot of scrambled eggs. This does not cause an error because the libsodium routine only does the check on one of its parameters and not the other one, and in this instance it's the other one which is all zeroes. Now I don't really have any idea what the fuck is going on there, except that it was quite clearly operating as designed, but I do know that all zeroes is a pathological value for loads and loads of things whereas decently random data pretty much never is. So I thought: I wonder what happens if I test for it being all zeroes and fill it up from /dev/urandom if I find it is? And, bugger me sideways, that worked too. It didn't throw an error and it didn't fail to understand its own encryption. It worked just as it did before except that I no longer had to worry whether this thing that didn't look right actually was worth worrying about or not.

/dev/net/tun

For any of this to work there first needs to exist /dev/net/tun. Possibly some people might find that being created automatically, but that doesn't happen for me. The incantation to create it is:

# mkdir -p /dev/net # mknod /dev/net/tun c 10 200 # chmod a+rw /dev/net/tun

Patch

Here is a patch containing the modification to make it use random values instead of all zeroes, and also one or two other things I found useful while fucking around with it, such as putting a few carriage returns into the description of the "salty" algorithm so I could read it, and changing some of the identical error messages in different places to not be identical any more so I could tell which one it was actually stopping at.

Patch: quicktun.patch.gz




Back to Pigeon's Nest


Be kind to pigeons




Valid HTML 4.01!