Ported tun_device de-/initialization to FreeBSD
[strongswan.git] / src / libstrongswan / utils / tun_device.c
index 889fe62..36f3359 100644 (file)
@@ -3,6 +3,7 @@
  * Copyright (C) 2012 Giuliano Grassi
  * Copyright (C) 2012 Ralf Sager
  * Hochschule fuer Technik Rapperswil
+ * Copyright (C) 2012 Martin Willi
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <linux/if.h>
+#include <net/if.h>
+
+#ifdef __APPLE__
+#include <net/if_utun.h>
+#include <netinet/in_var.h>
+#include <sys/kern_control.h>
+#elif defined(__linux__)
 #include <linux/if_tun.h>
+#else
+#include <net/if_tun.h>
+#endif
 
 #include "tun_device.h"
 
@@ -127,6 +137,14 @@ METHOD(tun_device_t, set_address, bool,
                         this->if_name, strerror(errno));
                return FALSE;
        }
+#ifdef __APPLE__
+       if (ioctl(this->sock, SIOCSIFDSTADDR, &ifr) < 0)
+       {
+               DBG1(DBG_LIB, "failed to set dest address on %s: %s",
+                        this->if_name, strerror(errno));
+               return FALSE;
+       }
+#endif /* __APPLE__ */
 
        set_netmask(&ifr, family, netmask);
 
