Hi,

inspired by mikeb@'s clonable bpf patch, this slightly more complex
diff implements clonable interface support to tun(4).

The idea is to split the fixed relation between device minor number
(/dev/tunX) and interface unit (ifconfig tunY).  In difference to the
current tun(4) implementation, opening /dev/tun0 will first do nothing
and return an unuseable device.  You first have to call the new
TUNSIFUNIT to assign/request a specified interface unit.

For example, if you want to open the network interface tun13:

        unit = 13;
        if ((fd = open("/dev/tun0", O_RDONLY)) == -1)
                err(1, "failed to open /dev/tun0");
        if (ioctl(fd, TUNSIFUNIT, &unit) == -1)
                err(1, "ioctl failed");

Or if you don't care and just want to open the next available interface:

        if ((fd = open("/dev/tun0", O_RDONLY)) == -1)
                err(1, "failed to open /dev/tun0");
        unit = -1;
        if (ioctl(fd, TUNSIFUNIT, &unit) == -1)
                err(1, "ioctl failed");
        fprintf(stderr, "got tun%d", unit);

You can pre-created an interface with ifconfig and attach to it later:

        # ifconfig tun5 create
        ... unit = 5 ... ioctl(fd, TUNSIFUNIT, &unit) ... -> tun5

But the code will not return a pre-created interface if you're not
explicitely asking for it:

        # ifconfig tun5 create
        ... unit = -1 ... ioctl(fd, TUNSIFUNIT, &unit) ... -> not tun5

Drawback: This diff would require to patch all the existing users of
/dev/tun* in ports and the tree to add the TUNSIFUNIT ioctl.  The
benefit is that you don't have to MAKEDEV all the tuns anymore and can
open up to around 1024 active tun(4) interfaces (with the specinfo
patch).

The concept is a bit similar to Linux's /dev/net/tun (*sorry*) that
also uses an additional initial ioctl to set the interface unit
(I added support for the Linux version to OpenSSH-portable in 2005:
http://anoncvs.mindrot.org/index.cgi/openssh/openbsd-compat/port-tun.c?view=markup)

Notes:
- Does this provide any benefit? (This doesn't have to go anywhere, I
hacked it as a PoC after chatting with mikeb.)
- Would 1024 devices even be enough?  Afaik, some daemons like
npppd(8) can use a single device for multiple connections and don't
seem to be affected by a fixed 1024 limit.
- It is probably nice to have fewer files in /dev/ ;-)
- Similar things could be done for pppx(4) (there is not manpage yet).

Comments?

Reyk

