Skip to content
Browse files

sniff: improve handling of packet headers

Modify sniff to convert packet headers from records to a printable
representation. This allows dumping packets with an arbitrary number of
headers such as IPv6 packets with extension headers.

Add support for logging the full packet in hex or binary format:

    sniff:start([{filter, "ip6"}, {format, [hex,binary]}]).
  • Loading branch information...
1 parent 4e6696d commit 51029be4f7fc7c7f8492c17219bbef2bda389096 @msantos committed Oct 27, 2013
Showing with 97 additions and 110 deletions.
  1. +24 −24 README.md
  2. +73 −86 examples/sniff.erl
View
48 README.md
@@ -15,22 +15,22 @@ epcap includes a small example program called sniff.
cd epcap
make
make examples
-
+
# Allow your user to epcap with root privs
sudo visudo
youruser ALL = NOPASSWD: /path/to/epcap/priv/epcap
-
+
erl -pa ebin deps/*/ebin # or: ./start.sh
% Start the sniffer process
sniff:start_link().
-
+
% Use your interface, or leave it out and trust in pcap
sniff:start([{interface, "eth0"}]).
-
+
% To change the filter
sniff:start([{filter, "icmp or (tcp and port 80)"},{interface, "eth0"}]).
-
+
% To stop sniffing
sniff:stop().
@@ -39,7 +39,7 @@ epcap includes a small example program called sniff.
epcap:start() -> {ok, pid()}
epcap:start(Args) -> {ok, pid()}
-
+
Types Args = [Options]
Options = {chroot, string()} | {group, string()} | {interface, string()} | {promiscuous, boolean()} |
{user, string()} | {filter, string()} | {progname, string()} | {file, string()} |
@@ -100,24 +100,24 @@ epcap includes a small example program called sniff.
## SCREENSHOT
- =INFO REPORT==== 6-Jan-2010::20:35:18 ===
- time: "2010-01-06 20:35:18"
- caplen: 562
- len: 562
- source_macaddr: "0:16:B6:xx:xx:xx"
- source_address: {207,97,227,239}
- source_port: 80
- destination_macaddr: "0:15:AF:xx:xx:xx"
- destination_address: {192,168,1,2}
- destination_port: 56934
- protocol: tcp
- protocol_header: [{flags,["ack","psh"]},
- {seq,2564452231},
- {ack,3156269309},
- {win,46}]
- payload_bytes: 492
- payload: "HTTP/1.1 301 Moved Permanently..Server: nginx/0.7.61..Date: Thu, 07 Jan 2010 01:35:17 GMT..Content-Type: text/html; charset=utf-8..Connection: close..Status: 301 Moved Permanently..Location: http://github.com/dashboard..X-Runtime: 2ms..Content-Length: 93..Set-Cookie: _github_ses=BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA%3D%3D--884981fc5aa85daf318eeff084d98e2cff92578f; path=/; expires=Wed, 01 Jan 2020 08:00:00 GMT; HttpOnly..Cache-Control: no-cache"
-
+ =INFO REPORT==== 27-Oct-2013::11:47:43 ===
+ pcap: [{time,"2013-10-27 11:47:43"},
+ {caplen,653},
+ {len,653},
+ {datalink,en10mb}]
+ ether: [{source_macaddr,"F0:BD:4F:AA:BB:CC"},
+ {destination_macaddr,"B3:4B:19:00:11:22"}]
+ ipv6: [{protocol,tcp},
+ {source_address,"2607:F8B0:400B:80B::1000"},
+ {destination_address,"2002:26F:92:AE::123"}]
+ tcp: [{source_port,80},
+ {destination_port,47980},
+ {flags,[ack,psh]},
+ {seq,686139900},
+ {ack,725208397},
+ {win,224}]
+ payload_size: 567
+ payload: "HTTP/1.0 301 Moved Permanently..Location: http://www.google.ca/..Content-Type: text/html; charset=UTF-8..Date: Sun, 27 Oct 2013 15:47:49 GMT..Expires: Tue, 26 Nov 2013 15:47:49 GMT..Cache-Control: public, max-age=2592000..Server: gws..Content-Length: 218..X-XSS-Protection: 1; mode=block..X-Frame-Options: SAMEORIGIN..Alternate-Protocol: 80:quic....<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">.<TITLE>301 Moved</TITLE></HEAD><BODY>.<H1>301 Moved</H1>.The document has moved.<A HREF=\"http://www.google.ca/\">here</A>...</BODY></HTML>.."
## TODO
View
159 examples/sniff.erl
@@ -42,10 +42,11 @@
-export([init/1, handle_event/3, handle_sync_event/4,
handle_info/3, terminate/3, code_change/4]).
--define(is_print(C), C >= $ , C =< $~).
+-define(is_print(C), C >= $\s, C =< $~).
-record(state, {
- pid
+ pid,
+ format = [] % full packet dump: binary, hex
}).
@@ -72,7 +73,6 @@ init([]) ->
process_flag(trap_exit, true),
{ok, waiting, #state{}}.
-
handle_event(_Event, StateName, State) ->
{next_state, StateName, State}.
@@ -83,39 +83,17 @@ handle_sync_event(_Event, _From, StateName, State) ->
%%
%% State: sniffing
%%
-handle_info({packet, DLT, Time, Len, Packet}, sniffing, State) ->
- [Ether, IP, Hdr, Payload] = decode(pkt:dlt(DLT), Packet),
-
- {Saddr, Daddr, Proto} = case IP of
- #ipv4{saddr = S, daddr = D, p = P} ->
- {S,D,P};
-
- #ipv6{saddr = S, daddr = D, next = P} ->
- {S,D,P}
- end,
+handle_info({packet, DLT, Time, Len, Data}, sniffing,
+ #state{format = Format} = State) ->
+ Packet = pkt:decapsulate({pkt:dlt(DLT), Data}),
+ Headers = header(Packet),
error_logger:info_report([
- {time, timestamp(Time)},
- {caplen, byte_size(Packet)},
- {len, Len},
- {datalink, pkt:dlt(DLT)},
-
- % Source
- {source_macaddr, string:join(ether_addr(Ether#ether.shost), ":")},
- {source_address, inet_parse:ntoa(Saddr)},
- {source_port, port(sport, Hdr)},
-
- % Destination
- {destination_macaddr, string:join(ether_addr(Ether#ether.dhost), ":")},
- {destination_address, inet_parse:ntoa(Daddr)},
- {destination_port, port(dport, Hdr)},
-
- {protocol, pkt:proto(Proto)},
- {protocol_header, header(Hdr)},
-
- {payload_bytes, byte_size(Payload)},
- {payload, payload(Payload)}
- ]),
+ {pcap, [{time, timestamp(Time)},
+ {caplen, byte_size(Data)},
+ {len, Len},
+ {datalink, pkt:dlt(DLT)}]}
+ ] ++ Headers ++ packet(Format, Data)),
{next_state, sniffing, State};
% epcap port stopped
@@ -130,7 +108,6 @@ handle_info({'EXIT', _Pid, normal}, sniffing, State) ->
handle_info({'EXIT', _Pid, normal}, waiting, State) ->
{next_state, waiting, State}.
-
terminate(_Reason, _StateName, _State) ->
ok.
@@ -142,8 +119,12 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%%% States
%%--------------------------------------------------------------------
waiting({start, Opt}, State) ->
+ Format = proplists:get_value(format, Opt, []),
{ok, Pid} = epcap:start(Opt),
- {next_state, sniffing, State#state{pid = Pid}}.
+ {next_state, sniffing, State#state{
+ pid = Pid,
+ format = Format
+ }}.
sniffing({start, Opt}, #state{pid = Pid} = State) ->
epcap:stop(Pid),
@@ -157,60 +138,66 @@ sniffing(stop, #state{pid = Pid} = State) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+header(Payload) ->
+ header(Payload, []).
+
+header([], Acc) ->
+ lists:reverse(Acc);
+header([#ether{shost = Shost, dhost = Dhost}|Rest], Acc) ->
+ header(Rest, [{ether, [{source_macaddr, ether_addr(Shost)},
+ {destination_macaddr, ether_addr(Dhost)}]}|Acc]);
+header([#ipv4{saddr = Saddr, daddr = Daddr, p = Proto}|Rest], Acc) ->
+ header(Rest, [{ipv4, [{protocol, pkt:proto(Proto)},
+ {source_address, inet_parse:ntoa(Saddr)},
+ {destination_address, inet_parse:ntoa(Daddr)}]}|Acc]);
+header([#ipv6{saddr = Saddr, daddr = Daddr, next = Proto}|Rest], Acc) ->
+ header(Rest, [{ipv6, [{protocol, pkt:proto(Proto)},
+ {source_address, inet_parse:ntoa(Saddr)},
+ {destination_address, inet_parse:ntoa(Daddr)}]}|Acc]);
+header([#tcp{sport = Sport, dport = Dport, ackno = Ackno, seqno = Seqno,
+ win = Win, cwr = CWR, ece = ECE, urg = URG, ack = ACK, psh = PSH,
+ rst = RST, syn = SYN, fin = FIN}|Rest], Acc) ->
+ Flags = [ F || {F,V} <- [{cwr, CWR}, {ece, ECE}, {urg, URG}, {ack, ACK},
+ {psh, PSH}, {rst, RST}, {syn, SYN}, {fin, FIN} ], V =:= 1 ],
+ header(Rest, [{tcp, [{source_port, Sport}, {destination_port, Dport},
+ {flags, Flags}, {seq, Seqno}, {ack, Ackno}, {win, Win}]}|Acc]);
+header([#udp{sport = Sport, dport = Dport, ulen = Ulen}|Rest], Acc) ->
+ header(Rest, [{udp, [{source_port, Sport}, {destination_port, Dport},
+ {ulen, Ulen}]}|Acc]);
+header([#icmp{type = Type, code = Code}|Rest], Acc) ->
+ header(Rest, [{icmp, [{type, Type}, {code, Code}]}|Acc]);
+header([#icmp6{type = Type, code = Code}|Rest], Acc) ->
+ header(Rest, [{icmp6, [{type, Type}, {code, Code}]}|Acc]);
+header([Hdr|Rest], Acc) when is_tuple(Hdr) ->
+ header(Rest, [{header, Hdr}|Acc]);
+header([Payload|Rest], Acc) when is_binary(Payload) ->
+ header(Rest, [{payload, to_ascii(Payload)},
+ {payload_size, byte_size(Payload)}|Acc]).
+
+packet(Format, Bin) ->
+ packet(Format, Bin, []).
+packet([], _Bin, Acc) ->
+ lists:reverse(Acc);
+packet([binary|Rest], Bin, Acc) ->
+ packet(Rest, Bin, [{packet, Bin}|Acc]);
+packet([hex|Rest], Bin, Acc) ->
+ packet(Rest, Bin, [{packet, to_hex(Bin)}|Acc]).
+
+to_ascii(Bin) when is_binary(Bin) ->
+ [ to_ascii(C) || <<C:8>> <= Bin ];
+to_ascii(C) when ?is_print(C) -> C;
+to_ascii(_) -> $..
+
+to_hex(Bin) when is_binary(Bin) ->
+ [ integer_to_list(N, 16) || <<N:8>> <= Bin ].
+
+ether_addr(MAC) ->
+ string:join(to_hex(MAC), ":").
+
timestamp(Now) when is_tuple(Now) ->
iso_8601_fmt(calendar:now_to_local_time(Now)).
iso_8601_fmt(DateTime) ->
{{Year,Month,Day},{Hour,Min,Sec}} = DateTime,
lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B",
[Year, Month, Day, Hour, Min, Sec])).
-
-header(#tcp{ackno = Ackno, seqno = Seqno, win = Win} = Hdr) ->
- [{flags, tcp_flags(Hdr)},
- {seq, Seqno},
- {ack, Ackno},
- {win, Win}];
-header(#udp{ulen = Ulen}) ->
- [{ulen, Ulen}];
-header(#icmp{code = Code, type = Type}) ->
- [{type, Type},
- {code, Code}];
-header(Packet) ->
- Packet.
-
-port(sport, #tcp{sport = SPort}) -> SPort;
-port(sport, #udp{sport = SPort}) -> SPort;
-port(dport, #tcp{dport = DPort}) -> DPort;
-port(dport, #udp{dport = DPort}) -> DPort;
-port(_,_) -> "".
-
-payload(Payload) ->
- [ to_ascii(C) || <<C:8>> <= Payload ].
-
-to_ascii(C) when ?is_print(C) -> C;
-to_ascii(_) -> $..
-
-ether_addr(B) when is_binary(B) ->
- ether_addr(binary_to_list(B));
-ether_addr(L) when is_list(L) ->
- [ hd(io_lib:format("~.16B", [N])) || N <- L ].
-
-tcp_flags(#tcp{cwr = CWR, ece = ECE, urg = URG, ack = ACK,
- psh = PSH, rst = RST, syn = SYN, fin = FIN}) ->
- [ F || {F,V} <- [
- {cwr, CWR},
- {ece, ECE},
- {urg, URG},
- {ack, ACK},
- {psh, PSH},
- {rst, RST},
- {syn, SYN},
- {fin, FIN}
- ], V =:= 1 ].
-
-decode(ether, Packet) ->
- pkt:decapsulate({ether, Packet});
-decode(DLT, Packet) ->
- % Add a fake ethernet header
- [_Linktype, IP, Hdr, Payload] = pkt:decapsulate({DLT, Packet}),
- [#ether{}, IP, Hdr, Payload].

0 comments on commit 51029be

Please sign in to comment.
Something went wrong with that request. Please try again.