diff options
Diffstat (limited to 'src/fib_macos.erl')
-rw-r--r-- | src/fib_macos.erl | 179 |
1 files changed, 179 insertions, 0 deletions
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. |