967 lines
21 KiB
C++
967 lines
21 KiB
C++
/*
|
|
* ip tunnel/ethertap device for MacOSX. Common functionality of tap_interface and tun_interface.
|
|
*
|
|
* tuntap_interface class definition
|
|
*/
|
|
/*
|
|
* Copyright (c) 2011 Mattias Nissler <mattias.nissler@gmx.de>
|
|
*
|
|
* 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 <sys/conf.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/param.h>
|
|
#include <sys/filio.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/kpi_socket.h>
|
|
|
|
#include <vm/vm_kern.h>
|
|
|
|
#include <net/if_types.h>
|
|
#include <net/if_var.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_arp.h>
|
|
|
|
#include <miscfs/devfs/devfs.h>
|
|
|
|
}
|
|
|
|
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, long unsigned int 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;
|
|
size = 0;
|
|
}
|
|
|
|
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);
|
|
in_ioctl = false;
|
|
}
|
|
|
|
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) {
|
|
interface_detached = false;
|
|
|
|
/* 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");
|
|
|
|
/* Wait until the interface has completely been detached. */
|
|
detach_lock.lock();
|
|
while (!interface_detached)
|
|
detach_lock.sleep(&interface_detached);
|
|
detach_lock.unlock();
|
|
|
|
dprintf("interface detached\n");
|
|
|
|
/* release the interface */
|
|
ifnet_release(ifp);
|
|
|
|
ifp = NULL;
|
|
}
|
|
|
|
dprintf("network interface unregistered\n");
|
|
}
|
|
|
|
void
|
|
tuntap_interface::cleanup_interface()
|
|
{
|
|
errno_t err;
|
|
ifaddr_t *addrs;
|
|
ifaddr_t *a;
|
|
struct ifreq ifr;
|
|
|
|
/* mark the interface down */
|
|
ifnet_set_flags(ifp, 0, IFF_UP | IFF_RUNNING);
|
|
|
|
/* Unregister all interface addresses. This works around a deficiency in the Darwin kernel.
|
|
* If we don't remove all IP addresses that are attached to the interface it can happen that
|
|
* the IP code fails to clean them up itself. When the interface is recycled, the IP code
|
|
* might then think some addresses are still attached to the interface...
|
|
*/
|
|
|
|
err = ifnet_get_address_list(ifp, &addrs);
|
|
if (!err) {
|
|
|
|
/* Execute a SIOCDIFADDR ioctl for each address. For technical reasons, we can only
|
|
* do that with a socket of the appropriate family. So try to create a dummy socket.
|
|
* I know this is a little expensive, but better than crashing...
|
|
*
|
|
* This really sucks.
|
|
*/
|
|
for (a = addrs; *a != NULL; a++) {
|
|
/* initialize the request parameters */
|
|
snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d",
|
|
ifnet_name(ifp), ifnet_unit(ifp));
|
|
ifaddr_address(*a, &(ifr.ifr_addr), sizeof(ifr.ifr_addr));
|
|
if (ifr.ifr_addr.sa_family != AF_INET)
|
|
continue;
|
|
|
|
dprintf("trying to delete address of family %d\n", ifr.ifr_addr.sa_family);
|
|
|
|
do_sock_ioctl(ifr.ifr_addr.sa_family, SIOCDIFADDR, &ifr);
|
|
}
|
|
|
|
/* release the address list */
|
|
ifnet_free_address_list(addrs);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void
|
|
tuntap_interface::do_sock_ioctl(sa_family_t af, unsigned long cmd, void* arg) {
|
|
if (in_ioctl) {
|
|
log(LOG_ERR, "tuntap: ioctl recursion detected, aborting.\n");
|
|
return;
|
|
}
|
|
|
|
socket_t sock;
|
|
errno_t err = sock_socket(af, SOCK_RAW, 0, NULL, NULL, &sock);
|
|
if (err) {
|
|
log(LOG_ERR, "tuntap: failed to create socket: %d\n", err);
|
|
return;
|
|
}
|
|
|
|
in_ioctl = true;
|
|
|
|
/* issue the ioctl */
|
|
err = sock_ioctl(sock, cmd, arg);
|
|
if (err)
|
|
log(LOG_ERR, "tuntap: socket ioctl %d failed: %d\n", cmd, err);
|
|
|
|
in_ioctl = false;
|
|
|
|
/* get rid of the socket */
|
|
sock_close(sock);
|
|
}
|
|
|
|
/* 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_interface();
|
|
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;
|
|
|
|
/* shut down the network interface */
|
|
shutdown_interface();
|
|
|
|
/* 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(this, NULL, PZERO | PCATCH, "tuntap", 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) */
|
|
// ... evidently not :) -- Adam Ierymenko <adam.ierymenko@zerotier.com>
|
|
//chunk_len = min(ifnet_mtu(ifp), min(uio_resid(uio), mlen));
|
|
chunk_len = 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;
|
|
if (uio_resid(uio) <= 0)
|
|
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(pkt);
|
|
mbuf_freem_list(m);
|
|
return ENOBUFS;
|
|
}
|
|
}
|
|
|
|
/* protect the wakeup calls with the lock, not sure they are safe. */
|
|
{
|
|
auto_lock l(&lock);
|
|
|
|
/* 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, which makes early mDNSresponder versions ignore
|
|
* the interface. Fix that here. This one is of the category "ugly
|
|
* workaround". Dumb Darwin...
|
|
*
|
|
* Meanwhile, Apple has fixed mDNSResponder, and recent versions of
|
|
* Leopard don't need this hack anymore. However, Tiger still has a
|
|
* broken version so we leave the hack in for now.
|
|
*
|
|
* TODO: Revisit when dropping Tiger support.
|
|
*
|
|
* 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...
|
|
*/
|
|
ifaddr_t ifa = (ifaddr_t) arg;
|
|
if (ifa == NULL)
|
|
return 0;
|
|
|
|
sa_family_t af = ifaddr_address_family(ifa);
|
|
if (af != AF_INET)
|
|
return 0;
|
|
|
|
struct ifaliasreq ifra;
|
|
int sa_size = sizeof(struct sockaddr);
|
|
if (ifaddr_address(ifa, &ifra.ifra_addr, sa_size)
|
|
|| ifaddr_dstaddress(ifa, &ifra.ifra_broadaddr, sa_size)
|
|
|| ifaddr_netmask(ifa, &ifra.ifra_mask, sa_size)) {
|
|
log(LOG_WARNING,
|
|
"tuntap: failed to parse interface address.\n");
|
|
return 0;
|
|
}
|
|
|
|
// Check that the address family fields match. If not, issue another
|
|
// SIOCAIFADDR to fix the entry.
|
|
if (ifra.ifra_addr.sa_family != af
|
|
|| ifra.ifra_broadaddr.sa_family != af
|
|
|| ifra.ifra_mask.sa_family != af) {
|
|
log(LOG_INFO, "tuntap: Fixing address family for %s%d\n",
|
|
family_name, unit);
|
|
|
|
snprintf(ifra.ifra_name, sizeof(ifra.ifra_name), "%s%d",
|
|
family_name, unit);
|
|
ifra.ifra_addr.sa_family = af;
|
|
ifra.ifra_broadaddr.sa_family = af;
|
|
ifra.ifra_mask.sa_family = af;
|
|
|
|
do_sock_ioctl(af, SIOCAIFADDR, &ifra);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
case SIOCSIFFLAGS:
|
|
return 0;
|
|
|
|
case SIOCGIFSTATUS:
|
|
{
|
|
struct ifstat *stat = (struct ifstat *) arg;
|
|
int len;
|
|
char *p;
|
|
|
|
if (stat == NULL)
|
|
return EINVAL;
|
|
|
|
/* print status */
|
|
len = strlen(stat->ascii);
|
|
p = stat->ascii + len;
|
|
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;
|
|
|
|
if (ifr == NULL)
|
|
return EINVAL;
|
|
|
|
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_detached\n");
|
|
|
|
/* wake unregister_interface() */
|
|
detach_lock.lock();
|
|
interface_detached = true;
|
|
detach_lock.wakeup(&interface_detached);
|
|
detach_lock.unlock();
|
|
|
|
dprintf("if_detached done\n");
|
|
}
|
|
|