%% Copyright (c) 2012-2017, Michael Santos %% 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(ewpcap). -include("ewpcap.hrl"). -export([ open/0, open/1, open/2, close/1, filter/2, filter/3, read/1, read/2, write/2, getifaddrs/0, dev/0, stats/1 ]). -define(PCAP_NETMASK_UNKNOWN, 16#ffffffff). -define(DLT_EN10MB, 1). -type ewpcap_resource() :: #ewpcap_resource{}. -type ewpcap_stat() :: #ewpcap_stat{}. -export_type([ewpcap_resource/0, ewpcap_stat/0]). -on_load(on_load/0). %%-------------------------------------------------------------------- %%% NIF stubs %%-------------------------------------------------------------------- on_load() -> case erlang:system_info(smp_support) of true -> erlang:load_nif(progname(), []); false -> {error, "Requires smp support (-smp enable)"} end. pcap_compile(_, _, _, _) -> erlang:nif_error(not_implemented). pcap_open_live(_, _, _, _, _, _, _) -> erlang:nif_error(not_implemented). pcap_close(_) -> erlang:nif_error(not_implemented). pcap_lookupdev() -> erlang:nif_error(not_implemented). pcap_findalldevs() -> erlang:nif_error(not_implemented). pcap_loop(_) -> erlang:nif_error(not_implemented). pcap_sendpacket(_, _) -> erlang:nif_error(not_implemented). pcap_stats(_) -> erlang:nif_error(not_implemented). %%-------------------------------------------------------------------- %%% API %%-------------------------------------------------------------------- -type time_unit() :: timestamp | microsecond. -type open_options() :: [ {snaplen, non_neg_integer()} | {promisc, boolean()} | {to_ms, non_neg_integer()} | {filter, iodata()} | {buffer, non_neg_integer()} | {monitor, boolean()} | {time_unit, time_unit()} ]. -spec open() -> {'ok', ewpcap_resource()} | {'error', string() | 'enomem'}. open() -> open(<<>>, []). -spec open(iodata()) -> {'ok', ewpcap_resource()} | {'error', string() | 'enomem'}. open(Dev) -> open(Dev, []). -spec open(iodata(), open_options()) -> {'ok', ewpcap_resource()} | {'error', string() | 'enomem'}. open(<<>>, Options) -> case pcap_lookupdev() of {ok, Dev} -> open(Dev, Options); Error -> Error end; open(Dev, Options) when is_list(Options) -> Snaplen = proplists:get_value(snaplen, Options, 16#ffff), Promisc = bool(proplists:get_value(promisc, Options, false)), To_ms = proplists:get_value(to_ms, Options, 500), Filter = proplists:get_value(filter, Options, <<>>), Buffer = proplists:get_value(buffer, Options, 0), Monitor = bool(proplists:get_value(monitor, Options, false)), TimeUnit = time_unit(proplists:get_value(time_unit, Options, timestamp)), case pcap_open_live(Dev, Snaplen, Promisc, To_ms, Buffer, Monitor, TimeUnit) of {ok, Socket} -> open_1(Socket, Options, Filter); Error -> Error end. open_1(Socket, _Options, <<>>) -> open_2(Socket); open_1(Socket, Options, Filter) -> case filter(Socket, Filter, Options) of ok -> open_2(Socket); Error -> Error end. open_2(Socket) -> case loop(Socket) of ok -> {ok, Socket}; Error -> Error end. -spec close(ewpcap_resource()) -> 'ok'. close(#ewpcap_resource{res = Res}) -> pcap_close(Res). -type filter_options() :: [ {optimize, boolean()} | {netmask, non_neg_integer()} ]. -spec filter(ewpcap_resource(), iodata()) -> 'ok' | {'error', string() | 'enomem'}. filter(Res, Filter) -> filter(Res, Filter, []). -spec filter(ewpcap_resource(), iodata(), filter_options()) -> 'ok' | {'error', string() | 'enomem'}. filter(#ewpcap_resource{res = Res}, Filter, Options) when is_binary(Filter); is_list(Filter) -> Optimize = bool(proplists:get_value(optimize, Options, true)), Netmask = mask(proplists:get_value(netmask, Options, ?PCAP_NETMASK_UNKNOWN)), Limit = proplists:get_value(limit, Options, 8192), case iolist_size(Filter) < Limit orelse Limit < 0 of true -> pcap_compile(Res, Filter, Optimize, Netmask); false -> {error, enomem} end. -spec loop(ewpcap_resource()) -> 'ok' | {'error', files:posix()}. loop(#ewpcap_resource{res = Res}) -> pcap_loop(Res). -spec read(ewpcap_resource()) -> {'ok', binary()} | {'error', string()}. read(Res) -> read(Res, infinity). -spec read(ewpcap_resource(), 'infinity' | non_neg_integer()) -> {'ok', binary()} | {'error', string() | 'eagain'}. read(#ewpcap_resource{ref = Ref}, Timeout) -> receive {ewpcap, Ref, _DatalinkType, _Time, _ActualLength, Packet} -> {ok, Packet}; {ewpcap_error, Ref, Error} -> {error, Error} after Timeout -> {error, eagain} end. -spec write(ewpcap_resource(), iodata()) -> 'ok' | {'error', string()}. write(#ewpcap_resource{res = Res}, Data) when is_list(Data); is_binary(Data) -> pcap_sendpacket(Res, Data). -spec dev() -> {'ok', string()} | {'error', string()}. dev() -> pcap_lookupdev(). -spec getifaddrs() -> {'ok', [] | [{string(), [proplists:proplist()]}]} | {'error', string()}. getifaddrs() -> case pcap_findalldevs() of {ok, Iflist} -> {ok, [ iface(N) || N <- lists:reverse(Iflist) ]}; Error -> Error end. -spec stats(ewpcap_resource()) -> {'ok', ewpcap_stat()} | {'error', string()}. stats(#ewpcap_resource{res = Res}) -> pcap_stats(Res). iface({If, Attr}) -> {If, addr(Attr)}. addr(Attr) -> addr(Attr, []). addr([], Attr) -> Attr; addr([{Key, <>}|T], Attr) -> addr(T, [{Key, {A,B,C,D}}|Attr]); addr([{Key, <>}|T], Attr) -> addr(T, [{Key, {A,B,C,D,E,F,G,H}}|Attr]); addr([N|T], Attr) -> addr(T, [N|Attr]). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- bool(true) -> 1; bool(false) -> 0. time_unit(timestamp) -> 0; time_unit(microsecond) -> 1. mask(N) when is_integer(N) -> N; mask({A,B,C,D}) -> (A bsl 24) bor (B bsl 16) bor (C bsl 8) bor D. progname() -> case code:priv_dir(?MODULE) of {error,bad_name} -> filename:join([ filename:dirname(code:which(?MODULE)), "..", "priv", ?MODULE ]); Dir -> filename:join([Dir,?MODULE]) end.