-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(<>) -> parse_rt_msghdr(Binary, add); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, delete); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, change); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, get); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, redirect); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, miss); parse_pf_route_packet(<>) -> parse_rt_msghdr(Binary, resolve); parse_pf_route_packet(<>) -> parse_ifa_msghdr(Binary, added); parse_pf_route_packet(<>) -> parse_ifa_msghdr(Binary, deleted); parse_pf_route_packet(<>) -> parse_if_msghdr(Binary, ifinfo); parse_pf_route_packet(<>) -> parse_ifma_msghdr(Binary, added); parse_pf_route_packet(<>) -> parse_ifma_msghdr(Binary, deleted); parse_pf_route_packet(<>) -> parse_if_msghdr2(Binary, ifinfo2); parse_pf_route_packet(<>) -> parse_ifma_msghdr2(Binary, added); parse_pf_route_packet(<>) -> 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(<>, [netmask | Rest], Rt, F) -> <> = 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(<>, [netmask | Rest], Rt, F) -> <> = Content, get_rt_addrs(Binary, Rest, F(netmask, parse_sockaddr(BinaryAddr, 255), Rt), F); get_rt_addrs(<>, [Type | Rest], Rt, F) -> <> = 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(<>, ?AF_LINK) -> <> = 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(<>, 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(<>, 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(<>, 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(<>, 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(<>, 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.