epcap\_compile is an Erlang library for compiling PCAP filters to BPF
programs (see pcap-filter(7)).
epcap\_compile uses the NIF interface to wrap pcap\_compile(3PCAP)
from libpcap.
## WARNING
Since the library passes the filter string to pcap\_compile(3PCAP)
directly, any bugs in pcap\_compile() may cause the Erlang VM to crash. Do
not use filters from untrusted sources.
Also note very large filters may block the scheduler or cause a stack
overflow. For example:
epcap_compile:compile(string:copies("ip and ", 50000) ++ "ip").
Filters larger than 8192 bytes will not be be allowed by default. See the
`limit` option to compile/2.
## REQUIREMENTS
* libpcap
On Ubuntu: sudo apt-get install libpcap-dev
These libraries are not required but can be used with epcap\_compile:
* pkt: https://github.com/msantos/pkt.git
Use pkt to map the datalinktype to a number:
pkt:dlt(en10mb)
* procket: https://github.com/msantos/procket.git
Set a BPF filter on any kind of socket (Linux) or on a BPF device
(BSD).
## COMPILING
rebar3 compile
## EXPORTS
compile(Filter) -> {ok, Fcode} | {error, Error}
compile(Filter, Options) -> {ok, Fcode} | {error, Error}
Types Filter = string() | binary()
Fcode = [ Insn ]
Insn = binary()
Error = enomem | string()
Options = [ Option ]
Option = {optimize, boolean()}
| {netmask, IPaddr}
| {dlt, integer()}
| {snaplen, integer()}
| {limit, integer()}
Filter is a string in pcap-filter(7) format.
If the PCAP filter is successfully compiled to a BPF program,
a list of BPF instructions is returned.
If an error occurs, a string describing the error is returned
to the caller.
compile/1 defaults to:
* optimization enabled
* an unspecified netmask (filters specifying the broadcast
will return an error)
* datalinktype set to ethernet (DLT_EN10MB)
* a packet length of 65535 bytes
* a limit of 8192 bytes for filters. Filters larger than
this limit will return `{error, enomem}`. A limit less than
0 disables the length check.
See pcap_compile(3PCAP) for information about each of these options.
## EXAMPLES
### Compile a PCAP Filter
$ erl -pa ebin
1> epcap_compile:compile("ip and ( src host 192.168.10.1 or dst host 192.168.10.1 )").
{ok,[<<40,0,0,0,12,0,0,0>>,
<<21,0,0,5,0,8,0,0>>,
<<32,0,0,0,26,0,0,0>>,
<<21,0,2,0,1,10,168,192>>,
<<32,0,0,0,30,0,0,0>>,
<<21,0,0,1,1,10,168,192>>,
<<6,0,0,0,255,255,0,0>>,
<<6,0,0,0,0,0,0,0>>]}
The same BPF program can be generated from Erlang by using the bpf module in procket:
ip({A,B,C,D}) ->
IP = (A bsl 24) bor (B bsl 16) bor (C bsl 8) bor D,
[
% Ethernet
?BPF_STMT(?BPF_LD+?BPF_H+?BPF_ABS, 12), % offset = Ethernet Type
?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, ?ETHERTYPE_IP, 0, 5), % type = IP
% IP
?BPF_STMT(?BPF_LD+?BPF_W+?BPF_ABS, 26), % offset = Source IP address
?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, IP, 2, 0), % source = {A,B,C,D}
?BPF_STMT(?BPF_LD+?BPF_W+?BPF_ABS, 30), % offset = Destination IP address
?BPF_JUMP(?BPF_JMP+?BPF_JEQ+?BPF_K, IP, 0, 1), % destination = {A,B,C,D}
% Amount of packet to return
?BPF_STMT(?BPF_RET+?BPF_K, 16#FFFFFFFF), % Return up to 2^32-1 bytes
?BPF_STMT(?BPF_RET+?BPF_K, 0) % Return 0 bytes: drop packet
].
### Apply a BPF Filter to a PF\_PACKET Socket (Linux)
-module(lsf).
-export([f/0, f/1]).
f() ->
{ok, Fcode} = epcap_compile:compile("tcp and ( port 80 or port 443 )"),
f(Fcode).
f(Fcode) when is_list(Fcode) ->
{ok, S} = packet:socket(),
{ok, _} = packet:filter(S, Fcode),
loop(S).
loop(S) ->
case procket:recv(S, 1500) of
{ok, Data} ->
error_logger:info_report(Data),
loop(S);
{error, eagain} ->
timer:sleep(10),
loop(S)
end.
### Apply a BPF Filter to a TCP Socket (Linux)
-module(lsf_inet).
-export([f/0]).
f() ->
{ok, Fcode} = epcap_compile:compile("tcp and port 443"),
unfiltered(Fcode),
filtered(Fcode).
unfiltered(Fcode) when is_list(Fcode) ->
{ok, S} = gen_tcp:connect("www.google.com", 80,
[binary, {packet, 0}, {active, false}]),
ok = gen_tcp:send(S, "GET / HTTP/1.0\r\n\r\n"),
{ok, R} = gen_tcp:recv(S, 0, 5000),
error_logger:info_report([{unfiltered, R}]),
ok = gen_tcp:close(S).
filtered(Fcode) when is_list(Fcode) ->
{ok, S} = gen_tcp:connect("www.google.com", 80,
[binary, {packet, 0}, {active, false}]),
{ok, FD} = inet:getfd(S),
{ok, _} = packet:filter(FD, Fcode),
ok = gen_tcp:send(S, "GET / HTTP/1.0\r\n\r\n"),
{error, timeout} = gen_tcp:recv(S, 0, 5000),
error_logger:info_report([{filtered, "connection timeout"}]),
ok = gen_tcp:close(S).
### Applying a BPF Filter on BSD
-module(bpf_ex).
-export([f/1, f/2]).
f(Dev) ->
f(Dev, "ip and ( src host 192.168.10.1 or dst host 192.168.10.1 )").
f(Dev, Filter) ->
{ok, Socket, Length} = bpf:open(Dev),
{ok, Fcode} = epcap_compile:compile(Filter),
{ok, _} = bpf:ctl(Socket, setf, Fcode),
loop(Socket, Length).
loop(Socket, Length) ->
case procket:read(Socket, Length) of
{ok, <<>>} ->
loop(Socket, Length);
{ok, Data} ->
{bpf_buf, Time, Datalen, Packet, Rest} = bpf:buf(Data),
error_logger:info_report([
{time, Time},
{packet_is_truncated, Datalen /= byte_size(Packet)},
{packet, Packet},
{packet_size, byte_size(Packet)},
{remaining, byte_size(Rest)}
]),
loop(Socket, Length);
{error, eagain} ->
timer:sleep(10),
loop(Socket, Length)
end.