diff options
Diffstat (limited to 'src/pf_route_macos.erl')
-rw-r--r-- | src/pf_route_macos.erl | 284 |
1 files changed, 284 insertions, 0 deletions
diff --git a/src/pf_route_macos.erl b/src/pf_route_macos.erl new file mode 100644 index 0000000..7a87f47 --- /dev/null +++ b/src/pf_route_macos.erl @@ -0,0 +1,284 @@ +-module(pf_route_macos). +-include("pf_route.hrl"). +-include("pf_route_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 () -> + pf_route_pf_route_socket:open(?PF_ROUTE, ?SOCK_RAW, ?AF_UNSPEC, fun (_) -> noop end, fun parse_pf_route_packet/1) + end); +monitor(Table) -> + {error, {no_table, Table}}. + +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_ADD:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, add); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_DELETE:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, delete); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_CHANGE:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, change); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_GET:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, get); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_REDIRECT:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, redirect); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_MISS:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, miss); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_RESOLVE:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, resolve); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_NEWADDR:?U_CHAR, Binary/binary>>) -> parse_ifa_msghdr(Binary, added); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_DELADDR:?U_CHAR, Binary/binary>>) -> parse_ifa_msghdr(Binary, deleted); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_IFINFO:?U_CHAR, Binary/binary>>) -> parse_if_msghdr(Binary, ifinfo); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_NEWMADDR:?U_CHAR, Binary/binary>>) -> parse_ifma_msghdr(Binary, added); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_DELMADDR:?U_CHAR, Binary/binary>>) -> parse_ifma_msghdr(Binary, deleted); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_IFINFO2:?U_CHAR, Binary/binary>>) -> parse_if_msghdr2(Binary, ifinfo2); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_NEWMADDR2:?U_CHAR, Binary/binary>>) -> parse_ifma_msghdr2(Binary, added); +parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_GET2:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr2(Binary, get); +parse_pf_route_packet(Binary) -> {error, {invalid_packet, Binary}}. + +%% rt_msghdr struct followed by rt_metrics and a list of sockaddr +parse_rt_msghdr(<<% struct rt_msghdr (len, version, type removed) + Index:?UINT32, + Flags:?INT, + Addrs:?INT, + Pid:?INT, + Seq:?INT, + Errno:?INT, + Use:?INT, + Inits:?UINT32, + % struct rt_metrics + Locks:?UINT32, + Mtu:?UINT32, + Hopcount:?UINT32, + Expire:?INT, + Recv:?UINT32, + Send:?UINT32, + Ssthresh:?UINT32, + Rtt:?UINT32, + Rttvar:?UINT32, + Pksent:?UINT32, + State:?UINT32, + Filler1:?UINT32, + Filler2:?UINT32, + Filler3:?UINT32, + % List of sockaddrs + BinaryAddrs/binary + >>, Type) -> + Rt0 = #macos_rt{type = Type, ifp_index = Index, flags = pf_route_util:read_bitmask(?RTF, Flags), addrs = pf_route_util:read_bitmask(?RTA, Addrs), + pid = Pid, seq = Seq, errno = Errno, use = Use, metrics_init = pf_route_util:read_bitmask(?RTV, Inits), + metrics_lock = pf_route_util:read_bitmask(?RTV, 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}}, + logger:warning("pf_route_macos:monitor Rt = ~p~nBinary: ~p", [Rt0, BinaryAddrs]), + Rt = get_rt_addrs(BinaryAddrs, Rt0#macos_rt.addrs, Rt0, fun set_rt_addr/3), + logger:warning("pf_route_macos:monitor Rt = ~p", [Rt]), + {ok, Rt}. + +parse_rt_msghdr2(<<% struct rt_msghdr2 (len, version, type removed) + Index:?UINT32, + Flags:?INT, + Addrs:?INT, + RefCount:?INT, + ParentFlags:?INT, + _Reserved:?INT, + Use:?INT, + Inits:?UINT32, + % struct rt_metrics + Locks:?UINT32, + Mtu:?UINT32, + Hopcount:?UINT32, + Expire:?INT, + Recv:?UINT32, + Send:?UINT32, + Ssthresh:?UINT32, + Rtt:?UINT32, + Rttvar:?UINT32, + Pksent:?UINT32, + State:?UINT32, + Filler1:?UINT32, + Filler2:?UINT32, + Filler3:?UINT32, + % List of sockaddrs + BinaryAddrs/binary + >>, Type) -> + Rt0 = #macos_rt{type = Type, ifp_index = Index, flags = pf_route_util:read_bitmask(?RTF, Flags), addrs = pf_route_util:read_bitmask(?RTA, Addrs), + refcount = RefCount, parent_flags = pf_route_util:read_bitmask(?RTF, ParentFlags), use = Use, + metrics_init = pf_route_util:read_bitmask(?RTV, Inits), metrics_lock = pf_route_util:read_bitmask(?RTV, 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}}, + Rt = get_rt_addrs(BinaryAddrs, Rt0#macos_rt.addrs, Rt0, fun set_rt_addr/3), + {ok, Rt}. + +%% macOS quirk: only 4 zero bytes for netmask. +get_rt_addrs(<<0:?UINT32, Binary/binary>>, [netmask | Rest], Rt, F) -> + get_rt_addrs(Binary, Rest, F(netmask, #pf_route_inet4{address = {0, 0, 0, 0}}, Rt), F); +%% macOS quirk: netmask len is off by one in family AF_INET +get_rt_addrs(<<Len:?U_CHAR, ?AF_INET:?U_CHAR, Content/binary>>, [netmask | Rest], Rt, F) -> + <<BinaryAddr:(Len - 1)/binary, Binary/binary>> = Content, + get_rt_addrs(Binary, Rest, F(netmask, parse_sockaddr(BinaryAddr, ?AF_INET), Rt), F); +%% macOS quirk: netmask len is off by one, family is 255 +get_rt_addrs(<<Len:?U_CHAR, 255:?U_CHAR, Content/binary>>, [netmask | Rest], Rt, F) -> + <<BinaryAddr:(Len - 1)/binary, Binary/binary>> = Content, + get_rt_addrs(Binary, Rest, F(netmask, parse_sockaddr(BinaryAddr, 255), Rt), F); +get_rt_addrs(<<Len:?U_CHAR, Family:?U_CHAR, Content/binary>>, [Type | Rest], Rt, F) -> + <<BinaryAddr:(Len - 2)/binary, Binary/binary>> = Content, + get_rt_addrs(Binary, Rest, F(Type, parse_sockaddr(BinaryAddr, Family), Rt), F); +get_rt_addrs(_, [], Rt, _) -> + Rt. + +set_rt_addr(destination, Value, Rt) -> Rt#macos_rt{destination = Value}; +set_rt_addr(gateway, Value, Rt) -> Rt#macos_rt{gateway = Value}; +set_rt_addr(netmask, Value, Rt) -> Rt#macos_rt{netmask = Value}; +set_rt_addr(clonemask, Value, Rt) -> Rt#macos_rt{clonemask = Value}; +set_rt_addr(ifp, Value, Rt) -> Rt#macos_rt{ifp = Value}; +set_rt_addr(ifa, Value, Rt) -> Rt#macos_rt{ifa = Value}; +set_rt_addr(author, Value, Rt) -> Rt#macos_rt{author = Value}; +set_rt_addr(brd, Value, Rt) -> Rt#macos_rt{brd = Value}. + +%% sockaddr_in, without the zeros (? only in netmasks) +parse_sockaddr(<<_Port:?U_SHORT, A, B, C, D>>, ?AF_INET) -> + #pf_route_inet4{address = {A, B, C, D}}; +%% sockaddr_in full ; _Zero is `sin_zero`. +parse_sockaddr(<<_Port:?U_SHORT, A, B, C, D, _Zero:?UINT64>>, ?AF_INET) -> + #pf_route_inet4{address = {A, B, C, D}}; +%% sockaddr_in6 +parse_sockaddr(<<_Port:?U_SHORT, FlowInfo:?UINT32, A:?U_SHORT, B:?U_SHORT, C:?U_SHORT, D:?U_SHORT, + E:?U_SHORT, F:?U_SHORT, G:?U_SHORT, H:?U_SHORT, ScopeId:?UINT32>>, ?AF_INET6) -> + #pf_route_inet6{address = {A, B, C, D, E, F, G, H}, flow = FlowInfo, scope = ScopeId}; +parse_sockaddr(<<Index:?U_SHORT, Type:?U_CHAR, NameLen:?U_CHAR, AddrLen:?U_CHAR, SelLen:?U_CHAR, Data/binary>>, ?AF_LINK) -> + <<Name:NameLen/binary, Address:AddrLen/binary, Selector:SelLen/binary, _/binary>> = Data, + #macos_link{index = Index, type = Type, name = Name, address = binary_to_list(Address), selector = Selector}; +%% ???????? netmask in a RTM_GET message. see pf_route_macos_SUITE:pckt_rt_get. +parse_sockaddr(<<255, 255, A, B, C, D>>, 255) -> + #pf_route_inet4{address = {A, B, C, D}}; +parse_sockaddr(Data, Family) -> + #pf_route_sockaddr{family = Family, data = Data}. + +parse_ifa_msghdr(<<Addrs:?INT, Flags:?INT, Index:?U_SHORT, Metric:?INT, _, _, BinaryAddrs/binary>>, Type) -> + Ifa0 = #macos_ifa{addrs = pf_route_util:read_bitmask(?RTA, Addrs), type = Type, flags = pf_route_util:read_bitmask(?IFF, Flags), + index = Index, metric = Metric}, + logger:error("macos_parse_ifa_msghdr: ~p~nBinary: ~p", [Ifa0, BinaryAddrs]), + Ifa = get_rt_addrs(BinaryAddrs, Ifa0#macos_ifa.addrs, Ifa0, fun set_ifa_addr/3), + logger:error("macos_parse_ifa_msghdr: ~p", [Ifa]), + {ok, Ifa}. + +set_ifa_addr(netmask, Value, Ifa) -> Ifa#macos_ifa{netmask = Value}; +set_ifa_addr(ifp, Value, Ifa) -> Ifa#macos_ifa{ifp = Value}; +set_ifa_addr(ifa, Value, Ifa) -> Ifa#macos_ifa{ifa = Value}. + +parse_ifma_msghdr(<<Addrs:?INT, Flags:?INT, Index:?U_SHORT, BinaryAddrs/binary>>, Type) -> + Ifma0 = #macos_ifma{addrs = pf_route_util:read_bitmask(?RTA, Addrs), type = Type, flags = pf_route_util:read_bitmask(?IFF, Flags), + index = Index}, + logger:error("macos_parse_ifma_msghdr: ~p~nBinary: ~p", [Ifma0, BinaryAddrs]), + Ifma = get_rt_addrs(BinaryAddrs, Ifma0#macos_ifma.addrs, Ifma0, fun set_ifma_addr/3), + logger:error("macos_parse_ifma_msghdr: ~p", [Ifma]), + {ok, Ifma}. + +parse_ifma_msghdr2(<<Addrs:?INT, Flags:?INT, Index:?U_SHORT, RefCount:?INT, BinaryAddrs/binary>>, Type) -> + Ifma0 = #macos_ifma{addrs = pf_route_util:read_bitmask(?RTA, Addrs), type = Type, flags = pf_route_util:read_bitmask(?IFF, Flags), + index = Index, refcount = RefCount}, + logger:error("macos_parse_ifma_msghdr: ~p~nBinary: ~p", [Ifma0, BinaryAddrs]), + Ifma = get_rt_addrs(BinaryAddrs, Ifma0#macos_ifma.addrs, Ifma0, fun set_ifma_addr/3), + logger:error("macos_parse_ifma_msghdr: ~p", [Ifma]), + {ok, Ifma}. + +set_ifma_addr(netmask, Value, Ifma) -> Ifma#macos_ifma{netmask = Value}; +set_ifma_addr(ifp, Value, Ifma) -> Ifma#macos_ifma{ifp = Value}; +set_ifma_addr(ifa, Value, Ifma) -> Ifma#macos_ifma{ifa = Value}. + +parse_if_msghdr(<<Addrs:?INT, Flags:?INT, Index:?U_SHORT, + Type:?U_CHAR, TypeLen:?U_CHAR, Physical:?U_CHAR, AddrLen:?U_CHAR, HdrLen:?U_CHAR, + Recvquota:?U_CHAR, Xmitquota:?U_CHAR, _:?U_CHAR, Mtu:?UINT32, Metric:?UINT32, Baudrate:?UINT32, + IPackets:?UINT32, IErrors:?UINT32, OPackets:?UINT32, OErrors:?UINT32, IBytes:?UINT32, OBytes:?UINT32, + IMcasts:?UINT32, OMcasts:?UINT32, IQdrops:?UINT32, Noproto:?UINT32, Recvtiming:?UINT32, Xmittiming:?UINT32, BinaryAddrs/binary>>, ifinfo) -> + If0 = #macos_if{type = Type, index = Index, flags = pf_route_util:read_bitmask(?IFF, Flags), addrs = pf_route_util:read_bitmask(?RTA, Addrs), + typelen = TypeLen, physical = Physical, addrlen = AddrLen, hdrlen = HdrLen, recvquota = Recvquota, + xmitquota = Xmitquota, mtu = Mtu, metric = Metric, baudrate = Baudrate, ipackets = IPackets, + ierrors = IErrors, opackets = OPackets, oerrors = OErrors, ibytes = IBytes, obytes = OBytes, + imcasts = IMcasts, omcasts = OMcasts, iqdrops = IQdrops, noproto = Noproto, + recvtiming = Recvtiming, xmittiming = Xmittiming}, + logger:warning("macos:parse_if_msghdr: ~p~nBinary: ~p", [If0, BinaryAddrs]), + {ok, If0}. + +parse_if_msghdr2(<<Addrs:?INT, Flags:?INT, Index:?U_SHORT, + Type:?U_CHAR, TypeLen:?U_CHAR, Physical:?U_CHAR, AddrLen:?U_CHAR, HdrLen:?U_CHAR, + Recvquota:?U_CHAR, Xmitquota:?U_CHAR, _:?U_CHAR, Mtu:?UINT32, Metric:?UINT32, Baudrate:?UINT64, + IPackets:?UINT64, IErrors:?UINT64, OPackets:?UINT64, OErrors:?UINT64, IBytes:?UINT64, OBytes:?UINT64, + IMcasts:?UINT64, OMcasts:?UINT64, IQdrops:?UINT64, Noproto:?UINT64, Recvtiming:?UINT32, Xmittiming:?UINT32, BinaryAddrs/binary>>, ifinfo2) -> + If0 = #macos_if{type = Type, index = Index, flags = pf_route_util:read_bitmask(?IFF, Flags), addrs = pf_route_util:read_bitmask(?RTA, Addrs), + typelen = TypeLen, physical = Physical, addrlen = AddrLen, hdrlen = HdrLen, recvquota = Recvquota, + xmitquota = Xmitquota, mtu = Mtu, metric = Metric, baudrate = Baudrate, ipackets = IPackets, + ierrors = IErrors, opackets = OPackets, oerrors = OErrors, ibytes = IBytes, obytes = OBytes, + imcasts = IMcasts, omcasts = OMcasts, iqdrops = IQdrops, noproto = Noproto, + recvtiming = Recvtiming, xmittiming = Xmittiming}, + logger:warning("macos:parse_if_msghdr: 2 ~p~nBinary: ~p", [If0, BinaryAddrs]), + {ok, If0}. + +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 -> pf_route_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. |