diff options
author | Jordan Bracco <href@random.sh> | 2021-10-27 18:38:03 +0200 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2021-10-27 18:38:03 +0200 |
commit | 27eec8413540bfd11454cba6e35b0cc4daa5c5ad (patch) | |
tree | 405a35c02ccbc3adfb7cddd7f71bea8b2714b289 /src |
🤷
Diffstat (limited to 'src')
-rw-r--r-- | src/fib.erl | 37 | ||||
-rw-r--r-- | src/fib.hrl | 3 | ||||
-rw-r--r-- | src/fib_app.erl | 16 | ||||
-rw-r--r-- | src/fib_behaviour.erl | 7 | ||||
-rw-r--r-- | src/fib_freebsd.erl | 35 | ||||
-rw-r--r-- | src/fib_linux.erl | 18 | ||||
-rw-r--r-- | src/fib_macos.erl | 179 | ||||
-rw-r--r-- | src/fib_macos.hrl | 146 | ||||
-rw-r--r-- | src/fib_pf_route_socket.erl | 23 | ||||
-rw-r--r-- | src/fib_sup.erl | 12 | ||||
-rw-r--r-- | src/fib_util.erl | 41 |
11 files changed, 517 insertions, 0 deletions
diff --git a/src/fib.erl b/src/fib.erl new file mode 100644 index 0000000..130e8ec --- /dev/null +++ b/src/fib.erl @@ -0,0 +1,37 @@ +-module(fib). +-export([tables/0, current_table/0]). +-export([list/0, list/1]). +-export_types([table/0, flags/0, entry/0]). + +-type table() :: non_neg_integer. + +-type flags() :: []. + +-type entry() :: #{destination := inet:ip_address() | default, + netmask := inet:ip_address() | undefined, + gateway := inet:ip_address() | {mac, string()} | {link, integer()}, + scope := string() | undefined, + interface := string(), + flags := flags(), + expire := undefined}. + +-spec tables() -> [table()]. +tables() -> + persistent_term:get('fib/tables'). + +-spec current_table() -> table(). +current_table() -> + persistent_term:get('fib/current_table'). + +-spec list() -> [entry()]. +list() -> + Mod = mod(), + Mod:list(Mod:default_table()). + +-spec list(table) -> [entry()]. +list(Table) -> + Mod = mod(), + Mod:list(Table). + +mod() -> + persistent_term:get('fib/module'). diff --git a/src/fib.hrl b/src/fib.hrl new file mode 100644 index 0000000..6f88bfb --- /dev/null +++ b/src/fib.hrl @@ -0,0 +1,3 @@ +-define(SOCK_RAW, 3). +-define(AF_UNSPEC, 0). +-define(PF_ROUTE, 17). diff --git a/src/fib_app.erl b/src/fib_app.erl new file mode 100644 index 0000000..3acb41c --- /dev/null +++ b/src/fib_app.erl @@ -0,0 +1,16 @@ +-module(fib_app). +-behaviour(application). + +-export([start/2]). +-export([stop/1]). + +start(_Type, _Args) -> + %% Find which platform module to use and cache persistent data. + {ok, Mod} = fib_util:module(), + persistent_term:put('fib/module', Mod), + persistent_term:put('fib/current_table', Mod:current_table()), + persistent_term:put('fib/tables', Mod:tables()), + fib_sup:start_link(). + +stop(_State) -> + ok. diff --git a/src/fib_behaviour.erl b/src/fib_behaviour.erl new file mode 100644 index 0000000..fd96ca7 --- /dev/null +++ b/src/fib_behaviour.erl @@ -0,0 +1,7 @@ +-module(fib_behaviour). + +-callback tables() -> [fib:table()]. +-callback default_table() -> fib:table(). +-callback current_table() -> fib:table(). + +-callback list(fib:table()) -> {ok, []} | {error, {no_table, fib:table()}}. diff --git a/src/fib_freebsd.erl b/src/fib_freebsd.erl new file mode 100644 index 0000000..b85d458 --- /dev/null +++ b/src/fib_freebsd.erl @@ -0,0 +1,35 @@ +-module(fib_freebsd). +-behaviour(fib_behaviour). +-export([default_table/0, current_table/0, tables/0]). +-export([list/1]). + +default_table() -> + 1. + +current_table() -> + case os:cmd("sysctl net.my_fibnum") of + [$n, $e, $t, $., $m, $y, $_, $f, $i, $n, $b, $u, $m, $:, $\s | Rest] -> + {ok, list_to_integer(string:trim(Rest))}; + _ -> + error + end. + +tables() -> + case os:cmd("sysctl net.fibs") of + [$n, $e, $t, $., $f, $i, $b, $s, $:, $\s | Rest] -> + Count = list_to_integer(string:trim(Rest)), + {ok, lists:seq(1, Count)}; + _ -> + error + end. + +list(Table) -> + case lists:member(fib:tables(), Table) of + true -> + do_list(Table); + _ -> + {error, {no_table, Table}} + end. + +do_list(Table) -> + os:cmd(["setfib ", Table, " netstat -rn"]). diff --git a/src/fib_linux.erl b/src/fib_linux.erl new file mode 100644 index 0000000..f87e885 --- /dev/null +++ b/src/fib_linux.erl @@ -0,0 +1,18 @@ +-module(fib_linux). +-behaviour(fib_behaviour). +-export([default_table/0, current_table/0, tables/0]). +-export([list/1]). + +%% Linux has 255 routing tables. + +default_table() -> + 254. + +current_table() -> + default_table(). + +tables() -> + lists:seq(1, 255). + +list(_Table) -> + todo. diff --git a/src/fib_macos.erl b/src/fib_macos.erl new file mode 100644 index 0000000..3df046d --- /dev/null +++ b/src/fib_macos.erl @@ -0,0 +1,179 @@ +-module(fib_macos). +-behaviour(fib_behaviour). +-include("fib_macos.hrl"). +-export([default_table/0, current_table/0, tables/0]). +-export([list/1]). +-export([monitor/1]). +-export([parse_pf_route_packet/1]). + +%% MacOS has no tables. + +default_table() -> + ?TABLE. + +current_table() -> + ?TABLE. + +tables() -> + {ok, [?TABLE]}. + +list(?TABLE) -> + Netstat = os:cmd("netstat -rn"), + List = string:split(string:trim(Netstat), [$\n], all), + do_list(List, []); +list(Table) -> + {error, {no_table, Table}}. + +monitor(?TABLE) -> + spawn_link(fun () -> + fib_pf_route_socket:open(fun (_) -> noop end, fun parse_pf_route_packet/1) + end); +monitor(Table) -> + {error, {no_table, Table}}. + +%% rt_msghdr struct followed by rt_metrics and a list of sockaddr + +%% MacOS 12 +parse_pf_route_packet(<<% struct rt_msghdr (len removed) + ?RTM_VERSION:8/little-unsigned-integer, + Type:8/little-unsigned-integer, + Index:32/little-unsigned-integer, + Flags:32/little-signed-integer, + Addrs:32/little-signed-integer, + Pid:32/little-signed-integer, + Seq:32/little-signed-integer, + Errno:32/little-signed-integer, + Use:32/little-signed-integer, + Inits:32/little-unsigned-integer, + % struct rt_metrics + Locks:32/little-unsigned-integer, + Mtu:32/little-unsigned-integer, + Hopcount:32/little-unsigned-integer, + Expire:32/little-signed-integer, + Recv:32/little-unsigned-integer, + Send:32/little-unsigned-integer, + Ssthresh:32/little-unsigned-integer, + Rtt:32/little-unsigned-integer, + Rttvar:32/little-unsigned-integer, + Pksent:32/little-unsigned-integer, + State:32/little-unsigned-integer, + Filler1:32/little-unsigned-integer, + Filler2:32/little-unsigned-integer, + Filler3:32/little-unsigned-integer, + % List of sockaddrs + BinaryAddrs/binary + >>) -> + Rt = #macos_rt{type = Type, ifp_index = Index, flags = Flags, addrs = Addrs, pid = Pid, seq = Seq, errno = Errno, use = Use, + metrics_init = Inits, metrics_lock = Locks, mtu = Mtu, hopcount = Hopcount, expire = Expire, recvpipe = Recv, + sendpipe = Send, ssthresh = Ssthresh, rtt = Rtt, rttvar = Rttvar, pksent = Pksent, state = State, + filler = {Filler1, Filler2, Filler3}}, + parse_rt(Rt, BinaryAddrs). + +parse_rt(Rt0, BinaryAddrs) -> + Rt1 = Rt0#macos_rt{type = maps:get(Rt0#macos_rt.type, ?RTM, Rt0#macos_rt.type), + flags = bitmask(?RTF, Rt0#macos_rt.flags), + addrs = bitmask(?RTA, Rt0#macos_rt.addrs), + metrics_init = bitmask(?RTV, Rt0#macos_rt.metrics_init), + metrics_lock = bitmask(?RTV, Rt0#macos_rt.metrics_lock)}, + logger:warning("fib_macos:monitor Rt = ~p~nBinary: ~p", [Rt1, BinaryAddrs]), + Rt = get_addrs(Rt1#macos_rt.addrs, BinaryAddrs, Rt1), + logger:warning("fib_macos:monitor Rt = ~p", [Rt]), + Rt. + +get_addrs([_Type | Rest], <<0:32/little-unsigned-integer, Binary/binary>>, Rt) -> + get_addrs(Rest, Binary, Rt); +get_addrs([Type | Rest], <<Len:8/little-unsigned-integer, Family:8/little-unsigned-integer, Content/binary>>, Rt) -> + logger:error("fib_macos:get_addrs() ~p len=~p family=~p ~nbinary ~p", [Type, Len, Family, Content]), + ToRead = case Type of + netmask -> Len - 1; + _Other -> Len - 2 + end, + <<BinaryAddr:ToRead/binary, Binary/binary>> = Content, + Idx = string:str(record_info(fields, macos_rt), [Type]), + logger:warning("fib_macos:get_addrs() - get_addr ~p = ~p~nRest ~p~nBinRest = ~p", [Type, BinaryAddr, Rest, Binary]), + get_addrs(Rest, Binary, setelement(Idx+1, Rt, process_address(Family, BinaryAddr))); +get_addrs([], _, Rt) -> + Rt. + +%% sockaddr_in, without the zeros (? only in netmasks) +process_address(?AF_INET, <<_Port:16/little-unsigned-integer, A, B, C, D>>) -> + {A, B, C, D}; +%% sockaddr_in full ; _Zero is `sin_zero`. +process_address(?AF_INET, <<_Port:16/little-unsigned-integer, A, B, C, D, _Zero:64/little-unsigned-integer>>) -> + {A, B, C, D}; +%% ???????? netmask in a RTM_GET message. see fib_macos_SUITE:pckt_rt_get. +process_address(255, <<255, 255, A, B, C, D>>) -> + {A, B, C, D}; +process_address(Family, Data) -> + logger:error("macos:process_address: no clause for family ~p = ~p", [Family, Data]), + {Family, Data}. + +bitmask(List, Integer) -> + bitmask(List, Integer, []). + +bitmask([{V, Name} | Rest], I, Acc) -> + if + (I band V) =:= V -> + bitmask(Rest, I, [Name | Acc]); + true -> + bitmask(Rest, I, Acc) + end; +bitmask([], _, Acc) -> + lists:reverse(Acc). + +do_list([], Acc) -> + Acc; +do_list([[] | Rest], Acc) -> + do_list(Rest, Acc); + +%% Header "Routing Tables" +do_list([[$R, $o, $u, $t, $i, $n, $g | _] | Rest], Acc) -> + do_list(Rest, Acc); +%% Header "Internet"/"Internet6" +do_list([[$I, $n, $t, $e, $r, $n, $e, $t | _] | Rest], Acc) -> + do_list(Rest, Acc); +%% Header fields +do_list([[$D, $e, $s, $t, $i, $n, $a, $t, $i, $o, $n | _] | Rest], Acc) -> + do_list(Rest, Acc); +%% Data line +do_list([Line | Rest], Acc) -> + [Destination, Gateway, Flags, Netif | Expire] = string:lexemes(Line, [$\s, $\t]), + {DestIp, Scope, Netmask} = case Destination of + "default" -> {default, undefined, undefined}; + IPStr -> fib_util:parse_ip_address_with_netmask(IPStr) + end, + GW = case Gateway of + [$l, $i, $n, $k, $# | Num] -> + {link, list_to_integer(Num)}; + GWStr -> + case inet:parse_address(GWStr) of + {ok, GwIP} -> GwIP; + {error, einval} -> {mac, GWStr} + end + end, + Route = #{destination => DestIp, netmask => Netmask, scope => Scope, gateway => GW, flags => [flag(F) || F <- Flags], interface => Netif, expire => Expire}, + do_list(Rest, [Route | Acc]). + +flag($1) -> proto1; +flag($2) -> proto2; +flag($3) -> proto3; +flag($B) -> blackhole; +flag($b) -> broadcast; +flag($C) -> cloning; +flag($c) -> prcloning; +flag($D) -> dynamic; +flag($G) -> gateway; +flag($H) -> host; +flag($I) -> ifscope; +flag($i) -> ifref; +flag($L) -> llinfo; +flag($M) -> modified; +flag($m) -> multicast; +flag($R) -> reject; +flag($r) -> router; +flag($S) -> static; +flag($U) -> up; +flag($W) -> wascloned; +flag($X) -> xresolve; +flag($Y) -> proxy; +flag($g) -> global. diff --git a/src/fib_macos.hrl b/src/fib_macos.hrl new file mode 100644 index 0000000..d1cd1a1 --- /dev/null +++ b/src/fib_macos.hrl @@ -0,0 +1,146 @@ +-define(TABLE, 1). + +%% Constants and structs are mostly defined in `net/route.h`. +%% /Library/Developer/CommandLineTools/SDKs/MacOSX12.0.sdk/usr/include/net/route.h + +-define(RTM_VERSION, 5). + +%% PF_ROUTE messages types +-define(RTM_ADD, 1). +-define(RTM_DELETE, 2). +-define(RTM_CHANGE, 3). +-define(RTM_GET, 4). +-define(RTM_REDIRECT, 6). +-define(RTM_MISS, 7). +-define(RTM_RESOLVE, 16#b). +-define(RTM_NEWADDR, 16#c). +-define(RTM_DELADDR, 16#d). +-define(RTM_IFINFO, 16#e). +-define(RTM_NEWMADDR, 16#f). +-define(RTM_DELMADDR, 16#10). +-define(RTM_IFINFO2, 16#12). +-define(RTM_NEWMADDR2, 16#13). +-define(RTM_GET2, 16#14). +-define(RTM, #{ + ?RTM_ADD => add, + ?RTM_DELETE => delete, + ?RTM_CHANGE => change, + ?RTM_GET => get, + ?RTM_REDIRECT => redirect, + ?RTM_MISS => miss, + ?RTM_RESOLVE => resolve, + ?RTM_NEWADDR => newaddr, + ?RTM_DELADDR => deladdr, + ?RTM_IFINFO => ifinfo, + ?RTM_NEWMADDR => newmaddr, + ?RTM_DELMADDR => delmaddr, + ?RTM_IFINFO2 => ifinfo2, + ?RTM_NEWMADDR2 => newmaddr2, + ?RTM_GET2 => get2 + }). + +%% Bitmask values for flags +-define(RTF_UP, 1). +-define(RTF_GATEWAY, 2). +-define(RTF_HOST, 4). +-define(RTF_REJECT, 8). +-define(RTF_DYNAMIC, 16#10). +-define(RTF_MODIFIED, 16#20). +-define(RTF_DONE, 16#40). +-define(RTF_DELCLONE, 16#80). +-define(RTF_CLONING, 16#100). +-define(RTF_XRESOLVE, 16#200). +-define(RTF_LLDATA, 16#400). +-define(RTF_STATIC, 16#800). +-define(RTF_BLACKHOLE, 16#1000). +-define(RTF_NOIFREF, 16#2000). +-define(RTF_PROTO2, 16#4000). +-define(RTF_PROTO1, 16#8000). +-define(RTF_PRCLONING, 16#10000). +-define(RTF_WASCLONED, 16#20000). +-define(RTF_PROTO3, 16#40000). +-define(RTF_PINNED, 16#100000). +-define(RTF_LOCAL, 16#200000). +-define(RTF_BROADCAST, 16#400000). +-define(RTF_MULTICAST, 16#800000). +-define(RTF_IFSCOPE, 16#1000000). +-define(RTF_CONDEMNED, 16#2000000). +-define(RTF_IFREF, 16#4000000). +-define(RTF_PROXY, 16#8000000). +-define(RTF_ROUTER, 16#10000000). +-define(RTF_DEAD, 16#20000000). +-define(RTF_GLOBAL, 16#40000000). +-define(RTF, [{?RTF_UP, up}, {?RTF_GATEWAY, gateway}, {?RTF_HOST, host}, {?RTF_REJECT, reject}, + {?RTF_DYNAMIC, dynamic}, {?RTF_MODIFIED, modified}, {?RTF_DONE, done}, {?RTF_DELCLONE, delclone}, + {?RTF_CLONING, cloning}, {?RTF_XRESOLVE, xresolve}, {?RTF_LLDATA, lldata}, {?RTF_STATIC, static}, + {?RTF_BLACKHOLE, blackhole}, {?RTF_NOIFREF, noifref}, {?RTF_PROTO2, proto2}, {?RTF_PROTO1, proto1}, + {?RTF_PRCLONING, prcloning}, {?RTF_WASCLONED, wascloned}, {?RTF_PROTO3, proto3}, {?RTF_PINNED, pinned}, + {?RTF_LOCAL, local}, {?RTF_BROADCAST, broadcast}, {?RTF_MULTICAST, multicast}, {?RTF_IFSCOPE, ifscope}, + {?RTF_CONDEMNED, condemned}, {?RTF_IFREF, ifref}, {?RTF_PROXY, proxy}, {?RTF_ROUTER, router}, + {?RTF_DEAD, dead}, {?RTF_GLOBAL, global}]). + +%% Bitmask values, rtm_inits and rtm_locks +-define(RTV_MTU, 1). +-define(RTV_HOPCOUNT, 2). +-define(RTV_EXPIRE, 4). +-define(RTV_RPIPE, 8). +-define(RTV_SPIPE, 16#10). +-define(RTV_SSTHRESH, 16#20). +-define(RTV_RTT, 16#40). +-define(RTV_RTTVAR, 16#80). +-define(RTV, [{?RTV_MTU, mtu}, {?RTV_HOPCOUNT, hopcount}, {?RTV_EXPIRE, expire}, {?RTV_RPIPE, recvpipe}, {?RTV_SPIPE, sendpipe}, + {?RTV_SSTHRESH, ssthresh}, {?RTV_RTT, rtt}, {?RTV_RTTVAR, rttvar}]). + +%% Bitmask values for rtm_addrs +-define(RTA_DST, 1). % destination sockaddr present +-define(RTA_GATEWAY, 2). % gateway sockaddr present +-define(RTA_NETMASK, 4). % netmask sockaddr present +-define(RTA_GENMASK, 8). % cloning mask sockaddr present +-define(RTA_IFP, 16#10). % interface name sockaddr present +-define(RTA_IFA, 16#20). % interface addr sockaddr present +-define(RTA_AUTHOR, 16#40). % sockaddr for addr of redirect +-define(RTA_BRD, 16#80). % for NEWADDR, broadcast or p-p dest addr +-define(RTA, [{?RTA_DST, destination}, {?RTA_GATEWAY, gateway}, {?RTA_NETMASK, netmask}, + {?RTA_GENMASK, genmask}, {?RTA_IFP, ifp}, {?RTA_IFA, ifa}, + {?RTA_AUTHOR, author}, {?RTA_BRD, brd}]). + +%% usr/include/sys/socket.h +-define(AF_UNSPEC, 0). +-define(AF_INET, 2). +-define(AF_LINK, 18). +-define(AF_INET6, 31). + +-record(macos_rt, {type, % Message type, atom + flags, % Route flags + addrs, % Addresses list + pid, % Originating pid + seq, % user data + errno, % error number + use, % documented as: "from rentry" + + % Metrics + metrics_init, % metrics that are initializing + metrics_lock, % metrics that are locked + mtu, % mtu + hopcount, % max hops expected + expire, % lifetime for route + recvpipe, % inbound delay-bandwith product + sendpipe, % outbound delay-bandwith product + ssthresh, % outbound gateway buffer limit + rtt, % estimated round trip time + rttvar, % estimated rtt variance + pksent, % packets sent using this route + state, % route state + filler, % "will be used for TCP's peer-MSS cache" + + % Addresses + destination, % Destination + gateway, % Gateway + netmask, % netmask + clonemask, % (C: genmask) cloning mask + ifp, % interface name + ifp_index, % (C: index) Scope for associated ifp, if present + ifa, % interface address + author, % author of redirect + brd % (only for type=newaddr) broadcast or p-p destination address +}). diff --git a/src/fib_pf_route_socket.erl b/src/fib_pf_route_socket.erl new file mode 100644 index 0000000..ea07003 --- /dev/null +++ b/src/fib_pf_route_socket.erl @@ -0,0 +1,23 @@ +-module(fib_pf_route_socket). +-export([test/0, open/2]). +-include("fib.hrl"). + +test() -> + open(fun (_) -> ok end, fun (_) -> ok end). + +open(OpenFun, ParseFun) -> + {ok, Socket} = socket:open(?PF_ROUTE, ?SOCK_RAW, ?AF_UNSPEC), + OpenFun(Socket), + recv(Socket, ParseFun). + +recv(Socket, ParseFun) -> + case socket:recv(Socket, 0) of + {ok, <<Len:16/little-unsigned-integer, Rest/binary>>} -> + ToRead = Len - 2, + <<Pckt:ToRead/binary>> = Rest, + logger:warning("Got a packet ~p", [Pckt]), + ParseFun(Pckt), + recv(Socket, ParseFun); + Error -> + Error + end. diff --git a/src/fib_sup.erl b/src/fib_sup.erl new file mode 100644 index 0000000..06efb9d --- /dev/null +++ b/src/fib_sup.erl @@ -0,0 +1,12 @@ +-module(fib_sup). +-behaviour(supervisor). + +-export([start_link/0]). +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +init([]) -> + Procs = [], + {ok, {{one_for_one, 1, 5}, Procs}}. diff --git a/src/fib_util.erl b/src/fib_util.erl new file mode 100644 index 0000000..b495f25 --- /dev/null +++ b/src/fib_util.erl @@ -0,0 +1,41 @@ +-module(fib_util). +-export([module/0, parse_ip_address_with_netmask/1]). + +module() -> + case os:type() of + {unix, linux} -> + {ok, fib_linux}; + {unix, darwin} -> + {ok, fib_macos}; + {unix, freebsd} -> + {ok, fib_freebsd}; + unsupported -> + {error, {unsupported_platform, unsupported}} + end. + +-spec parse_ip_address_with_netmask(string()) -> {inet:ip_address(), non_neg_integer()}. +parse_ip_address_with_netmask(S) -> + logger:warning("IPNets ~p", [S]), + case re:split(S, "/", [{return, list}, {parts, 2}]) of + [Prefix, LenStr] -> + {Prefix1, Scope} = ipv6_scope(Prefix), + {ok, Addr} = inet:parse_address(Prefix1), + {PrefixLen, _} = string:to_integer(LenStr), + {Addr, Scope, PrefixLen}; + [Prefix] -> + {Prefix1, Scope} = ipv6_scope(Prefix), + {ok, Addr} = inet:parse_address(Prefix1), + PrefixLen = case inet_cidr:is_ipv6(Addr) of + true -> 128; + false -> 32 + end, + {Addr, Scope, PrefixLen} + end. + +ipv6_scope(Addr) -> + case re:split(Addr, "%", [{return, list}, {parts, 2}]) of + [IP, Scope] -> + {IP, Scope}; + [IP] -> + {IP, undefined} + end. |