Browse files

tuncer: support {active,true} mode

Support {active,true} mode in tuncer. {active,true} mode sends the data
to the process' mailbox, like gen_tcp's {active,true} mode. Introduce
controlling_process/2 to switch controlling processes.

Issues:

1. race condition: tuncer flushes the mailbox of the controlling process
and sends the data to the new process, then switches control, then
flushes again.

This ensures the original process flushes all its messages but messages
might be delivered out of order.

2. Some light testing shows that performance doesn't seem to be improved,
but the test only used {active,true} on one side of the tunnel. Should
be better for CPU, since it reduces the amount of polling.
  • Loading branch information...
1 parent cfb4edc commit 09a98a6cec5a997107b25c40f3425ade2aa5f3cd @msantos committed Jun 20, 2011
Showing with 54 additions and 7 deletions.
  1. +54 −7 src/tuncer.erl
View
61 src/tuncer.erl
@@ -45,14 +45,18 @@
header/1,
up/2, down/1,
- mtu/1, mtu/2
+ mtu/1, mtu/2,
+
+ controlling_process/2
]).
-export([start_link/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
+ port, % false, port
+ pid, % PID of controlling process
fd, % TUN/TAP file descriptor
dev, % device name
flag % TUNSETIFF ifr flags
@@ -123,27 +127,45 @@ write(Ref, Data) when is_pid(Ref), is_binary(Data) ->
Fd = fd(Ref),
procket:write(Fd, Data).
+% FIXME: race condition: events can be delivered out of order
+controlling_process(Ref, Pid) when is_pid(Ref), is_pid(Pid) ->
+ flush_events(Ref, Pid),
+ gen_server:call(Ref, {controlling_process, Pid}),
+ flush_events(Ref, Pid).
+
start_link(Ifname, Opt) when is_binary(Ifname), is_list(Opt) ->
- gen_server:start_link(?MODULE, [Ifname, Opt], []).
+ Pid = self(),
+ gen_server:start_link(?MODULE, [Pid, Ifname, Opt], []).
%%--------------------------------------------------------------------
%%% Callbacks
%%--------------------------------------------------------------------
-init([Ifname, Flag]) ->
+init([Pid, Ifname, Flag]) ->
process_flag(trap_exit, true),
+
% if Dev is NULL, the tuntap driver will choose an
% interface name
{ok, FD, Dev} = tunctl:create(Ifname, Flag),
+
+ Active = proplists:get_value(active, Flag, false),
+
+ Port = case Active of
+ true -> set_active(FD);
+ false -> false
+ end,
+
{ok, #state{
+ port = Port,
+ pid = Pid,
fd = FD,
dev = Dev,
flag = Flag
}}.
%%
-%% retrieve gen_server state
+%% retrieve/modify gen_server state
%%
handle_call(devname, _From, #state{dev = Dev} = State) ->
{reply, Dev, State};
@@ -154,6 +176,9 @@ handle_call(flags, _From, #state{flag = Flag} = State) ->
handle_call(fd, _From, #state{fd = FD} = State) ->
{reply, FD, State};
+handle_call({controlling_process, Pid}, {Owner,_}, #state{pid = Owner} = State) ->
+ {reply, ok, State#state{pid = Pid}};
+
%%
%% manipulate the tun/tap device
@@ -187,16 +212,38 @@ handle_call(destroy, _From, State) ->
handle_cast(_Msg, State) ->
{noreply, State}.
+%%
+%% {active, true} mode
+%%
+handle_info({Port, {data, Data}}, #state{port = Port, pid = Pid} = State) ->
+ Pid ! {tuntap, self(), Data},
+ {noreply, State};
+
% WTF?
handle_info(Info, State) ->
error_logger:error_report([wtf, Info]),
{noreply, State}.
terminate(_Reason, #state{fd = FD, dev = Dev}) ->
- ok = tunctl:down(Dev),
- ok = tunctl:persist(FD, false),
- ok = procket:close(FD),
+ tunctl:down(Dev),
+ tunctl:persist(FD, false),
+ procket:close(FD),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+set_active(FD) ->
+ open_port({fd, FD, FD}, [stream, binary]).
+
+flush_events(Ref, Pid) ->
+ receive
+ {tuntap, Ref, _} = Event ->
+ Pid ! Event,
+ flush_events(Ref, Pid)
+ after
+ 0 -> ok
+ end.

0 comments on commit 09a98a6

Please sign in to comment.