From 54694abc333483cbe15dc414e6b36b110f9ec71b Mon Sep 17 00:00:00 2001 From: Michael Santos Date: Wed, 30 Dec 2009 21:37:45 -0500 Subject: [PATCH] tuntap driver imported from http://jungerl.sourceforge.net/. --- Makefile | 2 + README | 3 + c_src/Makefile | 23 +++++ c_src/tun_drv.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++ c_src/tunctl.c | 112 ++++++++++++++++++++++ doc/short-desc | 1 + src/Makefile | 7 ++ src/eth2udp.erl | 51 ++++++++++ src/tuntap.erl | 88 +++++++++++++++++ 9 files changed, 534 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 c_src/Makefile create mode 100644 c_src/tun_drv.c create mode 100644 c_src/tunctl.c create mode 100644 doc/short-desc create mode 100644 src/Makefile create mode 100644 src/eth2udp.erl create mode 100644 src/tuntap.erl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..43b1822 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +include ../../support/subdir.mk + diff --git a/README b/README new file mode 100644 index 0000000..4c83bd0 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ + +Copy of the tuntap Erlang driver from Jungerl, by Luke Gorrie. + diff --git a/c_src/Makefile b/c_src/Makefile new file mode 100644 index 0000000..ba4cbe2 --- /dev/null +++ b/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 + diff --git a/c_src/tun_drv.c b/c_src/tun_drv.c new file mode 100644 index 0000000..2faf010 --- /dev/null +++ b/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 in November 2001, and + updated in February 2003 to support TAP interfaces and the new + Erlang driver interface. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 [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; +} + diff --git a/c_src/tunctl.c b/c_src/tunctl.c new file mode 100644 index 0000000..14e5989 --- /dev/null +++ b/c_src/tunctl.c @@ -0,0 +1,112 @@ +/* Copyright 2002 Jeff Dike + * Licensed under the GPL + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/doc/short-desc b/doc/short-desc new file mode 100644 index 0000000..44ada4a --- /dev/null +++ b/doc/short-desc @@ -0,0 +1 @@ +Linux "Universal TUN/TAP device" driver diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d926242 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,7 @@ +include ../../../support/include.mk + +all: $(ERL_OBJECTS) + +clean: + -rm $(ERL_OBJECTS) + diff --git a/src/eth2udp.erl b/src/eth2udp.erl new file mode 100644 index 0000000..a1271ec --- /dev/null +++ b/src/eth2udp.erl @@ -0,0 +1,51 @@ +%%%---------------------------------------------------------------------- +%%% File : eth2udp.erl +%%% Author : Luke Gorrie +%%% Purpose : Ethernet-over-UDP tunnels +%%% Created : 18 Feb 2003 by Luke Gorrie +%%%---------------------------------------------------------------------- + +-module(eth2udp). +-author('luke@bluetail.com'). + +-import(lists, [foreach/2]). + +-export([start_link/2, start_link/3]). +-export([init/3]). +-export([loop/3]). + +%% start_link(Port, [EndPoint]) -> pid() +%% +%% Port = integer() (UDP port for local endpoint) +%% EndPoint = {IP, Port} (UDP endpoint of remote tunnel) +start_link(Port, EPs) -> + start_link(Port, EPs, undefined). +start_link(Port, EPs, Dev) -> + spawn_link(?MODULE, init, [Port, EPs, Dev]). + +init(Port, EPs, Dev) -> + {ok, Socket} = gen_udp:open(Port, [binary]), + {ok, Tunnel} = init_tunnel(Dev), + tuntap:set_active(Tunnel, true), + loop(Tunnel, Socket, EPs). + +loop(Tunnel, Socket, EPs) -> + receive + {Tunnel, {data, Packet}} -> + io:format("Got ~p byte packet on tap~n", [size(Packet)]), + foreach(fun({IP, Port}) -> + gen_udp:send(Socket, IP, Port, Packet) + end, + EPs); + {udp, Socket, _, _, Packet} -> + io:format("Got ~p byte packet on UDP~n", [size(Packet)]), + tuntap:write(Tunnel, Packet) + end, + ?MODULE:loop(Tunnel, Socket, EPs). + +init_tunnel(Dev) -> + Tun = tuntap:open_tuntap(tap, Dev), + Name = tuntap:device_name(Tun), + io:format("Alive and kicking on ~p~n", [Name]), + {ok, Tun}. + diff --git a/src/tuntap.erl b/src/tuntap.erl new file mode 100644 index 0000000..5381855 --- /dev/null +++ b/src/tuntap.erl @@ -0,0 +1,88 @@ +%%%------------------------------------------------------------------- +%%% File : tuntap.erl +%%% Author : Luke Gorrie +%%% Purpose : Linux "Universal TUN/TAP device" driver +%%% See /usr/src/linux/Documentation/networking/tuntap.txt +%%% +%%% Created : 10 Nov 2001 by Luke Gorrie +%%%------------------------------------------------------------------- + +%% Note: if you want to be able to create tunnels as some user other +%% than root, you should chmod "/dev/net/tun" to be read/write by the +%% appropriate user. + +%% To actually communicate between your host machine and Erlang via a +%% tun/tap interface, you will probably want to assign it an IP +%% address and setup routing. For example (on Linux): +%% +%% ifconfig tun0 200.0.0.1 pointopoint 200.0.0.2 +%% +%% Will configure the tun0 interface so that the host machine (Linux +%% itself) has the address 200.0.0.1 and that it believes Erlang has +%% the address 200.0.0.2 (i.e. it sets up routing of that address into +%% the tunnel). +%% +%% The exact setup varies from unix to unix. The point is that you +%% configure it exactly like a normal network interface: assign an +%% address for the local machine to use and a route for any addresses +%% you want to talk to "out there". + +-module(tuntap). + +-author('luke@bluetail.com'). + +-export([init/0, + open_tun/0, open_tap/0, open_tuntap/2, + device_name/1, write/2, set_active/2]). + +-define(REPLY_ERROR, 0). +-define(REPLY_OK, 1). + +-define(REQUEST_GET_DEVICE, 0). +-define(REQUEST_WRITE, 1). +-define(REQUEST_ACTIVE, 2). + +-define(ACTIVE_FALSE, 0). +-define(ACTIVE_TRUE, 1). +-define(ACTIVE_ONCE, 2). + +%% Returns: ok +init() -> + erl_ddll:start(), + ok = erl_ddll:load_driver(code:priv_dir(tuntap), "tun_drv"). + +%% Returns port() +open_tun() -> open_tuntap(tun, undefined). +open_tap() -> open_tuntap(tap, undefined). + +%% open_tuntap(tun|tap, undefined|string()) -> port() +open_tuntap(Type, Dev) -> + %% It seems to be okay to init() multiple times.. + ok = init(), + TypeName = case Type of + tun -> "tun"; + tap -> "tap" + end, + DevArg = if Dev == undefined -> ""; + list(Dev) -> " " ++ Dev + end, + open_port({spawn, "tun_drv "++TypeName++DevArg}, [binary]). + +%% Returns: string() (e.g. "tun0" or "tap42") +device_name(Port) -> + [?REPLY_OK|Name] = erlang:port_control(Port, ?REQUEST_GET_DEVICE, []), + Name. + +%% Returns: ok +write(Port, Packet) -> + erlang:port_command(Port, Packet), + ok. + +set_active(Port, false) -> set_active1(Port, ?ACTIVE_FALSE); +set_active(Port, true) -> set_active1(Port, ?ACTIVE_TRUE); +set_active(Port, once) -> set_active1(Port, ?ACTIVE_ONCE). + +set_active1(Port, Arg) -> + [?REPLY_OK] = erlang:port_control(Port, ?REQUEST_ACTIVE, [Arg]), + ok. +