// -*- c-basic-offset: 4 -*-
/*
 * kerneltap.{cc,hh} -- element accesses network via /dev/tap device
 * Robert Morris, Douglas S. J. De Couto, Eddie Kohler
 *
 * Copyright (c) 1999-2000 Massachusetts Institute of Technology
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, subject to the conditions
 * listed in the Click LICENSE file. These conditions include: you must
 * preserve this copyright notice, and you cannot mention the copyright
 * holders in advertising related to the Software without their permission.
 * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
 * notice is a summary of the Click LICENSE file; the license in that file is
 * legally binding.
 */

#include <click/config.h>
#include "kerneltap.hh"
#include <click/error.hh>
#include <click/bitvector.hh>
#include <click/confparse.hh>
#include <click/straccum.hh>
#include <click/glue.hh>
#include <clicknet/ether.h>
#include <click/standard/scheduleinfo.hh>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>

#if defined(__linux__) && defined(HAVE_LINUX_IF_TUN_H)
# define KERNELTUN_LINUX 1
#elif defined(HAVE_NET_IF_TUN_H)
# define KERNELTUN_NET 1
#elif defined(__APPLE__)
# define KERNELTUN_OSX 1 
// assume tun driver installed from http://chrisp.de/en/projects/tunnel.html
// this driver doesn't produce or expect packets with an address family prepended
#endif

#include <net/if.h>
#if HAVE_NET_IF_TUN_H
# include <net/if_tun.h>
#elif HAVE_LINUX_IF_TUN_H
# include <linux/if_tun.h>
#endif

CLICK_DECLS

KernelTap::KernelTap()
  : _fd(-1), _task(this),
    _macaddr((const unsigned char *)"\000\001\002\003\004\005"),
    _ignore_q_errs(false), _printed_write_err(false), _printed_read_err(false)
{
}

KernelTap::~KernelTap()
{
}

int
KernelTap::configure(Vector<String> &conf, ErrorHandler *errh)
{
    _gw = IPAddress();
    _headroom = Packet::DEFAULT_HEADROOM;
    _mtu_out = DEFAULT_MTU;

    if (cp_va_parse(conf, this, errh,
		    cpIPPrefix, "network address", &_near, &_mask,
		    cpOptional,
		    cpKeywords,
		    "HEADROOM", cpUnsigned, "default headroom for generated packets", &_headroom,
		    "ETHER", cpEthernetAddress, "fake device Ethernet address", &_macaddr,
		    "IGNORE_QUEUE_OVERFLOWS", cpBool, "ignore queue overflow errors?", &_ignore_q_errs,
		    "MTU", cpInteger, "MTU", &_mtu_out,
		    cpEnd) < 0)
	return -1;

    if (_gw) { // then it was set to non-zero by arg
	// check net part matches 
	unsigned int g = _gw.in_addr().s_addr;
	unsigned int m = _mask.in_addr().s_addr;
	unsigned int n = _near.in_addr().s_addr;
	if ((g & m) != (n & m)) {
	    _gw = 0;
	    errh->warning("not setting up default route\n(network address and gateway are on different networks)");
	}
    }
    return 0;
}

#if KERNELTUN_LINUX
int
KernelTap::try_linux_universal(ErrorHandler *errh)
{
    int fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
    if (fd < 0)
	return -errno;

    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TAP;
    int err = ioctl(fd, TUNSETIFF, (void *)&ifr);
    if (err < 0) {
	errh->warning("Linux universal tun failed: %s", strerror(errno));
	close(fd);
	return -errno;
    }

    _dev_name = ifr.ifr_name;
    _type = LINUX_UNIVERSAL;
    _fd = fd;
    return 0;
}
#endif

int
KernelTap::try_tun(const String &dev_name, ErrorHandler *)
{
    String filename = "/dev/" + dev_name;
    int fd = open(filename.c_str(), O_RDWR | O_NONBLOCK);
    if (fd < 0)
	return -errno;

    _dev_name = dev_name;
    _fd = fd;
    return 0;
}

/*
 * Find an available kernel tap, or report error if none are available.
 * Does not set up the tap.
 * On success, _dev_name, _type, and _fd are valid.
 */
