/* * ip tunnel/ethertap device for MacOSX. Common functionality of tap_interface and tun_interface. * * tuntap_interface class definition */ /* * Copyright (c) 2004, 2005, 2006 Mattias Nissler * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided * with the distribution. * 3. The name of the author may not be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "tuntap.h" #if 0 #define dprintf(...) log(LOG_INFO, __VA_ARGS__) #else #define dprintf(...) #endif extern "C" { #include #include #include #include #include #include #include #include #include #include #include #include } extern "C" { /* interface service functions that delegate to the appropriate tuntap_interface instance */ errno_t tuntap_if_output(ifnet_t ifp, mbuf_t m) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_output(m); } if (m != NULL) mbuf_freem_list(m); return ENODEV; } errno_t tuntap_if_ioctl(ifnet_t ifp, u_int32_t cmd, void *arg) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_ioctl(cmd, arg); } return ENODEV; } errno_t tuntap_if_set_bpf_tap(ifnet_t ifp, bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_set_bpf_tap(mode, cb); } return ENODEV; } errno_t tuntap_if_demux(ifnet_t ifp, mbuf_t m, char *header, protocol_family_t *proto) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_demux(m, header, proto); } return ENODEV; } errno_t tuntap_if_framer(ifnet_t ifp, mbuf_t *m, const struct sockaddr *dest, const char *dest_linkaddr, const char *frame_type) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_framer(m, dest, dest_linkaddr, frame_type); } return ENODEV; } errno_t tuntap_if_add_proto(ifnet_t ifp, protocol_family_t proto, const struct ifnet_demux_desc *ddesc, u_int32_t ndesc) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_add_proto(proto, ddesc, ndesc); } return ENODEV; } errno_t tuntap_if_del_proto(ifnet_t ifp, protocol_family_t proto) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_del_proto(proto); } return ENODEV; } errno_t tuntap_if_check_multi(ifnet_t ifp, const struct sockaddr* maddr) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) return ttif->if_check_multi(maddr); } return ENODEV; } void tuntap_if_detached(ifnet_t ifp) { if (ifp != NULL) { tuntap_interface *ttif = (tuntap_interface *) ifnet_softc(ifp); if (ttif != NULL) ttif->if_detached(); } } errno_t tuntap_if_noop_output(ifnet_t, mbuf_t) { return ENODEV; } errno_t tuntap_if_noop_demux(ifnet_t, mbuf_t, char*, protocol_family_t*) { return ENODEV; } errno_t tuntap_if_noop_add_proto(ifnet_t, protocol_family_t, const struct ifnet_demux_desc*, u_int32_t) { return ENODEV; } errno_t tuntap_if_noop_del_proto(ifnet_t, protocol_family_t) { return ENODEV; } } /* extern "C" */ /* tuntap_mbuf_queue */ tuntap_mbuf_queue::tuntap_mbuf_queue() { head = tail = NULL; } tuntap_mbuf_queue::~tuntap_mbuf_queue() { clear(); } bool tuntap_mbuf_queue::enqueue(mbuf_t mb) { if (size == QUEUE_SIZE) return false; mbuf_setnextpkt(mb, NULL); if (head == NULL) head = tail = mb; else { mbuf_setnextpkt(tail, mb); tail = mb; } size++; return true; } mbuf_t tuntap_mbuf_queue::dequeue() { mbuf_t ret; /* check wether there is a packet in the queue */ if (head == NULL) return NULL; /* fetch it */ ret = head; head = mbuf_nextpkt(head); mbuf_setnextpkt(ret, NULL); size--; return ret; } void tuntap_mbuf_queue::clear() { /* free mbufs that are in the queue */ if (head != NULL) mbuf_freem_list(head); head = NULL; tail = NULL; size = 0; } /* tuntap_interface members */ tuntap_interface::tuntap_interface() { /* initialize the members */ ifp = NULL; open = false; block_io = true; dev_handle = NULL; pid = 0; selthreadclear(&rsel); bpf_mode = BPF_MODE_DISABLED; bpf_callback = NULL; bzero(unique_id, UIDLEN); } tuntap_interface::~tuntap_interface() { } bool tuntap_interface::register_chardev(unsigned short major) { /* register character device */ dev_handle = devfs_make_node(makedev(major, unit), DEVFS_CHAR, 0, 0, 0660, "%s%d", family_name, (int) unit); if (dev_handle == NULL) { log(LOG_ERR, "tuntap: could not make /dev/%s%d\n", family_name, (int) unit); return false; } return true; } void tuntap_interface::unregister_chardev() { dprintf("unregistering character device\n"); /* unregister character device */ if (dev_handle != NULL) devfs_remove(dev_handle); dev_handle = NULL; } bool tuntap_interface::register_interface(const struct sockaddr_dl* lladdr, void *bcaddr, u_int32_t bcaddrlen) { struct ifnet_init_params ip; errno_t err; dprintf("register_interface\n"); /* initialize an initialization info struct */ ip.uniqueid_len = UIDLEN; ip.uniqueid = unique_id; ip.name = family_name; ip.unit = unit; ip.family = family; ip.type = type; ip.output = tuntap_if_output; ip.demux = tuntap_if_demux; ip.add_proto = tuntap_if_add_proto; ip.del_proto = tuntap_if_del_proto; ip.check_multi = tuntap_if_check_multi; ip.framer = tuntap_if_framer; ip.softc = this; ip.ioctl = tuntap_if_ioctl; ip.set_bpf_tap = tuntap_if_set_bpf_tap; ip.detach = tuntap_if_detached; ip.event = NULL; ip.broadcast_addr = bcaddr; ip.broadcast_len = bcaddrlen; dprintf("tuntap: tuntap_if_check_multi is at 0x%08x\n", (void*) tuntap_if_check_multi); /* allocate the interface */ err = ifnet_allocate(&ip, &ifp); if (err) { log(LOG_ERR, "tuntap: could not allocate interface for %s%d: %d\n", family_name, (int) unit, err); ifp = NULL; return false; } /* activate the interface */ err = ifnet_attach(ifp, lladdr); if (err) { log(LOG_ERR, "tuntap: could not attach interface %s%d: %d\n", family_name, (int) unit, err); ifnet_release(ifp); ifp = NULL; return false; } dprintf("setting interface flags\n"); /* set interface flags */ ifnet_set_flags(ifp, IFF_RUNNING | IFF_MULTICAST | IFF_SIMPLEX, (u_int16_t) ~0UL); dprintf("flags: %x\n", ifnet_flags(ifp)); return true; } void tuntap_interface::unregister_interface() { errno_t err; dprintf("unregistering network interface\n"); if (ifp != NULL) { /* mark the interface down */ ifnet_set_flags(ifp, 0, IFF_UP | IFF_RUNNING); /* grab the shutdown lock */ shutdown_lock.lock(); /* detach interface */ err = ifnet_detach(ifp); if (err) log(LOG_ERR, "tuntap: error detaching interface %s%d: %d\n", family_name, unit, err); dprintf("interface detaching\n"); /* we will hang here until if_detached releases the lock */ shutdown_lock.lock(); /* release the interface */ ifnet_release(ifp); ifp = NULL; shutdown_lock.unlock(); #if 0 /* Here goes another hack that we need to prevent darwin from crashing. * Unfortunately, not all of the interface service function pointers are cleared on * detach properly. In particular, I had crashes from calling the demux() interface * function after the kernel module had been unloaded. So we need to make sure all * our function pointers get out of the ifnet struct. We do that by allocating the * interface again, this time passing NULLs for the optional functions and pointers * to noop-dummies for the required functions. Then, we release the interface again. */ struct ifnet_init_params ip; errno_t err; dprintf("re-registering interface\n"); /* initialize an initialization info struct */ ip.uniqueid_len = UIDLEN; ip.uniqueid = unique_id; ip.name = family_name; ip.unit = unit; ip.family = family; ip.type = type; ip.output = tuntap_if_noop_output; ip.demux = tuntap_if_noop_demux; ip.add_proto = tuntap_if_noop_add_proto; ip.del_proto = tuntap_if_noop_del_proto; ip.check_multi = NULL; ip.framer = NULL; ip.softc = this; ip.ioctl = NULL; ip.set_bpf_tap = NULL; ip.detach = NULL; ip.event = NULL; ip.broadcast_addr = NULL; ip.broadcast_len = 0; /* re-register the interface */ err = ifnet_allocate(&ip, &ifp); if (err) { log(LOG_ERR, "tuntap: dummy-allocate interface for %s%d: %d\n", family_name, (int) unit, err); /* well, bail out. this error shouldn't occur anyway. */ } else { /* give it back */ ifnet_release(ifp); } #endif /* finally done. */ ifp = NULL; } dprintf("network interface unregistered\n"); } bool tuntap_interface::idle() { return !(open); } void tuntap_interface::notify_bpf(mbuf_t mb, bool out) { auto_lock l(&bpf_lock); if ((out && bpf_mode == BPF_MODE_OUTPUT) || (!out && bpf_mode == BPF_MODE_INPUT) || (bpf_mode == BPF_MODE_INPUT_OUTPUT)) (*bpf_callback)(ifp, mb); } /* character device service methods */ int tuntap_interface::cdev_open(int flags, int devtype, proc_t p) { dprintf("tuntap: cdev_open()\n"); /* grab the lock so that there can only be one thread inside */ auto_lock l(&lock); /* check wether it is already open */ if (open) return EBUSY; /* bring the network interface up */ int error = initialize_netif(); if (error) return error; open = true; pid = proc_pid(p); return 0; } int tuntap_interface::cdev_close(int flags, int devtype, proc_t p) { dprintf("tuntap: cdev_close()\n"); auto_lock l(&lock); if (open) { open = false; /* shutdown the network interface */ shutdown_netif(); /* clear the queue */ send_queue.clear(); /* wakeup the cdev thread and notify selects */ wakeup(this); selwakeup(&rsel); return 0; } return EBADF; } int tuntap_interface::cdev_read(uio_t uio, int ioflag) { auto_lock l(&lock); unsigned int nb = 0; int error; dprintf("tuntap: cdev read\n"); if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) return EIO; /* fetch a new mbuf from the queue if necessary */ mbuf_t cur_mbuf = NULL; while (cur_mbuf == NULL) { dprintf("tuntap: fetching new mbuf\n"); cur_mbuf = send_queue.dequeue(); if (cur_mbuf == NULL) { /* nothing in queue, block or return */ if (!block_io) { dprintf("tuntap: aborting (nbio)\n"); return EWOULDBLOCK; } else { /* block */ dprintf("tuntap: waiting\n"); /* release the lock while waiting */ l.unlock(); error = msleep((caddr_t) this, (lck_mtx_t *) NULL, PZERO | PCATCH, "tuntap", (struct timespec *) NULL); l.lock(); if (error) return error; /* see whether the device was closed in the meantime */ if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) return EIO; } } } /* notify bpf */ notify_bpf(cur_mbuf, true); /* output what we have */ do { dprintf("tuntap: got new mbuf: %p uio_resid: %d\n", cur_mbuf, uio_resid(uio)); /* now we have an mbuf */ int chunk_len = min(mbuf_len(cur_mbuf), uio_resid(uio)); error = uiomove((char *) mbuf_data(cur_mbuf), chunk_len, uio); if (error) { mbuf_freem(cur_mbuf); return error; } nb += chunk_len; dprintf("tuntap: moved %d bytes to userspace uio_resid: %d\n", chunk_len, uio_resid(uio)); /* update cur_mbuf */ cur_mbuf = mbuf_free(cur_mbuf); } while (uio_resid(uio) > 0 && cur_mbuf != NULL); /* update statistics */ ifnet_stat_increment_out(ifp, 1, nb, 0); /* still data left? forget about that ;-) */ if (cur_mbuf != NULL) mbuf_freem(cur_mbuf); dprintf("tuntap: read done\n"); return 0; } int tuntap_interface::cdev_write(uio_t uio, int ioflag) { auto_lock l(&lock); if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) return EIO; dprintf("tuntap: cdev write. uio_resid: %d\n", uio_resid(uio)); /* pack the data into an mbuf chain */ mbuf_t first, mb; /* first we need an mbuf having a header */ mbuf_gethdr(MBUF_WAITOK, MBUF_TYPE_DATA, &first); if (first == NULL) { log(LOG_ERR, "tuntap: could not get mbuf.\n"); return ENOMEM; } mbuf_setlen(first, 0); unsigned int mlen = mbuf_maxlen(first); unsigned int chunk_len; unsigned int copied = 0; int error; /* stuff the data into the mbuf(s) */ mb = first; while (uio_resid(uio) > 0) { /* copy a chunk. enforce mtu (don't know if this is correct behaviour) */ chunk_len = min(ifnet_mtu(ifp), min(uio_resid(uio), mlen)); error = uiomove((caddr_t) mbuf_data(mb), chunk_len, uio); if (error) { log(LOG_ERR, "tuntap: could not copy data from userspace: %d\n", error); mbuf_freem(first); return error; } dprintf("tuntap: copied %d bytes, uio_resid %d\n", chunk_len, uio_resid(uio)); mlen -= chunk_len; mbuf_setlen(mb, mbuf_len(mb) + chunk_len); copied += chunk_len; /* if done, break the loop */ if (uio_resid(uio) <= 0 || copied >= ifnet_mtu(ifp)) break; /* allocate a new mbuf if the current is filled */ if (mlen == 0) { mbuf_t next; mbuf_get(MBUF_WAITOK, MBUF_TYPE_DATA, &next); if (next == NULL) { log(LOG_ERR, "tuntap: could not get mbuf.\n"); mbuf_freem(first); return ENOMEM; } mbuf_setnext(mb, next); mb = next; mbuf_setlen(mb, 0); mlen = mbuf_maxlen(mb); } } /* fill in header info */ mbuf_pkthdr_setrcvif(first, ifp); mbuf_pkthdr_setlen(first, copied); mbuf_pkthdr_setheader(first, mbuf_data(first)); mbuf_set_csum_performed(first, 0, 0); /* update statistics */ ifnet_stat_increment_in(ifp, 1, copied, 0); dprintf("tuntap: mbuf chain constructed. first: %p mb: %p len: %d data: %p\n", first, mb, mbuf_len(first), mbuf_data(first)); /* notify bpf */ notify_bpf(first, false); /* need to adjust the data pointer to point directly behind the linklevel header. The header * itself is later accessed via m_pkthdr.header. Well, if something is ugly, here is it. */ mbuf_adj(first, ifnet_hdrlen(ifp)); /* pass the packet over to the network stack */ error = ifnet_input(ifp, first, NULL); if (error) { log(LOG_ERR, "tuntap: could not input packet into network stack.\n"); mbuf_freem(first); return error; } return 0; } int tuntap_interface::cdev_ioctl(u_long cmd, caddr_t data, int fflag, proc_t p) { auto_lock l(&lock); dprintf("tuntap: cdev ioctl: %d\n", (int) (cmd & 0xff)); switch (cmd) { case FIONBIO: /* set i/o mode */ block_io = *((int *) data) ? false : true; return 0; case FIOASYNC: /* don't allow switching it on */ if (*((int *) data)) return ENOTTY; return 0; } return ENOTTY; } int tuntap_interface::cdev_select(int which, void *wql, proc_t p) { auto_lock l(&lock); int ret = 0; dprintf("tuntap: select. which: %d\n", which); switch (which) { case FREAD: /* check wether data is available */ { if (!send_queue.empty()) ret = 1; else { dprintf("tuntap: select: waiting\n"); selrecord(p, &rsel, wql); } } break; case FWRITE: /* we are always writeable */ ret = 1; } return ret; } /* interface service methods */ errno_t tuntap_interface::if_output(mbuf_t m) { mbuf_t pkt; dprintf("tuntap: if output\n"); /* just to be sure */ if (m == NULL) return 0; if (!open || ifp == NULL || !(ifnet_flags(ifp) & IFF_UP)) { mbuf_freem_list(m); return EHOSTDOWN; } /* check whether packet has a header */ if ((mbuf_flags(m) & MBUF_PKTHDR) == 0) { log(LOG_ERR, "tuntap: packet to be output has no mbuf header.\n"); mbuf_freem_list(m); return EINVAL; } /* put the packet(s) into the output queue */ while (m != NULL) { /* keep pointer, iterate */ pkt = m; m = mbuf_nextpkt(m); mbuf_setnextpkt(pkt, NULL); auto_lock l(&lock); if (!send_queue.enqueue(pkt)) { mbuf_freem_list(pkt); return ENOBUFS; } } /* wakeup the cdev thread and notify selects */ wakeup(this); selwakeup(&rsel); return 0; } errno_t tuntap_interface::if_ioctl(u_int32_t cmd, void *arg) { dprintf("tuntap: if ioctl: %d\n", (int) (cmd & 0xff)); switch (cmd) { case SIOCSIFADDR: dprintf("tuntap: if_ioctl: SIOCSIFADDR\n"); /* Unfortunately, ifconfig sets the address family field of an INET netmask * to zero. However, this makes mDNSresponder ignore the interface. Fix that * here. This one is of the category "ugly workaround". Dumb Darwin... * * Btw. If you configure other network interfaces using ifconfig, you run * into the same problem. I still don't know how to make the tap devices * show up in the network configuration panel... */ struct ifaddr *ifa = (struct ifaddr *) arg; if (ifa->ifa_netmask != NULL && ifa->ifa_addr != NULL) { dprintf("tuntap: if_ioctl: addr af %d netmask af %d\n", ifa->ifa_netmask->sa_family, ifa->ifa_addr->sa_family); if (ifa->ifa_netmask->sa_family != ifa->ifa_addr->sa_family) { /* Fix the address family field of the netmask */ dprintf("tuntap: if_ioctl: fixing netmask af.\n"); ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family; } } return 0; case SIOCSIFFLAGS: return 0; case SIOCGIFSTATUS: { struct ifstat *stat = (struct ifstat *) arg; int len = strlen(stat->ascii); char *p = stat->ascii + len; /* print status */ if (open) { snprintf(p, IFSTATMAX - len, "\topen (pid %u)\n", pid); } else { snprintf(p, IFSTATMAX - len, "\tclosed\n"); } return 0; } case SIOCSIFMTU: { struct ifreq *ifr = (struct ifreq *) arg; ifnet_set_mtu(ifp, ifr->ifr_mtu); return 0; } case SIOCDIFADDR: return 0; } return EOPNOTSUPP; } errno_t tuntap_interface::if_set_bpf_tap(bpf_tap_mode mode, int (*cb)(ifnet_t, mbuf_t)) { dprintf("tuntap: mode %d\n", mode); auto_lock l(&bpf_lock); bpf_callback = cb; bpf_mode = mode; return 0; } errno_t tuntap_interface::if_check_multi(const struct sockaddr *maddr) { dprintf("tuntap: if_check_multi\n"); return EOPNOTSUPP; } void tuntap_interface::if_detached() { dprintf("tuntap: if_detach\n"); /* unlock the lock. This will let unregister_interface() continue */ shutdown_lock.unlock(); }