From 12c08607d0c2aa0f3115fdb022641b50c2efd049 Mon Sep 17 00:00:00 2001 From: Geoff Cant Date: Sat, 28 Jan 2012 16:52:09 -0800 Subject: [PATCH] Adding the experimental tcpdump tap. --- src/enet_tap_tcpd.erl | 58 ++++++++++++ src/enet_tcpd_iface.erl | 199 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 src/enet_tap_tcpd.erl create mode 100644 src/enet_tcpd_iface.erl diff --git a/src/enet_tap_tcpd.erl b/src/enet_tap_tcpd.erl new file mode 100644 index 0000000..50efbae --- /dev/null +++ b/src/enet_tap_tcpd.erl @@ -0,0 +1,58 @@ +%%%------------------------------------------------------------------- +%% @copyright Geoff Cant +%% @author Geoff Cant +%% @version {@vsn}, {@date} {@time} +%% @doc Tap port program interface +%% @end +%%%------------------------------------------------------------------- +-module(enet_tap_tcpd). + +%% API +-export([open/0 + ,open/1 + ,open/2 + ,close/1 + ]). + +-compile(export_all). + +-include("enet_types.hrl"). + +%%==================================================================== +%% API +%%==================================================================== + +open() -> open("en0", ""). +open(Args) -> + open_tcpdump(Args). + +open(Device, Filter) -> + open_tcpdump(args(Device, Filter)). + +open_tcpdump(Args) -> + open_port({spawn_executable, driver()}, + [{args, Args} + ,stream + ,binary + ,exit_status + ,in + ,use_stdio + ,{env, env()}]). + +driver() -> + case os:type() of + {unix, _} -> + os:find_executable("tcpdump") + end. + +args(Device, Filter) -> + [ "-w", "-", "-i", Device | Filter ]. + +env() -> + case os:type() of + {unix, _} -> + [] + end. + +close(P) -> + port_close(P). diff --git a/src/enet_tcpd_iface.erl b/src/enet_tcpd_iface.erl new file mode 100644 index 0000000..6f3f698 --- /dev/null +++ b/src/enet_tcpd_iface.erl @@ -0,0 +1,199 @@ +%%%------------------------------------------------------------------- +%% @copyright Geoff Cant +%% @author Geoff Cant +%% @version {@vsn}, {@date} {@time} +%% @doc Enet Interface process +%% @end +%%%------------------------------------------------------------------- +-module(enet_tcpd_iface). + +-behaviour(gen_server). + +-include_lib("logging.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include("enet_types.hrl"). +-include("enet_pcap.hrl"). + +-define(TAP, enet_tap_tcpd). + +%% API +-export([start_link/1 + ,start/1 + ,start_link/2 + ,start/2 + ,send/2 + ]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {port :: port(), + pubsub = pubsub:new() :: pubsub:pubsub(), + args :: list(), + header :: #pcap_hdr{}, + buf :: 'undefined' | {BytesNeeded::non_neg_integer(), binary()} + }). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% @spec start_link(Device, OSIfconfig) -> {ok,Pid} | ignore | {error,Error} +%% @doc Starts the server +%% @end +%%-------------------------------------------------------------------- +start_link(Args) when is_list(Args) -> + gen_server:start_link(?MODULE, [#state{args=Args}], []). + +start_link(Args, Pubsub) when is_list(Args) -> + gen_server:start_link(?MODULE, [#state{args=Args, pubsub=Pubsub}], []). + + +start(Args) when is_list(Args) -> + gen_server:start(?MODULE, [#state{args=Args}], []). + +start(Args, Pubsub) when is_list(Args) -> + gen_server:start(?MODULE, [#state{args=Args, pubsub=Pubsub}], []). + + +send(_Interface, _Data) -> + erlang:error(not_supported). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @doc Initialises the server's state +%% @end +%%-------------------------------------------------------------------- +init([S = #state{args=Args}]) -> + Port = ?TAP:open(Args), + {ok, S#state{port=Port}}. + +%%-------------------------------------------------------------------- +%% @private +handle_call({pubsub, Op}, _From, S = #state{pubsub=P}) -> + NewPub = pubsub:process_msg(Op, P), + {reply, ok, S#state{pubsub=NewPub}}; +handle_call(Call, _From, State) -> + ?WARN("Unexpected call ~p.", [Call]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +handle_cast(Msg, State) -> + ?WARN("Unexpected cast ~p", [Msg]), + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +handle_info({Port, {exit_status, 0}}, S = #state{port=Port}) -> + {stop, normal, S#state{port=undefined}}; +handle_info({Port, {exit_status, N}}, S = #state{port=Port}) -> + {stop, {tcpdump_error, N}, S#state{port=undefined}}; + +handle_info({Port, {data, Data}}, S = #state{port=Port, + buf=undefined}) -> + S1 = handle_port_data(Data, S), + {noreply, S1}; +handle_info({Port, {data, Data}}, S = #state{port=Port, + buf={N, Buf}}) -> + case byte_size(Data) of + Sz when Sz >= N -> + {noreply, + handle_port_data(<>, + S#state{buf=undefined})}; + Sz when Sz < N -> + {noreply, + S#state{buf={N-Sz, <>}}} + end; + +handle_info({pubsub, Op}, S = #state{pubsub=P}) -> + {noreply, S#state{pubsub=pubsub:process_msg(Op, P)}}; +handle_info(PubSubOp = {'DOWN', _, _, _, _}, S = #state{pubsub=P}) -> + {noreply, S#state{pubsub=pubsub:process_msg(PubSubOp, P)}}; +handle_info(Info, State) -> + ?WARN("Unexpected info ~p", [Info]), + {noreply, State}. + +%% handle_frame(Frame, S) -> +%% case enet_codec:decode(eth, Frame, [eth]) of +%% E = #eth{dst=D} when D =:= S#state.mac; D =:= broadcast -> +%% %% Frames destined for me. +%% publish_rx(Frame, E, S); +%% E = #eth{} when S#state.promisc =:= true -> +%% %% Frames destined for something else but we're in promiscuous mode. +%% publish_rx(Frame, E, S); +%% Frame when S#state.promisc =:= true -> +%% publish_rx(Frame, unknown, S); +%% _ -> +%% drop_frame +%% end, +%% {noreply, S}. + +%%-------------------------------------------------------------------- +%% @private +%% @spec terminate(Reason, State) -> void() +%% @doc This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%% @end +%%-------------------------------------------------------------------- +terminate(Reason, S = #state{port=P}) when is_port(P) -> + catch ?TAP:close(P), + terminate(Reason, S#state{port=undefined}); +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +%% @private +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @doc Convert process state when code is changed +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + +publish_rx(Frame, Decoded, S = #state{}) -> + publish({rx, Frame, Decoded}, S). + +publish_tx(Frame, Term, S = #state{}) -> + publish({tx, Frame, Term}, S). + +publish(Message, #state{pubsub=P}) -> + pubsub:send({enet, {?MODULE, self()}, Message}, P). + +handle_port_data(Header, S = #state{header=undefined}) + when byte_size(Header) < 24 -> + S#state{buf={24 - byte_size(Header), Header}}; +handle_port_data(Header, S = #state{header=undefined}) + when byte_size(Header) >= 24 -> + case enet_pcap:decode_header(Header) of + {PcapHdr = #pcap_hdr{}, Rest} -> + handle_port_data(Rest, S#state{header=PcapHdr}) + end; +handle_port_data(Data, S = #state{header=Hdr}) -> + case enet_pcap:partial_decode(Data, Hdr) of + {Pkt, Rest} -> + handle_packet(Pkt, S), + handle_port_data(Rest, S); + N when is_integer(N) -> + S#state{buf={N, Data}} + end. + +handle_packet(Pcap = #pcap_pkt{}, + S = #state{header=Hdr}) -> + publish(Pcap, S), + S.