aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJordan Bracco <href@random.sh>2021-11-06 22:11:00 +0100
committerJordan Bracco <href@random.sh>2021-11-06 22:11:00 +0100
commit756e5aaf9deb0900962f91ebac30e8a756884717 (patch)
tree42036da0c0cba79ff9353826e7d53013bd3a2a81 /src
re-initial commitHEADmain
Diffstat (limited to 'src')
-rw-r--r--src/pf_route.hrl3
-rw-r--r--src/pf_route_app.erl11
-rw-r--r--src/pf_route_freebsd.erl179
-rw-r--r--src/pf_route_freebsd.hrl223
-rw-r--r--src/pf_route_macos.erl284
-rw-r--r--src/pf_route_macos.hrl197
-rw-r--r--src/pf_route_socket.erl22
-rw-r--r--src/pf_route_sup.erl12
-rw-r--r--src/pf_route_util.erl55
9 files changed, 986 insertions, 0 deletions
diff --git a/src/pf_route.hrl b/src/pf_route.hrl
new file mode 100644
index 0000000..4f94096
--- /dev/null
+++ b/src/pf_route.hrl
@@ -0,0 +1,3 @@
+-record(pf_route_inet4, {address}).
+-record(pf_route_inet6, {address, flow, scope}).
+-record(pf_route_sockaddr, {family, data}).
diff --git a/src/pf_route_app.erl b/src/pf_route_app.erl
new file mode 100644
index 0000000..9ce6ee0
--- /dev/null
+++ b/src/pf_route_app.erl
@@ -0,0 +1,11 @@
+-module(pf_route_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+
+start(_Type, _Args) ->
+ pf_route_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/src/pf_route_freebsd.erl b/src/pf_route_freebsd.erl
new file mode 100644
index 0000000..871211c
--- /dev/null
+++ b/src/pf_route_freebsd.erl
@@ -0,0 +1,179 @@
+-module(pf_route_freebsd).
+-include("pf_route.hrl").
+-include("pf_route_freebsd.hrl").
+-export([default_table/0, current_table/0, tables/0]).
+-export([list/1]).
+-export([monitor/1]).
+-export([parse_pf_route_packet/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.
+
+monitor(Table) ->
+ Self = self(),
+ OpenFun = fun (_Socket) ->
+ {todo_set_table, Table}
+ end,
+ PcktFun = fun (Binary) ->
+ Packet = parse_pf_route_packet(Binary),
+ Self ! {pf_route_monitor, Packet}
+ end,
+ spawn_link(fun () ->
+ pf_route_pf_route_socket:open(?PF_ROUTE, ?SOCK_RAW, ?AF_UNSPEC, OpenFun, PcktFun)
+ end).
+
+
+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_LOSING:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, losing);
+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_LOCK:?U_CHAR, Binary/binary>>) -> parse_rt_msghdr(Binary, lock);
+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, newaddr);
+%%parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_DELADDR:?U_CHAR, Binary/binary>>) -> parse_ifa_msghdr(Binary, deladdr);
+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, newmaddr);
+%%parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_DELMADDR:?U_CHAR, Binary/binary>>) -> parse_if_mamsghdr(Binary, delmaddr);
+%%parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_IFANNOUNCE:?U_CHAR, Binary/binary>>) -> parse_ifannounce_msghdr(Binary, ifannounce);
+%%parse_pf_route_packet(<<?RTM_VERSION:?U_CHAR, ?RTM_IEEE80211:?U_CHAR, Binary/binary>>) -> parse_ifannounce_msghdr(Binary, ieee80211).
+parse_pf_route_packet(Binary) -> {error, {invalid_packet, Binary}}.
+
+parse_rt_msghdr(<<% struct rt_msghdr (len, ver and type removed)
+ IfpIndex:?U_SHORT,
+ _Spare1:?U_SHORT,
+ Flags:?INT,
+ Addrs:?INT,
+ Pid:?PID_T,
+ Seq:?INT,
+ Errno:?INT,
+ FMask:?INT, % bitmask used in RTM_CHANGE
+ Inits:?U_LONG,
+ % rt_metrics
+ Locks:?U_LONG,
+ Mtu:?U_LONG,
+ Hopcount:?U_LONG,
+ Expire:?U_LONG,
+ Recv:?U_LONG,
+ Send:?U_LONG,
+ Ssthresh:?U_LONG,
+ Rtt:?U_LONG,
+ Rttvar:?U_LONG,
+ Pksent:?U_LONG,
+ Weight:?U_LONG,
+ Nhidx:?U_LONG,
+ Filler1:?U_LONG,
+ Filler2:?U_LONG,
+ % List of addrs
+ BinaryAddrs/binary
+ >>, Type) ->
+ Rt = #freebsd_rt{type = Type, ifp_index = IfpIndex, flags = Flags, addrs = Addrs, pid = Pid, seq = Seq, errno = Errno,
+ fmask = FMask, metrics_init = Inits, metrics_lock = Locks, mtu = Mtu, hopcount = Hopcount, expire = Expire,
+ recvpipe = Recv, sendpipe = Send, ssthresh = Ssthresh, rtt = Rtt, rttvar = Rttvar, pksent = Pksent,
+ weight = Weight, nhidx = Nhidx, filler = {Filler1, Filler2}},
+ parse_rt(Rt, BinaryAddrs).
+
+parse_rt(Rt0, BinaryAddrs) ->
+ Rt1 = Rt0#freebsd_rt{flags = pf_route_util:read_bitmask(?RTF, Rt0#freebsd_rt.flags),
+ addrs = pf_route_util:read_bitmask(?RTA, Rt0#freebsd_rt.addrs),
+ metrics_init = pf_route_util:read_bitmask(?RTV, Rt0#freebsd_rt.metrics_init),
+ metrics_lock = pf_route_util:read_bitmask(?RTV, Rt0#freebsd_rt.metrics_lock)},
+ logger:warning("pf_route_freebsd:monitor Rt = ~p~nBinary: ~p", [Rt1, BinaryAddrs]),
+ Rt = get_addrs(BinaryAddrs, Rt1#freebsd_rt.addrs, Rt1),
+ logger:warning("pf_route_freebsd:monitor Rt = ~p", [Rt]),
+ Rt.
+
+get_addrs(<<Len:?U_CHAR, Family:?U_CHAR, Content/binary>>, [Type | Rest], Rt) ->
+ <<BinaryAddr:(Len - 2)/binary, Binary/binary>> = Content,
+ get_addrs(Binary, Rest, set_rt_addr(Type, process_address(BinaryAddr, Family), Rt));
+get_addrs(_, [], Rt) ->
+ Rt.
+
+set_rt_addr(destination, Value, Rt) -> Rt#freebsd_rt{destination = Value};
+set_rt_addr(gateway, Value, Rt) -> Rt#freebsd_rt{gateway = Value};
+set_rt_addr(netmask, Value, Rt) -> Rt#freebsd_rt{netmask = Value};
+set_rt_addr(ifp, Value, Rt) -> Rt#freebsd_rt{ifp = Value};
+set_rt_addr(ifa, Value, Rt) -> Rt#freebsd_rt{ifa = Value};
+set_rt_addr(author, Value, Rt) -> Rt#freebsd_rt{author = Value};
+set_rt_addr(brd, Value, Rt) -> Rt#freebsd_rt{brd = Value}.
+
+process_address(<<_Port:?U_SHORT, A, B, C, D, _Zero:64/little-unsigned-integer>>, ?AF_INET) ->
+ {A, B, C, D};
+process_address(<<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, _Rest/binary>> = Data,
+ Link = #freebsd_link{index = Index, type = Type, name = Name, address = binary_to_list(Address), selector = Selector},
+ Link;
+process_address(Data, Family) ->
+ logger:error("freebsd:process_address: no clause for family ~p = ~p", [Family, Data]),
+ {sockaddr, Family, Data}.
+
+parse_if_msghdr(<<% if_msghdr
+ Addrs:?INT,
+ Flags:?INT,
+ Index:?U_SHORT,
+ _Spare1:?U_SHORT,
+ % if_data
+ Type:?UINT8,
+ Physical:?UINT8,
+ AddrLen:?UINT8,
+ HdrLen:?UINT8,
+ Linkstate:?UINT8,
+ Vhid:?UINT8,
+ DataLen:?UINT16,
+ Mtu:?UINT32,
+ Metric:?UINT32,
+ Baudrate:?UINT64,
+ IPackets:?UINT64,
+ IErrors:?UINT64,
+ OPackets:?UINT64,
+ OErrors:?UINT64,
+ Collisions:?UINT64,
+ IBytes:?UINT64,
+ OBytes:?UINT64,
+ IMcasts:?UINT64,
+ OMcasts:?UINT64,
+ IQdrops:?UINT64,
+ OQdrops:?UINT64,
+ Noproto:?UINT64,
+ Hwassist:?UINT64,
+ Data/binary>>, ifinfo) ->
+ If = #freebsd_if{addrs = pf_route_util:read_bitmask(?RTA, Addrs), flags = pf_route_util:read_bitmask(?IFF, Flags),
+ index = Index, type = Type, physical = Physical,
+ addrlen = AddrLen,hdrlen = HdrLen,
+ linkstate = maps:get(Linkstate, ?LINK_STATE), vhid = Vhid,datalen = DataLen,mtu = Mtu,metric = Metric,
+ baudrate = Baudrate,
+ ipackets = IPackets,ierrors = IErrors,opackets = OPackets,oerrors = OErrors,collisions = Collisions,
+ ibytes = IBytes,obytes = OBytes,imcasts = IMcasts,omcasts = OMcasts,iqdrops = IQdrops,
+ oqdrops = OQdrops,noproto = Noproto,hwassist = Hwassist},
+ logger:warning("pf_route_freebsd: freebsd_if: ~p~nBinary: ~p", [If, Data]),
+ If.
+
+do_list(Table) ->
+ os:cmd(["setfib ", Table, " netstat -rn"]).
diff --git a/src/pf_route_freebsd.hrl b/src/pf_route_freebsd.hrl
new file mode 100644
index 0000000..ad05f37
--- /dev/null
+++ b/src/pf_route_freebsd.hrl
@@ -0,0 +1,223 @@
+%% sys/types.h
+-define(U_CHAR, 8/little-unsigned-integer).
+-define(U_SHORT, 16/little-unsigned-integer).
+-define(U_LONG, 64/little-unsigned-integer).
+-define(INT, 32/little-signed-integer).
+-define(PID_T, ?INT).
+-define(UINT8, 8/little-unsigned-integer).
+-define(UINT16, 16/little-unsigned-integer).
+-define(UINT32, 32/little-unsigned-integer).
+-define(UINT64, 64/little-unsigned-integer).
+
+%% sys/socket.h
+-define(AF_UNSPEC, 0).
+-define(AF_INET, 2).
+-define(AF_LINK, 18).
+-define(AF_INET6, 31).
+-define(SOCK_RAW, 3).
+-define(PF_ROUTE, 17).
+
+%% net/route.h
+-define(RTM_VERSION, 5).
+
+-define(RTM_ADD, 16#1). % add route
+-define(RTM_DELETE, 16#2). % delete route
+-define(RTM_CHANGE, 16#3). % change metrics or flags
+-define(RTM_GET, 16#4). % 1 report metrics
+-define(RTM_LOSING, 16#5). % 1 kernel suspects partitionning
+-define(RTM_REDIRECT, 16#6). % 1 told to use different route
+-define(RTM_MISS, 16#7). % 1 lookup failed on this address
+-define(RTM_LOCK, 16#8). % 1 fix specified metrics
+-define(RTM_RESOLVE, 16#b). % 1 req to resolve dst to LL addr
+-define(RTM_NEWADDR, 16#c). % 2 address being added to iface
+-define(RTM_DELADDR, 16#d). % 2 address being removed from iface
+-define(RTM_IFINFO, 16#e). % 3 iface going up/down
+-define(RTM_NEWMADDR, 16#f). % 4 mcast group membership being added
+-define(RTM_DELMADDR, 16#10). % 4mcast group membership being deleted
+-define(RTM_IFANNOUNCE, 16#11). % 5 iface arrival/departure
+-define(RTM_IEEE80211, 16#12). % 5 IEEE80211 wireless event
+-define(RTM, #{?RTM_ADD => add, ?RTM_DELETE => delete, ?RTM_CHANGE => change, ?RTM_GET => get,
+ ?RTM_LOSING => losing, ?RTM_REDIRECT => redirect, ?RTM_MISS => miss, ?RTM_LOCK => lock,
+ ?RTM_RESOLVE => resolve, ?RTM_NEWADDR => newaddr, ?RTM_DELADDR => deladdr, ?RTM_IFINFO => ifinfo,
+ ?RTM_NEWMADDR => newmaddr, ?RTM_DELMADDR => delmaddr, ?RTM_IFANNOUNCE => ifannounce, ?RTM_IEEE80211 => ieee80211}).
+
+-define(RT_MSGHDRS, [?RTM_ADD, ?RTM_DELETE, ?RTM_CHANGE, ?RTM_GET, ?RTM_LOSING, ?RTM_REDIRECT, ?RTM_MISS, ?RTM_LOCK, ?RTM_RESOLVE]).
+-define(IFA_MSGHDRS, [?RTM_NEWADDR, ?RTM_DELADDR]).
+-define(IF_MSGHDRS, [?RTM_IFINFO]).
+-define(IFMA_MSGHDRS, [?RTM_NEWMADDR, ?RTM_DELMADDR]).
+-define(IF_ANNOUNCEMSGHDRS, [?RTM_IFANNOUNCE, ?RTM_IEEE80211]).
+
+% Bitmask values for flags
+-define(RTF_UP, 16#1). % route usable
+-define(RTF_GATEWAY, 16#2). % destination is a gateway
+-define(RTF_HOST, 16#4). % host entry
+-define(RTF_REJECT, 16#8). % host or net unreachable
+-define(RTF_DYNAMIC, 16#10). % created dynamically (by redirect)
+-define(RTF_MODIFIED, 16#20). % modified dynamically (by redirect)
+-define(RTF_DONE, 16#40). % message confirmed
+-define(RTF_DELCLONE, 16#80). % unused, was RTF_DELCLONE
+-define(RTF_CLONING, 16#100). % unused, was RTF_CLONING
+-define(RTF_XRESOLVE, 16#200). % external daemon resolves name
+-define(RTF_LLINFO, 16#400). % deprecated
+-define(RTF_LLDATA, 16#400). % used by apps to add/del L2 entries
+-define(RTF_STATIC, 16#800). % manually added
+-define(RTF_BLACKHOLE, 16#1000). % just discard pkts
+-define(RTF_PROTO2, 16#4000). % protocole specific routing flag
+-define(RTF_PROTO1, 16#8000). % protocol specific routing flag
+-define(RTF_PRCLONING, 16#10000). % deprecated
+-define(RTF_WASCLONED, 16#20000). % deprecated
+-define(RTF_PROTO3, 16#40000). % protocol specific routing flag
+-define(RTF_FIXEDMTU, 16#80000). % mtu was explicitly specified
+-define(RTF_PINNED, 16#100000). % route is immutable
+-define(RTF_LOCAL, 16#200000). % route represents a local address
+-define(RTF_BROADCAST, 16#400000). % route represents a bcast address
+-define(RTF_MULTICAST, 16#800000). % route represents a mcast address
+-define(RTF_STICKY, 16#10000000). % always route dst->src
+-define(RTF_GWFLAG_COMPAT, 16#80000000). % compatibility bit for interacting with existing routing apps (?)
+-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_LLINFO, llinfo}, {?RTF_LLDATA, lldata}, {?RTF_STATIC, static},
+ {?RTF_BLACKHOLE, blackhole}, {?RTF_PROTO2, proto2}, {?RTF_PROTO1, proto1}, {?RTF_PRCLONING, prcloning},
+ {?RTF_WASCLONED, wascloned}, {?RTF_PROTO3, proto3}, {?RTF_FIXEDMTU, fixed_mtu}, {?RTF_PINNED, pinned},
+ {?RTF_LOCAL, local}, {?RTF_BROADCAST, broadcast}, {?RTF_MULTICAST, multicast}, {?RTF_STICKY, sticky},
+ {?RTF_GWFLAG_COMPAT, gw_flag_compat}]).
+-define(RTF_CAN_CHANGE, [proto1, proto2, proto3, blackhole, reject, static, sticky]).
+
+% fib_ nexthob info flags
+-define(NHF_MULTIPATH, 16#0008). % Nexhop is a nexthop group
+-define(NFH_REJECT, 16#0010). % RTF_REJECT
+-define(NFH_BLACKHOLE, 16#0020). % RTF_BLACKHOLE
+-define(NFH_REDIRECT, 16#0040). % RTF_DYNAMIC|RTF_MODIFIED
+-define(NHF_DEFAULT, 16#0080). % Default route
+-define(NHF_BROADCAST, 16#0100). % RTF_BROADCAST
+-define(NHF_GATEWAY, 16#0200). % RTF_GATEWAY
+-define(NHF_HOST, 16#0400). % RTF_HOST
+-define(NHF, [{?NHF_MULTIPATH, multipath}, {?NHF_REJECT, reject}, {?NHF_BLACKHOLE, blackhole}, {?NHF_REDIRECT, redirect},
+ {?NHF_DEFAULT, default}, {?NHF_BROADCAST, broadcast}, {?NHF_GATEWAY, gateway}, {?NHF_HOST, host}]).
+
+% nexthop request flags
+-define(NHR_NONE, 16#00). % empty flags field
+-define(NHR_REF, 16#01). % reference nexhop
+-define(NHR_NODEFAULT, 16#02). % uRPF: do not consider default route
+% control plane route request flags
+-define(NHR_COPY, 16#100). % copy rte data
+-define(NHR_UNLOCKED, 16#200). % do not lock table
+-define(NHR, [{?NHR_NONE, none}, {?NHR_REF, reference}, {?NHR_NODEFAULT, no_default}, {?NHR_COPY, copy}, {?NHR_UNLOCKED, unlocked}]).
+
+% Bitmask values for rtm_inits and rmx_locks.
+-define(RTV_MTU, 16#1).
+-define(RTV_HOPCOUNT, 16#2).
+-define(RTV_EXPIRE, 16#4).
+-define(RTV_RPIPE, 16#8).
+-define(RTV_SPIPE, 16#10).
+-define(RTV_SSTHRESH, 16#20).
+-define(RTV_RTT, 16#40).
+-define(RTV_RTTVAR, 16#80).
+-define(RTV_WEIGHT, 16#100).
+-define(RTV, [{?RTV_MTU, mtu}, {?RTV_HOPCOUNT, hopcount}, {?RTV_EXPIRE, expire}, {?RTV_RPIPE, rpipe}, {?RTV_SPIPE, spipe},
+ {?RTV_SSTHRESH, ssthresh}, {?RTV_RTT, rtt}, {?RTV_RTTVAR, rttvar}, {?RTV_WEIGHT, weight}]).
+
+% Bitmask values for rtm_addrs.
+-define(RTA_DST, 16#1).
+-define(RTA_GATEWAY, 16#2).
+-define(RTA_NETMASK, 16#4).
+-define(RTA_GENMASK, 16#8).
+-define(RTA_IFP, 16#10).
+-define(RTA_IFA, 16#20).
+-define(RTA_AUTHOR, 16#40).
+-define(RTA_BRD, 16#80).
+-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}]).
+
+% rt_msghdr
+-record(freebsd_rt, {type,
+ flags,
+ addrs,
+ pid,
+ seq,
+ errno,
+ fmask,
+ metrics_init,
+
+ %% metrics
+ metrics_lock,
+ mtu,
+ hopcount,
+ expire,
+ recvpipe,
+ sendpipe,
+ ssthresh,
+ rtt,
+ rttvar,
+ pksent,
+ weight,
+ nhidx,
+ filler,
+
+ %% addrs
+ destination,
+ netmask,
+ gateway,
+ genmask,
+ ifp,
+ ifp_index,
+ ifa,
+ author,
+ brd}).
+
+% sockaddr_dl
+-record(freebsd_link, {index, type, name, address, selector}).
+
+% net/if.h
+
+%% if flags
+-define(IFF_UP, 16#1).
+-define(IFF_BROADCAST, 16#2).
+-define(IFF_DEBUG, 16#4).
+-define(IFF_LOOPBACK, 16#8).
+-define(IFF_POINTOPOINT, 16#10).
+-define(IFF_KNOWSEPOCH, 16#20).
+-define(IFF_DRV_RUNNING, 16#40).
+-define(IFF_NOARP, 16#80).
+-define(IFF_PROMISC, 16#100).
+-define(IFF_ALLMULTI, 16#200).
+-define(IFF_DRV_OACTIVE, 16#400).
+-define(IFF_SIMPLEX, 16#800).
+-define(IFF_LINK0, 16#1000).
+-define(IFF_LINK1, 16#2000).
+-define(IFF_LINK2, 16#4000).
+%-define(IFF_ALTPHYS, IFF_LINK2).
+-define(IFF_MULTICAST, 16#8000).
+-define(IFF_CANTCONFIG, 16#10000).
+-define(IFF_PPROMISC, 16#20000).
+-define(IFF_MONITOR, 16#40000).
+-define(IFF_STATICARP, 16#80000).
+-define(IFF_DYING, 16#200000).
+-define(IFF_RENAMING, 16#400000).
+-define(IFF, [{?IFF_UP, up}, {?IFF_BROADCAST, broadcast}, {?IFF_DEBUG, debug}, {?IFF_LOOPBACK, loopback}, {?IFF_POINTOPOINT, pointopoint},
+ {?IFF_KNOWSEPOCH, knowsepoch}, {?IFF_DRV_RUNNING, drv_running}, {?IFF_NOARP, noarp}, {?IFF_PROMISC, promisc},
+ {?IFF_ALLMULTI, allmulti}, {?IFF_DRV_OACTIVE, drv_oactive}, {?IFF_SIMPLEX, simplex}, {?IFF_LINK0, link0}, {?IFF_LINK1, link1},
+ {?IFF_LINK2, link2}, {?IFF_MULTICAST, multicast}, {?IFF_CANTCONFIG, cantconfig}, {?IFF_PPROMISC, ppromisc}, {?IFF_MONITOR, monitor},
+ {?IFF_STATICARP, staticarp}, {?IFF_DYING, dying}, {?IFF_RENAMING, renaming}]).
+
+-define(LINK_STATE_UNKNOWN, 0).
+-define(LINK_STATE_DOWN, 1).
+-define(LINK_STATE_UP, 2).
+-define(LINK_STATE, #{?LINK_STATE_UNKNOWN => unknown, ?LINK_STATE_DOWN => down, ?LINK_STATE_UP => up}).
+
+% if_msghdr + ifm_data
+-record(freebsd_if, {index, addrs, flags,
+ % if_data: generic interface information
+ type, physical, addrlen, hdrlen, linkstate, vhid, datalen, mtu, metric, baudrate,
+ % if_data: volatile statistics
+ ipackets, ierrors, opackets, oerrors, collisions, ibytes, obytes, imcasts, omcasts, iqdrops, oqdrops, noproto, hwassist
+}).
+
+% ifa_msghdr
+-record(freebsd_ifa, {addrs, flags, index, metric}).
+
+% ifma_msghdr
+-record(freebsd_ifma, {addrs, flags, index}).
+
+% ifannounce_msghdr
+-record(freebsd_ifannounce, {index, name, announcement}). %% 0 Arrival 1 departure
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.
diff --git a/src/pf_route_macos.hrl b/src/pf_route_macos.hrl
new file mode 100644
index 0000000..c7de4fe
--- /dev/null
+++ b/src/pf_route_macos.hrl
@@ -0,0 +1,197 @@
+-define(TABLE, 1).
+
+%% sys/types.h
+-define(U_CHAR, 8/little-unsigned-integer).
+-define(U_SHORT, 16/little-unsigned-integer).
+-define(U_LONG, 64/little-unsigned-integer).
+-define(INT, 32/little-signed-integer).
+-define(PID_T, ?INT).
+-define(UINT8, 8/little-unsigned-integer).
+-define(UINT16, 16/little-unsigned-integer).
+-define(UINT32, 32/little-unsigned-integer).
+-define(UINT64, 64/little-unsigned-integer).
+
+%% usr/include/sys/socket.h
+-define(AF_UNSPEC, 0).
+-define(AF_INET, 2).
+-define(AF_LINK, 18).
+-define(AF_INET6, 30).
+-define(SOCK_RAW, 3).
+-define(PF_ROUTE, 17).
+
+%% 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}]).
+
+-record(macos_rt, {type, % Message type, atom
+ flags, % Route flags
+ addrs, % Addresses list
+ pid, % Originating pid
+ seq, % user data
+ errno, % error number
+ refcount, % reference count (msghdr2)
+ parent_flags, % flags of the parent route (msghdr2)
+ 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
+}).
+
+-record(macos_link, {index, % system index for interface
+ type, % interface type
+ name, % interface name
+ address, % link level address
+ selector % link layer selector,
+ }).
+
+%% net/if.h
+
+-define(IFF_UP, 16#1).
+-define(IFF_BROADCAST, 16#2).
+-define(IFF_DEBUG, 16#4).
+-define(IFF_LOOPBACK, 16#8).
+-define(IFF_POINTOPOINT, 16#10).
+-define(IFF_NOTRAILERS, 16#20).
+-define(IFF_RUNNING, 16#40).
+-define(IFF_NOARP, 16#80).
+-define(IFF_PROMISC, 16#100).
+-define(IFF_ALLMULTI, 16#200).
+-define(IFF_OACTIVE, 16#400).
+-define(IFF_SIMPLEX, 16#800).
+-define(IFF_LINK0, 16#1000).
+-define(IFF_LINK1, 16#2000).
+-define(IFF_LINK2, 16#4000).
+-define(IFF_MULTICAST, 16#8000).
+-define(IFF, [{?IFF_UP, up}, {?IFF_BROADCAST, broadcast}, {?IFF_DEBUG, debug}, {?IFF_LOOPBACK, loopback}, {?IFF_POINTOPOINT, pointopoint},
+ {?IFF_NOTRAILERS, notrailers}, {?IFF_RUNNING, running}, {?IFF_NOARP, noarp}, {?IFF_PROMISC, promisc}, {?IFF_ALLMULTI, allmulti},
+ {?IFF_OACTIVE, oactive}, {?IFF_SIMPLEX, simplex}, {?IFF_LINK0, link0}, {?IFF_LINK1, link1}, {?IFF_LINK2, link2}, {?IFF_MULTICAST, multicast}]).
+
+-record(macos_if, {type, flags, addrs, index, typelen, physical, addrlen, hdrlen, recvquota, xmitquota, mtu, metric, baudrate,
+ ipackets, ierrors, opackets, oerrors, ibytes, obytes, imcasts, omcasts, iqdrops, noproto, recvtiming, xmittiming}).
+
+-record(macos_ifa, {type, flags, addrs, index, metric, netmask, ifp, ifa}).
+
+-record(macos_ifma, {type, flags, addrs, index, refcount, netmask, ifp, ifa}).
diff --git a/src/pf_route_socket.erl b/src/pf_route_socket.erl
new file mode 100644
index 0000000..a4f6ed5
--- /dev/null
+++ b/src/pf_route_socket.erl
@@ -0,0 +1,22 @@
+-module(pf_route_socket).
+-export([test/0, open/5]).
+
+test() ->
+ open(17, 3, 0, fun (_) -> ok end, fun (_) -> ok end).
+
+open(PfRoute, SockRaw, AfUnspec, OpenFun, ParseFun) ->
+ {ok, Socket} = socket:open(PfRoute, SockRaw, AfUnspec),
+ 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/pf_route_sup.erl b/src/pf_route_sup.erl
new file mode 100644
index 0000000..cbdf24a
--- /dev/null
+++ b/src/pf_route_sup.erl
@@ -0,0 +1,12 @@
+-module(pf_route_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/pf_route_util.erl b/src/pf_route_util.erl
new file mode 100644
index 0000000..23ba4bc
--- /dev/null
+++ b/src/pf_route_util.erl
@@ -0,0 +1,55 @@
+-module(pf_route_util).
+-export([module/0, parse_ip_address_with_netmask/1]).
+-export([read_bitmask/2]).
+
+read_bitmask(List, Integer) ->
+ read_bitmask(List, Integer, []).
+
+read_bitmask([{V, Name} | Rest], I, Acc) ->
+ if
+ (I band V) =:= V ->
+ read_bitmask(Rest, I, [Name | Acc]);
+ true ->
+ read_bitmask(Rest, I, Acc)
+ end;
+read_bitmask([], _, Acc) ->
+ lists:reverse(Acc).
+
+module() ->
+ case os:type() of
+ {unix, linux} ->
+ {ok, pf_route_linux};
+ {unix, darwin} ->
+ {ok, pf_route_macos};
+ {unix, freebsd} ->
+ {ok, pf_route_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.