Skip to content

Commit

Permalink
Merge branch 'traceroute'
Browse files Browse the repository at this point in the history
  • Loading branch information
msantos committed Aug 24, 2011
2 parents a8c79b5 + 1184b2d commit 87c051a
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/gen_icmp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
-export([
echo/2, echo/3,
type/1, code/1,
packet/2
packet/2,
parse/1
]).

-export([start_link/2]).
Expand Down
292 changes: 292 additions & 0 deletions src/tracert.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
%% Copyright (c) 2011, 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.

%%%
%%% traceroute
%%%
%%% Send a probe packet with the time to live set from 1. Monitor
%%% an ICMP socket for ICMP responses or timeout.
%%%

-module(tracert).

-include("pkt.hrl").

-define(IP_HDRINCL, 3).

-export([
host/1, host/2,
path/1
]).
-export([
open/1,
proplist_to_record/1,
probe/1
]).

-record(state, {
protocol = icmp,
ttl = 1,
max_hops = 31,
timeout = 1000, % 1 second

packet,
handler,
dport = 0,
sport = 0,
next_port,

saddr = {192,168,213,54},
daddr,
ws,
rs
}).


%%-------------------------------------------------------------------------
%%% API
%%-------------------------------------------------------------------------

open(Protocol) ->
P = case Protocol of
icmp -> icmp;
_ -> raw
end,

{ok, Socket} = procket:open(0, [
{protocol, P},
{type, raw},
{family, inet}
]),

ok = procket:setsockopt(Socket, ?IPPROTO_IP, ?IP_HDRINCL, <<1:32/native>>),
{ok, Socket}.


host(Host) ->
host(Host, []).

host(Host, Options) ->
State = proplist_to_record(Options),

% Write socket: probes
{ok, WS} = open(State#state.protocol),

% Read socket: ICMP trace
{ok, RS} = gen_icmp:open(),

trace(State#state{
daddr = gen_icmp:parse(Host),
ws = WS,
rs = RS
}).


path(Path) when is_list(Path) ->
[ begin
case N of
{Saddr, Microsec, {icmp, Packet}} ->
ICMP = icmp_to_proplist(Packet),
{Saddr, Microsec, ICMP};
Any ->
Any
end
end || N <- Path ].


%%-------------------------------------------------------------------------
%%% Probe and watch for the responses
%%-------------------------------------------------------------------------

%%
%% Send out probes and wait for the response
%%
trace(#state{handler = Handler} = State) when is_function(Handler) ->
spawn_link(Handler),
trace(State, []);
trace(State) ->
trace(State, []).

% Traceroute complete
trace(#state{ttl = 0}, Acc) ->
lists:reverse(Acc);
% Max hops reached
trace(#state{ttl = TTL, max_hops = TTL}, Acc) ->
lists:reverse(Acc);
trace(#state{
daddr = Daddr,
rs = Socket,
ttl = TTL,
dport = Port,
next_port = Next,
timeout = Timeout
} = State0, Acc) ->

State = State0#state{dport = Next(Port)},
ok = probe(State),

Now = erlang:now(),

receive
% Response from destination
{icmp, Socket, Daddr, Data} ->
trace(
State#state{ttl = 0},
[{Daddr, timer:now_diff(erlang:now(), Now), {icmp, Data}}|Acc]
);

% Response from intermediate host
{icmp, Socket, Saddr, Data} ->
trace(
State#state{ttl = TTL+1},
[{Saddr, timer:now_diff(erlang:now(), Now), {icmp, Data}}|Acc]
);

% Response from protocol handler
{tracert, Saddr, Data} ->
trace(
State#state{ttl = 0},
[{Saddr, timer:now_diff(erlang:now(), Now), Data}|Acc]
)
after
Timeout ->
trace(
State#state{ttl = TTL+1},
[timeout|Acc]
)
end.


%%
%% Generates a probe packet
%%
probe(#state{
packet = Fun,
ws = Socket,
saddr = {SA1,SA2,SA3,SA4},
sport = Sport,
daddr = {DA1,DA2,DA3,DA4},
dport = Dport,
ttl = TTL
}) ->
Sockaddr = <<
?PF_INET:16/native, % Address family
Dport:16, % Destination Port
DA1,DA2,DA3,DA4, % IPv4 address
0:64
>>,
Packet = Fun({{SA1,SA2,SA3,SA4}, Sport}, {{DA1,DA2,DA3,DA4}, Dport}, TTL),
procket:sendto(Socket, Packet, 0, Sockaddr).


%%-------------------------------------------------------------------------
%%% Internal Functions
%%-------------------------------------------------------------------------
proplist_to_record(Options) ->
Default = #state{},

Protocol = proplists:get_value(protocol, Options, Default#state.protocol),
Packet = proplists:get_value(packet, Options, protocol(Protocol)),
Handler = proplists:get_value(handler, Options, Default#state.handler),

Initial_ttl = proplists:get_value(ttl, Options, Default#state.ttl),
Max_hops = proplists:get_value(max_hops, Options, Default#state.max_hops),
Timeout = proplists:get_value(timeout, Options, Default#state.timeout),

Saddr = proplists:get_value(saddr, Options, Default#state.saddr),
Sport = proplists:get_value(sport, Options, crypto:rand_uniform(1,16#FFFF)),
Dport = proplists:get_value(dport, Options, dport(Protocol)),

Next_port = proplists:get_value(next_port, Options, next_port(Protocol)),

#state{
protocol = Protocol,
packet = Packet,
handler = Handler,
ttl = Initial_ttl,
max_hops = Max_hops,
timeout = Timeout,
saddr = Saddr,
sport = Sport,

dport = Dport,
next_port = Next_port
}.


icmp_to_proplist(ICMP) when is_binary(ICMP) ->
{#icmp{type = Type,
code = Code}, _Payload} = pkt:icmp(ICMP),
{icmp, gen_icmp:code({Type, Code})}.


%%
%% Construct the protocol headers for the probe
%%

% Default UDP packet
protocol(udp) ->
fun({Saddr, _Sport}, {Daddr, Dport}, TTL) ->
list_to_binary([
pkt:ipv4(#ipv4{
saddr = Saddr,
daddr = Daddr,
p = ?IPPROTO_UDP,
ttl = TTL
}),
pkt:udp(#udp{dport = Dport})
])
end;
% Default ICMP echo packet
protocol(icmp) ->
fun({Saddr, _Sport}, {Daddr, _Dport}, TTL) ->
list_to_binary([
pkt:ipv4(#ipv4{
saddr = Saddr,
daddr = Daddr,
p = ?IPPROTO_ICMP,
ttl = TTL
}),
gen_icmp:packet([], <<(list_to_binary(lists:seq($\s, $W)))/binary>>)
])
end.


%%
%% Calculate the port for different protocol types
%%
dport(udp) -> 1 bsl 15 + 666;
dport(icmp) -> 0.

next_port(udp) ->
fun(N) -> N+1 end;
next_port(_) ->
fun(N) -> N end.

0 comments on commit 87c051a

Please sign in to comment.