diff options
author | Jordan Bracco <href@random.sh> | 2021-11-06 22:11:00 +0100 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2021-11-06 22:11:00 +0100 |
commit | 756e5aaf9deb0900962f91ebac30e8a756884717 (patch) | |
tree | 42036da0c0cba79ff9353826e7d53013bd3a2a81 /src |
Diffstat (limited to 'src')
-rw-r--r-- | src/pf_route.hrl | 3 | ||||
-rw-r--r-- | src/pf_route_app.erl | 11 | ||||
-rw-r--r-- | src/pf_route_freebsd.erl | 179 | ||||
-rw-r--r-- | src/pf_route_freebsd.hrl | 223 | ||||
-rw-r--r-- | src/pf_route_macos.erl | 284 | ||||
-rw-r--r-- | src/pf_route_macos.hrl | 197 | ||||
-rw-r--r-- | src/pf_route_socket.erl | 22 | ||||
-rw-r--r-- | src/pf_route_sup.erl | 12 | ||||
-rw-r--r-- | src/pf_route_util.erl | 55 |
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. |