int
KernelTap::alloc_tun(ErrorHandler *errh)
{
#if !KERNELTUN_LINUX && !KERNELTUN_NET && !KERNELTUN_OSX
    return errh->error("KernelTap is not yet supported on this system.\n(Please report this message to click@pdos.lcs.mit.edu.)");
#endif

    int error, saved_error = 0;
    String saved_device, saved_message;
    StringAccum tried;
    
#if KERNELTUN_LINUX
    _type = LINUX_UNIVERSAL;
    if ((error = try_linux_universal(errh)) >= 0)
	return error;
    else if (!saved_error || error != -ENOENT) {
	saved_error = error, saved_device = "net/tun";
	if (error == -ENODEV)
	    saved_message = "\n(Perhaps you need to enable tun in your kernel or load the `tun' module.)";
    }
    tried << "/dev/net/tun, ";
#endif

#ifdef __linux__
    _type = LINUX_ETHERTAP;
    String dev_prefix = "tap";
#elif defined(KERNELTUN_OSX)
    _type = OSX_TUN;
    String dev_prefix = "tun";
#else
    _type = BSD_TUN;
    String dev_prefix = "tun";
#endif

    for (int i = 0; i < 6; i++) {
	if ((error = try_tun(dev_prefix + String(i), errh)) >= 0)
	    return error;
	else if (!saved_error || error != -ENOENT)
	    saved_error = error, saved_device = dev_prefix + String(i), saved_message = String();
	tried << "/dev/" << dev_prefix << i << ", ";
    }
    
    if (saved_error == -ENOENT) {
	tried.pop_back(2);
	return errh->error("could not find a tap device\n(checked %s)\nYou may need to enable tap support in your kernel.", tried.c_str());
    } else
	return errh->error("could not allocate device /dev/%s: %s%s", saved_device.c_str(), strerror(-saved_error), saved_message.c_str());
}

int
KernelTap::setup_tun(struct in_addr near, struct in_addr mask, ErrorHandler *errh)
{
    char tmp[512], tmp0[64], tmp1[64];

// #if defined(__OpenBSD__)  && !defined(TUNSIFMODE)
//     /* see OpenBSD bug: http://cvs.openbsd.org/cgi-bin/wwwgnats.pl/full/782 */
// #define       TUNSIFMODE      _IOW('t', 88, int)
// #endif
#if defined(TUNSIFMODE) || defined(__FreeBSD__)
    {
	int mode = IFF_BROADCAST;
	if (ioctl(_fd, TUNSIFMODE, &mode) != 0)
	    return errh->error("TUNSIFMODE failed: %s", strerror(errno));
    }
#endif
#if defined(__OpenBSD__)
    {
	struct tuninfo ti;
	memset(&ti, 0, sizeof(struct tuninfo));
	if (ioctl(_fd, TUNGIFINFO, &ti) != 0)
	    return errh->error("TUNGIFINFO failed: %s", strerror(errno));
	ti.flags &= IFF_BROADCAST;
	if (ioctl(_fd, TUNSIFINFO, &ti) != 0)
	    return errh->error("TUNSIFINFO failed: %s", strerror(errno));
    }
#endif
    

    if (_macaddr) {
	sprintf(tmp, "/sbin/ifconfig %s hw ether %s", _dev_name.c_str(),
		_macaddr.s().c_str());
	if (system(tmp) != 0) {
	    errh->error("%s: %s", tmp, strerror(errno));
	}
	
	sprintf(tmp, "/sbin/ifconfig %s arp", _dev_name.c_str());
	if (system(tmp) != 0) 
	    return errh->error("%s: %s", tmp, strerror(errno));
    }

    

#if defined(TUNSIFHEAD) || defined(__FreeBSD__)
    // Each read/write prefixed with a 32-bit address family,
    // just as in OpenBSD.
    int yes = 1;
    if (ioctl(_fd, TUNSIFHEAD, &yes) != 0)
	return errh->error("TUNSIFHEAD failed: %s", strerror(errno));
#endif        

    strcpy(tmp0, inet_ntoa(near));
    strcpy(tmp1, inet_ntoa(mask));
    sprintf(tmp, "/sbin/ifconfig %s %s netmask %s up 2>/dev/null", _dev_name.c_str(), tmp0, tmp1);
    if (system(tmp) != 0) {
# if defined(__linux__)
	// Is Ethertap available? If it is moduleified, then it might not be.
	// beside the ethertap module, you may also need the netlink_dev
	// module to be loaded.
	return errh->error("%s: `%s' failed\n(Perhaps Ethertap is in a kernel module that you haven't loaded yet?)", _dev_name.c_str(), tmp);
# else
	return errh->error("%s: `%s' failed", _dev_name.c_str(), tmp);
# endif
    }
    
    if (_gw) {
#if defined(__linux__)
	sprintf(tmp, "/sbin/route -n add default gw %s", _gw.s().c_str());
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
	sprintf(tmp, "/sbin/route -n add default %s", _gw.s().c_str());
#endif
	if (system(tmp) != 0)
	    return errh->error("%s: %s", tmp, strerror(errno));
    }

        // calculate maximum packet size needed to receive data from
    // tun/tap.
    if (_type == LINUX_UNIVERSAL)
	_mtu_in = _mtu_out + 4;
    else if (_type == BSD_TUN)
	_mtu_in = _mtu_out + 4;
    else if (_type == OSX_TUN)
	_mtu_in = _mtu_out + 4; // + 0?
    else /* _type == LINUX_ETHERTAP */
	_mtu_in = _mtu_out + 16;
    return 0;
}

