diff options
Diffstat (limited to 'sys/net/if_tun.c')
-rw-r--r-- | sys/net/if_tun.c | 690 |
1 files changed, 690 insertions, 0 deletions
diff --git a/sys/net/if_tun.c b/sys/net/if_tun.c new file mode 100644 index 0000000..ff60749 --- /dev/null +++ b/sys/net/if_tun.c @@ -0,0 +1,690 @@ +/* $NetBSD: if_tun.c,v 1.14 1994/06/29 06:36:25 cgd Exp $ */ + +/* + * Copyright (c) 1988, Julian Onions <jpo@cs.nott.ac.uk> + * Nottingham University 1987. + * + * This source may be freely distributed, however I would be interested + * in any changes that are made. + * + * This driver takes packets off the IP i/f and hands them up to a + * user process to have its wicked way with. This driver has it's + * roots in a similar driver written by Phil Cockcroft (formerly) at + * UCL. This driver is based much more on read/write/poll mode of + * operation though. + * + * $FreeBSD$ + */ + +#include "opt_inet.h" + +#include <sys/param.h> +#include <sys/proc.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/filio.h> +#include <sys/sockio.h> +#include <sys/ttycom.h> +#include <sys/poll.h> +#include <sys/signalvar.h> +#include <sys/filedesc.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <sys/conf.h> +#include <sys/uio.h> +#include <sys/vnode.h> +#include <sys/malloc.h> + +#include <net/if.h> +#include <net/netisr.h> +#include <net/route.h> + +#ifdef INET +#include <netinet/in.h> +#include <netinet/in_var.h> +#endif + +#ifdef NS +#include <netns/ns.h> +#include <netns/ns_if.h> +#endif + +#include <net/bpf.h> + +#include <net/if_tunvar.h> +#include <net/if_tun.h> + +static MALLOC_DEFINE(M_TUN, "tun", "Tunnel Interface"); + +static void tunattach __P((void *)); +PSEUDO_SET(tunattach, if_tun); + +static void tuncreate __P((dev_t dev)); + +#define TUNDEBUG if (tundebug) printf +static int tundebug = 0; +SYSCTL_INT(_debug, OID_AUTO, if_tun_debug, CTLFLAG_RW, &tundebug, 0, ""); + +static int tunoutput __P((struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *rt)); +static int tunifioctl __P((struct ifnet *, u_long, caddr_t)); +static int tuninit __P((struct ifnet *)); + +static d_open_t tunopen; +static d_close_t tunclose; +static d_read_t tunread; +static d_write_t tunwrite; +static d_ioctl_t tunioctl; +static d_poll_t tunpoll; + +#define CDEV_MAJOR 52 +static struct cdevsw tun_cdevsw = { + /* open */ tunopen, + /* close */ tunclose, + /* read */ tunread, + /* write */ tunwrite, + /* ioctl */ tunioctl, + /* poll */ tunpoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "tun", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* bmaj */ -1 +}; + +static void +tunattach(dummy) + void *dummy; +{ + + cdevsw_add(&tun_cdevsw); +} + +static void +tuncreate(dev) + dev_t dev; +{ + struct tun_softc *sc; + struct ifnet *ifp; + + dev = make_dev(&tun_cdevsw, minor(dev), + UID_UUCP, GID_DIALER, 0600, "tun%d", lminor(dev)); + + MALLOC(sc, struct tun_softc *, sizeof(*sc), M_TUN, M_WAITOK); + bzero(sc, sizeof *sc); + sc->tun_flags = TUN_INITED; + + ifp = &sc->tun_if; + ifp->if_unit = lminor(dev); + ifp->if_name = "tun"; + ifp->if_mtu = TUNMTU; + ifp->if_ioctl = tunifioctl; + ifp->if_output = tunoutput; + ifp->if_flags = IFF_POINTOPOINT | IFF_MULTICAST; + ifp->if_snd.ifq_maxlen = ifqmaxlen; + ifp->if_softc = sc; + if_attach(ifp); + bpfattach(ifp, DLT_NULL, sizeof(u_int)); + dev->si_drv1 = sc; +} + +/* + * tunnel open - must be superuser & the device must be + * configured in + */ +static int +tunopen(dev, flag, mode, p) + dev_t dev; + int flag, mode; + struct proc *p; +{ + struct ifnet *ifp; + struct tun_softc *tp; + register int error; + + error = suser(p); + if (error) + return (error); + + tp = dev->si_drv1; + if (!tp) { + tuncreate(dev); + tp = dev->si_drv1; + } + if (tp->tun_flags & TUN_OPEN) + return EBUSY; + tp->tun_pid = p->p_pid; + ifp = &tp->tun_if; + tp->tun_flags |= TUN_OPEN; + TUNDEBUG("%s%d: open\n", ifp->if_name, ifp->if_unit); + return (0); +} + +/* + * tunclose - close the device - mark i/f down & delete + * routing info + */ +static int +tunclose(dev, foo, bar, p) + dev_t dev; + int foo; + int bar; + struct proc *p; +{ + register int s; + struct tun_softc *tp; + struct ifnet *ifp; + struct mbuf *m; + + tp = dev->si_drv1; + ifp = &tp->tun_if; + + tp->tun_flags &= ~TUN_OPEN; + tp->tun_pid = 0; + + /* + * junk all pending output + */ + do { + s = splimp(); + IF_DEQUEUE(&ifp->if_snd, m); + splx(s); + if (m) + m_freem(m); + } while (m); + + if (ifp->if_flags & IFF_UP) { + s = splimp(); + if_down(ifp); + splx(s); + } + + if (ifp->if_flags & IFF_RUNNING) { + register struct ifaddr *ifa; + + s = splimp(); + /* find internet addresses and delete routes */ + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) + if (ifa->ifa_addr->sa_family == AF_INET) + rtinit(ifa, (int)RTM_DELETE, + tp->tun_flags & TUN_DSTADDR ? RTF_HOST : 0); + ifp->if_flags &= ~IFF_RUNNING; + splx(s); + } + + funsetown(tp->tun_sigio); + selwakeup(&tp->tun_rsel); + + TUNDEBUG ("%s%d: closed\n", ifp->if_name, ifp->if_unit); + return (0); +} + +static int +tuninit(ifp) + struct ifnet *ifp; +{ + struct tun_softc *tp = ifp->if_softc; + register struct ifaddr *ifa; + + TUNDEBUG("%s%d: tuninit\n", ifp->if_name, ifp->if_unit); + + ifp->if_flags |= IFF_UP | IFF_RUNNING; + getmicrotime(&ifp->if_lastchange); + + for (ifa = ifp->if_addrhead.tqh_first; ifa; + ifa = ifa->ifa_link.tqe_next) { +#ifdef INET + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *si; + + si = (struct sockaddr_in *)ifa->ifa_addr; + if (si && si->sin_addr.s_addr) + tp->tun_flags |= TUN_IASET; + + si = (struct sockaddr_in *)ifa->ifa_dstaddr; + if (si && si->sin_addr.s_addr) + tp->tun_flags |= TUN_DSTADDR; + } +#endif + } + return 0; +} + +/* + * Process an ioctl request. + */ +int +tunifioctl(ifp, cmd, data) + struct ifnet *ifp; + u_long cmd; + caddr_t data; +{ + struct ifreq *ifr = (struct ifreq *)data; + struct tun_softc *tp = ifp->if_softc; + struct ifstat *ifs; + int error = 0, s; + + s = splimp(); + switch(cmd) { + case SIOCGIFSTATUS: + ifs = (struct ifstat *)data; + if (tp->tun_pid) + sprintf(ifs->ascii + strlen(ifs->ascii), + "\tOpened by PID %d\n", tp->tun_pid); + return(0); + case SIOCSIFADDR: + tuninit(ifp); + TUNDEBUG("%s%d: address set\n", + ifp->if_name, ifp->if_unit); + break; + case SIOCSIFDSTADDR: + tuninit(ifp); + TUNDEBUG("%s%d: destination address set\n", + ifp->if_name, ifp->if_unit); + break; + case SIOCSIFMTU: + ifp->if_mtu = ifr->ifr_mtu; + TUNDEBUG("%s%d: mtu set\n", + ifp->if_name, ifp->if_unit); + break; + case SIOCADDMULTI: + case SIOCDELMULTI: + break; + + + default: + error = EINVAL; + } + splx(s); + return (error); +} + +/* + * tunoutput - queue packets from higher level ready to put out. + */ +int +tunoutput(ifp, m0, dst, rt) + struct ifnet *ifp; + struct mbuf *m0; + struct sockaddr *dst; + struct rtentry *rt; +{ + struct tun_softc *tp = ifp->if_softc; + int s; + + TUNDEBUG ("%s%d: tunoutput\n", ifp->if_name, ifp->if_unit); + + if ((tp->tun_flags & TUN_READY) != TUN_READY) { + TUNDEBUG ("%s%d: not ready 0%o\n", ifp->if_name, + ifp->if_unit, tp->tun_flags); + m_freem (m0); + return EHOSTDOWN; + } + + /* BPF write needs to be handled specially */ + if (dst->sa_family == AF_UNSPEC) { + dst->sa_family = *(mtod(m0, int *)); + m0->m_len -= sizeof(int); + m0->m_pkthdr.len -= sizeof(int); + m0->m_data += sizeof(int); + } + + if (ifp->if_bpf) { + /* + * We need to prepend the address family as + * a four byte field. Cons up a dummy header + * to pacify bpf. This is safe because bpf + * will only read from the mbuf (i.e., it won't + * try to free it or keep a pointer to it). + */ + struct mbuf m; + u_int af = dst->sa_family; + + m.m_next = m0; + m.m_len = 4; + m.m_data = (char *)⁡ + + bpf_mtap(ifp, &m); + } + + /* prepend sockaddr? this may abort if the mbuf allocation fails */ + if (tp->tun_flags & TUN_LMODE) { + /* allocate space for sockaddr */ + M_PREPEND(m0, dst->sa_len, M_DONTWAIT); + + /* if allocation failed drop packet */ + if (m0 == NULL){ + s = splimp(); /* spl on queue manipulation */ + IF_DROP(&ifp->if_snd); + splx(s); + ifp->if_oerrors++; + return (ENOBUFS); + } else { + bcopy(dst, m0->m_data, dst->sa_len); + } + } + + switch(dst->sa_family) { +#ifdef INET + case AF_INET: + s = splimp(); + if (IF_QFULL(&ifp->if_snd)) { + IF_DROP(&ifp->if_snd); + m_freem(m0); + splx(s); + ifp->if_collisions++; + return (ENOBUFS); + } + ifp->if_obytes += m0->m_pkthdr.len; + IF_ENQUEUE(&ifp->if_snd, m0); + splx(s); + ifp->if_opackets++; + break; +#endif + default: + m_freem(m0); + return EAFNOSUPPORT; + } + + if (tp->tun_flags & TUN_RWAIT) { + tp->tun_flags &= ~TUN_RWAIT; + wakeup((caddr_t)tp); + } + if (tp->tun_flags & TUN_ASYNC && tp->tun_sigio) + pgsigio(tp->tun_sigio, SIGIO, 0); + selwakeup(&tp->tun_rsel); + return 0; +} + +/* + * the cdevsw interface is now pretty minimal. + */ +static int +tunioctl(dev, cmd, data, flag, p) + dev_t dev; + u_long cmd; + caddr_t data; + int flag; + struct proc *p; +{ + int s; + struct tun_softc *tp = dev->si_drv1; + struct tuninfo *tunp; + + switch (cmd) { + case TUNSIFINFO: + tunp = (struct tuninfo *)data; + if (tunp->mtu < IF_MINMTU) + return (EINVAL); + tp->tun_if.if_mtu = tunp->mtu; + tp->tun_if.if_type = tunp->type; + tp->tun_if.if_baudrate = tunp->baudrate; + break; + case TUNGIFINFO: + tunp = (struct tuninfo *)data; + tunp->mtu = tp->tun_if.if_mtu; + tunp->type = tp->tun_if.if_type; + tunp->baudrate = tp->tun_if.if_baudrate; + break; + case TUNSDEBUG: + tundebug = *(int *)data; + break; + case TUNGDEBUG: + *(int *)data = tundebug; + break; + case TUNSLMODE: + if (*(int *)data) + tp->tun_flags |= TUN_LMODE; + else + tp->tun_flags &= ~TUN_LMODE; + break; + case TUNSIFMODE: + /* deny this if UP */ + if (tp->tun_if.if_flags & IFF_UP) + return(EBUSY); + + switch (*(int *)data) { + case IFF_POINTOPOINT: + tp->tun_if.if_flags |= IFF_POINTOPOINT; + tp->tun_if.if_flags &= ~IFF_BROADCAST; + break; + case IFF_BROADCAST: + tp->tun_if.if_flags &= ~IFF_POINTOPOINT; + tp->tun_if.if_flags |= IFF_BROADCAST; + break; + default: + return(EINVAL); + } + break; + case FIONBIO: + break; + case FIOASYNC: + if (*(int *)data) + tp->tun_flags |= TUN_ASYNC; + else + tp->tun_flags &= ~TUN_ASYNC; + break; + case FIONREAD: + s = splimp(); + if (tp->tun_if.if_snd.ifq_head) { + struct mbuf *mb = tp->tun_if.if_snd.ifq_head; + for( *(int *)data = 0; mb != 0; mb = mb->m_next) + *(int *)data += mb->m_len; + } else + *(int *)data = 0; + splx(s); + break; + case FIOSETOWN: + return (fsetown(*(int *)data, &tp->tun_sigio)); + + case FIOGETOWN: + *(int *)data = fgetown(tp->tun_sigio); + return (0); + + /* This is deprecated, FIOSETOWN should be used instead. */ + case TIOCSPGRP: + return (fsetown(-(*(int *)data), &tp->tun_sigio)); + + /* This is deprecated, FIOGETOWN should be used instead. */ + case TIOCGPGRP: + *(int *)data = -fgetown(tp->tun_sigio); + return (0); + + default: + return (ENOTTY); + } + return (0); +} + +/* + * The cdevsw read interface - reads a packet at a time, or at + * least as much of a packet as can be read. + */ +static int +tunread(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + struct tun_softc *tp = dev->si_drv1; + struct ifnet *ifp = &tp->tun_if; + struct mbuf *m, *m0; + int error=0, len, s; + + TUNDEBUG ("%s%d: read\n", ifp->if_name, ifp->if_unit); + if ((tp->tun_flags & TUN_READY) != TUN_READY) { + TUNDEBUG ("%s%d: not ready 0%o\n", ifp->if_name, + ifp->if_unit, tp->tun_flags); + return EHOSTDOWN; + } + + tp->tun_flags &= ~TUN_RWAIT; + + s = splimp(); + do { + IF_DEQUEUE(&ifp->if_snd, m0); + if (m0 == 0) { + if (flag & IO_NDELAY) { + splx(s); + return EWOULDBLOCK; + } + tp->tun_flags |= TUN_RWAIT; + if((error = tsleep((caddr_t)tp, PCATCH | (PZERO + 1), + "tunread", 0)) != 0) { + splx(s); + return error; + } + } + } while (m0 == 0); + splx(s); + + while (m0 && uio->uio_resid > 0 && error == 0) { + len = min(uio->uio_resid, m0->m_len); + if (len == 0) + break; + error = uiomove(mtod(m0, caddr_t), len, uio); + MFREE(m0, m); + m0 = m; + } + + if (m0) { + TUNDEBUG("Dropping mbuf\n"); + m_freem(m0); + } + return error; +} + +/* + * the cdevsw write interface - an atomic write is a packet - or else! + */ +static int +tunwrite(dev, uio, flag) + dev_t dev; + struct uio *uio; + int flag; +{ + struct tun_softc *tp = dev->si_drv1; + struct ifnet *ifp = &tp->tun_if; + struct mbuf *top, **mp, *m; + int error=0, s, tlen, mlen; + + TUNDEBUG("%s%d: tunwrite\n", ifp->if_name, ifp->if_unit); + + if (uio->uio_resid == 0) + return 0; + + if (uio->uio_resid < 0 || uio->uio_resid > TUNMRU) { + TUNDEBUG("%s%d: len=%d!\n", ifp->if_name, ifp->if_unit, + uio->uio_resid); + return EIO; + } + tlen = uio->uio_resid; + + /* get a header mbuf */ + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) + return ENOBUFS; + mlen = MHLEN; + + top = 0; + mp = ⊤ + while (error == 0 && uio->uio_resid > 0) { + m->m_len = min(mlen, uio->uio_resid); + error = uiomove(mtod (m, caddr_t), m->m_len, uio); + *mp = m; + mp = &m->m_next; + if (uio->uio_resid > 0) { + MGET (m, M_DONTWAIT, MT_DATA); + if (m == 0) { + error = ENOBUFS; + break; + } + mlen = MLEN; + } + } + if (error) { + if (top) + m_freem (top); + return error; + } + + top->m_pkthdr.len = tlen; + top->m_pkthdr.rcvif = ifp; + + if (ifp->if_bpf) { + /* + * We need to prepend the address family as + * a four byte field. Cons up a dummy header + * to pacify bpf. This is safe because bpf + * will only read from the mbuf (i.e., it won't + * try to free it or keep a pointer to it). + */ + struct mbuf m; + u_int af = AF_INET; + + m.m_next = top; + m.m_len = 4; + m.m_data = (char *)⁡ + + bpf_mtap(ifp, &m); + } + +#ifdef INET + s = splimp(); + if (IF_QFULL (&ipintrq)) { + IF_DROP(&ipintrq); + splx(s); + ifp->if_collisions++; + m_freem(top); + return ENOBUFS; + } + IF_ENQUEUE(&ipintrq, top); + splx(s); + ifp->if_ibytes += tlen; + ifp->if_ipackets++; + schednetisr(NETISR_IP); +#endif + return error; +} + +/* + * tunpoll - the poll interface, this is only useful on reads + * really. The write detect always returns true, write never blocks + * anyway, it either accepts the packet or drops it. + */ +static int +tunpoll(dev, events, p) + dev_t dev; + int events; + struct proc *p; +{ + int s; + struct tun_softc *tp = dev->si_drv1; + struct ifnet *ifp = &tp->tun_if; + int revents = 0; + + s = splimp(); + TUNDEBUG("%s%d: tunpoll\n", ifp->if_name, ifp->if_unit); + + if (events & (POLLIN | POLLRDNORM)) { + if (ifp->if_snd.ifq_len > 0) { + TUNDEBUG("%s%d: tunpoll q=%d\n", ifp->if_name, + ifp->if_unit, ifp->if_snd.ifq_len); + revents |= events & (POLLIN | POLLRDNORM); + } else { + TUNDEBUG("%s%d: tunpoll waiting\n", ifp->if_name, + ifp->if_unit); + selrecord(p, &tp->tun_rsel); + } + } + if (events & (POLLOUT | POLLWRNORM)) + revents |= events & (POLLOUT | POLLWRNORM); + + splx(s); + return (revents); +} |