Perl tracert

#!/usr/bin/perl
use strict;
$|=1;

# $Id: traceiface.pl,v 1.1 2007/02/04 22:58:04 jtk Exp $

# traceiface - traceroute with extension to discover router egress hop address

# TODO: IPv6 support

use Net::Traceroute::PurePerl;
use NetAddr::IP;
use Getopt::Std;
use Socket;

use constant PROGRAM_NAME => 'traceiface';

# gather parameters and validate them
my %opts = ();
getopts('d:f:i:m:M:np:P:q:s:w:', \%opts);

# expect at least a host argument, optional packet_size argument
usage() if ($#ARGV+1 < 1 || $#ARGV+1 > 3);
my $host = $ARGV[0];

# if it looks like a domain name, resolve it
my $domain_name;
if ($host !~ /^\d{1,3}(?:\.\d{1,3}){3}4/) {
    $domain_name = $host;
    my $packed_address = gethostbyname($domain_name) ||
        die PROGRAM_NAME, ": unknown host $host\n";
    # WARNING: using the first address we get
    $host = inet_ntoa($packed_address);
} 

my $packet_size;
$packet_size = $ARGV[1] if $#ARGV+1 == 2;
if ($packet_size) {
    usage() if ($packet_size < 0 || $packet_size > 65535);
}

my $debuglvl = $opts{d} || 0;
usage() if ($debuglvl < 0 || $debuglvl > 9);

my $first_ttl = $opts{f} || 1;
usage() if ($first_ttl < 0 || $first_ttl > 255);

my $interface = $opts{i} || undef;

my $max_ttl = $opts{m} || 32;
if ($max_ttl) { usage() if $max_ttl <= $first_ttl; }

# TODO: support IPv6 masks?
my $mask = $opts{M} || 24;
usage() if ($mask < 0 || $mask > 32);

my $name_resolution = 1 if $opts{n} || undef;

# PurePerl limits port selection to 1 to 65280 inclusive (no wrap)
my $base_port = $opts{p} || 33434;
usage() if ($base_port < 1 || $base_port > 65280);

# limited to what PurePerl module supports
# WARNING: PurePerl.pm has insecure dependency with UDP, this won't work
# TODO: fix PurePerl so UDP will work
use constant VALID_PROTOCOLS => 'icmp|udp';
my $protocol = lc($opts{P}) || 'icmp';
usage() if VALID_PROTOCOLS !~ /$protocol/;

my $nqueries = $opts{q} || 1;
usage() if ($nqueries < 1 || $nqueries > 65535);

#TODO: src_addr integrity check necessary?
my $src_addr = $opts{s} || undef;

my $wait_time = $opts{w} || 5;
usage() if ($wait_time < 1 || $wait_time > 65535);

# initialize traceroute object (host, first_hop and max_ttl set later)
my $t = new Net::Traceroute::PurePerl(
     debug => $debuglvl,
     device => $interface,
     base_port => $base_port,
     protocol => $protocol,
     queries => $nqueries,
     source_address => $src_addr,
     query_timeout => $wait_time,
     concurrent_hops => 1,
     packetlen => $packet_size,
);

# TODO: print summary line of trace to be performed

# main routine.  process each forward and backward hop
my $complete = 0;
for(my $hop = $first_ttl; $hop < $max_ttl; $hop++) {
    my $result;

    # the forward facing (probe ingress) interface
    $result = forward_trace($hop);
    print_hop($hop, 'forward');
    $complete = 1 if $result;

    # no backward facing interface on the first hop
    next if $hop == 1;

    # the backward facing (probe egress) interface
    $result = backward_trace($hop);
    if ($result) {
        print_hop($hop-1, 'backward');
    } else {
        printf "%s <  no backward hop found\n", $hop;
    }
    last if $complete;
}

#
# we have to post-process each hop so we set first_ttl and max_ttl to
# the current hop we receive from main.  this routine tries to get the
# forward ingress interface result for the hop we get from main.  we
# use the results later (available in the global var $t) in the
# backward_trace routine.  we also access the print_hop routine from
# here to display our results before returning our result to main.
#
sub forward_trace {
    my $hop = shift;

    # update per hop specific options
    $t->first_hop($hop);  # current hop
    $t->max_ttl($hop);    # current hop
    $t->host($host);      # user specified destination

    my $result = $t->traceroute();
    return $result;
}

#
# here is the novel addition to traceroute.  we attempt to find a response
# from a route hop one less than the last forward ingress response received.
# we do this by brute force, but somewhat intelligently guessing the backward
# facing neighbor address of the forward hop.  We reduce the TTL to one so
# that we know we got the backward facing router if we get an actual complete
# response as opposed to a TTL.  technically this could be another interface
# on the backward facing router, but we assume the first one we find in
# spreading range is the backward hop.
#
sub backward_trace {
    my $hop = shift;

    # TODO: how to examine all hosts that respond
    #       will hop_query_host($hop) return them all?
    #
    # return addr of the first host that responded
    my $forward_host = $t->hop_query_host($hop, 0) || return 0;

    # $mask is a global var
    my $backward_host = new NetAddr::IP "$forward_host/$mask";

    # TODO: verify this works and for /31's and /32's too
    # we loop out from addr as far as mask allows
    # we compute spreading range based on addresses in netblock
    # we add one because NetAddr::IP doesn't cont network/broacast addr
    # and we need to fan spread at least one for /31's and /32's
    my $range = round($backward_host->num() / 2) + 1;

    # try to find the neighbor address of the forward hop
    for (my $counter = 1; $counter < $range; $counter++) {
        my $host_to_test;

        # update traceroute object params
        $t->first_hop($hop-1);
        $t->max_ttl($hop-1);

        # first we decrement the address
        $host_to_test = $backward_host - $counter;
        $t->host($host_to_test->addr());
        my $result = $t->traceroute();
        return $result if $result;

        # now try incrementing the address 
        $host_to_test = $backward_host + $counter;
        $t->host($host_to_test->addr());
        $result = $t->traceroute();
        return $result if $result;
    }
    # no neighbor found
    return 0;
}

# print a line of output
#
sub print_hop {
    my $hop = shift;
    my $direction = shift;

    if ($direction eq 'forward') {
        printf '%d >  ', $hop;
    } else {
        # add one to align forward and backward output
        printf '%d <  ', $hop+1;
    }

    if (not $t->hop_queries($hop)) {
        print "error: no responses\n";
        return;
    }

    # loop through each of $nqueries
    for (my $query = 1; $query <= $t->hop_queries($hop); $query++) {
        my $host = $t->hop_query_host($hop, $query) || undef;

        # if we didn't get any response, move to the next  query
        if (not defined($host)) {
            print '* ';
            next;
        }
 
        # resolve names if applicable
        my $name;
        if ($name_resolution) {
            #TODO: create local cache?
            $name = gethostbyaddr(inet_aton($host), AF_INET) || undef;
        }
        if (defined $name) {
            printf '%-s  (%s)  ', $name, $host;
        } else {
            printf '%-s  ', $host;
        }

        my $time = $t->hop_query_time($hop, $query);
        if (defined $time && $time > 0) {
            printf '%7s ms ', $time;
        } else {
            print '* ';
        }
    }
    print "\n";
}

#
# from old Perl FAQ 4.13
sub round {
    my $number = shift;
    return int($number + .5 * ($number <=> 0));
}

#
# invalid invocation or help requested
#
sub usage {
    my $usage =
"Usage: traceiface [-n] [-d debuglvl] [-f first_ttl] [-i iface]
                    [-m max_ttl] [-P proto] [-p base_port] [-q nqueries]
                    [-s src_addr] [-w waittime] host [packetsize]\n";

   die $usage;
}

 

posted @ 2013-11-21 17:10  墨迹哥's  阅读(271)  评论(0编辑  收藏  举报