Git Product home page Git Product logo

tunctl's Introduction

tunctl is an Erlang API for creating and using TUN/TAP interfaces.

PRIVILEGES

Linux

For IPv4 addresses, beam needs to have privileges to configure interfaces.

To add cap_net_admin capabilities:

 sudo setcap cap_net_admin=ep /path/to/bin/beam.smp

To check the privileges:

 getcap /path/to/bin/beam.smp

To remove the privileges:

 sudo setcap -r cap_net_admin=ep /path/to/bin/beam.smp

Currently, IPv6 addresses are configured by calling ifconfig using sudo (see below).

Mac OS X

Requires the tun/tap driver from:

http://tuntaposx.sourceforge.net/

Allow the user running tunctl to call ifconfig using sudo:

sudo visudo
youruser ALL=NOPASSWD: /sbin/ifconfig tap*
youruser ALL=NOPASSWD: /sbin/ifconfig tun*

FreeBSD

tunctl uses the FreeBSD tuntap legacy interface.

  1. Ensure the tap device kernel module is loaded:

     $ kldstat
     $ kldload if_tap
    

    If you want the tap driver loaded on boot, add to /boot/loader.conf:

     if_tap_load="YES"
    
  2. Check cloning is enabled:

     $ sysctl net.link.tun.devfs_cloning
     net.link.tun.devfs_cloning: 1
    
     $ sysctl net.link.tap.devfs_cloning
     net.link.tap.devfs_cloning: 1
    
  3. Allow the user running tunctl to call ifconfig using sudo:

     sudo visudo
     youruser ALL=NOPASSWD: /sbin/ifconfig tap*
     youruser ALL=NOPASSWD: /sbin/ifconfig tun*
    

EXPORTS

tuncer

Tuncer is a stand up guy and just like him, tuncer has your back.

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 | multi_queue | vnet_hdr | tun_excl
                    | {active, false} | {active, true} | {namespace, NameSpace} ]

    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").

    When the device is in {active, true} mode, data is sent as
    messages:

        {tuntap, PID, binary()}

    If an error is encountered:

        {tuntap_error, PID, posix()}

    Retrieving data from devices in {active, false} mode can be done
    using recv/1,2 or read/1,2.

    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, {active, false}].

destroy(Ref) -> ok

    Types   Ref = pid()

    Remove the TUN/TAP interface.

send(Ref, Data) -> ok | {error, posix()}

    Types   Ref = pid()
            Data = binary()

    Write data to the tun/tap device.

recv(Ref) -> {ok, Buf} | {error, posix()}
recv(Ref, Size) -> {ok, Buf} | {error, posix()}

    Types   Ref = pid()
            Size = integer()
            Buf = binary()

    Read data from the tuntap interface.

    If the device is in {active, true} mode, recv/1,2 will return
    {error, einval}.

setopt(Ref, Option) -> ok | {error, posix()}

    Types   Ref = pid()
            Option = {active, true} | {active, false}

    Set an option. setopt/2 can be used for performing flow control
    when active mode is enabled.

controlling_process(Ref, NewOwner) -> ok | {error, posix()}

    Types   Ref = NewOwner = pid()

    Transfer ownership of the tuntap device to another process.

up(Ref, IP) -> ok | {error, posix()}

    Types   Ref = pid()
            IP = IPv4 | IPv6
            IPv4 = list() | tuple()
            IPv6 = 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.

dstaddr(Ref, IP) -> ok | {error, posix()}

    Types   Ref = pid()
            IP = IPv4
            IPv4 = list() | tuple()

    Configure the remote address for a TUN/TAP interface in
    point-to-point mode.

    Currently only IPv4 addresses on Linux are supported.

broadcast(Ref, IP) -> ok | {error, posix()}

    Types   Ref = pid()
            IP = IPv4
            IPv4 = list() | tuple()

    Configure the broadcast address for a TUN/TAP interface.

    Currently only IPv4 addresses on Linux are supported.

persist(Ref, Boolean) -> ok | {error, posix()}

    Types   Ref = pid()
            Boolean = [ true | false ]

    (Linux only)

    Set the interface to exist after the Erlang process exits.

