Skip to content
Browse files

tuntap driver imported from http://jungerl.sourceforge.net/.

  • Loading branch information...
0 parents commit 54694abc333483cbe15dc414e6b36b110f9ec71b @msantos committed Dec 30, 2009
Showing with 534 additions and 0 deletions.
  1. +2 −0 Makefile
  2. +3 −0 README
  3. +23 −0 c_src/Makefile
  4. +247 −0 c_src/tun_drv.c
  5. +112 −0 c_src/tunctl.c
  6. +1 −0 doc/short-desc
  7. +7 −0 src/Makefile
  8. +51 −0 src/eth2udp.erl
  9. +88 −0 src/tuntap.erl
2 Makefile
@@ -0,0 +1,2 @@
+include ../../support/subdir.mk
+
3 README
@@ -0,0 +1,3 @@
+
+Copy of the tuntap Erlang driver from Jungerl, by Luke Gorrie.
+
23 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 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 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 doc/short-desc
@@ -0,0 +1 @@
+Linux "Universal TUN/TAP device" driver
7 src/Makefile
@@ -0,0 +1,7 @@
+include ../../../support/include.mk
+
+all: $(ERL_OBJECTS)
+
+clean:
+ -rm $(ERL_OBJECTS)
+
51 src/eth2udp.erl
@@ -0,0 +1,51 @@
+%%%----------------------------------------------------------------------
+%%% File : eth2udp.erl
+%%% Author : Luke Gorrie <luke@bluetail.com>
+%%% Purpose : Ethernet-over-UDP tunnels
+%%% Created : 18 Feb 2003 by Luke Gorrie <luke@bluetail.com>
+%%%----------------------------------------------------------------------
+
+-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}.
+
88 src/tuntap.erl
@@ -0,0 +1,88 @@
+%%%-------------------------------------------------------------------
+%%% File : tuntap.erl
+%%% Author : Luke Gorrie <luke@bluetail.com>
+%%% Purpose : Linux "Universal TUN/TAP device" driver
+%%% See /usr/src/linux/Documentation/networking/tuntap.txt
+%%%
+%%% Created : 10 Nov 2001 by Luke Gorrie <luke@bluetail.com>
+%%%-------------------------------------------------------------------
+
+%% 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.
+

0 comments on commit 54694ab

Please sign in to comment.
Something went wrong with that request. Please try again.