Permalink
Browse files

Compile PCAP filters to BPF programs

  • Loading branch information...
0 parents commit 47d1a6a95730a5864ae58a8ea44e83d058c63829 @msantos committed May 2, 2012
Showing with 446 additions and 0 deletions.
  1. +5 −0 .gitignore
  2. +19 −0 Makefile
  3. +134 −0 README.md
  4. +148 −0 c_src/epcap_compile.c
  5. +52 −0 examples/lsf.erl
  6. +7 −0 rebar.config
  7. +3 −0 src/epcap_compile.app.src
  8. +78 −0 src/epcap_compile.erl
@@ -0,0 +1,5 @@
+*.[oa]
+*.beam
+*.swp
+ebin/
+priv/
@@ -0,0 +1,19 @@
+REBAR=$(shell which rebar || echo ./rebar)
+
+all: compile
+
+./rebar:
+ erl -noshell -s inets start -s ssl start \
+ -eval 'httpc:request(get, {"https://github.com/downloads/basho/rebar/rebar", []}, [], [{stream, "./rebar"}])' \
+ -s inets stop -s init stop
+ chmod +x ./rebar
+
+compile: $(REBAR)
+ @$(REBAR) compile
+
+clean: $(REBAR)
+ @$(REBAR) clean
+
+examples: eg
+eg:
+ @erlc -I deps -o ebin examples/*.erl
@@ -0,0 +1,134 @@
+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.
+
+
+## REQUIREMENTS
+
+* libpcap
+
+ On Ubuntu: sudo apt-get install libpcap-dev
+
+These libraries are not required 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 the BPF filter on a socket (Linux) or BPF device (BSD).
+
+
+## COMPILING
+
+ make
+
+
+## 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()}
+
+ 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), the
+ datalinktype set to ethernet (DLT_EN10MB) and a packet length
+ of 65535 bytes. See pcap\_compile(7) for the meaning of 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 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.
+
+
+## TODO
+
+* BSD example
@@ -0,0 +1,148 @@
+/* Copyright (c) 2012, Michael Santos <michael.santos@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <pcap.h>
+#include <string.h>
+#include "erl_nif.h"
+
+
+static ERL_NIF_TERM atom_ok;
+static ERL_NIF_TERM atom_error;
+static ERL_NIF_TERM atom_enomem;
+
+
+ static int
+load(ErlNifEnv *env, void **priv_data, ERL_NIF_TERM load_info)
+{
+ atom_ok = enif_make_atom(env, "ok");
+ atom_error = enif_make_atom(env, "error");
+ atom_enomem = enif_make_atom(env, "enomem");
+
+ return 0;
+}
+ static int
+reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ return 0;
+}
+
+ static int
+upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
+{
+ return 0;
+}
+
+ void
+unload(ErlNifEnv* env, void* priv_data)
+{
+}
+
+ static ERL_NIF_TERM
+nif_pcap_compile(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifBinary filter = {0};
+ int optimize = 0;
+ u_int32_t netmask = 0;
+ int linktype = 0;
+ int snaplen = 0;
+
+ pcap_t *p = NULL;
+ struct bpf_program fp = {0};
+
+ int i = 0;
+ ERL_NIF_TERM insns = {0};
+ ERL_NIF_TERM res = {0};
+
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &filter))
+ return enif_make_badarg(env);
+
+ if (!enif_get_int(env, argv[1], &optimize))
+ return enif_make_badarg(env);
+
+ if (!enif_get_uint(env, argv[2], &netmask))
+ return enif_make_badarg(env);
+
+ if (!enif_get_int(env, argv[3], &linktype))
+ return enif_make_badarg(env);
+
+ if (!enif_get_int(env, argv[4], &snaplen))
+ return enif_make_badarg(env);
+
+ /* NULL terminate the filter */
+ if (!enif_realloc_binary(&filter, filter.size+1))
+ return enif_make_tuple2(env, atom_error, atom_enomem);
+
+ filter.data[filter.size-1] = '\0';
+
+ p = pcap_open_dead(linktype, snaplen);
+
+ if (p == NULL)
+ return enif_make_tuple2(env, atom_error, atom_enomem);
+
+ if (pcap_compile(p, &fp, (const char *)filter.data,
+ optimize, netmask) != 0) {
+ res = enif_make_tuple2(env,
+ atom_error,
+ enif_make_string(env, pcap_geterr(p), ERL_NIF_LATIN1));
+ goto ERR;
+ }
+
+ insns = enif_make_list(env, 0);
+
+ /* Build the list from the end of the buffer, so the list does
+ * not need to be reversed. */
+ for (i = fp.bf_len-1; i >= 0; i--) {
+ ErlNifBinary fcode = {0};
+
+ if (!enif_alloc_binary(sizeof(struct bpf_insn), &fcode)) {
+ res = enif_make_tuple2(env, atom_error, atom_enomem);
+ goto ERR;
+ }
+
+ (void)memcpy(fcode.data, fp.bf_insns+i, fcode.size);
+ insns = enif_make_list_cell(env, enif_make_binary(env, &fcode), insns);
+ }
+
+ res = enif_make_tuple2(env, atom_ok, insns);
+
+ERR:
+ pcap_close(p);
+
+ return res;
+}
+
+
+static ErlNifFunc nif_funcs[] = {
+ {"pcap_compile", 5, nif_pcap_compile},
+};
+
+ERL_NIF_INIT(epcap_compile, nif_funcs, load, reload, upgrade, unload)
@@ -0,0 +1,52 @@
+%% Copyright (c) 2012, Michael Santos <michael.santos@gmail.com>
+%% All rights reserved.
+%%
+%% Redistribution and use in source and binary forms, with or without
+%% modification, are permitted provided that the following conditions
+%% are met:
+%%
+%% Redistributions of source code must retain the above copyright
+%% notice, this list of conditions and the following disclaimer.
+%%
+%% Redistributions in binary form must reproduce the above copyright
+%% notice, this list of conditions and the following disclaimer in the
+%% documentation and/or other materials provided with the distribution.
+%%
+%% Neither the name of the author nor the names of its contributors
+%% may be used to endorse or promote products derived from this software
+%% without specific prior written permission.
+%%
+%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+%% FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+%% COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+%% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+%% BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+%% CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+%% LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+%% ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+%% POSSIBILITY OF SUCH DAMAGE.
+-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.
@@ -0,0 +1,7 @@
+{port_env, [
+ {"LDFLAGS", "-lpcap $LDFLAGS"}
+ ]}.
+
+{port_specs, [
+ {"priv/epcap_compile.so", ["c_src/epcap_compile.c"]}
+ ]}.
@@ -0,0 +1,3 @@
+{application, epcap_compile,
+ [{description, "Compile PCAP filters to BPF programs"},
+ {vsn, "0.01"}]}.
Oops, something went wrong. Retry.

0 comments on commit 47d1a6a

Please sign in to comment.