feat: add full Zonemaster stack with Docker and Spanish UI
- Clone all 5 Zonemaster component repos (LDNS, Engine, CLI, Backend, GUI) - Dockerfile.backend: 8-stage multi-stage build LDNS→Engine→CLI→Backend - Dockerfile.gui: Astro static build served via nginx - docker-compose.yml: backend (internal) + frontend (port 5353) - nginx.conf: root redirects to /es/, /api/ proxied to backend - zonemaster-gui/config.ts: defaultLanguage set to 'es' (Spanish) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
238
zonemaster-engine/lib/Zonemaster/Engine/ASNLookup.pm
Normal file
238
zonemaster-engine/lib/Zonemaster/Engine/ASNLookup.pm
Normal file
@@ -0,0 +1,238 @@
|
||||
package Zonemaster::Engine::ASNLookup;
|
||||
|
||||
use v5.16.0;
|
||||
use warnings;
|
||||
|
||||
use version; our $VERSION = version->declare( "v1.0.11" );
|
||||
|
||||
use Zonemaster::Engine;
|
||||
use Zonemaster::Engine::Util qw( name );
|
||||
use Zonemaster::Engine::Nameserver;
|
||||
use Zonemaster::Engine::Profile;
|
||||
|
||||
use IO::Socket;
|
||||
use IO::Socket::INET;
|
||||
use Net::IP::XS;
|
||||
use Scalar::Util qw( looks_like_number );
|
||||
|
||||
our @db_sources;
|
||||
our $db_style;
|
||||
|
||||
sub get_with_prefix {
|
||||
my ( $class, $ip ) = @_;
|
||||
|
||||
if ( not @db_sources ) {
|
||||
$db_style = Zonemaster::Engine::Profile->effective->get( q{asn_db.style} );
|
||||
my %db_sources = %{ Zonemaster::Engine::Profile->effective->get( q{asn_db.sources} ) };
|
||||
@db_sources = map { name( $_ ) } @{ $db_sources{ $db_style } };
|
||||
}
|
||||
|
||||
if ( not ref( $ip ) or not $ip->isa( 'Net::IP::XS' ) ) {
|
||||
$ip = Net::IP::XS->new( $ip );
|
||||
}
|
||||
|
||||
if ( not @db_sources ) {
|
||||
die "ASN database sources undefined";
|
||||
}
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code );
|
||||
|
||||
if ( $db_style eq q{cymru} ) {
|
||||
( $asnref, $prefix, $raw, $ret_code ) = _cymru_asn_lookup($ip);
|
||||
}
|
||||
elsif ( $db_style eq q{ripe} ) {
|
||||
( $asnref, $prefix, $raw, $ret_code ) = _ripe_asn_lookup($ip);
|
||||
}
|
||||
else {
|
||||
if ( not $db_style ) {
|
||||
die "ASN database style undefined";
|
||||
}
|
||||
else {
|
||||
die "ASN database style value '$db_style' is illegal";
|
||||
}
|
||||
}
|
||||
|
||||
map { looks_like_number( $_ ) || die "ASN lookup value isn't numeric: '$_'" } @$asnref;
|
||||
|
||||
return ( $asnref, $prefix, $raw, $ret_code );
|
||||
|
||||
} ## end sub get_with_prefix
|
||||
|
||||
sub _cymru_asn_lookup {
|
||||
my $ip = shift;
|
||||
my @asns = ();
|
||||
|
||||
my $db_source_nb = 0;
|
||||
foreach my $db_source ( @db_sources ) {
|
||||
Zonemaster::Engine->logger->add( ASN_LOOKUP_SOURCE => { name => $db_source } );
|
||||
my $reverse = $ip->reverse_ip;
|
||||
my $domain = $db_source->string;
|
||||
my $pair = {
|
||||
'in-addr.arpa.' => "origin.$domain",
|
||||
'ip6.arpa.' => "origin6.$domain",
|
||||
};
|
||||
$db_source_nb++;
|
||||
|
||||
foreach my $root ( keys %{$pair} ) {
|
||||
if ( $reverse =~ s/$root/$pair->{$root}/ix ) {
|
||||
my $p = Zonemaster::Engine->recurse( $reverse, 'TXT' );
|
||||
|
||||
if ( $p ) {
|
||||
if ( $p->rcode eq q{NXDOMAIN} ) {
|
||||
if ( $p->get_records( 'SOA', 'authority' ) and scalar $p->get_records( 'SOA', 'authority' ) == 1 and ($p->get_records( 'SOA', 'authority' ))[0]->owner eq name( $db_source ) ) {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
elsif ( $p->rcode eq q{NOERROR} ) {
|
||||
if ( $p->answer ) {
|
||||
my @rr = $p->get_records( 'TXT', 'answer' );
|
||||
|
||||
if ( @rr ) {
|
||||
my $max_length = 0;
|
||||
my @fields;
|
||||
my $str;
|
||||
|
||||
foreach my $rr ( @rr ) {
|
||||
my $_str = $rr->txtdata;
|
||||
my @_fields = split( /[ ][|][ ]?/x, $_str );
|
||||
|
||||
next if scalar @_fields <= 1;
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE} unless Net::IP::XS->new( $_fields[1] )->overlaps( $ip );
|
||||
|
||||
my @_asns = split( /\s+/x, $_fields[0] );
|
||||
my $_prefix_length = ($_fields[1] =~ m!^.*[/](.*)!x)[0];
|
||||
if ( $_prefix_length > $max_length ) {
|
||||
$str = $_str;
|
||||
@asns = @_asns;
|
||||
@fields = @_fields;
|
||||
$max_length = $_prefix_length;
|
||||
}
|
||||
}
|
||||
|
||||
if ( @fields ) {
|
||||
if ( Net::IP::XS->new( $fields[1] )->overlaps( $ip ) ) {
|
||||
return \@asns, Net::IP::XS->new( $fields[1] ), $str, q{AS_FOUND}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
}
|
||||
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
|
||||
last;
|
||||
}
|
||||
}
|
||||
} ## end foreach my $db_source ( @db_sources )
|
||||
return;
|
||||
}
|
||||
|
||||
sub _ripe_asn_lookup {
|
||||
my $ip = shift;
|
||||
my @asns = ();
|
||||
|
||||
my $db_source_nb = 0;
|
||||
foreach my $db_source ( @db_sources ) {
|
||||
$db_source_nb++;
|
||||
my $socket = IO::Socket::INET->new( PeerAddr => $db_source->string,
|
||||
PeerPort => q{43},
|
||||
Proto => q{tcp} );
|
||||
unless ( $socket ) {
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
};
|
||||
|
||||
printf $socket "-F -M %s\n", $ip->short();
|
||||
|
||||
my $data;
|
||||
my $str;
|
||||
my $has_answer = 0;
|
||||
while ( defined ($data = <$socket>) ) {
|
||||
$has_answer = 1;
|
||||
chop $data;
|
||||
if ( $data !~ /^%/x and $data !~ /^\s*$/x ) {
|
||||
$str = $data;
|
||||
last;
|
||||
}
|
||||
}
|
||||
$socket->close();
|
||||
if ( not $has_answer ) {
|
||||
if ( $db_source_nb == scalar @db_sources ) {
|
||||
return \@asns, undef, q{}, q{ERROR_ASN_DATABASE};
|
||||
}
|
||||
else {
|
||||
next;
|
||||
}
|
||||
}
|
||||
elsif ( $str ) {
|
||||
my @fields = split( /\s+/x, $str );
|
||||
my @asns = split( '/', $fields[0] );
|
||||
return \@asns, Net::IP::XS->new( $fields[1] ), $str, q{AS_FOUND};
|
||||
}
|
||||
else {
|
||||
return \@asns, undef, q{}, q{EMPTY_ASN_SET};
|
||||
}
|
||||
} ## end foreach my $db_source ( @
|
||||
return;
|
||||
}
|
||||
|
||||
sub get {
|
||||
my ( $class, $ip ) = @_;
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = $class->get_with_prefix( $ip );
|
||||
|
||||
if ( $asnref ) {
|
||||
return @{$asnref};
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Zonemaster::Engine::ASNLookup - do lookups of ASNs for IP addresses
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my ( $asnref, $prefix, $raw, $ret_code ) = Zonemaster::Engine::ASNLookup->get_with_prefix( '8.8.4.4' );
|
||||
my $asnref = Zonemaster::Engine::ASNLookup->get( '192.168.0.1' );
|
||||
|
||||
=head1 FUNCTION
|
||||
|
||||
=over
|
||||
|
||||
=item get($addr)
|
||||
|
||||
As L<get_with_prefix()>, except it returns only the list of AS numbers
|
||||
for the address, if any.
|
||||
|
||||
=item get_with_prefix($addr)
|
||||
|
||||
Takes a string (or a L<Net::IP::XS> object) with a single IP address, and
|
||||
does a lookup in either: a) Cymru-style DNS zone or b) RIPE whois server,
|
||||
depending on L<Zonemaster::Engine::Profile> setting "asn_db{style}".
|
||||
|
||||
Returns a list of a reference to a list of AS numbers, a Net::IP::XS object
|
||||
of the covering prefix for that AS, a string of the raw query, and a string
|
||||
of the return code for that query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user