Skip to content

Commit

Permalink
tuntap driver imported from http://jungerl.sourceforge.net/.
Browse files Browse the repository at this point in the history
  • Loading branch information
msantos committed Dec 31, 2009
0 parents commit 54694ab
Show file tree
Hide file tree
Showing 9 changed files with 534 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Makefile
@@ -0,0 +1,2 @@
include ../../support/subdir.mk

3 changes: 3 additions & 0 deletions README
@@ -0,0 +1,3 @@

Copy of the tuntap Erlang driver from Jungerl, by Luke Gorrie.

23 changes: 23 additions & 0 deletions c_src/Makefile
@@ -0,0 +1,23 @@
include ../../../support/include.mk

CFLAGS += -I $(ERL_C_INCLUDE_DIR) -I../../../support

TUN_DRV_SO = ../priv/tun_drv.so
TUNCTL = ../priv/tunctl

all: $(TUN_DRV_SO) $(TUNCTL)

$(TUN_DRV_SO): tun_drv.o
ld -G -o $@ $<

tun_drv.o: tun_drv.c
$(CC) $(CFLAGS) -o $@ -c -fpic $(ERL_INCLUDE) $<

$(TUNCTL): tunctl.c
$(CC) $(CFLAGS) -o $@ $<

clean:
-rm $(TUN_DRV_SO) $(TUNCTL)

.INTERMEDIATE: tun_drv.o

247 changes: 247 additions & 0 deletions c_src/tun_drv.c
@@ -0,0 +1,247 @@
/* Tunnel device linked-in driver.
See /usr/src/linux/Documentation/networking/tuntap.txt
Written by Luke Gorrie <luke@bluetail.com> in November 2001, and
updated in February 2003 to support TAP interfaces and the new
Erlang driver interface. */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <assert.h>

#include <linux/if_tun.h>

#include "erl_driver.h"

#define REPLY_ERROR 0
#define REPLY_OK 1

#define REQUEST_GET_DEVICE 0
#define REQUEST_ACTIVE 2

#define ACTIVE_FALSE 0
#define ACTIVE_TRUE 1
#define ACTIVE_ONCE 2

/* Generous buffer size for reading single ethernet frames.
FIXME: Do we need to worry about bigger packets, e.g. if we have a
giant MTU in 'tap' or receive pre-defragmented IP packets in
'tun'? */
#define TUNNEL_BUF_SIZE 2048

/*
*
* Helpers
*
*/

/* parse driver argument string: "tunnel_drv <tun|tap> [device_name]" */
static int parse_args(char *str, int *mode, char *dev_name, int max_name_len)
{
/* skip to start of first argument */
for (; *str != ' '; str++) { if (*str == '\0') return 0; }
for (; *str == ' '; str++) { if (*str == '\0') return 0; }

/* looking at "tun" or "tap" - parse and move over */
if (strncmp(str, "tun", 3) == 0) {
*mode = IFF_TUN;
} else if (strncmp(str, "tap", 3) == 0) {
*mode = IFF_TAP;
} else {
return 0;
}
str += 3;
/* optional device name - skip any whitespace, then copy */
while (*str == ' ') str++;
strncpy(dev_name, str, max_name_len);

return 1;
}

/* make a tunnel interface */
static int make_if(int mode, char *dev) {
struct ifreq ifr;
int fd, err;

if ((fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK)) < 0) return -1;

memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = mode | IFF_NO_PI;
if( *dev )
strncpy(ifr.ifr_name, dev, IFNAMSIZ);

if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}


/*
*
* erl_driver interface
*
*/

struct tun_state {
ErlDrvPort port;
int fd;
char dev[IFNAMSIZ];
char buf[TUNNEL_BUF_SIZE];
int active;
};

static int ctl_reply(int rep, char *buf, int len, char **rbuf, int rsize)
{
char* ptr;

if ((len+1) > rsize) {
ptr = (char *)sys_alloc(len+1);
assert(ptr);
*rbuf = ptr;
}
else
ptr = *rbuf;
*ptr++ = rep;
memcpy(ptr, buf, len);
return len+1;
}

static void set_input(struct tun_state *state, int flag)
{
driver_select(state->port, (ErlDrvEvent)state->fd, DO_READ, flag);
}

static int tun_ctl(ErlDrvData data,
unsigned int cmd,
char *buf,
int len,
char **rbuf,
int rsize)
{
struct tun_state *state = (struct tun_state *)data;
int read_len;
switch (cmd) {
case REQUEST_GET_DEVICE:
return ctl_reply(REPLY_OK, state->dev, strlen(state->dev), rbuf, rsize);
case REQUEST_ACTIVE:
state->active = (int)*buf;
switch (state->active) {
case ACTIVE_FALSE:
set_input(state, 0);
break;
case ACTIVE_TRUE:
set_input(state, 1);
break;
case ACTIVE_ONCE:
/* optimization: try to read a packet immediately if it
exists, to avoid going back to select() */
read_len = read(state->fd, state->buf, TUNNEL_BUF_SIZE);
if (read_len > 0) {
/* got a packet */
driver_output(state->port, state->buf, read_len);
state->active = ACTIVE_FALSE;
set_input(state, 0);
} else {
/* no packet available yet */
set_input(state, 1);
}
break;
default:
assert(0);
}
return ctl_reply(REPLY_OK, "", 0, rbuf, rsize);
default:
return ctl_reply(REPLY_ERROR, "", 0, rbuf, rsize);
}
}

