summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Wiger <ulf@feuerlabs.com>2016-03-03 16:54:48 -0800
committerUlf Wiger <ulf@feuerlabs.com>2016-03-04 11:17:58 -0800
commit06ae2f45ec6e82dfdae7ebca26cfaa039859b79b (patch)
tree8d2903b671773fc18b0af300e2a90cc6ac298991
parent77c704a3d333e48b699d778f06b5195f63effdd3 (diff)
downloadrvi_core-06ae2f45ec6e82dfdae7ebca26cfaa039859b79b.tar.gz
w.i.p. netlink monitor
-rw-r--r--components/rvi_common/src/rvi_netlink.erl113
1 files changed, 107 insertions, 6 deletions
diff --git a/components/rvi_common/src/rvi_netlink.erl b/components/rvi_common/src/rvi_netlink.erl
index 2d03a23..5a81ecb 100644
--- a/components/rvi_common/src/rvi_netlink.erl
+++ b/components/rvi_common/src/rvi_netlink.erl
@@ -10,7 +10,7 @@
-behaviour(gen_server).
-export([is_network_up/0,
- subscribe/0]).
+ subscribe/0, subscribe/1, subscribe/2]).
-export([start_link/0]).
@@ -22,8 +22,16 @@
terminate/2,
code_change/3]).
+-record(iface, {name,
+ status = down,
+ opts = []}).
+
+-record(sub, {name, field, pid, ref}).
+
-record(st, {connected = false,
- subscribers = []}).
+ ifs = [],
+ subscribers = [],
+ poll_ref}).
-define(BADARG, {?MODULE, '__BADARG__'}).
@@ -31,25 +39,62 @@ is_network_up() ->
call(is_network_up).
subscribe() ->
- call(subscribe).
+ subscribe(all, operstate).
+
+subscribe(Iface) ->
+ subscribe(Iface, operstate).
+
+subscribe(Iface, Field) ->
+ call({subscribe, Iface, Field}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
- {ok, #st{}}.
+ Ref = case code:is_loaded(netlink_drv) of
+ false ->
+ %% Must fake event mechanism through polling
+ start_poll_timer();
+ {file, _} ->
+ netlink:subscribe("", [operstate], [flush]),
+ undefined
+ end,
+ Interfaces = get_interfaces(),
+ {ok, #st{ifs = Interfaces,
+ poll_ref = Ref}}.
handle_call(is_network_up, _From, #st{connected = Conn} = St) ->
{reply, Conn, St};
-handle_call(subscribe, {Pid, _}, #st{subscribers = Subs} = St) ->
+handle_call({subscribe, Iface, Field}, {Pid, _},
+ #st{subscribers = Subs} = St) ->
Ref = erlang:monitor(process, Pid),
- {reply, ok, St#st{subscribers = [{Pid, Ref}|Subs]}};
+ {reply, ok, St#st{subscribers = [#sub{name = Iface,
+ field = Field,
+ pid = Pid,
+ ref = Ref}|Subs]}};
handle_call(_, _From, St) ->
{reply, ?BADARG, St}.
handle_cast(_, St) ->
{noreply, St}.
+handle_info({timeout, _Ref, poll}, #st{ifs = Ifs} = S) ->
+ NewPollRef = start_poll_timer(),
+ NewIfs = get_interfaces(),
+ Diffs = lists:foldr(
+ fun(#iface{name = Name, status = St}, Acc) ->
+ case lists:keyfind(Name, #iface.name, Ifs) of
+ #iface{status = St0} when St0 =/= St ->
+ [{Name, operstate, St0, St}|Acc];
+ _ ->
+ Acc
+ end
+ end, [], NewIfs),
+ {noreply, tell_subscribers(Diffs, S#st{ifs = NewIfs,
+ poll_ref = NewPollRef})};
+handle_info({netlink, NRef, Iface, Field, Prev, New}, St) ->
+ {Prev1, New1} = adjust_status(IFace, Field, Prev, New),
+ {noreply, tell_subscribers([{Iface, Field, Prev1, New1}], St)};
handle_info(_, St) ->
{noreply, St}.
@@ -66,3 +111,59 @@ call(Req) ->
Reply ->
Reply
end.
+
+tell_subscribers(Evts, #st{subscribers = Subs} = St) ->
+ lists:foreach(
+ fun({Name, Field, Old New}) ->
+ [Pid ! {rvi_netlink, Ref, Name, Field, Old New}
+ || #sub{name = N, pid = Pid, ref = Ref} <- Subs,
+ match_name(N, Name)]
+ end, Evts),
+ St.
+
+get_interfaces() ->
+ case inet:getifaddrs() of
+ {ok, IFs} ->
+ [if_entry(I) || {_, Flags} <- IFs];
+ Error ->
+ ?error("getifaddrs() -> ~p", [Error]),
+ []
+ end.
+
+if_entry({Name, Opts}) ->
+ #iface{name = Name,
+ status = if_status(Opts),
+ opts = Opts}.
+
+if_status(Opts) ->
+ case lists:member(up, opt(flags, Opts, [])) of
+ true -> up;
+ false -> down
+ end.
+
+adjust_status(IF, operstate, A, B) ->
+ {adjust_operstate(A, IF), adjust_operstate(B, IF)};
+adjust_status(_, A, B) ->
+ {A, B}.
+
+adjust_operstate(undefined, _) -> down;
+adjust_operstate(unknown, "lo") -> up;
+adjust_operstate(State, _) -> State.
+
+opt(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Val} ->
+ Val;
+ false ->
+ Default
+ end.
+
+match_name(_, all) -> true;
+match_name(N, N ) -> true;
+match_name(A, B) when is_binary(A), is_list(B) ->
+ binary_to_list(A) == B;
+match_name(_, _) ->
+ false.
+
+start_poll_timer() ->
+ erlang:start_timer(?POLL_INTERVAL, self(), poll).