1 |
root |
1.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 |
root |
1.2 |
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 |
root |
1.3 |
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 |
root |
1.2 |
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 |
root |
1.3 |
$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 |
root |
1.2 |
$self->{cache} = Net::Whois::IP::new_btree "whois_" . lc $self->{name}; |
95 |
|
|
$server{$self->{name}} = $self; |
96 |
|
|
} |
97 |
|
|
|
98 |
|
|
sub _failure { |
99 |
root |
1.3 |
my ($self, $min) = shift; |
100 |
root |
1.2 |
undef $self->{fh}; |
101 |
root |
1.3 |
$self->{retry} = $min if $self->{retry} < $min; |
102 |
root |
1.2 |
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 |
root |
1.3 |
$request->{response} =~ s/\015//g; |
130 |
|
|
$request->{response} =~ s/\012/\n/g if "\012" ne "\n"; |
131 |
|
|
|
132 |
root |
1.2 |
$response = time . "\x00" . $request->{response}; |
133 |
|
|
|
134 |
|
|
$self->{cache}->db_put($query, $response); |
135 |
root |
1.3 |
|
136 |
root |
1.2 |
$request->{response}; |
137 |
|
|
} |
138 |
|
|
|
139 |
|
|
sub ip_query { |
140 |
|
|
my ($self, $ip) = @_; |
141 |
root |
1.3 |
$self->sanitize_ip_response($self->_query($ip)); |
142 |
|
|
} |
143 |
|
|
|
144 |
|
|
sub sanitize_ip_response { |
145 |
|
|
my ($self, $response) = @_; |
146 |
|
|
$response; |
147 |
root |
1.2 |
} |
148 |
|
|
|
149 |
|
|
sub parse_ip_response { |
150 |
root |
1.3 |
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 |
root |
1.2 |
my ($self, $response) = @_; |
173 |
root |
1.3 |
# ($iprange, $server) |
174 |
|
|
(); |
175 |
root |
1.2 |
} |
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 |
root |
1.3 |
my $request; |
186 |
|
|
while ($request = $self->{request}->get) { |
187 |
root |
1.2 |
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 |
root |
1.3 |
|
193 |
|
|
length $request->{response} or $self->_failure, redo; |
194 |
|
|
$request->{response} =~ /^% query rate limit exceeded/im and $self->_failure(9), redo; |
195 |
root |
1.2 |
|
196 |
|
|
$self->_ok; |
197 |
|
|
$request->{ready}->up; |
198 |
|
|
} |
199 |
|
|
} |
200 |
|
|
|
201 |
root |
1.3 |
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 |
root |
1.2 |
} |
210 |
|
|
|
211 |
|
|
sub parse_ip_response { |
212 |
root |
1.3 |
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 |
root |
1.2 |
my ($self, $response) = @_; |
249 |
root |
1.3 |
$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 |
root |
1.2 |
} |
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 |
root |
1.3 |
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 |
root |
1.2 |
sub parse_ip_response { |
278 |
|
|
my ($self, $response) = @_; |
279 |
root |
1.3 |
$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 |
root |
1.2 |
} |
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 |
root |
1.3 |
my $request; |
307 |
|
|
while ($request = $self->{request}->get) { |
308 |
root |
1.2 |
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 |
root |
1.3 |
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 |
root |
1.2 |
package Net::Whois::IP::rwhois; |
332 |
|
|
|
333 |
root |
1.3 |
# rwhois 1.5, of course, as usual arin's implementation is utterly broken |
334 |
|
|
# and doesn't help very much |
335 |
root |
1.2 |
|
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 |
root |
1.3 |
$line =~ s/[\015\012]+$/\012/g; |
345 |
root |
1.2 |
$response .= $line; |
346 |
|
|
$line =~ /^%(ok|error)/ and return $response; |
347 |
|
|
} |
348 |
|
|
return; |
349 |
|
|
} |
350 |
|
|
|
351 |
|
|
sub _daemon { |
352 |
|
|
my $self = shift; |
353 |
root |
1.3 |
my $request; |
354 |
|
|
while ($request = $self->{request}->get) { |
355 |
root |
1.2 |
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 |
root |
1.3 |
sub sanitize_ip_response { |
377 |
root |
1.2 |
my ($self, $response) = @_; |
378 |
root |
1.3 |
$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 |
root |
1.2 |
$response =~ s/^([^:]+):([^:]+):/$1-\L$2: /mg; |
383 |
root |
1.3 |
$response; |
384 |
root |
1.2 |
} |
385 |
|
|
|
386 |
root |
1.3 |
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 |
root |
1.2 |
|
409 |
root |
1.3 |
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 |
root |
1.2 |
} |
446 |
root |
1.3 |
(); |
447 |
root |
1.2 |
} |
448 |
|
|
|
449 |
root |
1.3 |
package Net::Whois::IP; |
450 |
|
|
|
451 |
root |
1.2 |
# add a referral to a specific server |
452 |
|
|
sub add_referral { |
453 |
|
|
my ($netblock, $server) = @_; |
454 |
|
|
my ($ip0, $ip1) = ip_range $netblock; |
455 |
root |
1.3 |
$db_referral->db_put( |
456 |
|
|
(pack "N*", $ip1, $ip0) ^ "\x00\x00\x00\x00\xff\xff\xff\xff", |
457 |
|
|
$server->{name}, |
458 |
|
|
); |
459 |
root |
1.2 |
} |
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 |
root |
1.3 |
=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 |
root |
1.2 |
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 |
root |
1.3 |
-Flags => DB_CREATE|DB_RECOVER_FATAL|DB_INIT_MPOOL|DB_INIT_TXN |
501 |
root |
1.2 |
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 |
root |
1.3 |
add_referral "129.13.0.0/16", $ripe; |
519 |
root |
1.2 |
add_referral "133.0.0.0/8", $jpnic; |
520 |
root |
1.3 |
add_referral "200.0.0.0/8", $lacnic; |
521 |
|
|
add_referral "200.128.0.0/9", $brnic; |
522 |
root |
1.2 |
} |
523 |
|
|
|
524 |
root |
1.3 |
=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 |
root |
1.2 |
|
532 |
root |
1.3 |
=cut |
533 |
root |
1.2 |
|
534 |
|
|
sub ip_query { |
535 |
|
|
my $ip = shift; |
536 |
|
|
|
537 |
root |
1.3 |
my $server = $arin; |
538 |
root |
1.2 |
|
539 |
root |
1.3 |
my ($prev_response, $prev_server); |
540 |
root |
1.2 |
|
541 |
root |
1.3 |
referral: |
542 |
root |
1.2 |
my $c = $db_referral->db_cursor; |
543 |
|
|
|
544 |
root |
1.3 |
# iterate over all possibly matching referral ranges |
545 |
|
|
# and choose the one that fits tightest |
546 |
root |
1.2 |
my ($k, $v) = (inet_aton $ip); |
547 |
|
|
if (!$c->c_get($k, $v, DB_SET_RANGE)) { |
548 |
root |
1.3 |
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 |
root |
1.2 |
|
583 |
root |
1.3 |
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 |
root |
1.2 |
} |
599 |
|
|
} |
600 |
|
|
|
601 |
root |
1.3 |
$db_env->txn_checkpoint(10,1,0); |
602 |
|
|
|
603 |
|
|
$res; |
604 |
root |
1.2 |
} |
605 |
|
|
|
606 |
root |
1.3 |
1; |
607 |
|
|
|
608 |
|
|
=back |
609 |
|
|
|
610 |
|
|
=head2 WHOIS EXAMPLES |
611 |
root |
1.2 |
|
612 |
root |
1.3 |
=over 4 |
613 |
root |
1.1 |
|
614 |
root |
1.3 |
=item RIPE |
615 |
root |
1.1 |
|
616 |
root |
1.3 |
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 |
root |
1.1 |
|
749 |
|
|
=back |
750 |
|
|
|
751 |
|
|
=head1 AUTHOR |
752 |
|
|
|
753 |
|
|
Marc Lehmann <pcg@goof.com> |
754 |
|
|
http://www.goof.com/pcg/marc/ |
755 |
|
|
|
756 |
|
|
=cut |
757 |
|
|
|