void
KernelTap::dealloc_tun()
{
    String cmd = "/sbin/ifconfig " + _dev_name + " down";
    if (system(cmd.c_str()) != 0) 
	click_chatter("%s: failed: %s", name().c_str(), cmd.c_str());
}

int
KernelTap::initialize(ErrorHandler *errh)
{
    if (alloc_tun(errh) < 0)
	return -1;
    if (setup_tun(_near, _mask, errh) < 0)
	return -1;
  if (input_is_pull(0))
    ScheduleInfo::join_scheduler(this, &_task, errh);
  add_select(_fd, SELECT_READ);
  return 0;
}

void
KernelTap::cleanup(CleanupStage)
{
    if (_fd >= 0) {
	close(_fd);
	remove_select(_fd, SELECT_READ);
	if (_type != LINUX_UNIVERSAL)
	    dealloc_tun();
    }
}

void
KernelTap::selected(int fd)
{
    if (fd != _fd)
	return;
    WritablePacket *p = Packet::make(_headroom, 0, _mtu_in, 0);
    if (!p) {
	click_chatter("out of memory!");
	return;
    }
    
    int cc = read(_fd, p->data(), _mtu_in);
    if (cc > 0) {
	p->take(_mtu_in - cc);
	
	if (_type == LINUX_UNIVERSAL) {
	    // 2-byte padding followed by an Ethernet type
	    p->pull(4);
	} else if (_type == BSD_TUN) {
	    // 4-byte address family followed by IP header
	    p->pull(4);
	} else if (_type == OSX_TUN) {
	} else { /* _type == LINUX_ETHERTAP */
	    p->pull(2);
	}
	
	p->timestamp_anno().set_now();
	output(0).push(p);
    } else {
	if (!_ignore_q_errs || !_printed_read_err || (errno != ENOBUFS)) {
	    _printed_read_err = true;
	    perror("KernelTap read");
	}
    }
}

bool
KernelTap::run_task()
{
    Packet *p = input(0).pull();
    if (p)
	push(0, p); 
    _task.fast_reschedule();
    return p != 0;
}

void
KernelTap::push(int, Packet *p)
{
    // Every packet has a 14-byte Ethernet header.
    // Extract the packet type, then ignore the Ether header.

    const click_ip *iph = p->ip_header();
    click_ether *e = (click_ether *) p->data();
    
    if (!iph) {
	click_chatter("KernelTap(%s): no network header", _dev_name.c_str());
	p->kill();
    }
    if (p->length() < sizeof(*e)){
	click_chatter("KernelTap(%s): packet too small", _dev_name.c_str());
	p->kill();
	return;
    }
    WritablePacket *q;
    if (_type == LINUX_UNIVERSAL) {
	// 2-byte padding followed by an Ethernet type
	uint32_t ethertype = (iph->ip_v == 4 ? htonl(ETHERTYPE_IP) : htonl(ETHERTYPE_IP6));
	if ((q = p->push(4)))
	    *(uint32_t *)(q->data()) = ethertype;
    } else if (_type == BSD_TUN) { 
	uint32_t af = (iph->ip_v == 4 ? htonl(AF_INET) : htonl(AF_INET6));
	if ((q = p->push(4)))
	    *(uint32_t *)(q->data()) = af;
    } else if (_type == OSX_TUN) {
	// send raw IP
	q = p->uniqueify();
    } else { /* _type == LINUX_ETHERTAP */
	q = p->push(2);
    }
    
    if (q) {
	int w = write(_fd, q->data(), q->length());
	if (w != (int) q->length() && (errno != ENOBUFS || !_ignore_q_errs || !_printed_write_err)) {
	    _printed_write_err = true;
	    click_chatter("KernelTap(%s): write failed: %s", _dev_name.c_str(), strerror(errno));
	}
	q->kill();
    } else
	click_chatter("%{element}: out of memory", this);
}


String
KernelTap::print_dev_name(Element *e, void *) 
{
  KernelTap *kt = (KernelTap *) e;
  return kt->_dev_name;
}


void
KernelTap::add_handlers()
{
  if (input_is_pull(0))
    add_task_handlers(&_task);

  add_read_handler("dev_name", print_dev_name, 0);
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(userlevel)
EXPORT_ELEMENT(KernelTap)


syntax highlighted by Code2HTML, v. 0.9.1