/* * if_tap.c, written by Mouse, based largely on NetBSD's if_tun.c 1.51.6.1, * which is marked thus: * *** Copyright (c) 1988, Julian Onions *** 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 its *** 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. * * Any intellectual property rights I (Mouse) have in this work I * hereby release into the public domain. */ #include #include "tap.h" /* Workarounds for bugs in the include files */ #include /* net/pfil.h (from net/if.h) uses u_long */ #include /* net/if.h uses struct sockaddr */ #include /* sys/proc.h uses MAXLOGNAME and MAXCOMLEN */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bpfilter.h" #if NBPFILTER > 0 #include #endif typedef struct tap_softc SOFTC; struct tap_softc { struct ethercom ec; unsigned short int flags; #define TAP_OPEN 0x0001 #define TAP_GONE 0x0002 #define TAP_RWAIT 0x0004 #define TAP_ASYNC 0x0008 #define TAP_NBIO 0x0010 #define TAP_VMNET 0x0020 int refs; int pgrp; struct selinfo rsel; int unit; struct simplelock lock; LIST_ENTRY(tap_softc) list; } ; #define TAPDEBUG if (tapdebug) printf int tapdebug = 0; unsigned char base_enaddr[6] = { 0, 0, 0, 0, 0, 0 }; /* Wish we didn't have to, but pulling into userland code that includes is a bit much. */ #if ETHER_ADDR_LEN != 6 #error ETHER_ADDR_LEN value inconsistent with userland interface #endif /* XXX LIST_HEAD demands a struct tag, rather than a type(!!) */ LIST_HEAD(,tap_softc) tap_softc_list; static struct simplelock tap_softc_lock; void tapattach(int); /* called from pseudo-device config machinery */ cdev_decl(tap); /* pointed to by cdevsw */ static void wakeup_readers(SOFTC *t) { if (t->flags & TAP_RWAIT) { t->flags &= ~TAP_RWAIT; wakeup(t); } if ((t->flags & TAP_ASYNC) && t->pgrp) { if (t->pgrp > 0) { gsignal(t->pgrp,SIGIO); } else { struct proc *p; p = pfind(-t->pgrp); if (p) psignal(p,SIGIO); } } selwakeup(&t->rsel); } /* We don't _want_ init or stop routines, but ether_ioctl requires them. */ static int tap_init(struct ifnet *ifp __attribute__((__unused__))) { return(0); } static void tap_stop(struct ifnet *ifp __attribute__((__unused__)), int onoff __attribute__((__unused__))) { } static void tap_start(struct ifnet *ifp) { struct mbuf *m; SOFTC *t; t = ifp->if_softc; simple_lock(&t->lock); if (t->flags & TAP_OPEN) { IFQ_POLL(&ifp->if_snd,m); if (m) wakeup_readers(ifp->if_softc); } else { while (1) { IF_DEQUEUE(&ifp->if_snd,m); if (! m) break; #if NBPFILTER > 0 if (ifp->if_bpf) bpf_mtap(ifp->if_bpf,m); #endif m_freem(m); } } simple_unlock(&t->lock); } static int tap_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data) { SOFTC *t; int s; int e; t = ifp->if_softc; simple_lock(&t->lock); s = splnet(); e = ether_ioctl(ifp,cmd,data); if (ifp->if_flags & IFF_UP) { if (t->flags & TAP_OPEN) ifp->if_flags |= IFF_RUNNING; } else { ifp->if_flags &= ~IFF_RUNNING; } if (e == ENETRESET) e = 0; splx(s); simple_unlock(&t->lock); return(e); } static void tap_attach_sc(SOFTC *sc) { struct ifnet *ifp; unsigned char enaddr[ETHER_ADDR_LEN]; int u; int i; ifp = &sc->ec.ec_if; sc->flags = 0; ifp->if_softc = sc; ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; ifp->if_init = &tap_init; ifp->if_start = &tap_start; ifp->if_stop = &tap_stop; ifp->if_ioctl = &tap_ioctl; IFQ_SET_READY(&ifp->if_snd); sc->ec.ec_capabilities |= ETHERCAP_VLAN_MTU | ETHERCAP_JUMBO_MTU; memset(&enaddr[0],0,ETHER_ADDR_LEN); u = sc->unit; for (i=6-1;i>=0;i--) { u += base_enaddr[i]; enaddr[i] = u & 0xff; u >>= 8; } enaddr[0] = (enaddr[0] & ~1) | 2; if_attach(ifp); ether_ifattach(ifp,&enaddr[0]); sc->refs = 1; } static int tap_clone_create(struct if_clone *ifc, int unit) { SOFTC *sc; sc = malloc(sizeof(SOFTC),M_DEVBUF,M_WAITOK); memset(sc,0,sizeof(SOFTC)); snprintf(&sc->ec.ec_if.if_xname[0],sizeof(sc->ec.ec_if.if_xname),"%s%d",ifc->ifc_name,unit); sc->unit = unit; simple_lock_init(&sc->lock); tap_attach_sc(sc); simple_lock(&tap_softc_lock); LIST_INSERT_HEAD(&tap_softc_list,sc,list); simple_unlock(&tap_softc_lock); return(0); } static void tap_ref(SOFTC *t) { t->refs ++; if (t->refs < 0) panic("tap_deref: refcnt overflow"); } static void tap_deref(SOFTC *t) { simple_lock(&t->lock); t->refs --; if (t->refs < 0) panic("tap_deref: refcnt underflow"); if (t->refs < 1) { simple_lock(&t->lock); free(t,M_DEVBUF); } else { simple_lock(&t->lock); } } static void tap_clone_destroy(struct ifnet *ifp) { SOFTC *t; t = ifp->if_softc; simple_lock(&tap_softc_lock); simple_lock(&t->lock); LIST_REMOVE(t,list); simple_unlock(&t->lock); simple_unlock(&tap_softc_lock); wakeup_readers(t); #if NBPFILTER > 0 bpfdetach(ifp); #endif if_detach(ifp); t->flags |= TAP_GONE; tap_deref(t); } static SOFTC *lookup_unit(int u) { SOFTC *t; simple_lock(&tap_softc_lock); LIST_FOREACH(t,&tap_softc_list,list) { if (u == t->unit) { simple_lock(&t->lock); break; } } simple_unlock(&tap_softc_lock); return(t); } static struct if_clone tap_cloner = IF_CLONE_INITIALIZER("tap",tap_clone_create,tap_clone_destroy); void tapattach(int arg __attribute__((__unused__))) { simple_lock_init(&tap_softc_lock); LIST_INIT(&tap_softc_list); if_clone_attach(&tap_cloner); } int tapopen(dev_t dev, int flag, int mode, struct proc *p) { int e; SOFTC *t; int u; u = minor(dev); e = suser(p->p_ucred,&p->p_acflag); if (e) return(e); if (NTAP < 1) return(ENXIO); if (u == 0) { printf("tapopen: control device\n"); return(0); } u --; t = lookup_unit(u); if (! t) { tap_clone_create(&tap_cloner,u); t = lookup_unit(u); if (! t) return(ENXIO); /* is this possible? */ } if (t->flags & TAP_OPEN) { simple_unlock(&t->lock); return(EBUSY); } t->flags |= TAP_OPEN; if (t->ec.ec_if.if_flags & IFF_UP) t->ec.ec_if.if_flags |= IFF_RUNNING; printf("tapopen: opened %s\n",&t->ec.ec_if.if_xname[0]); simple_unlock(&t->lock); return(0); } int tapclose(dev_t dev, int flag, int mode, struct proc *p) { SOFTC *t; int s; int u; u = minor(dev); if (u == 0) { printf("tapclose: control device\n"); return(0); } u --; t = lookup_unit(u); if (! t) return(0); s = splnet(); IFQ_PURGE(&t->ec.ec_if.if_snd); splx(s); if (t->ec.ec_if.if_flags & IFF_UP) { s = splnet(); if_down(&t->ec.ec_if); splx(s); } t->pgrp = 0; selwakeup(&t->rsel); t->flags &= ~TAP_OPEN; t->ec.ec_if.if_flags &= ~IFF_RUNNING; printf("tapclose: closed %s\n",&t->ec.ec_if.if_xname[0]); simple_unlock(&t->lock); return(0); } int tapread(dev_t dev, struct uio *uio, int ioflag) { int u; SOFTC *t; int s; struct mbuf *m; int total; int e; u = minor(dev); if (u == 0) { printf("tapread: control device\n"); if (uio->uio_offset != 0) return(0); if (uio->uio_resid < sizeof(unsigned int)) return(0); { unsigned int flags; flags = TAPF__ALL; e = uiomove(&flags,sizeof(flags),uio); if (e) return(e); } if (uio->uio_resid < sizeof(struct tapf_mac)) return(0); { struct tapf_mac m; bzero(&m,sizeof(m)); bcopy(&base_enaddr[0],&m.mac[0],6); e = uiomove(&m,sizeof(m),uio); if (e) return(e); } return(0); } u --; t = lookup_unit(u); if (! t) return(ENXIO); tap_ref(t); s = splnet(); while (1) { IFQ_DEQUEUE(&t->ec.ec_if.if_snd,m); if (m) break; if (t->flags & TAP_NBIO) { splx(s); simple_unlock(&t->lock); return(EWOULDBLOCK); } t->flags |= TAP_RWAIT; simple_unlock(&t->lock); if (tsleep(t,PZERO|PCATCH,"tapread",0)) { splx(s); return(EINTR); } else { if (t->flags & TAP_GONE) { simple_unlock(&t->lock); tap_deref(t); return(ENXIO); } } } #if NBPFILTER > 0 if (t->ec.ec_if.if_bpf) bpf_mtap(t->ec.ec_if.if_bpf,m); #endif splx(s); e = 0; total = 0; while (m && (uio->uio_resid > 0) && !e) { int len; struct mbuf *m2; len = min(uio->uio_resid,m->m_len); if (len) e = uiomove(mtod(m,caddr_t),len,uio); total += len; MFREE(m,m2); m = m2; } if (m) { m_freem(m); } else if (!e && (total < ETHER_MIN_LEN)) { static char z[ETHER_MIN_LEN] = { 0 }; int n; n = ETHER_MIN_LEN - total; if (n > uio->uio_resid) n = uio->uio_resid; if (n) e = uiomove(&z[0],n,uio); } if (e) t->ec.ec_if.if_oerrors ++; simple_unlock(&t->lock); tap_deref(t); return(e); } int tapwrite(dev_t dev, struct uio *uio, int ioflag) { int u; SOFTC *t; struct mbuf *pkt; struct mbuf **mt; struct mbuf *m; int mlen; int e; unsigned char etherdest[6]; u = minor(dev); if (u == 0) { unsigned int flags; unsigned int flagtmp; unsigned int bit; struct tapf_mac mac; printf("tapwrite: control device\n"); if (uio->uio_resid < sizeof(unsigned int)) return(EMSGSIZE); e = uiomove(&flags,sizeof(unsigned int),uio); if (e) return(e); if (flags & ~TAPF__ALL) return(EINVAL); for (flagtmp=flags;flagtmp;flagtmp&=flagtmp-1) { bit = flagtmp & ~(flagtmp-1); switch (bit) { case TAPF_MAC: if (uio->uio_resid < sizeof(mac)) return(EMSGSIZE); e = uiomove(&mac,sizeof(mac),uio); if (e) return(e); break; default: panic("tapwrite: unexpected bit %#x set",bit); break; } } for (flagtmp=flags;flagtmp;flagtmp&=flagtmp-1) { bit = flagtmp & ~(flagtmp-1); switch (bit) { case TAPF_MAC: bcopy(&mac.mac[0],&base_enaddr[0],6); break; } } return(0); } u --; t = lookup_unit(u); if (! t) return(ENXIO); if ( (uio->uio_resid < ETHER_MIN_LEN) || (uio->uio_resid > ETHER_MAX_LEN_JUMBO) ) { simple_unlock(&t->lock); return(EMSGSIZE); } pkt = 0; mt = &pkt; while (uio->uio_resid) { if (pkt) { MGET(m,M_WAIT,MT_DATA); mlen = MLEN; } else { MGETHDR(m,M_WAIT,MT_DATA); m->m_pkthdr.rcvif = &t->ec.ec_if; m->m_pkthdr.len = 0; mlen = MHLEN; } if (! m) { m_freem(pkt); simple_unlock(&t->lock); return(ENOBUFS); } if (uio->uio_resid > mlen) { MCLGET(m,M_WAIT); if (! (m->m_flags & M_EXT)) { m_freem(pkt); MFREE(m,m); simple_unlock(&t->lock); return(ENOBUFS); } mlen = MCLBYTES; } *mt = m; mt = &m->m_next; if (mlen > uio->uio_resid) mlen = uio->uio_resid; m->m_len = mlen; pkt->m_pkthdr.len += mlen; e = uiomove(mtod(m,void *),mlen,uio); if (e) { m_freem(pkt); simple_unlock(&t->lock); return(e); } } if (! (t->ec.ec_if.if_flags & IFF_PROMISC)) { bcopy(mtod(pkt,void *),ðerdest[0],6); if (etherdest[0] & 1) { if (! (t->ec.ec_if.if_flags & IFF_ALLMULTI)) { struct ether_multistep step; struct ether_multi *enm; if (bcmp(ðerdest[0],ðerbroadcastaddr[0],6)) { ETHER_FIRST_MULTI(step,&t->ec,enm); while (enm) { if ( (memcmp(&enm->enm_addrlo[0],ðerdest[0],6) <= 0) && (memcmp(ðerdest[0],&enm->enm_addrhi[0],6) <= 0) ) break; ETHER_NEXT_MULTI(step,enm) } if (! enm) { m_freem(pkt); simple_unlock(&t->lock); return(0); } } } } else { if (bcmp(ðerdest[0],LLADDR(t->ec.ec_if.if_sadl),6)) { m_freem(pkt); simple_unlock(&t->lock); return(0); } } } #if NBPFILTER > 0 if (t->ec.ec_if.if_bpf) bpf_mtap(t->ec.ec_if.if_bpf,pkt); #endif { int s; s = splnet(); (*t->ec.ec_if.if_input)(&t->ec.ec_if,pkt); splx(s); } simple_unlock(&t->lock); return(0); } int tapioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p) { SOFTC *t; int u; int s; u = minor(dev); if (u == 0) return(ENOTTY); u --; t = lookup_unit(u); if (! t) return(ENXIO); switch (cmd) { case TAPGDEBUG: *(int *)data = tapdebug; break; case TAPSDEBUG: tapdebug = *(int *)data; break; case FIONBIO: if (*(int *)data) t->flags |= TAP_NBIO; else t->flags &= ~TAP_NBIO; break; case FIOASYNC: if (*(int *)data) t->flags |= TAP_ASYNC; else t->flags &= ~TAP_ASYNC; break; case FIONREAD: s = splnet(); *(int *)data = t->ec.ec_if.if_snd.ifq_head ? t->ec.ec_if.if_snd.ifq_head->m_pkthdr.len : 0; splx(s); break; case TIOCSPGRP: t->pgrp = *(int *)data; break; case TIOCGPGRP: *(int *)data = t->pgrp; break; default: simple_unlock(&t->lock); return(ENOTTY); } simple_unlock(&t->lock); return(0); } int tappoll(dev_t dev, int events, struct proc *p) { SOFTC *t; int u; int s; int revents; u = minor(dev); if (u == 0) return(events); u --; t = lookup_unit(u); if (! t) return(0); revents = events & (POLLOUT|POLLWRNORM); s = splnet(); if (events & (POLLIN|POLLRDNORM)) { if (IFQ_IS_EMPTY(&t->ec.ec_if.if_snd)) { selrecord(p,&t->rsel); } else { revents |= events & (POLLIN|POLLRDNORM); } } splx(s); simple_unlock(&t->lock); return(revents); }