owner(Ref, Owner) -> ok | {error, posix()}

    Types   Ref = pid()
            Owner = integer()

    (Linux only)

    Set the uid owning the interface.

group(Ref, Group) -> ok | {error, posix()}

    Types   Ref = pid()
            Group = integer()

    (Linux only)

    Set the gid owning the interface.

getfd(Ref) -> integer()

    Types   Ref = pid()

    Get the file descriptor associated with the process. Use getfd/1
    with read/1,2 and write/2 to interact directly with the tuntap device
    (bypassing the gen_server).

read(Fd) -> {ok, Buf} | {error, posix()}
read(Fd, Size) -> {ok, Buf} | {error, posix()}

    Types   Fd = integer()
            Size = integer()
            Buf = binary()

    Read data from the tuntap interface.

write(Fd, Buf) -> ok | {error, posix()}

    Types   Ref = integer()
            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.

tunctl

tunctl does the actual tun/tap device manipulation. Some functions take a device name, others a file descriptor. It is up to the caller to make sure the file descriptors are closed (the device will disappear after the fd is closed if the device is not persistent).

create() -> {ok, FD, Device}
create(Ifname) -> {ok, FD, Device}
create(Ifname, Flags) -> {ok, FD, Device}

    Types   FD = integer()
            Device = binary()
            Flags = list()

persist(FD, Bool) -> ok | {error, posix()}

    Types   FD = integer()
            Bool = true | false

owner(FD, UID) -> ok | {error, posix()}

    Types   FD = integer()
            UID = integer()

group(FD, GID) -> ok | {error, posix()}

    Types   FD = integer()
            UID = integer()

up(Device, IPv4Address) -> ok

    Types   Device = binary()
            IPv4Address = tuple()

down(Device) -> ok

    Types   Device = binary()

EXAMPLES

  • "Passive" mode

      1> {ok, Ref} = tuncer:create().
      {ok,<0.34.0>}
    
      2> tuncer:devname(Ref).
      <<"tap0">>
    
      3> tuncer:up(Ref, "192.168.123.4").
      ok
    
      4> FD = tuncer:getfd(Ref).
      9
    
      5> {ok, Buf} = tuncer:read(FD, 1500).
      {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,...>>}
    
      6> tuncer:destroy(Ref).
      ok
    
  • Active mode

      1> {ok, Ref} = tuncer:create(<<>>, [tap, no_pi, {active, true}]).
      {ok,<0.34.0>}
    
      2> tuncer:devname(Ref).
      <<"tap0">>
    
      3> tuncer:up(Ref, "192.168.123.4").
      ok
    
      4> flush().
      Shell got {tuntap,<0.47.0>,
                        <<51,51,0,0,0,22,250,6,27,10,131,177,134,221,96,0,0,0,0,36,
                          0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,2,0,0,0,0,0,0,0,0,
                          0,0,0,0,0,22,58,0,5,2,0,0,1,0,143,0,235,206,0,0,0,1,4,0,0,
                          0,255,2,0,0,0,0,0,0,0,0,0,1,255,10,131,177>>}
      Shell got {tuntap,<0.47.0>,
                        <<51,51,255,10,131,177,250,6,27,10,131,177,134,221,96,0,0,0,
                          0,24,58,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,2,0,0,0,0,
                          0,0,0,0,0,1,255,10,131,177,135,0,98,169,0,0,0,0,254,128,0,
                          0,0,0,0,0,248,6,27,255,254,10,131,177>>}
    
      5> tuncer:destroy(Ref).
      ok
    

vpwn

vpwn will set up a point to point tunnel over the Erlang distribution protocol.

Compile vpwn on the source and destination nodes:

erlc -I deps -o ebin examples/*.erl

Run Erlang on the destination node:

erl -pa deps/*/ebin ebin -setcookie OMNOMNOM -name node

And on the source node:

