aboutsummaryrefslogtreecommitdiff
path: root/src/pf_route_macos.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/pf_route_macos.erl')
-rw-r--r--src/pf_route_macos.erl284
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.