ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-Whois-IP/IP.pm
Revision: 1.3
Committed: Sun Dec 1 14:51:23 2002 UTC (21 years, 7 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +464 -90 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 Net::Whois::IP - find whois data for ip addresses
4
5 =head1 SYNOPSIS
6
7 use Net::Whois::IP;
8
9 =head1 DESCRIPTION
10
11 =over 4
12
13 =cut
14
15 package Net::Whois::IP;
16
17 BEGIN {
18 $VERSION = 0.01;
19 @EXPORT_OK = qw();
20 }
21
22 use base Exporter;
23
24 use Carp;
25
26 use Socket;
27
28 use BerkeleyDB;
29
30 use Coro;
31 use Coro::Event;
32 use Coro::Semaphore;
33 use Coro::SemaphoreSet;
34 use Coro::Socket;
35 use Coro::Timer;
36 use Coro::Channel;
37
38 my %server;
39
40 sub new_btree {
41 new BerkeleyDB::Btree
42 -Env => $db_env,
43 -Filename => $_[0],
44 -Flags => DB_CREATE,
45 or die "$_[0]: unable to create/open table";
46 }
47
48 sub ip_range {
49 my $range = $_[0];
50
51 if ($range =~ /^
52 (2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
53 (?:\.
54 (2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
55 (?:\.
56 (2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
57 (?:\.
58 (2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])
59 )?
60 )?
61 )?
62 \/
63 ([0-9]+)
64 $/x) {
65 my $ip = inet_aton sprintf "%d.%d.%d.%d", $1, $2, $3, $4;
66 my $net = 1 << (31 - $5);
67 my $mask = inet_aton 2 ** 32 - $net;
68
69 my $ip1 = $ip & $mask;
70 my $ip2 = $ip1 | inet_aton $net * 2 - 1;
71 return unpack "N2", "$ip1$ip2";
72 } elsif ($range =~ /^\s*([0-9.]+)\s*-\s*([0-9.]+)\s*$/) {
73 unpack "N*", (inet_aton $1) . (inet_aton $2);
74 } else {
75 die "$range: unable to parse ip range";
76 }
77 }
78
79 package Net::Whois::IP::base;
80
81 sub new {
82 my $class = shift;
83 my $self = bless {
84 name => @_,
85 }, $class;
86 $self->{request} ||= new Coro::Channel $self->{request_backlog} || 10;
87 $self->{daemon} = Coro::async {
88 while () {
89 eval { $self->_daemon };
90 warn "restarting daemon for whois server $self->{name}: $@";
91 }
92 };
93 $self->{daemon}->prio(1);
94 $self->{cache} = Net::Whois::IP::new_btree "whois_" . lc $self->{name};
95 $server{$self->{name}} = $self;
96 }
97
98 sub _failure {
99 my ($self, $min) = shift;
100 undef $self->{fh};
101 $self->{retry} = $min if $self->{retry} < $min;
102 Coro::Timer::sleep 1.5 ** $self->{retry} - 2 if $self->{retry};
103 $self->{retry}++ if $self->{retry} < 17;
104 }
105
106 sub _ok {
107 my $self = shift;
108 $self->{retry} = 0;
109 }
110
111 sub _query {
112 my ($self, $query) = @_;
113
114 my $response;
115
116 if (!$self->{cache}->db_get($query, $response)) {
117 $response =~ s/^[^\x00]+\x00//;
118 return $response;
119 }
120
121 my $request = {
122 ready => (new Coro::Semaphore 0),
123 query => $query,
124 };
125
126 $self->{request}->put($request);
127 $request->{ready}->down;
128
129 $request->{response} =~ s/\015//g;
130 $request->{response} =~ s/\012/\n/g if "\012" ne "\n";
131
132 $response = time . "\x00" . $request->{response};
133
134 $self->{cache}->db_put($query, $response);
135
136 $request->{response};
137 }
138
139 sub ip_query {
140 my ($self, $ip) = @_;
141 $self->sanitize_ip_response($self->_query($ip));
142 }
143
144 sub sanitize_ip_response {
145 my ($self, $response) = @_;
146 $response;
147 }
148
149 sub parse_ip_response {
150 my ($self, $response, $tags, $res) = @_;
151 $res->{whois_server} = $self->{name};
152 while ($response =~ /^([^:]+):[ \t]*
153 (
154 .*
155 (?:\n[\t ].*)*
156 )
157 /mgx) {
158 my ($tag, $val) = ($1, $2);
159 if (exists $tags->{$tag}) {
160 push @{$res->{$tags->{$tag}}}, $val if defined $tags->{$tag};
161 } else {
162 use Carp;
163 confess "unknown tag $tag=$val";
164 }
165 }
166 $res;
167 }
168
169 # check wether the given response is a referral to another server
170 # find out which one, and the ip range we were referred
171 sub is_ip_referral {
172 my ($self, $response) = @_;
173 # ($iprange, $server)
174 ();
175 }
176
177 package Net::Whois::IP::whois;
178
179 # plain whois, as used by ripe and apnic
180
181 use base Net::Whois::IP::base;
182
183 sub _daemon {
184 my $self = shift;
185 my $request;
186 while ($request = $self->{request}->get) {
187 my $fh = new Coro::Socket PeerHost => $self->{server}
188 or $self->_failure, redo;
189
190 $fh->print("$request->{query}\012");
191 $fh->sysread($request->{response}, 16384);
192
193 length $request->{response} or $self->_failure, redo;
194 $request->{response} =~ /^% query rate limit exceeded/im and $self->_failure(9), redo;
195
196 $self->_ok;
197 $request->{ready}->up;
198 }
199 }
200
201 sub sanitize_ip_response {
202 my ($self, $response) = @_;
203 $response = "" if $response =~ /^% not assigned/im;
204 $response =~ s/^%.*//mg;
205 $response =~ s/[ \t]+$//mg; # brnic, maybe others
206 $response =~ s/^\n+//;
207 $response =~ s/\n\n.*//s;
208 $self->SUPER::sanitize_ip_response($response);
209 }
210
211 sub parse_ip_response {
212 my ($self, $response, $tags) = @_;
213 $self->SUPER::parse_ip_response($response, $tags || {
214 inetnum => "netblock",
215 netname => "netname",
216 descr => "description",
217 owner => "description",
218 address => "description",
219 phone => "description",
220 country => "country",
221 ownerid => "owner-c",
222 "admin-c" => "admin-c",
223 "tech-c" => "tech-c",
224 "owner-c" => "owner-c",
225 "abuse-c" => "abuse-c",
226 status => "status",
227 notify => "notify",
228 changed => "changed",
229 created => "created",
230 source => "source",
231 remarks => "remarks",
232 "mnt-by" => "mnt-by",
233 "mnt-irt" => "irt-c", # incident response team
234 "rev-srv" => "nameserver",
235 nserver => "nameserver",
236 "mnt-lower" => undef,
237 "mnt-routes"=> undef,
238 responsible => undef,
239 nsstat => undef,
240 nslastaa => undef,
241 "aut-num" => undef,
242 inetrev => undef, # nameserver-dependent
243 "inetnum-up" => undef,
244 });
245 }
246
247 sub is_ip_referral {
248 my ($self, $response) = @_;
249 $response =~ /^inetnum:\s+(.*)/m
250 or return;
251 my $iprange = $1;
252 if ($response =~ /^remarks:\s+These addresses have been further assigned to Brazilian users./m) {
253 return ($iprange, $server{BRNIC});
254 }
255 # ($iprange, $server)
256 ();
257 }
258
259 package Net::Whois::IP::jpnic;
260
261 # totally different result format
262
263 use base Net::Whois::IP::whois;
264
265 sub ip_query {
266 my ($self, $ip) = @_;
267 $self->SUPER::ip_query("$ip/e");
268 }
269
270 sub sanitize_ip_response {
271 my ($self, $response) = @_;
272 $response =~ s/^\[\s.*//mg;
273 $response =~ s/^(?:\w\.\ )? \[ ([^\]]+) \] (.*)/$1: $2/mgx;
274 $self->SUPER::sanitize_ip_response($response);
275 }
276
277 sub parse_ip_response {
278 my ($self, $response) = @_;
279 $self->SUPER::parse_ip_response($response, {
280 "Network Information" => undef,
281 "Network Number" => "netblock",
282 "Network Name" => "netname",
283 "Organization" => "description",
284 "Administrative Contact" => "admin-c",
285 "Technical Contact" => "tech-c",
286 "Nameserver" => "nameserver",
287 "Reply Mail" => undef,
288 "Assigned Date" => "created",
289 "Return Date" => "expires",
290 "Last Update" => "changed",
291 }, {
292 country => "JP",
293 });
294 }
295
296 package Net::Whois::IP::ripe;
297
298 # keepalive whois, as used by ripe and apnic
299
300 use base Net::Whois::IP::whois;
301
302 sub _daemon {
303 my $self = shift;
304 my $fh;
305
306 my $request;
307 while ($request = $self->{request}->get) {
308 unless ($self->{fh}) {
309 $fh = new Coro::Socket PeerHost => $self->{server};
310 $fh or $self->_failure, redo;
311 }
312
313 $fh->print("-k $request->{query}\012");
314 $request->{response} = $fh->readline("\012\012\012");
315 length $request->{response}
316 or $self->_failure, redo;
317
318 $self->_ok;
319 $request->{ready}->up;
320 }
321 }
322
323 sub sanitize_ip_response {
324 my ($self, $response) = @_;
325 $response = "" if $response =~ /^netname:\s+IANA-BLK$/m;
326 $response = "" if $response =~ /This network range is not allocated to APNIC./;
327 # $response = "" if $response =~ /^remarks:\s+.*the IP has not been allocated by APNIC./m;
328 $self->SUPER::sanitize_ip_response($response);
329 }
330
331 package Net::Whois::IP::rwhois;
332
333 # rwhois 1.5, of course, as usual arin's implementation is utterly broken
334 # and doesn't help very much
335
336 use base Net::Whois::IP::base;
337
338 sub _request {
339 my $self = shift;
340 my ($response, $error);
341
342 $self->{fh}->print("$_[0]\015\012");
343 while (defined (my $line = $self->{fh}->readline)) {
344 $line =~ s/[\015\012]+$/\012/g;
345 $response .= $line;
346 $line =~ /^%(ok|error)/ and return $response;
347 }
348 return;
349 }
350
351 sub _daemon {
352 my $self = shift;
353 my $request;
354 while ($request = $self->{request}->get) {
355 unless ($self->{fh}) {
356 $self->{fh} = new Coro::Socket PeerHost => $self->{server};
357 $self->{fh} or $self->_failure, redo;
358 $self->{fh}->readline =~ /^%rwhois / or $self->_failure, redo;
359 $self->_request("-rwhois V-1.5 Net::Whois::IP") or $self->_failure, redo;
360 $self->_request("-forward off") or $self->_failure, redo;
361 $self->_request("-holdconnect on") or $self->_failure, redo;
362 }
363 $request->{response} = $self->_request($request->{query})
364 or $self->_failure, redo;
365 $request->{response} =~ s/^%error 330.*/%ok/m;
366 $self->_ok;
367 $request->{ready}->up;
368 }
369 }
370
371 sub ip_query {
372 my ($self, $ip) = @_;
373 $self->SUPER::ip_query("network $ip");
374 }
375
376 sub sanitize_ip_response {
377 my ($self, $response) = @_;
378 $response =~ s/^% referral/REFERRAL:/mg;
379 $response =~ s/^%.*//mg;
380 $response =~ s/\n\n.*//s;
381 $response = "" if $response =~ /^network:Org-Name:Various Registries \(Maintained by ARIN\)/m;
382 $response =~ s/^([^:]+):([^:]+):/$1-\L$2: /mg;
383 $response;
384 }
385
386 sub parse_ip_response {
387 my ($self, $response) = @_;
388 $self->SUPER::parse_ip_response($response, {
389 "network-class-name" => undef,
390 "network-auth-area" => "auth-area",
391 "network-id" => "id",
392 "network-handle" => "handle",
393 "network-network-name" => "netname",
394 "network-ip-network" => undef,
395 "network-ip-network-block" => "netblock",
396 "network-org-name" => "description",
397 "network-street-address" => "address",
398 "network-city" => "address",
399 "network-state" => "address",
400 "network-postal-code" => "address",
401 "network-country-code" => "country",
402 "network-tech-contact;i" => "tech-c",
403 "network-admin-contact;i" => "admin-c",
404 "network-created" => "created",
405 "network-updated" => "changed",
406 });
407 }
408
409 sub is_ip_referral {
410 my ($self, $response) = @_;
411 $response =~ /^network-ip-network-block:(.*)$/m
412 or return ();
413 my $iprange = $1;
414 if ($response =~ /^REFERRAL: rwhois:\/\/([^\/]+)/m) {
415 my $server = $1;
416 if ($server =~ /^whois.ripe.net/) {
417 return ($iprange, $server{RIPE});
418 } elsif ($server =~ /^rwhois.nic.ad.jp/) {
419 return ($iprange, $server{JPNIC});
420 } elsif ($server =~ /^r?whois.apnic.net/) {
421 return ($iprange, $server{APNIC});
422 } elsif ($server =~ /^rwhois.lacnic.net/) {
423 return ($iprange, $server{LACNIC}); # not yet seen, hope it will be implemented like this
424 } elsif ($server =~ /^rwhois.internex.net/) {
425 # alive(!)
426 return ();
427 } elsif ($server =~ /^rwhois.nstar.net/) {
428 return ();
429 } elsif ($server =~ /^rwhois.sesqui.net/) {
430 return ();
431 } elsif ($server =~ /^nic.ddn.mil/) {
432 # whois.nic.mil (the actual address) only supports the antique
433 # nonparsable arin whois format
434 return ();
435 } else {
436 die "$response\nreferral to whois server $server";
437 }
438 die "$server $iprange";
439 } elsif ($response =~ /^network-org-name: Latin American and Caribbean IP address Regional Registry/m) {
440 return ($iprange, $server{LACNIC});
441 } elsif ($response =~ /^network-org-name: Asia Pacific Network Information Centre/m) {
442 return ($iprange, $server{APNIC});
443 } elsif ($response =~ /^network-org-name: RIPE Network Coordination Centre/m) {
444 return ($iprange, $server{RIPE});
445 }
446 ();
447 }
448
449 package Net::Whois::IP;
450
451 # add a referral to a specific server
452 sub add_referral {
453 my ($netblock, $server) = @_;
454 my ($ip0, $ip1) = ip_range $netblock;
455 $db_referral->db_put(
456 (pack "N*", $ip1, $ip0) ^ "\x00\x00\x00\x00\xff\xff\xff\xff",
457 $server->{name},
458 );
459 }
460
461 # get all referrals
462 sub parse_referral {
463 my ($query, $server) = @_;
464 my $referral = $arin->_query("referral $query");
465
466 while ($referral =~ /^referral:Referred-Auth-Area:([0-9.\/]+)$/mg) {
467 add_referral $1, $server;
468 }
469 }
470
471 =item init db_home => $path, ...
472
473 Initializes the module.
474
475 This function must be called before calling any of the other functions,
476 and the only required agrument is C<db_home>, the path where the module
477 will store it's cache (will be created if neccessary).
478
479 Other Arguments:
480
481 db_home database home directory
482 db_env the database env (should be created by the module itself)
483 db_errfile the file wheer the database will output and errors (/dev/fd/2)
484 db_cachesize size of the in-memory cache (1_000_000)
485
486 =cut
487
488 sub init(;%) {
489 my (%arg) = @_;
490 $arg{db_home} or $arg{db_env} or Carp::croak "either db_home or db_env home must be set";
491 $db_env = $arg{db_env} || do {
492 mkdir $arg{db_home}, 0700;
493
494 $db_env = new BerkeleyDB::Env
495 -Home => $arg{db_home},
496 -Cachesize => $arg{db_cachesize} || 1_000_000,
497 -ErrFile => $arg{db_errfile} || "/dev/fd/2",
498 -ErrPrefix => "NET-WHOIS-IP",
499 -Verbose => 1,
500 -Flags => DB_CREATE|DB_RECOVER_FATAL|DB_INIT_MPOOL|DB_INIT_TXN
501 or die "$arg{db_home}: unable to create database home";
502 };
503
504 $arin = new Net::Whois::IP::rwhois "ARIN", server => "rwhois.arin.net:rwhois(4321)";
505
506 $ripe = new Net::Whois::IP::ripe "RIPE", server => "whois.ripe.net:whois(43)";
507 $lacnic = new Net::Whois::IP::whois "LACNIC", server => "whois.lacnic.net:whois(43)";
508 $brnic = new Net::Whois::IP::whois "BRNIC", server => "whois.registro.br:whois(43)";
509 $apnic = new Net::Whois::IP::ripe "APNIC", server => "whois.apnic.net:whois(43)";
510 $jpnic = new Net::Whois::IP::jpnic "JPNIC", server => "whois.nic.ad.jp:whois(43)";
511
512 $db_referral = new_btree "referral";
513
514 parse_referral "32.0.0.0", $ripe;
515 parse_referral "202.0.0.0", $apnic;
516 parse_referral "192.50.0.0", $apnic;
517
518 add_referral "129.13.0.0/16", $ripe;
519 add_referral "133.0.0.0/8", $jpnic;
520 add_referral "200.0.0.0/8", $lacnic;
521 add_referral "200.128.0.0/9", $brnic;
522 }
523
524 =item ip_query $ip
525
526 Queries the whois server for the given ip address (which must be something
527 inet_aton understands).
528
529 The return value currently is a hash, which looks slightly difefrent for
530 different registries, see examples.
531
532 =cut
533
534 sub ip_query {
535 my $ip = shift;
536
537 my $server = $arin;
538
539 my ($prev_response, $prev_server);
540
541 referral:
542 my $c = $db_referral->db_cursor;
543
544 # iterate over all possibly matching referral ranges
545 # and choose the one that fits tightest
546 my ($k, $v) = (inet_aton $ip);
547 if (!$c->c_get($k, $v, DB_SET_RANGE)) {
548 find: {
549 do {
550 my ($ip1, $ip0) = unpack "N*", $k ^ "\x00\x00\x00\x00\xff\xff\xff\xff";
551 my $ipn = unpack "N", inet_aton $ip;
552 0 && printf "Q=%15s < %15s < %15s, $v\n",
553 (inet_ntoa pack "N", $ip0),
554 (inet_ntoa pack "N", $ipn),
555 (inet_ntoa pack "N", $ip1);
556
557 if ($ipn <= $ip1) {
558 if ($ip0 <= $ipn) {
559 my ($name, $rip) = split /\t/, $v;
560 $server = $server{$name} if $server{$name};
561 $ip = $ip if $rip;
562 last;
563 }
564 } else {
565 last;
566 }
567 } while !$c->c_get($k, $v, DB_NEXT);
568 }
569 }
570
571 my $response = $server->ip_query($ip);
572 if ($response) {
573 if (my ($iprange, $refer) = $server->is_ip_referral($response)) {
574 ($prev_server, $prev_response) = ($server, $response);
575 $server = $refer;
576 add_referral $iprange, $server;
577 goto referral;
578 }
579 } else {
580 ($server, $response) = ($prev_server, $prev_response);
581 }
582
583 if (!$response) {
584 ($server, $response) = ($arin, $arin->ip_query($ip));
585 }
586
587 my $res = $server->parse_ip_response($response);
588 for my $range (@{$res->{netblock} || []}) {
589 if (my ($ip0, $ip1) = eval { ip_range $range }) {
590 # limit to /24
591 my $ipn = unpack "N", inet_aton $ip;
592 $ip0 = $ipn & 0xffffff00 if $ip0 < ($ipn & 0xffffff00);
593 $ip1 = ($ipn + 256 & 0xffffff00) - 1 if $ip1 > ($ipn + 256 & 0xffffff00);
594 $db_referral->db_put(
595 (pack "N*", $ip1, $ip0) ^ "\x00\x00\x00\x00\xff\xff\xff\xff",
596 "$server->{name}\t$ip",
597 );
598 }
599 }
600
601 $db_env->txn_checkpoint(10,1,0);
602
603 $res;
604 }
605
606 1;
607
608 =back
609
610 =head2 WHOIS EXAMPLES
611
612 =over 4
613
614 =item RIPE
615
616 ip_query "129.13.162.91" =>
617 (
618 source => [ "RIPE" ],
619 "mnt-by" => [ "DFN-NTFY" ],
620 country => [ "DE" ],
621 netname => [ "KLICK" ],
622 status => [ "ASSIGNED PI" ],
623 description => [ "Karlsruher Lichtleiter Kommunikationsnetz",
624 "University of Karlsruhe",
625 "Germany" ],
626 nameserver => [ "netserv.rz.uni-karlsruhe.de",
627 "iraun1.ira.uka.de",
628 "deneb.dfn.de",
629 "ns.germany.eu.net" ],
630 "tech-c" => [ "HUK2-RIPE" ],
631 changed => [ "lortz\@rz.uni-karlsruhe.de 19910212",
632 "nipper\@ira.uka.de 19920422",
633 "nipper\@xlink.net 19930513",
634 "rv\@Informatik.Uni-Dortmund.DE 19931129",
635 "poldi\@dfn.de 19940309",
636 "dolderer\@nic.de 19940930",
637 "dolderer\@nic.de 19970821",
638 "schweikh\@noc 19990819" ],
639 netblock => [ "129.13.0.0 - 129.13.255.255" ],
640 "admin-c" => [ "BL118" ],
641 notify => [ "guardian\@nic.de" ],
642 whois_server => "RIPE"
643 )
644
645 =item LACNIC
646
647 ip_query "200.5.0.0" =>
648 (
649 source => [ "ARIN-LACNIC-TRANSITION" ],
650 created => [ 19960205 ],
651 country => [ "PR" ],
652 changed => [ 19960205 ],
653 "owner-c" => [ "PR-PRMS-LACNIC",
654 "RS564-ARIN" ],
655 netblock => [ "200.5.0/21" ],
656 status => [ "assigned" ],
657 description => [ "Puerto Rico Medical Services Administration",
658 "Management Information Systems Department",
659 "PO Box 2129",
660 "San Juan, PR 00922-2129",
661 "PR" ],
662 whois_server => "LACNIC"
663 )
664
665 =item BRNIC
666
667 ip_query "200.128.0.0" =>
668 (
669 created => [ 20000215 ],
670 "tech-c" => [ "ALG149" ],
671 changed => [ 20001017 ],
672 "owner-c" => [ "003.508.097/0001-36",
673 "ALG149" ],
674 netblock => [ "200.128/16" ],
675 "abuse-c" => [ "SIC128" ],
676 description => [ "Associa\347\343o Rede Nacional de Ensino e Pesquisa",
677 "Estrada Dona Castorina, 110, 353",
678 "22460-320 - Rio de Janeiro - RJ",
679 "(021) 274-7445 []" ],
680 whois_server => "BRNIC",
681 nameserver => [ "NS.POP-BA.RNP.BR",
682 "SERVER1.POP-DF.RNP.BR",
683 "SERVER1.AGR.UFBA.BR",
684 "DNS.UFBA.BR" ]
685 )
686
687 =item JPNIC
688
689 ip_query "133.11.128.254" =>
690 (
691 created => [ "" ],
692 "tech-c" => [ "AK003JP",
693 "MN010JP" ],
694 netname => [ "UTSNET" ],
695 changed => [ "2002/10/15 13:06:48 (JST)\n kato\@wide.ad.jp" ],
696 netblock => [ "133.11.0.0" ],
697 "admin-c" => [ "MN010JP" ],
698 description => [ "University of Tokyo" ],
699 whois_server => "JPNIC",
700 expires => [ "" ],
701 nameserver => [ "dns1.nc.u-tokyo.ac.jp",
702 "dns2.nc.u-tokyo.ac.jp",
703 "dns3.nc.u-tokyo.ac.jp",
704 "ns.nc.u-tokyo.ac.jp",
705 "ns.s.u-tokyo.ac.jp" ]
706 )
707
708 =item APNIC
709
710 ip_query "203.2.75.99" =>
711 (
712 source => [ "APNIC" ],
713 "mnt-by" => [ "APNIC-HM" ],
714 "tech-c" => [ "TN38-AP" ],
715 country => [ "AU" ],
716 netname => [ "OPTUSINTERNET-AU" ],
717 changed => [ "nobody\@aunic.net 20010524",
718 "aunic-transfer\@apnic.net 20010525",
719 "hostmaster\@apnic.net 20011004" ],
720 netblock => [ "203.2.75.0 - 203.2.75.255" ],
721 status => [ "ALLOCATED PORTABLE" ],
722 "admin-c" => [ "TN38-AP" ],
723 description => [ "OPTUS INTERNET - RETAIL",
724 "INTERNET SERVICES",
725 "St Leonards, NSW" ],
726 whois_server => "APNIC"
727 )
728
729 =item ARIN
730
731 ip_query "68.52.164.8" =>
732 (
733 country => [ "US" ],
734 netname => [ "JUMPSTART-DC-5" ],
735 "auth-area" => [ "0.0.0.0/0" ],
736 description => [ "Comcast Cable Communications, Inc." ],
737 created => [ "20200718050000000" ],
738 "tech-c" => [ "FG200-ARIN.0.0.0.0/0" ],
739 changed => [ "19200307050000000" ],
740 handle => [ "NET-68-52-160-0-1" ],
741 netblock => [ "68.52.160.0 - 68.52.175.255" ],
742 id => [ "NET-68-52-160-0-1.0.0.0.0/0" ],
743 address => [ "3 Executive Campus Cherry Hill",
744 "NJ",
745 "08002" ],
746 whois_server => "ARIN"
747 )
748
749 =back
750
751 =head1 AUTHOR
752
753 Marc Lehmann <pcg@goof.com>
754 http://www.goof.com/pcg/marc/
755
756 =cut
757