summaryrefslogtreecommitdiff
path: root/deps/netlink/src/netlink.erl
diff options
context:
space:
mode:
Diffstat (limited to 'deps/netlink/src/netlink.erl')
-rw-r--r--deps/netlink/src/netlink.erl737
1 files changed, 737 insertions, 0 deletions
diff --git a/deps/netlink/src/netlink.erl b/deps/netlink/src/netlink.erl
new file mode 100644
index 0000000..bd08477
--- /dev/null
+++ b/deps/netlink/src/netlink.erl
@@ -0,0 +1,737 @@
+%%%---- BEGIN COPYRIGHT -------------------------------------------------------
+%%%
+%%% Copyright (C) 2012 Feuerlabs, Inc. All rights reserved.
+%%%
+%%% This Source Code Form is subject to the terms of the Mozilla Public
+%%% License, v. 2.0. If a copy of the MPL was not distributed with this
+%%% file, You can obtain one at http://mozilla.org/MPL/2.0/.
+%%%
+%%%---- END COPYRIGHT ---------------------------------------------------------
+%%%-------------------------------------------------------------------
+%%% @author Tony Rogvall <tony@rogvall.se>
+%%% @doc
+%%% Netlink state monitor
+%%% @end
+%%% Created : 11 Jun 2012 by Tony Rogvall <tony@rogvall.se>
+%%%-------------------------------------------------------------------
+-module(netlink).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0, start_link/1]).
+-export([start/0, stop/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+-export([i/0, list/1]).
+-export([subscribe/1, subscribe/2, subscribe/3]).
+-export([unsubscribe/1]).
+-export([invalidate/2]).
+-export([get_root/2, get_match/3, get/4]).
+
+-include("log.hrl").
+-include("netlink.hrl").
+-include("netl_codec.hrl").
+
+-define(SERVER, ?MODULE).
+
+-type if_addr_field() :: address | local | broadcast | anycast | multicast.
+
+-type if_link_field() :: name | index | mtu | txqlen | flags |
+ operstate | qdisc | address | broadcast.
+
+-type uint8_t() :: 0..16#ff.
+-type uint16_t() :: 0..16#ffff.
+
+-type ipv4_addr() :: {uint8_t(),uint8_t(),uint8_t(),uint8_t()}.
+-type ipv6_addr() :: {uint16_t(),uint16_t(),uint16_t(),uint16_t(),
+ uint16_t(),uint16_t(),uint16_t(),uint16_t()}.
+
+-type if_addr() :: ipv4_addr() | ipv6_addr().
+
+-type if_field() :: if_link_field() | if_addr_field() |
+ {link,if_link_field()} | {addr,if_addr_field()}.
+
+-type if_name() :: string().
+
+
+-record(link,
+ {
+ name :: if_name(), %% interface name
+ index :: non_neg_integer(), %% interface index
+ attr :: term() %% attributes {atom(),term}
+ }).
+
+-record(addr,
+ {
+ addr :: if_addr(), %% the address
+ name :: if_name(), %% interface label
+ index :: non_neg_integer(), %% interface index
+ attr :: term() %% attributes
+ }).
+
+-record(subscription,
+ {
+ pid :: pid(), %% subscriber
+ mon :: reference(), %% monitor
+ name :: string(), %% name
+ fields=all :: all | addr | link | [if_field()]
+ }).
+
+-define(MIN_RCVBUF, (128*1024)).
+-define(MIN_SNDBUF, (32*1024)).
+
+-define(REQUEST_TMO, 2000).
+
+-record(request,
+ {
+ tmr, %% timer reference
+ call, %% call request
+ from, %% caller
+ reply=ok, %% reply to send when done
+ seq=0 %% sequence to expect in reply
+ }).
+
+-record(state,
+ {
+ port,
+ link_list = [] :: [#link {}],
+ addr_list = [] :: [#addr {}],
+ sub_list = [] :: [#subscription {}],
+ request :: undefined | #request{},
+ request_queue = [] :: [#request{}],
+ o_seq = 0,
+ i_seq = 0,
+ ospid
+ }).
+
+%%%===================================================================
+%%% API
+%%%===================================================================
+
+start() ->
+ application:start(netlink).
+
+i() ->
+ gen_server:call(?SERVER, {list,[]}).
+
+stop() ->
+ gen_server:call(?SERVER, stop).
+
+list(Match) ->
+ gen_server:call(?SERVER, {list,Match}).
+
+%% @doc
+%% Subscribe to interface changes, notifications will be
+%% sent in {netlink,reference(),if_name(),if_field(),OldValue,NewValue}
+%% @end
+
+-spec subscribe(Name::string()) ->
+ {ok,reference()}.
+
+subscribe(Name) ->
+ subscribe(Name,all,[]).
+
+-spec subscribe(Name::string(),Fields::all|[if_field()]) ->
+ {ok,reference()}.
+
+subscribe(Name,Fields) ->
+ subscribe(Name,Fields, []).
+
+-spec subscribe(Name::string(),Fields::all|[if_field()],Otions::[atom()]) ->
+ {ok,reference()}.
+subscribe(Name,Fields,Options) ->
+ gen_server:call(?SERVER, {subscribe, self(),Name,Options,Fields}).
+
+unsubscribe(Ref) ->
+ gen_server:call(?SERVER, {unsubscribe,Ref}).
+
+%% clear all attributes for interface Name
+invalidate(Name,Fields) ->
+ gen_server:call(?SERVER, {invalidate,Name,Fields}).
+
+get_root(What,Fam) ->
+ get(What,Fam,[root,match,request],[]).
+
+get_match(What,Fam,GetAttrs) ->
+ get(What,Fam,[match,request],GetAttrs).
+
+get(What,Fam,GetFlags,GetAttrs) ->
+ gen_server:call(?SERVER, {get,What,Fam,GetFlags,GetAttrs}).
+
+%%--------------------------------------------------------------------
+%% @doc
+%% Starts the server
+%%
+%% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
+%% @end
+%%--------------------------------------------------------------------
+start_link() ->
+ start_link([]).
+start_link(Opts) ->
+ gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], []).
+
+%%%===================================================================
+%%% gen_server callbacks
+%%%===================================================================
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Initializes the server
+%%
+%% @spec init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% @end
+%%--------------------------------------------------------------------
+init([Opts]) ->
+ OsPid = list_to_integer(os:getpid()),
+ I_Seq = O_Seq = 1234, %% element(2,now()),
+ State = #state{ ospid = OsPid,
+ o_seq = O_Seq,
+ i_seq = I_Seq },
+
+ case os:type() of
+ {unix, linux} ->
+ init_drv(Opts, State);
+ _ ->
+ {ok, State}
+ end.
+
+init_drv(Opts, State) ->
+ Port = netlink_drv:open(?NETLINK_ROUTE),
+
+ netlink_drv:debug(Port, proplists:get_value(debug,Opts,none)),
+
+ {ok,Rcvbuf} = update_rcvbuf(Port, ?MIN_RCVBUF),
+ {ok,Sndbuf} = update_sndbuf(Port, ?MIN_SNDBUF),
+
+ ?info("Rcvbuf: ~w, Sndbuf: ~w", [Rcvbuf, Sndbuf]),
+
+ {ok,Sizes} = netlink_drv:get_sizeof(Port),
+ ?info("Sizes: ~w", [Sizes]),
+
+ ok = netlink_drv:add_membership(Port, ?RTNLGRP_LINK),
+ ok = netlink_drv:add_membership(Port, ?RTNLGRP_IPV4_IFADDR),
+ ok = netlink_drv:add_membership(Port, ?RTNLGRP_IPV6_IFADDR),
+
+ netlink_drv:activate(Port),
+ %% init sequence to fill the cache
+ T0 = erlang:start_timer(200, self(), request_timeout),
+ R0 = #request { tmr = T0,
+ call = noop,
+ from = {self(),make_ref()}
+ },
+ R1 = #request { tmr = {relative, ?REQUEST_TMO},
+ call = {get,link,unspec,
+ [root,match,request],
+ []},
+ from = {self(),make_ref()}
+ },
+ R2 = #request { tmr = {relative, 1000},
+ call = noop,
+ from = {self(),make_ref()}
+ },
+ R3 = #request { tmr = {relative,?REQUEST_TMO},
+ call = {get,addr,unspec,
+ [root,match,request],
+ []},
+ from = {self(),make_ref()}
+ },
+ {ok, State#state{ port=Port,
+ request = R0,
+ request_queue = [R1,R2,R3]
+ }}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling call messages
+%%
+%% @spec handle_call(Request, From, State) ->
+%% {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_call({list,Match}, _From, State) ->
+ lists:foreach(
+ fun(L) ->
+ %% select addresses that belong to link L
+ Ys = [Y || Y <- State#state.addr_list,
+ Y#addr.index =:= L#link.index],
+ FYs = [format_addr(Y) || Y <- Ys ],
+ case match(L#link.attr,dict:new(),Match) of
+ true ->
+ io:format("link {~s~s}\n",
+ [FYs,format_link(L)]);
+ false ->
+ ok
+ end
+ end, State#state.link_list),
+ {reply, ok, State};
+handle_call({subscribe, Pid, Name, Options, Fs}, _From, State) ->
+ Mon = erlang:monitor(process, Pid),
+ S = #subscription { pid=Pid, mon=Mon, name=Name, fields=Fs },
+ SList = [S | State#state.sub_list],
+ case proplists:get_bool(flush, Options) of
+ false ->
+ {reply, {ok,Mon}, State#state { sub_list = SList }};
+ true ->
+ lists:foreach(
+ fun(L) ->
+ As = dict:to_list(L#link.attr),
+ update_attrs(L#link.name, link, As, dict:new(), [S])
+ end, State#state.link_list),
+ lists:foreach(
+ fun(Y) ->
+ As = dict:to_list(Y#addr.attr),
+ update_attrs(Y#addr.name, addr, As, dict:new(), [S])
+ end, State#state.addr_list),
+ {reply, {ok,Mon}, State#state { sub_list = SList }}
+ end;
+handle_call({unsubscribe,Ref}, _From, State) ->
+ case lists:keytake(Ref, #subscription.mon, State#state.sub_list) of
+ false ->
+ {reply,ok,State};
+ {value,_S,SubList} ->
+ erlang:demonitor(Ref),
+ {reply,ok,State#state { sub_list=SubList }}
+ end;
+handle_call({invalidate,Name,Fields},_From,State) ->
+ case lists:keytake(Name, #link.name, State#state.link_list) of
+ false -> {reply, {error,enoent}, State};
+ {value,L,Ls} ->
+ Attr = lists:foldl(
+ fun(F,D) when is_atom(F) ->
+ dict:erase(F, D)
+ end, L#link.attr, Fields),
+ L1 = L#link { attr = Attr },
+ {reply, ok, State#state { link_list = [L1|Ls] } }
+ end;
+
+handle_call(Req={get,_What,_Fam,_Flags,_Attrs}, From, State) ->
+ ?debug("handle_call: GET: ~p", [Req]),
+ State1 = enq_request(Req, From, State),
+ State2 = dispatch_command(State1),
+ {noreply, State2};
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_Request, _From, State) ->
+ Reply = ok,
+ {reply, Reply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling cast messages
+%%
+%% @spec handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Handling all non call/cast messages
+%%
+%% @spec handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% @end
+%%--------------------------------------------------------------------
+
+handle_info(_Info={nl_data,Port,Data},State) when Port =:= State#state.port ->
+ try netlink_codec:decode(Data,[]) of
+ MsgList ->
+ %% FIXME: the messages should be delivered one by one from
+ %% the driver so the decoding could simplified.
+ State1 =
+ lists:foldl(
+ fun(Msg,StateI) ->
+ ?debug("handle_info: msg=~p", [Msg]),
+ _Hdr = Msg#nlmsg.hdr,
+ MsgData = Msg#nlmsg.data,
+ handle_nlmsg(MsgData, StateI)
+ end, State, MsgList),
+ {noreply, State1}
+ catch
+ error:_ ->
+ ?error("netlink: handle_info: Crash: ~p",
+ [erlang:get_stacktrace()]),
+ {noreply, State}
+ end;
+
+handle_info({'DOWN',Ref,process,Pid,Reason}, State) ->
+ case lists:keytake(Ref, #subscription.mon, State#state.sub_list) of
+ false ->
+ {noreply,State};
+ {value,_S,SubList} ->
+ ?debug("subscription from pid ~p deleted reason=~p",
+ [Pid, Reason]),
+ {noreply,State#state { sub_list=SubList }}
+ end;
+handle_info({timeout,Tmr,request_timeout}, State) ->
+ R = State#state.request,
+ if R#request.tmr =:= Tmr ->
+ ?debug("Timeout: ref current", []),
+ gen_server:reply(R#request.from, {error,timeout}),
+ State1 = State#state { request = undefined },
+ {noreply, dispatch_command(State1)};
+ true ->
+ case lists:keytake(Tmr, #request.tmr, State#state.request_queue) of
+ false ->
+ ?debug("Timeout: ref not found", []),
+ {noreply, State};
+ {value,#request { from = From},Q} ->
+ ?debug("Timeout: ref in queue", []),
+ gen_server:reply(From, {error,timeout}),
+ State1 = State#state { request_queue = Q },
+ {noreply,dispatch_command(State1)}
+ end
+ end;
+handle_info({Tag, Reply}, State) when is_reference(Tag) ->
+ ?debug("INFO: SELF Reply=~p", [Reply]),
+ {noreply, State};
+handle_info(_Info, State) ->
+ ?debug("INFO: ~p", [_Info]),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_server terminates
+%% with Reason. The return value is ignored.
+%%
+%% @spec terminate(Reason, State) -> void()
+%% @end
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Convert process state when code is changed
+%%
+%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% @end
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%%===================================================================
+%%% Internal functions
+%%%===================================================================
+
+enq_request(Call, From, State) ->
+ Tmr = erlang:start_timer(?REQUEST_TMO, self(), request_timeout),
+ R = #request { tmr = Tmr,
+ call = Call,
+ from = From
+ },
+ Q = State#state.request_queue ++ [R],
+ State#state { request_queue = Q }.
+
+dispatch_command(State) when State#state.request =:= undefined ->
+ case State#state.request_queue of
+ [R=#request { call = {get,What,Fam,Flags,Attrs} } | Q ] ->
+ R1 = update_timer(R),
+ State1 = State#state { request_queue = Q, request = R1 },
+ ?debug("dispatch_command: ~p", [R1]),
+ get_command(What,Fam,Flags,Attrs,State1);
+ [R=#request { call = noop } | Q ] ->
+ R1 = update_timer(R),
+ State1 = State#state { request_queue = Q, request = R1 },
+ ?debug("dispatch_command: ~p", [R1]),
+ State1; %% let it timeout
+ [] ->
+ State
+ end;
+dispatch_command(State) ->
+ State.
+
+update_timer(R = #request { tmr = {relative,Tmo} })
+ when is_integer(Tmo), Tmo >= 0 ->
+ Tmr = erlang:start_timer(Tmo, self(), request_timeout),
+ R#request { tmr = Tmr };
+update_timer(R = #request { tmr = Tmr }) when is_reference(Tmr) ->
+ R.
+
+update_sndbuf(Port, Min) ->
+ case netlink_drv:get_sndbuf(Port) of
+ {ok,Size} when Size >= Min ->
+ {ok,Size};
+ {ok,_Size} ->
+ netlink_drv:set_sndbuf(Port, Min),
+ netlink_drv:get_sndbuf(Port);
+ Err -> Err
+ end.
+
+
+update_rcvbuf(Port, Min) ->
+ case netlink_drv:get_rcvbuf(Port) of
+ {ok,Size} when Size >= Min ->
+ {ok,Size};
+ {ok,_Size} ->
+ netlink_drv:set_rcvbuf(Port, Min),
+ netlink_drv:get_rcvbuf(Port);
+ Err -> Err
+ end.
+
+get_command(link,Fam,Flags,Attrs,State) ->
+ Seq = State#state.o_seq,
+ Get = #getlink{family=Fam,arphrd=ether,index=0,
+ flags=[], change=[], attributes=Attrs},
+ Hdr = #nlmsghdr { type = getlink,
+ flags = Flags,
+ seq = Seq,
+ pid = State#state.ospid },
+ Request = netlink_codec:encode(Hdr,Get),
+ netlink_drv:send(State#state.port, Request),
+ State#state { o_seq = (Seq+1) band 16#ffffffff };
+get_command(addr,Fam,Flags,Attrs,State) ->
+ Seq = State#state.o_seq,
+ Get = #getaddr{family=Fam,prefixlen=0,flags=[],scope=0,
+ index=0,attributes=Attrs},
+ Hdr = #nlmsghdr { type=getaddr,
+ flags=Flags,
+ seq=Seq,
+ pid=State#state.ospid },
+ Request = netlink_codec:encode(Hdr,Get),
+ netlink_drv:send(State#state.port, Request),
+ State#state { o_seq = (Seq+1) band 16#ffffffff}.
+
+handle_nlmsg(RTM=#newlink{family=_Fam,index=Index,flags=Fs,change=Cs,
+ attributes=As}, State) ->
+ ?debug("RTM = ~p", [RTM]),
+ Name = proplists:get_value(ifname, As, ""),
+ As1 = [{index,Index},{flags,Fs},{change,Cs}|As],
+ case lists:keytake(Index, #link.index, State#state.link_list) of
+ false ->
+ Attr = update_attrs(Name, link, As1, dict:new(), State#state.sub_list),
+ L = #link { index = Index, name = Name, attr = Attr },
+ Ls = [L|State#state.link_list],
+ State#state { link_list = Ls };
+ {value,L,Ls} ->
+ Attr = update_attrs(Name, link, As1, L#link.attr, State#state.sub_list),
+ L1 = L#link { name = Name, attr = Attr },
+ State#state { link_list = [L1|Ls] }
+ end;
+handle_nlmsg(RTM=#dellink{family=_Fam,index=Index,flags=_Fs,change=_Cs,
+ attributes=As}, State) ->
+ ?debug("RTM = ~p\n", [RTM]),
+ Name = proplists:get_value(ifname, As, ""),
+ %% does this delete the link?
+ case lists:keytake(Index, #link.index, State#state.link_list) of
+ false ->
+ ?warning("Warning link index=~w not found", [Index]),
+ State;
+ {value,L,Ls} ->
+ As1 = dict:to_list(L#link.attr),
+ update_attrs(Name, link, As1, undefined, State#state.sub_list),
+ State#state { link_list = Ls }
+ end;
+handle_nlmsg(RTM=#newaddr { family=Fam, prefixlen=Prefixlen,
+ flags=Flags, scope=Scope,
+ index=Index, attributes=As },
+ State) ->
+ ?debug("RTM = ~p", [RTM]),
+ Addr = proplists:get_value(address, As, {}),
+ Name = proplists:get_value(label, As, ""),
+ As1 = [{family,Fam},{prefixlen,Prefixlen},{flags,Flags},
+ {scope,Scope},{index,Index} | As],
+ case lists:keymember(Index, #link.index, State#state.link_list) of
+ false ->
+ ?warning("link index ~p does not exist", [Index]);
+ true ->
+ ok
+ end,
+ case lists:keytake(Addr, #addr.addr, State#state.addr_list) of
+ false ->
+ Attrs = update_attrs(Name,addr,As1,dict:new(),State#state.sub_list),
+ Y = #addr { addr=Addr, name = Name, index=Index, attr=Attrs },
+ Ys = [Y|State#state.addr_list],
+ State#state { addr_list = Ys };
+ {value,Y,Ys} ->
+ Attr = update_attrs(Name,addr,As1,Y#addr.attr,State#state.sub_list),
+ Y1 = Y#addr { index=Index, name=Name, attr = Attr },
+ State#state { addr_list = [Y1|Ys] }
+ end;
+
+handle_nlmsg(RTM=#deladdr { family=_Fam, index=_Index, attributes=As },
+ State) ->
+ ?debug("RTM = ~p", [RTM]),
+ Addr = proplists:get_value(address, As, {}),
+ Name = proplists:get_value(label, As, ""),
+ case lists:keytake(Addr, #addr.addr, State#state.addr_list) of
+ false ->
+ ?warning("Warning addr=~s not found", [Addr]),
+ State;
+ {value,Y,Ys} ->
+ As1 = dict:to_list(Y#addr.attr),
+ update_attrs(Name, addr, As1, undefined, State#state.sub_list),
+ State#state { addr_list = Ys }
+ end;
+handle_nlmsg(#done { }, State) ->
+ case State#state.request of
+ undefined ->
+ dispatch_command(State);
+ #request { tmr = Tmr, from = From, reply = Reply } ->
+ ?debug("handle_nlmsg: DONE: ~p",
+ [State#state.request]),
+ erlang:cancel_timer(Tmr),
+ gen_server:reply(From, Reply),
+ State1 = State#state { request = undefined },
+ dispatch_command(State1)
+ end;
+handle_nlmsg(Err=#error { errno=Err }, State) ->
+ ?debug("handle_nlmsg: ERROR: ~p", [State#state.request]),
+ case State#state.request of
+ undefined ->
+ dispatch_command(State);
+ #request { tmr = Tmr, from = From } ->
+ ?debug("handle_nlmsg: DONE: ~p",
+ [State#state.request]),
+ erlang:cancel_timer(Tmr),
+ %% fixme: convert errno to posix error (netlink.inc?)
+ gen_server:reply(From, {error,Err}),
+ State1 = State#state { request = undefined },
+ dispatch_command(State1)
+ end;
+
+handle_nlmsg(RTM, State) ->
+ ?debug("netlink: handle_nlmsg, ignore ~p", [RTM]),
+ State.
+
+%% update attributes form interface "Name"
+%% From to To Type is either link | addr
+update_attrs(Name,Type,As,undefined,Subs) ->
+ lists:foreach(
+ fun({K,Vold}) ->
+ send_event(Name,Type,K,Vold,undefined,Subs)
+ end, As),
+ undefined;
+update_attrs(Name,Type,As,To,Subs) ->
+ lists:foldl(
+ fun({K,Vnew},D) ->
+ case dict:find(K,D) of
+ error ->
+ send_event(Name,Type,K,undefined,Vnew,Subs),
+ dict:store(K,Vnew,D);
+ {ok,Vnew} -> D; %% already exist
+ {ok,Vold} ->
+ send_event(Name,Type,K,Vold,Vnew,Subs),
+ dict:store(K,Vnew,D)
+ end
+ end, To, As).
+
+
+send_event(Name,Type,Field,Old,New,[S|SList]) when
+ S#subscription.name =:= Name; S#subscription.name =:= "" ->
+ case S#subscription.fields =:= all orelse
+ S#subscription.fields =:= Type orelse
+ lists:member(Field,S#subscription.fields) orelse
+ lists:member({Type,Field},S#subscription.fields) of
+ true ->
+ S#subscription.pid ! {netlink,S#subscription.mon,
+ Name,Field,Old,New},
+ send_event(Name,Type,Field,Old,New,SList);
+ false ->
+ send_event(Name,Type,Field,Old,New,SList)
+ end;
+send_event(Name,Type,Field,Old,New,[_|SList]) ->
+ send_event(Name,Type,Field,Old,New,SList);
+send_event(_Name,_Type,_Field,_Old,_New,[]) ->
+ ok.
+
+
+match(Y,L,[{Field,Value}|Match]) when is_atom(Field) ->
+ case find2(Field,Y,L) of
+ {ok,Value} -> match(Y, L, Match);
+ _ -> false
+ end;
+match(Y,L,[{Op,Field,Value}|Match]) when is_atom(Op),is_atom(Field) ->
+ case find2(Y,L,Field) of
+ {ok,FValue} ->
+ case compare(Op,FValue,Value) of
+ true -> match(Y,L,Match);
+ false -> false
+ end;
+ error ->
+ false
+ end;
+match(_Y, _L, []) ->
+ true.
+
+find2(Key,D1,D2) ->
+ case dict:find(Key,D1) of
+ error ->
+ dict:find(Key,D2);
+ Res -> Res
+ end.
+
+
+format_link(L) ->
+ dict:fold(
+ fun(af_spec,_V,A) -> A;
+ (map,_V,A) -> A;
+ (stats,_V,A) -> A;
+ (stats64,_V,A) -> A;
+ (change,_V,A) -> A;
+ (K,V,A) ->
+ [["\n ",name_to_list(K), " ",value_to_list(K,V),";"]|A]
+ end, [], L#link.attr).
+
+format_addr(Y) ->
+ ["\n", " addr {",
+ dict:fold(
+ fun(cacheinfo,_V,A) -> A;
+ (K,V,A) ->
+ [[" ",name_to_list(K), " ",value_to_list(K,V),";"]|A]
+ end, [], Y#addr.attr), "}"].
+
+name_to_list(K) when is_atom(K) ->
+ atom_to_list(K);
+name_to_list(K) when is_integer(K) ->
+ integer_to_list(K).
+
+
+value_to_list(local,V) -> format_a(V);
+value_to_list(address,V) -> format_a(V);
+value_to_list(broadcast,V) -> format_a(V);
+value_to_list(multicast,V) -> format_a(V);
+value_to_list(anycast,V) -> format_a(V);
+value_to_list(_, V) -> io_lib:format("~p", [V]).
+
+format_a(undefined) -> "";
+format_a(A) when is_tuple(A), tuple_size(A) =:= 6 ->
+ io_lib:format("~2.16.0b:~2.16.0b:~2.16.0b:~2.16.0b:~2.16.0b:~2.16.0b",
+ tuple_to_list(A));
+format_a(A) when is_tuple(A), tuple_size(A) =:= 4 ->
+ inet_parse:ntoa(A);
+format_a(A) when is_tuple(A), tuple_size(A) =:= 8 ->
+ inet_parse:ntoa(A).
+
+compare('==',A,B) -> A == B;
+compare('=:=',A,B) -> A =:= B;
+compare('<' ,A,B) -> A < B;
+compare('=<' ,A,B) -> A =< B;
+compare('>' ,A,B) -> A > B;
+compare('>=' ,A,B) -> A >= B;
+compare('/=' ,A,B) -> A /= B;
+compare('=/=' ,A,B) -> A =/= B;
+compare(_,_,_) -> false.