Subject: | [patch] Support for Nmap's traceroute feature |
Nmap provides a traceroute feature when started with --traceroute (or
-A), but so far Nmap::Parser was not including it in its results.
This patch provides support for it through additional Nmap::Parser::Host
methods and a new Nmap::Parser::Host::TraceHop class. The documentation
and tests have been updated accordingly.
Thank you much for this useful module!
Regards,
--
Thomas Equeter
Straton IT
Subject: | nmap-parser-traceroute.patch |
diff -ur Nmap-Parser-1.19/Parser.pm Nmap-Parser-1.20/Parser.pm
--- Nmap-Parser-1.19/Parser.pm 2008-11-07 08:57:44.000000000 +0100
+++ Nmap-Parser-1.20/Parser.pm 2009-06-18 13:17:51.000000000 +0200
@@ -282,7 +282,8 @@
$D{$$}{HOSTS}{$id}{tcptssequence} = __host_tcptssequence_tag_hdlr($tag);
$D{$$}{HOSTS}{$id}{distance} =
__host_distance_tag_hdlr($tag); #returns simple value
-
+ $D{$$}{HOSTS}{$id}{trace} = __host_trace_tag_hdlr($tag);
+ $D{$$}{HOSTS}{$id}{trace_error} = __host_trace_error_tag_hdlr($tag);
}
#CREATE HOST OBJECT FOR USER
@@ -530,6 +531,54 @@
return $distance->{att}->{value};
}
+sub __host_trace_tag_hdlr {
+ my $tag = shift;
+ my $trace_tag = $tag->first_child('trace');
+ my $trace_hashref = { hops => [], };
+
+ if ( defined $trace_tag ) {
+
+ my $proto = $trace_tag->{att}->{proto};
+ $trace_hashref->{proto} = $proto if defined $proto;
+
+ my $port = $trace_tag->{att}->{port};
+ $trace_hashref->{port} = $port if defined $port;
+
+ for my $hop_tag ( $trace_tag->children('hop') ) {
+
+ # Copy the known hop attributes, they will go in
+ # Nmap::Parser::Host::TraceHop
+ my %hop_data;
+ $hop_data{$_} = $hop_tag->{att}->{$_} for qw( ttl rtt ipaddr host );
+ delete $hop_data{rtt} if $hop_data{rtt} !~ /^[\d.]+$/;
+
+ push @{ $trace_hashref->{hops} }, \%hop_data;
+ }
+
+ }
+
+ return $trace_hashref;
+}
+
+sub __host_trace_error_tag_hdlr {
+ my $tag = shift;
+ my $trace_tag = $tag->first_child('trace');
+
+ if ( defined $trace_tag ) {
+
+ my $error_tag = $trace_tag->first_child('error');
+ if ( defined $error_tag ) {
+
+ # If an error happens, always provide a true value even if
+ # it doesn't contains a useful string
+ my $errorstr = $error_tag->{att}->{errorstr} || 1;
+ return $errorstr;
+ }
+ }
+
+ return;
+}
+
#/*****************************************************************************/
# NMAP::PARSER::SESSION
#/*****************************************************************************/
@@ -625,6 +674,19 @@
sub extraports_count { return $_[0]->{ports}{extraports}{count}; }
sub distance { return $_[0]->{distance}; }
+sub all_trace_hops {
+
+ my $self = shift;
+
+ return unless defined $self->{trace}->{hops};
+ return map { Nmap::Parser::Host::TraceHop->new( $_ ) }
+ @{ $self->{trace}->{hops} };
+}
+
+sub trace_port { return $_[0]->{trace}->{port} }
+sub trace_proto { return $_[0]->{trace}->{proto} }
+sub trace_error { return $_[0]->{trace_error} }
+
sub _del_port {
my $self = shift;
my $proto = pop; #portid might be empty, so this goes first
@@ -844,6 +906,39 @@
return $self->{ $type . '_' . $param }[$index];
}
+#/*****************************************************************************/
+# NMAP::PARSER::HOST::TRACEHOP
+#/*****************************************************************************/
+
+package Nmap::Parser::Host::TraceHop;
+use vars qw($AUTOLOAD);
+
+sub new {
+ my $class = shift;
+ $class = ref($class) || $class;
+ my $self = shift || {};
+ bless( $self, $class );
+ return $self;
+}
+
+sub AUTOLOAD {
+ ( my $param = $AUTOLOAD ) =~ s{.*::}{}xms;
+ return if ( $param eq 'DESTROY' );
+ no strict 'refs';
+ $param = lc($param);
+
+ # Supported accessors:
+ my %subs;
+ @subs{ qw( ttl rtt ipaddr host ) } = 1;
+
+ if ( exists $subs{$param} ) {
+
+ *$AUTOLOAD = sub { $_[0]->{$param} };
+ goto &$AUTOLOAD;
+ }
+ else { die '[Nmap-Parser] method ->' . $param . "() not defined!\n"; }
+}
+
1;
__END__
@@ -1161,6 +1256,30 @@
Return the distance (in hops) of the target machine from the machine that performed the scan.
+=item B<trace_error()>
+
+Returns a true value (usually a meaningful error message) if the traceroute was
+performed but could not reach the destination. In this case C<all_trace_hops()>
+contains only the part of the path that could be determined.
+
+=item B<all_trace_hops()>
+
+Returns an array of Nmap::Parser::Host::TraceHop objects representing the path
+to the target host. This array may be empty if Nmap did not perform the
+traceroute for some reason (same network, for example).
+
+Some hops may be missing if Nmap could not figure out information about them.
+In this case there is a gap between the C<ttl()> values of consecutive returned
+hops. See also C<trace_error()>.
+
+=item B<trace_proto()>
+
+Returns the name of the protocol used to perform the traceroute.
+
+=item B<trace_port()>
+
+Returns the port used to perform the traceroute.
+
=item B<os_sig()>
Returns an Nmap::Parser::Host::OS object that can be used to obtain all the
@@ -1428,6 +1547,35 @@
=back
+=head3 Nmap::Parser::Host::TraceHop
+
+This object represents a router on the IP path towards the destination or the
+destination itself. This is similar to what the C<traceroute> command outputs.
+
+Nmap::Parser::Host::TraceHop objects are obtained through the
+C<all_trace_hops()> and C<trace_hop()> Nmap::Parser::Host methods.
+
+=over 4
+
+=item B<ttl()>
+
+The Time To Live is the network distance of this hop.
+
+=item B<rtt()>
+
+The Round Trip Time is roughly equivalent to the "ping" time towards this hop.
+It is not always available (in which case it will be undef).
+
+=item B<ipaddr()>
+
+The known IP address of this hop.
+
+=item B<host()>
+
+The host name of this hop, if known.
+
+=back
+
=head1 EXAMPLES
I think some of us best learn from examples. These are a couple of examples to help
diff -ur Nmap-Parser-1.19/t/nmap_results.xml Nmap-Parser-1.20/t/nmap_results.xml
--- Nmap-Parser-1.19/t/nmap_results.xml 2008-11-07 08:55:51.000000000 +0100
+++ Nmap-Parser-1.20/t/nmap_results.xml 2009-06-18 13:17:51.000000000 +0200
@@ -139,6 +139,11 @@
<service name="rndc" method="table" conf="3" servicefp="SF-Port21-TCP:V=3.48%D=11/5%Time=3FA9032C%r(NULL,32,"220\x20Oracle\x20Internet\x20File\x20System\x20FTP\x20Server\x20ready\r\n")%r(GenericLines,53,"220\x20Oracle\x20Internet\x20File\x20System\x20FTP\x20Server\x20ready\r\n200\x20Connection\x20closed,\x20good\x20bye\r\n")%r(Help,57,"220\x20Oracle\x20Internet\x20File\x20System\x20FTP\x20Server\x20ready\r\n500\x20HELP:\x20command\x20not\x20understood\.\r\n");"/>
</port>
</ports>
+ <trace port="80" proto="tcp">
+ <hop ttl="1" rtt="1.17" ipaddr="192.168.1.1"/>
+ <hop ttl="2" rtt="26.86" ipaddr="88.181.1.1"/>
+ <error errorstr="Error" /><!-- According to the DTD... -->
+ </trace>
</host>
<host>
<status state="up"/>
@@ -175,6 +180,12 @@
<ipidsequence class="Incremental" values="C4BE,C4C6,C4CC,C4CE,C4D4,C4DC"/>
<tcptssequence class="none returned (unsupported)"/>
<distance value="10"/>
+ <trace port="80" proto="tcp">
+ <hop ttl="1" rtt="--" ipaddr="192.168.1.1"/>
+ <hop ttl="2" rtt="26.86" ipaddr="88.181.1.1"/>
+ <!-- Missing hop3 -->
+ <hop ttl="4" rtt="26.48" ipaddr="1.1.1.1" host="www.straton-it.fr"/>
+ </trace>
</host>
<runstats>
<finished time="1057088900" timestr="Tue Jul 1 14:48:20 2003"/>
diff -ur Nmap-Parser-1.19/t/parser.t Nmap-Parser-1.20/t/parser.t
--- Nmap-Parser-1.19/t/parser.t 2008-11-07 08:55:51.000000000 +0100
+++ Nmap-Parser-1.20/t/parser.t 2009-06-18 13:17:51.000000000 +0200
@@ -5,7 +5,7 @@
use Nmap::Parser;
use File::Spec;
use Cwd;
-use Test::More tests => 153;
+use Test::More tests => 168;
use constant HOST1 => '127.0.0.1';
use constant HOST2 => '127.0.0.2';
@@ -268,6 +268,9 @@
" SEQ(SP=C5%GCD=1%ISR=C7%TI=Z%II=I%TS=8) ECN(R=Y%DF=Y%T=40%W=16D0%O=M5B4NNSNW2%CC=N%Q=) T1(R=Y%DF=Y%T=40%S=O%A=S+%F=AS%RD=0%Q=) T2(R=N) T3(R=Y%DF=Y%T=40%W=16A0%S=O%A=S+%F=AS%O=M5B4ST11NW2%RD=0%Q=) T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=) T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=) U1(R=Y%DF=N%T=40%TOS=C0%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUL=G%RUD=G) IE(R=Y%DFI=N%T=40%TOSI=S%CD=S%SI=S%DLI=S) ";
isa_ok( $os = $host->os_sig(), 'Nmap::Parser::Host::OS', 'os_sig()' );
is( $os->os_fingerprint(), $fingerprint, 'HOST1: os_fingerprint()' );
+
+ #TESTING NON-EXISTENT TRACE FOR HOST1
+ ok( !$host->all_trace_hops(), 'Host1 has no trace information' );
}
sub host_2 {
@@ -337,6 +340,11 @@
isa_ok( $np->del_host(HOST3), 'Nmap::Parser::Host', 'DEL ' . HOST3 );
is( $np->get_host(HOST3), undef, 'Testing deletion of ' . HOST3 );
+ #TESTING TRACE FOR HOST3
+ my $hops_count = $host->all_trace_hops();
+ is( $hops_count, 2, 'Host3 has trace information' );
+ is( $host->trace_error(), 'Error', 'Host3 trace is in error' );
+
}
sub host_4 {
@@ -412,4 +420,21 @@
isa_ok( $np->del_host(HOST4), 'Nmap::Parser::Host', 'DEL ' . HOST4 );
is( $np->get_host(HOST4), undef, 'Testing deletion of ' . HOST4 );
+ #TESTING TRACE FOR HOST4
+ my @hops = $host->all_trace_hops();
+ ok( !$host->trace_error(), 'Host4 trace is not in error' );
+ is( $host->trace_port(), 80, 'Host4 trace port information' );
+ is( $host->trace_proto(), 'tcp', 'Host4 trace proto information' );
+ is( ( scalar @hops ), 3, 'Host4 trace size' );
+
+ is( $hops[0]->ttl(), 1, 'Host4 hop1 TTL' );
+ ok( !$hops[0]->rtt(), 'Host4 hop1 has no RTT' );
+ is( $hops[0]->ipaddr(), '192.168.1.1', 'Host4 hop1 IP address' );
+ ok( !$hops[0]->host(), 'Host4 hop1 has no hostname' );
+
+ is( $hops[2]->ttl(), 4, 'Host4 hop4 TTL' );
+ is( $hops[2]->rtt(), 26.48, 'Host4 hop4 RTT' );
+ is( $hops[2]->ipaddr(), '1.1.1.1', 'Host4 hop4 IP address' );
+ is( $hops[2]->host(), 'www.straton-it.fr', 'Host4 hop4 hostname' );
+
}