Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Manipulate TUN/TAP interfaces from Erlang

  • Loading branch information...
commit ec4c2c7ef2b220c92eb7f1b5c883b272a6fb1875 0 parents
@msantos authored
2  .gitignore
@@ -0,0 +1,2 @@
+*.[oa]
+*.beam
22 Makefile
@@ -0,0 +1,22 @@
+
+REBAR=$(shell which rebar || echo ./rebar)
+
+all: dirs deps compile
+
+./rebar:
+ erl -noshell -s inets start -s ssl start \
+ -eval 'httpc:request(get, {"https://github.com/downloads/basho/rebar/rebar", []}, [], [{stream, "./rebar"}])' \
+ -s inets stop -s init stop
+ chmod +x ./rebar
+
+dirs:
+ @mkdir -p priv/tmp
+
+compile: $(REBAR)
+ @$(REBAR) compile
+
+clean: $(REBAR)
+ @$(REBAR) clean
+
+deps: $(REBAR)
+ @$(REBAR) check-deps || $(REBAR) get-deps
117 README.md
@@ -0,0 +1,117 @@
+
+tunctl is a pure Erlang API for creating and using TUN/TAP interfaces.
+
+
+## EXPORTS
+
+create() -> {ok, PID}
+create(Device) -> {ok, PID}
+create(Device, Options) -> {ok, PID}
+
+ Types Device = [ string() | binary() ]
+ Options = [ Flag ]
+ Flag = [ tun | tap | no_pi | one_queue | vnet_hdr | tun_excl ]
+
+ Device is the TUN/TAP interface name. If an interface name is not
+ specified, the TUN/TAP driver will choose one (for tap devices,
+ starting from "tap0"; for tun devices, beginning from "tun0").
+
+ Options contains a list of flags.
+
+ tun: create a tun interface
+
+ tap: create a tap interface
+
+ no_pi: do not prepend the data with a 4 byte header describing
+ the physical interface
+
+ The options default to [tap, no_pi].
+
+destroy(Ref) -> ok
+
+ Types Ref = pid()
+
+ Remove the TUN/TAP interface.
+
+up(Ref, IPv4) -> ok | {error, posix()}
+
+ Types Ref = pid()
+ IPv4 = list() | tuple()
+
+ Configure a TUN/TAP interface using the default netmask and broadcast
+ for the network.
+
+down(Ref) -> ok | {error, posix()}
+
+ Types Ref = pid()
+
+ Unconfigure a TUN/TAP interface.
+
+persist(Ref, Boolean) -> ok | {error, posix()}
+
+ Types Ref = pid()
+ Boolean = [ true | false ]
+
+ Enable/disable interface persistence.
+
+owner(Ref, Owner) -> ok | {error, posix()}
+
+ Types Ref = pid()
+ Owner = integer()
+
+ Set the uid owning the interface.
+
+group(Ref, Group) -> ok | {error, posix()}
+
+ Types Ref = pid()
+ Group = integer()
+
+ Set the gid owning the interface.
+
+read(Ref, Size) -> {ok, Buf} | {error, posix()}
+
+ Types Ref = pid()
+ Size = integer()
+ Buf = binary()
+
+ Read _Size_ bytes from the interface.
+
+write(Ref, Buf) -> ok | {error, posix()}
+
+ Types Ref = pid()
+ Buf = binary()
+
+ Write _Buf_ to the interface.
+
+devname(Ref) -> Devname
+
+ Types Devname = binary()
+
+ Returns the TUN/TAP device name.
+
+flags(Ref) -> integer()
+
+ Returns an integer holding the interface creation flags.
+
+
+
+## EXAMPLES
+
+ 1> {ok, Ref} = tunctl:create()
+ {ok,<0.34.0>}
+
+ 2> tunctl:devname(Ref).
+ <<"tap0">>
+
+ 3> tunctl:up(Ref, "192.168.123.4").
+ ok
+
+ 4> tunctl:read(Ref, 1024).
+ {ok,<<1,0,94,0,0,22,190,138,20,22,76,120,8,0,70,192,0,40,
+ 0,0,64,0,1,2,200,76,192,...>>}
+
+ 5> tunctl:destroy(Ref).
+
+## TODO
+
+* make sure tunctl never leaks file descriptors
15 ebin/tunctl.app
@@ -0,0 +1,15 @@
+{application, tunctl,
+ [
+ {description, "TUN/TAP interface"},
+ {vsn, "0.01"},
+ {modules, [
+ tunctl
+ ]},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []}
+ ]}.
+
56 include/tuntap.hrl
@@ -0,0 +1,56 @@
+%% Copyright (c) 2011, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% 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.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "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
+%% COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+
+-define(SIZEOF_INT, 4).
+-define(SIZEOF_UNSIGNED_INT, ?SIZEOF_INT).
+
+% Ioctl defines
+-define(TUNSETNOCSUM, ?IOW($T, 200, ?SIZEOF_INT)).
+-define(TUNSETDEBUG, ?IOW($T, 201, ?SIZEOF_INT)).
+-define(TUNSETIFF, ?IOW($T, 202, ?SIZEOF_INT)).
+-define(TUNSETPERSIST, ?IOW($T, 203, ?SIZEOF_INT)).
+-define(TUNSETOWNER, ?IOW($T, 204, ?SIZEOF_INT)).
+-define(TUNSETLINK, ?IOW($T, 205, ?SIZEOF_INT)).
+-define(TUNSETGROUP, ?IOW($T, 206, ?SIZEOF_INT)).
+-define(TUNGETFEATURES, ?IOR($T, 207, ?SIZEOF_UNSIGNED_INT)).
+-define(TUNSETOFFLOAD, ?IOW($T, 208, ?SIZEOF_UNSIGNED_INT)).
+-define(TUNSETTXFILTER, ?IOW($T, 209, ?SIZEOF_UNSIGNED_INT)).
+-define(TUNGETIFF, ?IOR($T, 210, ?SIZEOF_UNSIGNED_INT)).
+-define(TUNGETSNDBUF, ?IOR($T, 211, ?SIZEOF_INT)).
+-define(TUNSETSNDBUF, ?IOW($T, 212, ?SIZEOF_INT)).
+
+% TUNSETIFF ifr flags
+-define(IFF_TUN, 16#0001).
+-define(IFF_TAP, 16#0002).
+-define(IFF_NO_PI, 16#1000).
+-define(IFF_ONE_QUEUE, 16#2000).
+-define(IFF_VNET_HDR, 16#4000).
+-define(IFF_TUN_EXCL, 16#8000).
4 rebar.config
@@ -0,0 +1,4 @@
+{deps, [
+ {procket, ".*", {git, "git://github.com/msantos/procket.git", "master"}}
+ ]}.
+{erl_opts, [{i, "deps/procket/include"}]}.
277 src/tunctl.erl
@@ -0,0 +1,277 @@
+%% Copyright (c) 2011, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% 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.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "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
+%% COPYRIGHT HOLDER OR CONTRIBUTORS 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.
+-module(tunctl).
+-behaviour(gen_server).
+
+-include("tuntap.hrl").
+-include("ioctl.hrl").
+-include("procket.hrl").
+
+-export([
+ create/0, create/1, create/2,
+ devname/1, flags/1,
+ destroy/1,
+
+ persist/2,
+ owner/2, group/2,
+
+ read/2, write/2,
+
+ up/2, down/1
+ ]).
+
+-export([start_link/2]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {
+ fd, % TUN/TAP file descriptor
+ dev, % device name
+ flag % TUNSETIFF ifr flags
+ }).
+
+-define(SIOCGIFFLAGS, 16#8913).
+-define(SIOCSIFFLAGS, 16#8914).
+-define(SIOCSIFADDR, 16#8916).
+
+-define(IFF_RUNNING, 16#40).
+-define(IFF_UP, 16#01).
+
+
+%%--------------------------------------------------------------------
+%%% Exports
+%%--------------------------------------------------------------------
+create() ->
+ create(<<0:(15*8)>>).
+create(Ifname) ->
+ create(Ifname, [tap, no_pi]).
+
+create(Ifname, Opt) when is_list(Ifname) ->
+ create(list_to_binary(Ifname), Opt);
+create(Ifname, Opt) when byte_size(Ifname) < ?IFNAMSIZ, is_list(Opt) ->
+ start_link(Ifname, Opt).
+
+devname(Ref) when is_pid(Ref) ->
+ gen_server:call(Ref, devname).
+
+flags(Ref) when is_pid(Ref) ->
+ gen_server:call(Ref, flags).
+
+destroy(Ref) when is_pid(Ref) ->
+ gen_server:call(Ref, destroy).
+
+persist(Ref, Bool) when is_pid(Ref), is_atom(Bool) ->
+ gen_server:call(Ref, {persist, bool(Bool)}).
+
+owner(Ref, Owner) when is_pid(Ref), is_integer(Owner) ->
+ gen_server:call(Ref, {owner, int_to_bin(Owner)}).
+
+group(Ref, Group) when is_pid(Ref), is_integer(Group) ->
+ gen_server:call(Ref, {group, int_to_bin(Group)}).
+
+up(Ref, Addr) when is_pid(Ref), is_list(Addr) ->
+ {ok, IPv4} = inet_parse:address(Addr),
+ up(Ref, IPv4);
+up(Ref, Addr) when is_pid(Ref), is_tuple(Addr) ->
+ gen_server:call(Ref, {up, Addr}).
+
+down(Ref) when is_pid(Ref) ->
+ gen_server:call(Ref, down).
+
+read(Ref, Len) when is_pid(Ref), is_integer(Len) ->
+ gen_server:call(Ref, {read, Len}).
+
+write(Ref, Data) when is_pid(Ref), is_binary(Data) ->
+ gen_server:call(Ref, {write, Data}).
+
+start_link(Ifname, Opt) when is_binary(Ifname), is_list(Opt) ->
+ gen_server:start_link(?MODULE, [Ifname, Opt], []).
+
+
+%%--------------------------------------------------------------------
+%%% Callbacks
+%%--------------------------------------------------------------------
+init([Dev, Opt]) ->
+ Flag = lists:foldl(fun(N, F) -> F bor flag(N) end, 0, Opt),
+ % if Dev is NULL, the tuntap driver will choose an
+ % interface name
+ {ok, FD, Dev1} = mkdev(Dev, Flag),
+ {ok, #state{
+ fd = FD,
+ dev = Dev1,
+ flag = Flag
+ }}.
+
+
+handle_call(devname, _From, #state{dev = Dev} = State) ->
+ {reply, Dev, State};
+
+handle_call(flags, _From, #state{flag = Flag} = State) ->
+ {reply, Flag, State};
+
+handle_call({read, Len}, _From, #state{fd = FD} = State) ->
+ Reply = procket:read(FD, Len),
+ {reply, Reply, State};
+
+handle_call({write, Data}, _From, #state{fd = FD} = State) ->
+ Reply = procket:write(FD, Data),
+ {reply, Reply, State};
+
+handle_call({persist, Status}, _From, #state{fd = FD} = State) ->
+ Reply = set_persist(FD, Status),
+ {reply, Reply, State};
+
+handle_call({owner, Owner}, _From, #state{fd = FD} = State) ->
+ Reply = ioctl(FD, ?TUNSETOWNER, Owner),
+ {reply, Reply, State};
+
+handle_call({group, Group}, _From, #state{fd = FD} = State) ->
+ Reply = ioctl(FD, ?TUNSETGROUP, Group),
+ {reply, Reply, State};
+
+handle_call({up, IP}, _From, #state{dev = Dev} = State) ->
+ Reply = ifup(Dev, IP),
+ {reply, Reply, State};
+
+handle_call(down, _From, #state{dev = Dev} = State) ->
+ Reply = ifdown(Dev),
+ {reply, Reply, State};
+
+handle_call(destroy, _From, State) ->
+ {stop, normal, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+% WTF?
+handle_info(Info, State) ->
+ error_logger:error_report([wtf, Info]),
+ {noreply, State}.
+
+terminate(_Reason, #state{fd = FD, dev = Dev}) ->
+ error_logger:info_report([{see, this}]),
+ ok = ifdown(Dev),
+ ok = set_persist(FD, bool(false)),
+ ok = procket:close(FD),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+ioctl(FD, Request, Opt) ->
+ case procket:ioctl(FD, Request, Opt) of
+ {ok, _} -> ok;
+ Error -> Error
+ end.
+
+mkdev(Dev, Flag) ->
+ {ok, FD} = procket:dev("net/tun"),
+ Result = procket:ioctl(FD, ?TUNSETIFF,
+ <<Dev/binary, 0:((15*8) - (byte_size(Dev)*8)), 0:8, % ifrn_name[IFNAMSIZ]: interface name
+ Flag:2/native-signed-integer-unit:8, % ifru_flags
+ 0:(14*8)>>),
+
+ case Result of
+ {ok, Dev1} ->
+ {ok, FD, hd(binary:split(Dev1, <<0>>))};
+ Error ->
+ ok = procket:close(FD),
+ Error
+ end.
+
+set_persist(FD, Status) ->
+ ioctl(FD, ?TUNSETPERSIST, Status).
+
+flag(tun) -> ?IFF_TUN;
+flag(tap) -> ?IFF_TAP;
+flag(no_pi) -> ?IFF_NO_PI;
+flag(one_queue) -> ?IFF_ONE_QUEUE;
+flag(vnet_hdr) -> ?IFF_VNET_HDR;
+flag(tun_excl) -> ?IFF_TUN_EXCL.
+
+int_to_bin(Int) ->
+ <<Int:4/native-integer-unsigned-unit:8>>.
+
+bool(true) -> <<1:4/native-integer-unsigned-unit:8>>;
+bool(false) -> <<0:4/native-integer-unsigned-unit:8>>.
+
+set_flag(Socket, Dev, Flag) ->
+ {ok, _} = procket:ioctl(Socket, ?SIOCSIFFLAGS,
+ <<Dev/bytes, 0:((15-byte_size(Dev))*8), 0:8,
+ Flag:2/native-signed-integer-unit:8,
+ 0:(14*8)>>),
+ ok.
+
+get_flag(Socket, Dev) ->
+ {ok, <<_:(16*8), Flag:2/native-signed-integer-unit:8, _/binary>>} = procket:ioctl(
+ Socket, ?SIOCGIFFLAGS, <<Dev/bytes, 0:((15-byte_size(Dev))*8), 0:(16*8)>>
+ ),
+ {ok, Flag}.
+
+
+%% Just like ifconfig but with fewer options
+ifup(Dev, {A,B,C,D}) when byte_size(Dev) < ?IFNAMSIZ ->
+ {ok, Socket} = procket:socket(inet, dgram, 0),
+
+ % struct sockaddr_in
+ % dev[IFNAMSIZ], family:2 bytes, port:2 bytes, ipaddr:4 bytes
+ Ifr = <<Dev/bytes, 0:( (?IFNAMSIZ - byte_size(Dev) - 1)*8), 0:8,
+ ?PF_INET:16/native, 0:16, A:8, B:8, C:8, D:8, 0:(8*8)>>,
+
+ Res = try ok = ioctl(Socket, ?SIOCSIFADDR, Ifr),
+ {ok, Flag} = get_flag(Socket, Dev),
+ ok = set_flag(Socket, Dev, Flag bor ?IFF_RUNNING bor ?IFF_UP) of
+ _ -> ok
+ catch
+ error:Error ->
+ Error
+ end,
+
+ ok = procket:close(Socket),
+ Res.
+
+ifdown(Dev) when byte_size(Dev) < ?IFNAMSIZ ->
+ {ok, Socket} = procket:socket(inet, dgram, 0),
+
+ Res = try {ok, Flags} = get_flag(Socket, Dev),
+ ok = set_flag(Socket, Dev, Flags band bnot(?IFF_UP)) of
+ _ -> ok
+ catch
+ error:Error ->
+ Error
+ end,
+
+ ok = procket:close(Socket),
+ Res.
3  start.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+erl -pa deps/*/ebin ebin
Please sign in to comment.
Something went wrong with that request. Please try again.