erl -pa deps/*/ebin ebin -setcookie OMNOMNOM -name node

Then start up the tunnel (replace the host name):

vpwn:start('[email protected]', "10.10.10.1", "10.10.10.2").

Then connect over the tunnel to the second node:

ping 10.10.10.2
ssh 10.10.10.2

Bridging

br is an example of a simple bridge that floods frames to all the switch ports. br uses a tap device plugged into a Linux bridge as an uplink port and 1 or more tap devices as the switch ports.

This example uses the tap devices as interfaces for Linux containers (LXC).

  • Create a bridge and attach the physical ethernet interface
# /etc/network/interfaces
iface br0 inet dhcp
    bridge_ports eth0
    bridge_stp off
    bridge_fd 0
    bridge_maxwait 0
  • Start the bridge:

    • erlbr0 is the name of the tap device connected to the bridge
    • erl0, erl1, erl2 are the tap devices used by the containers
br:start(["erlbr0", "erl0", "erl1", "erl2"]).
  • In another shell, as root, bring up the uplink and attach it to the bridge:
# ifconfig erlbr0 up
# brctl addif br0 erlbr0
# brctl show br0
bridge name     bridge id               STP enabled     interfaces
br0             8000.4aec6d3a44d1       no              erlbr0
  • Move the switch port interface into the container. The interface name inside the container will be known as "erl0".
lxc.network.type=phys
lxc.network.link=erl0
lxc.network.flags=up

TODO

  • Linux:

    • the TUNSETIFF ioctl request to create the interface requires CAP_NET_ADMIN privileges. Look at moving the interface creation into the procket setuid binary for OSes that use the multiplexing dev.

    • add support for tun filtering

  • make sure tuncer can never leak file descriptors

tunctl's People

Contributors

arashbm avatar jbdamiano avatar meox avatar msantos avatar yamt avatar zanebeckwith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tunctl's Issues

using tun/tap device the erlang way

I am interresting to setup such tunnel, but how would you open such device from erlang and read it? For example if you want to accept connections for an http server over it. Any hint is welcome :)

using tun device not working

'Im on linux.

a)

  1. Create tun on gen_server using:

{ok,Dev} = tuncer:create("tun1",[tun,no_pi,{active,true]),
ok = tuncer:up(Dev,"10.8.0.2")

this works :-)

  1. start gen_tcp server listen on 10.8.0.2 on port 8081
  2. start gen_tcp client connect to 10.8.0.2:8081 with option {ip,{10,8,0,2}}

I expect to get message {tuntap,Pid,Data} on gen_server from 0)

But nothing happens!

b)

  1. Create tun on gen_server using:

{ok,Dev} = tuncer:create("tun1",[tun,no_pi,{active,true]),
ok = tuncer:up(Dev,"10.8.0.2")

this works :-)

  1. start gen_tcp server listen on 10.8.0.2 on port 8081
  2. send an syn packet using tuncer:send(Dev,Packet)

Packet have srcAddr and destAddr of 10.8.0.2, destPort = 8081, srcPort = 4711
Packet is ok, i see it on tshark -i tun1

I expect to see Syn,Ack in tshark but always get an TCP Retransmission instead

I'm wrong?

Any hints?

what about dirty shedulers

i have some schedulers collapse when one or more tuncer:send call takes more than 1ms

2018-03-27 14:18:44.900 [warning]  tuncer_send 1687
2018-03-27 14:18:44.905 [warning]  tuncer_send 1492
2018-03-27 14:18:44.907 [warning]  tuncer_send 1296
2018-03-27 14:18:44.908 [warning]  tuncer_send 831
2018-03-27 14:18:44.910 [warning]  tuncer_send 1802
2018-03-27 14:18:44.958 [warning]  tuncer_send 822
2018-03-27 14:18:50.408 [warning]  tuncer_send 1702
2018-03-27 14:18:50.431 [warning]  tuncer_send 199
2018-03-27 14:18:50.431 [warning]  tuncer_send 140

Tun device interface down and drestroyed in case of genserver crash

I have a use case where we have to configure a tunnel externally (for dome deployement constraint).
The only functions that I call is the create and the persist (I can't call the up because I don'have the IP).
I noticed that in case of a fault the tun device goes complitely down or has been drestroyed.

Platform: Linux (Debian 5.10.46-4 )

Example:

-module(crash_server).

-behaviour(gen_server).

-export([init/1, start_link/0]).
-export([handle_call/3]).
-export([crash/0]).

start_link() ->
  gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

init(_) ->
  {ok, Ref} = tuncer:create("tun1", [tun, no_pi, {active, false}]),
  ok = tuncer:persist(Ref, true),
  {ok, Ref}.

crash() ->
  gen_server:call(?MODULE, crash).

handle_call(crash, _From, State) ->
  {ok, exit("boom"), State}.

don't down interface

for example we have two interfaces:
eth1 and tap2.
we connect to eth1 via ewpcap, work and than use ewpcap:close. after interface is still up.
after we restart application which works with tap2 via tunctl we need every type to up interface manually like 'ifup tap2'. it's not useful to make it via tunctl:up, because application works on different nodes and we need custom configs with ips for interfaces.

is there any possibility to keep interface up? if we delete call of '_ = tunctl:down(Dev),' or 'procket:close(FD)' interface is up, but we get 'ebusy' error

tunctl with external configure device

I have configured manually a tun0 device with a specific destination:

ip tuntap add dev tun0 mode tun user `id -un`
ip link set dev tun0 up
ip addr add dev tun0 local 192.168.123.1 remote 192.168.123.2

And than using tunctl I wrote a simple function to use this device:

tun_attach(DevName) ->
  Opts = [tun, no_pi, {active, false}],
  tuncer:create(DevName, Opts).

The problem with this approach is if the tuncer crash: the interface tun0 goes down.

So I have 2 questions:

  1. Should I pass the persist flag to the create function?
  2. Is there a way to create the tun device from this lib setting a destination address?

tuncer:send timeout

I don't understand why I got sporadic {error, timeout},
is this a known issue?

{error, eagain}

Hi,
I experimented a bit but I must be doing something wrong.
Can someone help?

Cheers
/Jocke

jocke@xev:/src/tunctl/trunk$ getcap /usr/lib/erlang/erts-5.9.1/bin/beam.smp
/usr/lib/erlang/erts-5.9.1/bin/beam.smp = cap_net_admin+ep
jocke@xev:
/src/tunctl/trunk$ getcap /usr/lib/erlang/erts-5.9.1/bin/beam
/usr/lib/erlang/erts-5.9.1/bin/beam = cap_net_admin+ep
jocke@xev:~/src/tunctl/trunk$ cat test.erl
-module(test).
-compile(export_all).

test() ->
case tuncer:create("tun0", [tun, no_pi, {active, false}]) of
{ok, Pid} ->
io:format("Device: pn", [tuncer:devname(Pid)]),
ok = tuncer:up(Pid, {10,0,0,1}),
recv(Pid);
Error ->
Error
end.

recv(Pid) ->
case tuncer:recv(Pid) of
{ok, Buf} ->
io:format("Buf: pn", [Buf]),
recv(Pid);
Error ->
io:format("Error: pn", [Error]),
timer:sleep(2000),
recv(Pid)
end.
jocke@xev:~/src/tunctl/trunk$ ./start.sh
Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false]

Eshell V5.9.1 (abort with ^G)
1> c(test).
{ok,test}
2> test:test().
Device: <<"tun0">>
Error: {error,eagain}
Error: {error,eagain}

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
^C
jocke@xev:~/src/tunctl/trunk$ sudo ./start.sh
[sudo] password for jocke:
Erlang R15B01 (erts-5.9.1) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false]

Eshell V5.9.1 (abort with ^G)
1> test:test().
Device: <<"tun0">>
Error: {error,eagain}
Error: {error,eagain}
Error: {error,eagain}

BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
(v)ersion (k)ill (D)b-tables (d)istribution
jocke@xev:~/src/tunctl/trunk$

Consider tagging

We need a way to include more easily the right tunctl version in our project.
Can you start using tags?

rebar3 error when I try to compile

make all
===> Error evaluating configuration script at "rebar.config.script":
{error,{81,file,
{error,{badmatch,{error,eexist}},
[{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,450}]},
{erl_eval,exprs,5,[{file,"erl_eval.erl"},{line,123}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,270}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,446}]},
{erl_eval,exprs,5,[{file,"erl_eval.erl"},{line,123}]},
{lists,foldl,3,[{file,"lists.erl"},{line,1267}]},
{erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,685}]},
{file,eval_stream2,6,[{file,"file.erl"},{line,1504}]}]}}}

===> Uncaught error in rebar_core. Run with DIAGNOSTIC=1 to see stacktrace or consult rebar3.crashdump
===> When submitting a bug report, please include the output of rebar3 report "your command"
make: *** [Makefile:9: compile] Error 1

rebar version is rebar 3.17.0

tunctl with gen_udp

I'm trying to create two interfaces, have an erlang process for each and use gen_udp to communicate between them. But on OSX it always crashes. One process is able to send over gen_udp, the other one not. Do you have any insight why the attached code does not work?

Exception:
** Generic server <0.37.0> terminating
** Last message in was {'EXIT',<0.35.0>,
{{badmatch,{error,ehostunreach}},
[{tt,route2,1,
[{file,"src/tt.erl"},{line,50}]}]}}

Code:

-module(tt).
-compile(export_all).

s() ->
    IP1 = {192,168,100,100},
    IP2 = {192,168,100,101},
    Pid1 = spawn(fun() -> proc(IP1,IP2,0,{0,0}) end), %{1000000,1}
    Pid2 = spawn(fun() -> proc(IP2,IP1,1,{0,0}) end),
    Pid1 ! {peerlist,[Pid2]},
    Pid2 ! {peerlist,[Pid1]},
    ok.


-record(dev,{name, dev, ip, index, peers = [], delay = {0,0},
    online = true, udp,ip2,
    in_buf = [], in_reversed = [], in_unreversed = [],
    out_buf = [], out_reversed = [], out_unreversed = []}).

proc(IP1,IP2,N,Delay) ->
    Nm = "tap"++integer_to_list(N),
    {ok, Dev} = tuncer:create(Nm, [tap, no_pi, {active, true}]),
    ok = tuncer:up(Dev, IP1),
    io:format("Network up ~p~n",[IP1]),
    {ok,Udp} = gen_udp:open(5000,[binary,{ip,IP1},{active,true}]),
    R = #dev{dev = Dev, ip = IP1, index = N, udp = Udp, ip2 = IP2,delay = Delay},
    erlang:send_after(2000,self(),send),
    route2(R).


route2(R) ->
    receive
            {tuntap, _Dev, Data} ->
                    io:format("rec tuntap ~p~n",[self()]),
                    [P ! Data || P <- R#dev.peers],
                    route2(R);
            <<_/binary>> = Bin ->
                    io:format("rec bin ~p~n",[self()]),
                    tuncer:send(R#dev.dev,Bin),
                    route2(R);
            {udp, _Socket, _IP, _InPortNo, Packet} ->
                    io:format("Received udp from time=~p, now=~p~n",[binary_to_term(Packet),os:timestamp()]),
                    self() ! send,
                    route2(R);
            {peerlist,L} ->
                    route2(R#dev{peers = L});
            send ->
                    io:format("Sending ~p to=~p~n",[self(),R#dev.ip2]),
                    ok = gen_udp:send(R#dev.udp,R#dev.ip2,5000,term_to_binary(os:timestamp())),
                    io:format("Sent to=~p~n",[R#dev.ip2]),
                    route2(R)
    end.

overruns and packet loss

I'm using tunctl to read UDP/VoiP packets from the TUN interface and at about 0.35MB/sec, or 1750 packets/sec, in each direction, I start seeing overruns and packets getting lost. Do you have any thoughts or suggestions on how to fix this? I optimized my code and got about a 10% improvement which indicates that it may have to do with how fast I'm reading from the TUN interface, but there's nothing left to optimize as I'm literally just reading and passing the data to a different process. The CPU is 50% idle. I'm using Ubuntu. Is there a known rate limit or any kernel settings I should look into?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.