Index: net/if_tun.c
===================================================================
RCS file: /cvs/src/sys/net/if_tun.c,v
retrieving revision 1.112
diff -u -p -r1.112 if_tun.c
--- net/if_tun.c        9 Jul 2011 00:47:18 -0000       1.112
+++ net/if_tun.c        28 Nov 2012 16:07:32 -0000
@@ -87,6 +87,7 @@ struct tun_softc {
        struct selinfo  tun_rsel;       /* read select */
        struct selinfo  tun_wsel;       /* write select (not used) */
        LIST_ENTRY(tun_softc) tun_list; /* all tunnel interfaces */
+       int             tun_minor;
        int             tun_unit;
        uid_t           tun_siguid;     /* uid for process that set tun_pgid */
        uid_t           tun_sigeuid;    /* euid for process that set tun_pgid */
@@ -111,6 +112,7 @@ int tundebug = TUN_DEBUG;
 void   tunattach(int);
 int    tunopen(dev_t, int, int, struct proc *);
 int    tunclose(dev_t, int, int, struct proc *);
+int    tuncreate(dev_t, int);
 int    tun_ioctl(struct ifnet *, u_long, caddr_t);
 int    tun_output(struct ifnet *, struct mbuf *, struct sockaddr *,
            struct rtentry *);
@@ -123,6 +125,7 @@ int tun_clone_create(struct if_clone *, 
 int    tun_create(struct if_clone *, int, int);
 int    tun_clone_destroy(struct ifnet *);
 struct tun_softc *tun_lookup(int);
+struct tun_softc *tun_lookup_cloner(int);
 void   tun_wakeup(struct tun_softc *);
 int    tun_switch(struct tun_softc *, int);
 
@@ -173,6 +176,7 @@ tun_create(struct if_clone *ifc, int uni
                return (ENOMEM);
 
        tp->tun_unit = unit;
+       tp->tun_minor = -1;
        tp->tun_flags = TUN_INITED|TUN_STAYUP;
 
        ifp = &tp->tun_if;
@@ -253,7 +257,18 @@ tun_clone_destroy(struct ifnet *ifp)
 }
 
 struct tun_softc *
-tun_lookup(int unit)
+tun_lookup(int minor)
+{
+       struct tun_softc *tp;
+
+       LIST_FOREACH(tp, &tun_softc_list, tun_list)
+               if (tp->tun_minor == minor)
+                       return (tp);
+       return (NULL);
+}
+
+struct tun_softc *
+tun_lookup_cloner(int unit)
 {
        struct tun_softc *tp;
 
@@ -267,7 +282,7 @@ int
 tun_switch(struct tun_softc *tp, int flags)
 {
        struct ifnet            *ifp = &tp->tun_if;
-       int                      unit, open, r, s;
+       int                      unit, minor, open, r, s;
        struct ifg_list         *ifgl;
        u_int                   ifgr_len;
        char                    *ifgrpnames, *p;
@@ -276,6 +291,7 @@ tun_switch(struct tun_softc *tp, int fla
                return (0);
 
        /* tp will be removed so store unit number */
+       minor = tp->tun_minor;
        unit = tp->tun_unit;
        open = tp->tun_flags & (TUN_OPEN|TUN_NBIO|TUN_ASYNC);
        TUNDEBUG(("%s: switching to layer %d\n", ifp->if_xname,
@@ -302,7 +318,7 @@ tun_switch(struct tun_softc *tp, int fla
        r = tun_create(&tun_cloner, unit, flags);
 
        if (r == 0) {
-               if ((tp = tun_lookup(unit)) == NULL) {
+               if ((tp = tun_lookup_cloner(unit)) == NULL) {
                        /* this should never fail */
                        r = ENXIO;
                        goto abort;
@@ -312,6 +328,7 @@ tun_switch(struct tun_softc *tp, int fla
                ifp = &tp->tun_if;
                for (p = ifgrpnames; p && *p; p += IFNAMSIZ)
                        if_addgroup(ifp, p);
+               tp->tun_minor = minor;
        }
        if (open && r == 0) {
                /* already opened before ifconfig tunX link0 */
@@ -328,41 +345,81 @@ tun_switch(struct tun_softc *tp, int fla
 }
 
 /*
- * tunnel open - must be superuser & the device must be
- * configured in
+ * tunnel create - must be called after opening the device
  */
 int
-tunopen(dev_t dev, int flag, int mode, struct proc *p)
+tuncreate(dev_t dev, int unit)
 {
+       char                     xname[IFNAMSIZ];
        struct tun_softc        *tp;
        struct ifnet            *ifp;
-       int                      error, s;
+       int                      error;
+
+       if ((tp = tun_lookup(minor(dev))) != NULL && tp->tun_unit == unit)
+               return (0);
+       else if (tp != NULL) {
+               /* Use existing interface which is not claimed by any device */
+               if (tp->tun_minor == -1)
+                       goto open;
+               return (EBUSY);
+       }
 
-       if ((tp = tun_lookup(minor(dev))) == NULL) {    /* create on demand */
-               char    xname[IFNAMSIZ];
+       if (unit < 0) {
+               /* XXX This could be slow with many devices */
+               for (unit = 0; unit < (TUNMAX - 1); unit++) {
+                       /*
+                        * Unlike above, we do _not_ use existing tun
+                        * interfaces with tun_minor == -1 here.
+                        */
+                       if ((tp = tun_lookup_cloner(unit)) == NULL)
+                               goto create;
+               }
 
-               snprintf(xname, sizeof(xname), "%s%d", "tun", minor(dev));
-               if ((error = if_clone_create(xname)) != 0)
-                       return (error);
+               return (ENXIO);
+       } else if ((tp = tun_lookup_cloner(unit)) == NULL)
+               goto create;
+       else if (tp->tun_minor == minor(dev))
+               goto open;
+       else
+               return (EBUSY);
 
-               if ((tp = tun_lookup(minor(dev))) == NULL)
-                       return (ENXIO);
-               tp->tun_flags &= ~TUN_STAYUP;
-       }
+ create:
+       snprintf(xname, sizeof(xname), "%s%d", "tun", unit);
+       if ((error = if_clone_create(xname)) != 0)
+               return (error);
 
+       if ((tp = tun_lookup_cloner(unit)) == NULL)
+               return (ENXIO);
+       tp->tun_flags &= ~TUN_STAYUP;
+
+ open:
        if (tp->tun_flags & TUN_OPEN)
-               return (EBUSY);
+               return (0);
 
        ifp = &tp->tun_if;
        tp->tun_flags |= TUN_OPEN;
+       tp->tun_minor = minor(dev);
 
        /* automatically mark the interface running on open */
-       s = splnet();
        ifp->if_flags |= IFF_RUNNING;
        tun_link_state(tp);
-       splx(s);
 
-       TUNDEBUG(("%s: open\n", ifp->if_xname));
+       TUNDEBUG(("%s: create (minor %d)\n", ifp->if_xname, minor(dev)));
+
+       return (0);
+}
+
+/*
+ * tunnel open - must be superuser & the device must be
+ * configured in
+ */
+int
+tunopen(dev_t dev, int flag, int mode, struct proc *p)
+{
+       if (flag & O_EXCL)
+               return (EBUSY); /* No exclusive opens */
+
+       TUNDEBUG(("tun: open (minor %d)\n", minor(dev)));
        return (0);
 }
 
@@ -377,8 +434,10 @@ tunclose(dev_t dev, int flag, int mode, 
        struct tun_softc        *tp;
        struct ifnet            *ifp;
 
-       if ((tp = tun_lookup(minor(dev))) == NULL)
-               return (ENXIO);
+       if ((tp = tun_lookup(minor(dev))) == NULL) {
+               TUNDEBUG(("tun: closed (minor %d)\n", minor(dev)));
+               return (0);
+       }
 
        ifp = &tp->tun_if;
        tp->tun_flags &= ~(TUN_OPEN|TUN_NBIO|TUN_ASYNC);
@@ -392,12 +451,13 @@ tunclose(dev_t dev, int flag, int mode, 
        IFQ_PURGE(&ifp->if_snd);
        splx(s);
 
-       TUNDEBUG(("%s: closed\n", ifp->if_xname));
+       TUNDEBUG(("%s: closed (minor %d)\n", ifp->if_xname, minor(dev)));
 
        if (!(tp->tun_flags & TUN_STAYUP))
                return (if_clone_destroy(ifp->if_xname));
        else {
                tp->tun_pgid = 0;
+               tp->tun_minor = -1;
                selwakeup(&tp->tun_rsel);
        }
 
@@ -639,16 +699,28 @@ tun_wakeup(struct tun_softc *tp)
 int
 tunioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
 {
-       int                      s;
+       int                      s, error;
        struct tun_softc        *tp;
        struct tuninfo          *tunp;
        struct mbuf             *m;
 
-       if ((tp = tun_lookup(minor(dev))) == NULL)
+       if ((tp = tun_lookup(minor(dev))) == NULL && cmd != TUNSIFUNIT)
                return (ENXIO);
 
        s = splnet();
        switch (cmd) {
+       case TUNSIFUNIT:
+               if ((error = tuncreate(dev, *(int *)data)) != 0) {
+                       splx(s);
+                       return (error);
+               }
+               if ((tp = tun_lookup(minor(dev))) == NULL) {
+                       /* this should never fail */
+                       splx(s);
+                       return (ENXIO);
+               }
+               *(int *)data = tp->tun_unit;
+               break;
        case TUNSIFINFO:
                tunp = (struct tuninfo *)data;
                if (tunp->mtu < ETHERMIN || tunp->mtu > TUNMRU) {
Index: net/if_tun.h
===================================================================
RCS file: /cvs/src/sys/net/if_tun.h,v
retrieving revision 1.15
diff -u -p -r1.15 if_tun.h
--- net/if_tun.h        6 Feb 2007 10:49:40 -0000       1.15
+++ net/if_tun.h        28 Nov 2012 16:07:32 -0000
@@ -51,6 +51,11 @@
 /* Maximum receive packet size (hard limit) */
 #define TUNMRU          16384
 
+/* Maximum number of "dynamic" tun interfaces */
+#define TUNMAX         65536
+
+#define TUNSIFUNIT     _IOWR('t', 90, int)
+
 /* iface info */
 struct tuninfo {
        u_int   mtu;
Index: sys/conf.h
===================================================================
RCS file: /cvs/src/sys/sys/conf.h,v
retrieving revision 1.119
diff -u -p -r1.119 conf.h
--- sys/conf.h  23 Aug 2012 06:12:49 -0000      1.119
+++ sys/conf.h  28 Nov 2012 16:07:32 -0000
@@ -291,7 +291,7 @@ extern struct cdevsw cdevsw[];
        dev_init(c,n,open), dev_init(c,n,close), dev_init(c,n,read), \
        dev_init(c,n,write), dev_init(c,n,ioctl), (dev_type_stop((*))) enodev, \
        0, dev_init(c,n,poll), (dev_type_mmap((*))) enodev, \
-       0, 0, dev_init(c,n,kqfilter) }
+       0, D_CLONE, dev_init(c,n,kqfilter) }
 
 /* open, close, ioctl, poll, kqfilter -- XXX should be generic device */
 #define cdev_vscsi_init(c,n) { \
Index: sys/specdev.h
===================================================================
RCS file: /cvs/src/sys/sys/specdev.h,v
retrieving revision 1.31
diff -u -p -r1.31 specdev.h
--- sys/specdev.h       5 Jul 2011 05:37:07 -0000       1.31
+++ sys/specdev.h       28 Nov 2012 16:07:32 -0000
@@ -46,7 +46,7 @@ struct specinfo {
        daddr64_t si_lastr;
        union {
                struct vnode *ci_parent; /* pointer back to parent device */
-               u_int8_t ci_bitmap[8]; /* bitmap of devices cloned off us */
+               u_int8_t ci_bitmap[128]; /* bitmap of devices cloned off us */
        } si_ci;
 };

Reply via email to