@@ -176,6 +194,8 @@ METHOD(tun_device_t, set_mtu, bool,
 
        if (ioctl(this->sock, SIOCSIFMTU, &ifr) < 0)
        {
+               DBG1(DBG_LIB, "failed to set MTU on %s: %s", this->if_name,
+                        strerror(errno));
                return FALSE;
        }
        this->mtu = mtu;
@@ -269,6 +289,20 @@ METHOD(tun_device_t, destroy, void,
        if (this->tunfd > 0)
        {
                close(this->tunfd);
+#ifdef __FreeBSD__
+               /* tun(4) says the following: "These network interfaces persist until
+                * the if_tun.ko module is unloaded, or until removed with the
+                * ifconfig(8) command."  So simply closing the FD is not enough. */
+               struct ifreq ifr;
+
+               memset(&ifr, 0, sizeof(ifr));
+               strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
+               if (ioctl(this->sock, SIOCIFDESTROY, &ifr) < 0)
+               {
+                       DBG1(DBG_LIB, "failed to destroy %s: %s", this->if_name,
+                                strerror(errno));
+               }
+#endif /* __FreeBSD__ */
        }
        if (this->sock > 0)
        {
@@ -278,18 +312,70 @@ METHOD(tun_device_t, destroy, void,
 }
 
 /**
- * Allocate a TUN device
+ * Initialize the tun device
  */
-static int tun_alloc(char dev[IFNAMSIZ])
+static bool init_tun(private_tun_device_t *this, const char *name_tmpl)
 {
+#ifdef __APPLE__
+
+       struct ctl_info info;
+       struct sockaddr_ctl addr;
+       socklen_t size = IFNAMSIZ;
+
+       memset(&info, 0, sizeof(info));
+       memset(&addr, 0, sizeof(addr));
+
+       this->tunfd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
+       if (this->tunfd < 0)
+       {
+               DBG1(DBG_LIB, "failed to open tundevice PF_SYSTEM socket: %s",
+                        strerror(errno));
+               return FALSE;
+       }
+
+       /* get a control identifier for the utun kernel extension */
+       strncpy(info.ctl_name, UTUN_CONTROL_NAME, strlen(UTUN_CONTROL_NAME));
+       if (ioctl(this->tunfd, CTLIOCGINFO, &info) < 0)
+       {
+               DBG1(DBG_LIB, "failed to ioctl tundevice: %s", strerror(errno));
+               close(this->tunfd);
+               return FALSE;
+       }
+
+       addr.sc_id = info.ctl_id;
+       addr.sc_len = sizeof(addr);
+       addr.sc_family = AF_SYSTEM;
+       addr.ss_sysaddr = AF_SYS_CONTROL;
+       /* allocate identifier dynamically */
+       addr.sc_unit = 0;
+
+       if (connect(this->tunfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
+       {
+               DBG1(DBG_LIB, "failed to connect tundevice: %s", strerror(errno));
+               close(this->tunfd);
+               return FALSE;
+       }
+       if (getsockopt(this->tunfd, SYSPROTO_CONTROL, UTUN_OPT_IFNAME,
+                                  this->if_name, &size) < 0)
+       {
+               DBG1(DBG_LIB, "getting tundevice name failed: %s", strerror(errno));
+               close(this->tunfd);
+               return FALSE;
+       }
+       return TRUE;
+
+#elif defined(IFF_TUN)
+
        struct ifreq ifr;
-       int fd;
 
-       fd = open("/dev/net/tun", O_RDWR);
-       if (fd < 0)
+       strncpy(this->if_name, name_tmpl ?: "tun%d", IFNAMSIZ);
+       this->if_name[IFNAMSIZ-1] = '\0';
+
+       this->tunfd = open("/dev/net/tun", O_RDWR);
+       if (this->tunfd < 0)
        {
                DBG1(DBG_LIB, "failed to open /dev/net/tun: %s", strerror(errno));
-               return fd;
+               return FALSE;
        }
 
        memset(&ifr, 0, sizeof(ifr));
@@ -297,16 +383,42 @@ static int tun_alloc(char dev[IFNAMSIZ])
        /* TUN device, no packet info */
        ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
 
-       strncpy(ifr.ifr_name, dev, IFNAMSIZ);
-
-       if (ioctl(fd, TUNSETIFF, (void*)&ifr) < 0)
+       strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ);
+       if (ioctl(this->tunfd, TUNSETIFF, (void*)&ifr) < 0)
        {
                DBG1(DBG_LIB, "failed to configure TUN device: %s", strerror(errno));
-               close(fd);
-               return -1;
+               close(this->tunfd);
+               return FALSE;
+       }
+       strncpy(this->if_name, ifr.ifr_name, IFNAMSIZ);
+       return TRUE;
+
+#else /* !IFF_TUN */
+
+       /* this works on FreeBSD and might also work on Linux with older TUN
+        * driver versions (no IFF_TUN) */
+       char devname[IFNAMSIZ];
+       int i;
+
+       if (name_tmpl)
+       {
+               DBG1(DBG_LIB, "arbitrary naming of TUN devices is not supported");
        }
-       strncpy(dev, ifr.ifr_name, IFNAMSIZ);
-       return fd;
+
+       for (i = 0; i < 256; i++)
+       {
+               snprintf(devname, IFNAMSIZ, "/dev/tun%d", i);
+               this->tunfd = open(devname, O_RDWR);
+               if (this->tunfd > 0)
+               {       /* for ioctl(2) calls only the interface name is used */
+                       snprintf(this->if_name, IFNAMSIZ, "tun%d", i);
+                       break;
+               }
+               DBG1(DBG_LIB, "failed to open %s: %s", this->if_name, strerror(errno));
+       }
+       return this->tunfd > 0;
+
+#endif /* !__APPLE__ */
 }
 
 /*
@@ -331,13 +443,9 @@ tun_device_t *tun_device_create(const char *name_tmpl)
                .sock = -1,
        );
 
-       strncpy(this->if_name, name_tmpl ?: "tun%d", IFNAMSIZ);
-       this->if_name[IFNAMSIZ-1] = '\0';
-
-       this->tunfd = tun_alloc(this->if_name);
-       if (this->tunfd < 0)
+       if (!init_tun(this, name_tmpl))
        {
-               destroy(this);
+               free(this);
                return NULL;
        }
        DBG1(DBG_LIB, "created TUN device: %s", this->if_name);