static void tun_output(ErlDrvData data, char* buf, int len)
{
struct tun_state *state = (struct tun_state *)data;
if (write(state->fd, buf, len) < 0)
driver_failure_posix(state->port, errno);
}

static void tun_input(ErlDrvData data, ErlDrvEvent nil)
{
struct tun_state *state = (struct tun_state *)data;
int len;

len = read(state->fd, state->buf, TUNNEL_BUF_SIZE);
if (len > 0) {
driver_output(state->port, state->buf, len);
}
if (state->active == ACTIVE_ONCE) {
state->active = ACTIVE_FALSE;
set_input(state, 0);
}
}

static void tun_stop(ErlDrvData data)
{
struct tun_state *state = (struct tun_state *)data;
set_input(state, 0);
close(state->fd);
sys_free(state);
}

static ErlDrvData tun_start(ErlDrvPort port, char *args)
{
struct tun_state *state;
int fd;

int mode;
char dev_name[IFNAMSIZ];

state = (struct tun_state*) sys_alloc(sizeof(struct tun_state));
if (state == NULL) {
errno = ENOMEM; /* appropriate to set errno? */
return ERL_DRV_ERROR_ERRNO;
}

if (!parse_args(args, &mode, state->dev, IFNAMSIZ - 1)) {
return ERL_DRV_ERROR_BADARG;
}

fd = make_if(mode, state->dev);
if (fd < 0) {
return ERL_DRV_ERROR_GENERAL;
}
state->port = port;
state->fd = fd;
state->active = ACTIVE_FALSE;
set_input(state, 0);

return (ErlDrvData)state;
}

static int tun_init(void)
{
return 0;
}

ErlDrvEntry tun_driver_entry;

ErlDrvEntry *driver_init(void)
{
memset(&tun_driver_entry, 0, sizeof(tun_driver_entry));
tun_driver_entry.init = tun_init;
tun_driver_entry.start = tun_start;
tun_driver_entry.stop = tun_stop;
tun_driver_entry.ready_input = tun_input;
tun_driver_entry.control = tun_ctl;
tun_driver_entry.output = tun_output;
tun_driver_entry.driver_name = "tun_drv";
return &tun_driver_entry;
}

112 changes: 112 additions & 0 deletions c_src/tunctl.c
@@ -0,0 +1,112 @@
/* Copyright 2002 Jeff Dike
* Licensed under the GPL
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pwd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <linux/if_tun.h>

static void Usage(char *name)
{
fprintf(stderr, "Create: %s [-b] [-u owner] [-t device-name] "
"[-f tun-clone-device]\n", name);
fprintf(stderr, "Delete: %s -d device-name [-f tun-clone-device]\n\n",
name);
fprintf(stderr, "The default tun clone device is /dev/net/tun - some systems"
" use\n/dev/misc/net/tun instead\n\n");
fprintf(stderr, "-b will result in brief output (just the device name)\n");
exit(1);
}

int main(int argc, char **argv)
{
struct ifreq ifr;
struct passwd *pw;
long owner = geteuid();
int tap_fd, opt, delete = 0, brief = 0;
char *tun = "", *file = "/dev/net/tun", *name = argv[0], *end;

while((opt = getopt(argc, argv, "bd:f:t:u:")) > 0){
switch(opt) {
case 'b':
brief = 1;
break;
case 'd':
delete = 1;
tun = optarg;
break;
case 'f':
file = optarg;
break;
case 'u':
pw = getpwnam(optarg);
if(pw != NULL){
owner = pw->pw_uid;
break;
}
owner = strtol(optarg, &end, 0);
if(*end != '\0'){
fprintf(stderr, "'%s' is neither a username nor a numeric uid.\n",
optarg);
Usage(name);
}
break;
case 't':
tun = optarg;
break;
case 'h':
default:
Usage(name);
}
}

argv += optind;
argc -= optind;

if(argc > 0)
Usage(name);

if((tap_fd = open(file, O_RDWR)) < 0){
perror("opening tun device");
exit(1);
}

memset(&ifr, 0, sizeof(ifr));

ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, tun, sizeof(ifr.ifr_name) - 1);
if(ioctl(tap_fd, TUNSETIFF, (void *) &ifr) < 0){
perror("TUNSETIFF");
exit(1);
}

if(delete){
if(ioctl(tap_fd, TUNSETPERSIST, 0) < 0){
perror("TUNSETPERSIST");
exit(1);
}
printf("Set '%s' nonpersistent\n", ifr.ifr_name);
}
else {
if(ioctl(tap_fd, TUNSETPERSIST, 1) < 0){
perror("TUNSETPERSIST");
exit(1);
}
if(ioctl(tap_fd, TUNSETOWNER, owner) < 0){
perror("TUNSETPERSIST");
exit(1);
}
if(brief)
printf("%s\n", ifr.ifr_name);
else printf("Set '%s' persistent and owned by uid %ld\n", ifr.ifr_name,
owner);
}
return(0);
}
1 change: 1 addition & 0 deletions doc/short-desc
@@ -0,0 +1 @@
Linux "Universal TUN/TAP device" driver

0 comments on commit 54694ab

Please sign in to comment.