XiCoN-FJS-
c6e1ca9c10
* FIX: Maxmind now allows redistributing GeoLite2-ASN database if requirements are fulfilled (#3) * ADD: Maxmind's TOS added as start-up message * ADD: Added some useful log output (and colors \o/) * ADD: INSTALL notes now giving hint about "setuid-bit" for traceroute command
347 lines
11 KiB
Perl
347 lines
11 KiB
Perl
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Net::Traceroute;
|
|
use Net::DNS::Resolver;
|
|
use Net::Whois::IP;
|
|
use Dancer2;
|
|
use Socket;
|
|
use File::Fetch;
|
|
use HTTP::Tiny;
|
|
use HTTP::Request;
|
|
use Data::Validate::Domain qw(is_domain);
|
|
use Data::Validate::IP qw(is_public_ipv4 is_public_ipv6);
|
|
use MaxMind::DB::Reader;
|
|
use Archive::Tar;
|
|
use Term::ANSIColor;
|
|
use Data::Dumper;
|
|
|
|
|
|
|
|
### README #########################################################################################################################################
|
|
# This is the client for the XTR (XiCoN Trace Route). It opens a port and listening to HTTP requests. It answers with a traceroute
|
|
# in a json hash. The hash contains for each hop the IP, the PTR, the request time and the AS (Autonomous System) including the AS name.
|
|
#
|
|
# This client also tries to connect to a "master server" which helps keeping track of all the traceroute clients and their availability.
|
|
#
|
|
# This software uses the MaxMind GeoLite2 database (https://www.maxmind.com) to determine the AS for each hop. To lookup the AS information the script
|
|
# uses the MaxMind perl module which uses the MaxMind DB library.
|
|
#
|
|
#
|
|
# This product includes GeoLite2 ASN data created by MaxMind, available from <a href="http://www.maxmind.com">http://www.maxmind.com</a>.
|
|
######################################################################################################################################################
|
|
|
|
|
|
### CHANGELOG ######################################################################################################################################
|
|
# v0.8 (2020-04-14)
|
|
# * FIX: Maxmind now allows redistributing GeoLite2-ASN database if requirements are fulfilled (https://git.xicon.eu/xicon/xtr/issues/3)
|
|
# * ADD: Maxmind's TOS added as start-up message
|
|
# * ADD: Added some useful log output (and colors \o/)
|
|
# * ADD: INSTALL notes now giving hint about "setuid-bit" for traceroute command
|
|
#
|
|
# v0.7 (2019-10-18)
|
|
# * ADD: IPv6 requests are now possible
|
|
# * ADD: Domain request are now possible (will be resolved to ip address)
|
|
# * DEL: removed ip2asn database as source for AS lookup (no more SQLite for client)
|
|
# * ADD: added MaxMind's GeoLite2-ASN database for AS lookup
|
|
# * FIX: unknown domains or invalid IPs will be reported as a valid result including error message as object
|
|
# * FIX: cleaned up source code by moving more code into funtions
|
|
#
|
|
# v0.6 (2019-08-22)
|
|
# * FIX: replaced bug-driven LWP::* with HTTP::Tiny
|
|
# * FIX: client_protocol_version moved from "3" to "4"
|
|
#
|
|
# v0.5 (2019-08-18)
|
|
# * FIX: dropped "timeout" option of Net::Traceroute (details -> https://rt.cpan.org/Ticket/Display.html?id=107066)
|
|
# * FIX: altered dependency on which an output will be send ("$tr->found" -> "$tr->hops > 1")
|
|
#
|
|
# v0.4 (2019-08-13)
|
|
# * ADD: added better documentation
|
|
# * ADD: updated API to v3
|
|
# * FIX: limied inputs to ipv4 addresses only (no more hostnames for now in requests)
|
|
######################################################################################################################################################
|
|
|
|
|
|
### INSTALL ########################################################################################################################################
|
|
# This is a standalone software which usually runs in the "foreground". Starting it in a screen session or via init.d/systemd/rc.d is
|
|
# highly recommended. For testing, starting this script in a screen session is also fine.
|
|
#
|
|
# To run properly as a non-root user you need to set the setuid-bit on the traceroute binary of your system:
|
|
# > chmod 4755 /usr/bin/traceroute
|
|
# Depending on your system /usr/bin/traceroute could just be a soft link, but you have to set the setuid to the real binary!
|
|
#
|
|
#
|
|
# Either install all listed modules with "cpan -i <module>" or use your system's package manager (apt, yum, yast).
|
|
#
|
|
# On Debian just install these packages:
|
|
# wget
|
|
# screen
|
|
# libnet-whois-ip-perl
|
|
# libdancer2-perl
|
|
# libnet-dns-perl
|
|
# libnet-traceroute-perl
|
|
# libdata-validate-ip-perl
|
|
# libdata-validate-domain-perl
|
|
# build-essential
|
|
# cpanminus
|
|
# libmaxminddb0
|
|
# libmaxminddb-dev
|
|
#
|
|
# After that, execute this command to install maxmind database reader module:
|
|
# cpanm -q -n MaxMind::DB::Reader::XS
|
|
#
|
|
######################################################################################################################################################
|
|
|
|
|
|
### VARS ###########################################################################################################################################
|
|
my $VERSION = "0.8";
|
|
my $dbfile = 'GeoLite2-ASN.mmdb';
|
|
my $db_source = 'http://xtr.xicon.eu/GeoLite2-ASN.tar.gz';
|
|
my $master_server = 'xtr-master.xicon.eu';
|
|
my $get_my_ip_service = 'http://ipv4.xicon.eu/'; # some service, which returns just the ip of the requesting host (in this case, us.)
|
|
my $client_protocol_version = 4;
|
|
my $my_ip = get_my_ipv4($get_my_ip_service);
|
|
my $my_ext_ip = ""; # set different ip, if access differs from default public ip
|
|
my $my_port = 12111;
|
|
my $my_ext_port = 0; # set different port, if public access port differs from app port
|
|
######################################################################################################################################################
|
|
|
|
|
|
######################################################################################################################################################
|
|
### NOTHING TO CHANGE BELOW HERE ###################################################################################################################
|
|
######################################################################################################################################################
|
|
|
|
### Required mentioning of Maxmind's TOS (https://git.xicon.eu/xicon/xtr/issues/3)
|
|
print_maxmind_tos();
|
|
|
|
### set vars
|
|
set port => $my_port;
|
|
if($my_ext_ip eq "") { $my_ext_ip = $my_ip; }
|
|
if($my_ext_ip eq "0.0.0.0") { print colored(['red'], "[ERR] Couldn't determine my own IP. Exiting...\n"); exit 1; }
|
|
if($my_ext_port eq 0 || $my_ext_port eq "") { $my_ext_port = $my_port; }
|
|
|
|
print colored(['green'], "[INFO] ") . color('reset');
|
|
print "External IP: ".$my_ext_ip."\n";
|
|
print colored(['green'], "[INFO] ") . color('reset');
|
|
print "External Port: ".$my_ext_port."\n";
|
|
|
|
|
|
get_maxmind_db($db_source,$dbfile);
|
|
|
|
### connect to database
|
|
my $maxmind_reader = MaxMind::DB::Reader->new( file => $dbfile );
|
|
|
|
### hook for HTTP "security"
|
|
hook 'before' => sub {
|
|
header 'Access-Control-Allow-Origin' => '*';
|
|
};
|
|
|
|
### main get route
|
|
get '/v3/client/request/:host' => sub {
|
|
my $host = route_parameters->get('host') || 8.8.8.8;
|
|
|
|
if(!is_public_ipv4($host) and !is_public_ipv6($host) and !is_domain($host))
|
|
{
|
|
#status('bad_request');
|
|
my @array;
|
|
push(@array, ["NO","ANSWER","FOUND",":(",""]);
|
|
return encode_json \@array
|
|
}
|
|
|
|
my $trace = traceit($maxmind_reader,$host);
|
|
return encode_json $trace;
|
|
};
|
|
|
|
### return client protocol verison
|
|
get '/v3/client/info/version' => sub {
|
|
return $client_protocol_version;
|
|
};
|
|
|
|
### give the master server the info, that we are available
|
|
if(send_server_status($master_server,$my_ext_ip,$my_ext_port,"1"))
|
|
{
|
|
print colored(['green'], "[INFO] ") . color('reset');
|
|
print "Sending the master server ".$master_server." the info, that we are online.\n";
|
|
}
|
|
else
|
|
{
|
|
print colored(['red'], "[ERR] ") . color('reset');
|
|
print "Failed to send our status to the master server ".$master_server." - nobody knows we are online :(\n";
|
|
}
|
|
|
|
### catch "INT" signal to send the status to the master server
|
|
$SIG{'INT'} = sub { send_server_status($master_server,$my_ext_ip,$my_ext_port,"0"); exit; };
|
|
|
|
### start the main loop
|
|
start;
|
|
|
|
### if you reach this, you're done
|
|
exit;
|
|
|
|
|
|
|
|
sub send_server_status
|
|
{
|
|
my ($master_server,$hostname,$port,$status) = @_;
|
|
my $url = "http://".$master_server."/v3/server/add/".$hostname."/".$port."/".$status;
|
|
my $httptiny = HTTP::Tiny->new("timeout" => 10);
|
|
my $response = $httptiny->request("PUT",$url);
|
|
return $response->{'success'};
|
|
}
|
|
|
|
sub get_my_ipv4
|
|
{
|
|
my ($ip_service) = @_;
|
|
my $httptiny = HTTP::Tiny->new("timeout" => 10);
|
|
my $response = $httptiny->request("GET",$ip_service);
|
|
if(is_public_ipv4($response->{'content'}))
|
|
{
|
|
return $response->{'content'};
|
|
}
|
|
else
|
|
{
|
|
# no valid ip returned from ip service
|
|
return "0.0.0.0";
|
|
}
|
|
}
|
|
|
|
sub traceit
|
|
{
|
|
my ($maxmind_reader,$host) = @_;
|
|
my @array = ();
|
|
|
|
if(is_domain($host))
|
|
{
|
|
if(resolve_dns($host,"4") eq "" and resolve_dns($host,"6") eq "")
|
|
{
|
|
push(@array, ["NO","ANSWER","FOUND",":(",""]);
|
|
return \@array;
|
|
}
|
|
}
|
|
|
|
my $tr = Net::Traceroute->new(host => $host, use_icmp => 1);
|
|
|
|
|
|
my $hops = $tr->hops;
|
|
if($hops > 1)
|
|
{
|
|
foreach my $hop (@{$tr->{'hops'}})
|
|
{
|
|
my $sum = sprintf("%.1f",($hop->[0]->[2] + $hop->[1]->[2] + $hop->[2]->[2])/3);
|
|
my $answer = resolve_dns($hop->[0]->[1],"PTR");
|
|
|
|
### get AS to IP
|
|
my $as = 0;
|
|
my $as_name = " ";
|
|
|
|
if($hop->[0]->[1] ne "255.255.255.255")
|
|
{
|
|
my $as_data = find_as($maxmind_reader, $hop->[0]->[1]);
|
|
if(defined($as_data->{'autonomous_system_number'})) { $as = $as_data->{'autonomous_system_number'}; }
|
|
if(defined($as_data->{'autonomous_system_organization'})) { $as_name = $as_data->{'autonomous_system_organization'}; }
|
|
}
|
|
else
|
|
{
|
|
$hop->[0]->[1] = "???";
|
|
}
|
|
push(@array, [$hop->[0]->[1],$answer,$sum,$as,$as_name]);
|
|
}
|
|
return \@array;
|
|
}
|
|
else
|
|
{
|
|
push(@array, ["NO","ANSWER","FOUND",":(",""]);
|
|
return \@array;
|
|
}
|
|
}
|
|
|
|
sub resolve_dns
|
|
{
|
|
my ($host,$type) = @_;
|
|
if($type eq 4) { $type = "A"; }
|
|
elsif($type eq 6) { $type = "AAAA"; }
|
|
elsif($type eq "PTR") { $type = "PTR"; }
|
|
else { return ""; }
|
|
|
|
my $res = Net::DNS::Resolver->new;
|
|
my $query = $res->query($host, $type);
|
|
my $answer = "";
|
|
if($query)
|
|
{
|
|
foreach my $rr ($query->answer)
|
|
{
|
|
if($rr->type eq "A") { $answer = $rr->address; }
|
|
if($rr->type eq "AAAA") { $answer = $rr->address_short; }
|
|
if($rr->type eq "PTR") { $answer = $rr->ptrdname; }
|
|
}
|
|
}
|
|
|
|
return $answer;
|
|
}
|
|
|
|
sub resolve_dns_reverse
|
|
{
|
|
my ($host) = @_;
|
|
my $res = Net::DNS::Resolver->new;
|
|
my $query = $res->query($host, 'PTR');
|
|
my $answer = " ";
|
|
if($query)
|
|
{
|
|
foreach my $rr ($query->answer)
|
|
{
|
|
if($rr->type eq "PTR")
|
|
{
|
|
$answer = $rr->ptrdname;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $answer;
|
|
}
|
|
|
|
sub find_as
|
|
{
|
|
my ($maxmind_reader, $ip) = @_;
|
|
my $record = $maxmind_reader->record_for_address($ip);
|
|
return $record;
|
|
}
|
|
|
|
sub get_maxmind_db
|
|
{
|
|
my ($db_source,$dbfile) = @_;
|
|
|
|
print colored(['green'], "[INFO] ") . color('reset');
|
|
print "Fetching latest IP2ASN database: ".$db_source."\n";
|
|
|
|
my $ff = File::Fetch->new(uri => $db_source);
|
|
my $temp_file = $ff->fetch( to => '/tmp' );
|
|
|
|
my $tar = Archive::Tar->new;
|
|
$tar->read($temp_file);
|
|
|
|
my @files = $tar->get_files();
|
|
foreach my $file (@files)
|
|
{
|
|
if($file->{'name'}=~/${dbfile}$/)
|
|
{
|
|
open(FILE,">",$dbfile) or die colored(['red'], "[ERR] Can't open file for writing: $!");
|
|
print FILE $file->{'data'};
|
|
close(FILE);
|
|
}
|
|
}
|
|
$tar->clear;
|
|
unlink($temp_file);
|
|
}
|
|
|
|
sub print_maxmind_tos
|
|
{
|
|
print qq{ --------------------------------------------------------------------------------------------------------------------------------------- };
|
|
print "\n";
|
|
print qq{ This product includes GeoLite2 ASN data created by MaxMind, available from <a href="http://www.maxmind.com">http://www.maxmind.com</a>. };
|
|
print "\n";
|
|
print qq{ --------------------------------------------------------------------------------------------------------------------------------------- };
|
|
print "\n";
|
|
print "\n";
|
|
}
|