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 |
root |
1.2 |
# http://www.irr.net/docs/rpsl.html |
16 |
|
|
|
17 |
root |
1.1 |
package Net::Whois::IP; |
18 |
|
|
|
19 |
|
|
BEGIN { |
20 |
|
|
$VERSION = 0.01; |
21 |
|
|
@EXPORT_OK = qw(); |
22 |
|
|
} |
23 |
|
|
|
24 |
|
|
use base Exporter; |
25 |
|
|
|
26 |
root |
1.2 |
use Carp; |
27 |
|
|
|
28 |
|
|
use Socket; |
29 |
|
|
|
30 |
|
|
use BerkeleyDB; |
31 |
|
|
|
32 |
|
|
use Coro; |
33 |
|
|
use Coro::Event; |
34 |
|
|
use Coro::Semaphore; |
35 |
|
|
use Coro::SemaphoreSet; |
36 |
|
|
use Coro::Socket; |
37 |
|
|
use Coro::Timer; |
38 |
|
|
use Coro::Channel; |
39 |
|
|
|
40 |
|
|
my %server; |
41 |
|
|
|
42 |
|
|
sub new_btree { |
43 |
|
|
new BerkeleyDB::Btree |
44 |
|
|
-Env => $db_env, |
45 |
|
|
-Filename => $_[0], |
46 |
|
|
-Flags => DB_CREATE, |
47 |
|
|
or die "$_[0]: unable to create/open table"; |
48 |
|
|
} |
49 |
|
|
|
50 |
|
|
package Net::Whois::IP::base; |
51 |
|
|
|
52 |
|
|
sub new { |
53 |
|
|
my $class = shift; |
54 |
|
|
my $self = bless { |
55 |
|
|
name => @_, |
56 |
|
|
}, $class; |
57 |
|
|
$self->{request} ||= new Coro::Channel $self->{request_backlog} || 10; |
58 |
|
|
$self->{daemon} = Coro::async { shift->_daemon } $self; |
59 |
|
|
$self->{cache} = Net::Whois::IP::new_btree "whois_" . lc $self->{name}; |
60 |
|
|
$server{$self->{name}} = $self; |
61 |
|
|
} |
62 |
|
|
|
63 |
|
|
sub _failure { |
64 |
|
|
my $self = shift; |
65 |
|
|
undef $self->{fh}; |
66 |
|
|
Coro::Timer::sleep 1.5 ** $self->{retry} - 2 if $self->{retry}; |
67 |
|
|
$self->{retry}++ if $self->{retry} < 17; |
68 |
|
|
} |
69 |
|
|
|
70 |
|
|
sub _ok { |
71 |
|
|
my $self = shift; |
72 |
|
|
$self->{retry} = 0; |
73 |
|
|
} |
74 |
|
|
|
75 |
|
|
sub _query { |
76 |
|
|
my ($self, $query) = @_; |
77 |
|
|
|
78 |
|
|
my $response; |
79 |
|
|
|
80 |
|
|
if (!$self->{cache}->db_get($query, $response)) { |
81 |
|
|
$response =~ s/^[^\x00]+\x00//; |
82 |
|
|
return $response; |
83 |
|
|
} |
84 |
|
|
|
85 |
|
|
my $request = { |
86 |
|
|
ready => (new Coro::Semaphore 0), |
87 |
|
|
query => $query, |
88 |
|
|
}; |
89 |
|
|
|
90 |
|
|
$self->{request}->put($request); |
91 |
|
|
$request->{ready}->down; |
92 |
|
|
|
93 |
|
|
$response = time . "\x00" . $request->{response}; |
94 |
|
|
|
95 |
|
|
$self->{cache}->db_put($query, $response); |
96 |
|
|
|
97 |
|
|
$request->{response}; |
98 |
|
|
} |
99 |
|
|
|
100 |
|
|
sub ip_query { |
101 |
|
|
my ($self, $ip) = @_; |
102 |
|
|
$self->_query($ip); |
103 |
|
|
} |
104 |
|
|
|
105 |
|
|
sub parse_ip_response { |
106 |
|
|
my ($self, $response) = @_; |
107 |
|
|
my %response; |
108 |
|
|
while ($response =~ /^([^:]+):[ \t]*(.*)$/mg) { |
109 |
|
|
push @{$response{$1}}, $2; |
110 |
|
|
} |
111 |
|
|
\%response; |
112 |
|
|
} |
113 |
|
|
|
114 |
|
|
package Net::Whois::IP::whois; |
115 |
|
|
|
116 |
|
|
# plain whois, as used by ripe and apnic |
117 |
|
|
|
118 |
|
|
use base Net::Whois::IP::base; |
119 |
|
|
|
120 |
|
|
sub _daemon { |
121 |
|
|
my $self = shift; |
122 |
|
|
while (my $request = $self->{request}->get) { |
123 |
|
|
my $fh = new Coro::Socket PeerHost => $self->{server} |
124 |
|
|
or $self->_failure, redo; |
125 |
|
|
|
126 |
|
|
$fh->print("$request->{query}\012"); |
127 |
|
|
$fh->sysread($request->{response}, 16384); |
128 |
|
|
length $request->{response} |
129 |
|
|
or $self->_failure, redo; |
130 |
|
|
|
131 |
|
|
$self->_ok; |
132 |
|
|
$request->{ready}->up; |
133 |
|
|
} |
134 |
|
|
} |
135 |
|
|
|
136 |
|
|
sub ip_query { |
137 |
|
|
my ($self, $ip) = @_; |
138 |
|
|
$self->SUPER::ip_query($ip); |
139 |
|
|
} |
140 |
|
|
|
141 |
|
|
sub parse_ip_response { |
142 |
|
|
my ($self, $response) = @_; |
143 |
|
|
$response =~ s/^%.*//mg; |
144 |
|
|
$response =~ s/^\012+//; |
145 |
|
|
$response =~ s/\012\012.*//s; |
146 |
|
|
$self->SUPER::parse_ip_response($response); |
147 |
|
|
} |
148 |
|
|
|
149 |
|
|
package Net::Whois::IP::jpnic; |
150 |
|
|
|
151 |
|
|
# totally different result format |
152 |
|
|
|
153 |
|
|
use base Net::Whois::IP::whois; |
154 |
|
|
|
155 |
|
|
sub ip_query { |
156 |
|
|
my ($self, $ip) = @_; |
157 |
|
|
$self->SUPER::ip_query("$ip/e"); |
158 |
|
|
} |
159 |
|
|
|
160 |
|
|
sub parse_ip_response { |
161 |
|
|
my ($self, $response) = @_; |
162 |
|
|
$response =~ s/^\[\s.*//mg; |
163 |
|
|
$response =~ s/^(?:\w\.\ )? \[ ([^\]]+) \] (.*) (?:\012(?=\s))?/$1: $2/mgx; |
164 |
|
|
$self->SUPER::parse_ip_response($response); |
165 |
|
|
} |
166 |
|
|
|
167 |
|
|
package Net::Whois::IP::ripe; |
168 |
|
|
|
169 |
|
|
# keepalive whois, as used by ripe and apnic |
170 |
|
|
|
171 |
|
|
use base Net::Whois::IP::whois; |
172 |
|
|
|
173 |
|
|
sub _daemon { |
174 |
|
|
my $self = shift; |
175 |
|
|
my $fh; |
176 |
|
|
|
177 |
|
|
while (my $request = $self->{request}->get) { |
178 |
|
|
unless ($self->{fh}) { |
179 |
|
|
$fh = new Coro::Socket PeerHost => $self->{server}; |
180 |
|
|
$fh or $self->_failure, redo; |
181 |
|
|
} |
182 |
|
|
|
183 |
|
|
$fh->print("-k $request->{query}\012"); |
184 |
|
|
$request->{response} = $fh->readline("\012\012\012"); |
185 |
|
|
length $request->{response} |
186 |
|
|
or $self->_failure, redo; |
187 |
|
|
|
188 |
|
|
$self->_ok; |
189 |
|
|
$request->{ready}->up; |
190 |
|
|
} |
191 |
|
|
} |
192 |
|
|
|
193 |
|
|
package Net::Whois::IP::rwhois; |
194 |
|
|
|
195 |
|
|
# rwhois 1.5 |
196 |
|
|
|
197 |
|
|
use base Net::Whois::IP::base; |
198 |
|
|
|
199 |
|
|
sub _request { |
200 |
|
|
my $self = shift; |
201 |
|
|
my ($response, $error); |
202 |
|
|
|
203 |
|
|
$self->{fh}->print("$_[0]\015\012"); |
204 |
|
|
while (defined (my $line = $self->{fh}->readline)) { |
205 |
|
|
$line =~ s/[\015\012]+$/\n/g; |
206 |
|
|
$response .= $line; |
207 |
|
|
$line =~ /^%(ok|error)/ and return $response; |
208 |
|
|
} |
209 |
|
|
return; |
210 |
|
|
} |
211 |
|
|
|
212 |
|
|
sub _daemon { |
213 |
|
|
my $self = shift; |
214 |
|
|
while (my $request = $self->{request}->get) { |
215 |
|
|
unless ($self->{fh}) { |
216 |
|
|
$self->{fh} = new Coro::Socket PeerHost => $self->{server}; |
217 |
|
|
$self->{fh} or $self->_failure, redo; |
218 |
|
|
$self->{fh}->readline =~ /^%rwhois / or $self->_failure, redo; |
219 |
|
|
$self->_request("-rwhois V-1.5 Net::Whois::IP") or $self->_failure, redo; |
220 |
|
|
$self->_request("-forward off") or $self->_failure, redo; |
221 |
|
|
$self->_request("-holdconnect on") or $self->_failure, redo; |
222 |
|
|
} |
223 |
|
|
$request->{response} = $self->_request($request->{query}) |
224 |
|
|
or $self->_failure, redo; |
225 |
|
|
$request->{response} =~ s/^%error 330.*/%ok/m; |
226 |
|
|
$self->_ok; |
227 |
|
|
$request->{ready}->up; |
228 |
|
|
} |
229 |
|
|
} |
230 |
|
|
|
231 |
|
|
sub ip_query { |
232 |
|
|
my ($self, $ip) = @_; |
233 |
|
|
$self->SUPER::ip_query("network $ip"); |
234 |
|
|
} |
235 |
|
|
|
236 |
|
|
sub parse_ip_response { |
237 |
|
|
my ($self, $response) = @_; |
238 |
|
|
$response =~ s/^([^:]+):([^:]+):/$1-\L$2: /mg; |
239 |
|
|
$response =~ s/^%.*//mg; |
240 |
|
|
$response =~ s/\012\012.*//s; |
241 |
|
|
$self->SUPER::parse_ip_response($response); |
242 |
|
|
} |
243 |
|
|
|
244 |
|
|
package Net::Whois::IP; |
245 |
|
|
|
246 |
|
|
sub ip_range { |
247 |
|
|
my $range = $_[0]; |
248 |
|
|
|
249 |
|
|
if ($range =~ /^ |
250 |
|
|
(2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]) |
251 |
|
|
(?:\. |
252 |
|
|
(2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]) |
253 |
|
|
(?:\. |
254 |
|
|
(2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]) |
255 |
|
|
(?:\. |
256 |
|
|
(2[0-5][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]) |
257 |
|
|
)? |
258 |
|
|
)? |
259 |
|
|
)? |
260 |
|
|
\/ |
261 |
|
|
([0-9]+) |
262 |
|
|
$/x) { |
263 |
|
|
my $ip = inet_aton sprintf "%d.%d.%d.%d", $1, $2, $3, $4; |
264 |
|
|
my $net = 1 << (31 - $5); |
265 |
|
|
my $mask = inet_aton 2 ** 32 - $net; |
266 |
|
|
|
267 |
|
|
my $ip1 = $ip & $mask; |
268 |
|
|
my $ip2 = $ip1 | inet_aton $net * 2 - 1; |
269 |
|
|
return unpack "N2", "$ip1$ip2"; |
270 |
|
|
} elsif ($range =~ /^\s*([0-9.]+)\s*-\s*([0-9.]+)\s*$/) { |
271 |
|
|
unpack "N2", pack "N2", $1, $2; |
272 |
|
|
} else { |
273 |
|
|
die "$range: unable to parse ip range"; |
274 |
|
|
} |
275 |
|
|
} |
276 |
|
|
|
277 |
|
|
# add a referral to a specific server |
278 |
|
|
sub add_referral { |
279 |
|
|
my ($netblock, $server) = @_; |
280 |
|
|
my ($ip0, $ip1) = ip_range $netblock; |
281 |
|
|
$db_referral->db_put((pack "N*", $ip1, $ip0) ^ "\x00\x00\x00\x00\xff\xff\xff\xff", "S" . $server->{name}); |
282 |
|
|
} |
283 |
|
|
|
284 |
|
|
# get all referrals |
285 |
|
|
sub parse_referral { |
286 |
|
|
my ($query, $server) = @_; |
287 |
|
|
my $referral = $arin->_query("referral $query"); |
288 |
|
|
|
289 |
|
|
while ($referral =~ /^referral:Referred-Auth-Area:([0-9.\/]+)$/mg) { |
290 |
|
|
add_referral $1, $server; |
291 |
|
|
} |
292 |
|
|
} |
293 |
|
|
|
294 |
|
|
sub init(;%) { |
295 |
|
|
my (%arg) = @_; |
296 |
|
|
$arg{db_home} or $arg{db_env} or Carp::croak "either db_home or db_env home must be set"; |
297 |
|
|
$db_env = $arg{db_env} || do { |
298 |
|
|
mkdir $arg{db_home}, 0700; |
299 |
|
|
|
300 |
|
|
$db_env = new BerkeleyDB::Env |
301 |
|
|
-Home => $arg{db_home}, |
302 |
|
|
-Cachesize => $arg{db_cachesize} || 1_000_000, |
303 |
|
|
-ErrFile => $arg{db_errfile} || "/dev/fd/2", |
304 |
|
|
-ErrPrefix => "NET-WHOIS-IP", |
305 |
|
|
-Verbose => 1, |
306 |
|
|
-Flags => DB_CREATE|DB_RECOVER|DB_INIT_MPOOL|DB_INIT_TXN |
307 |
|
|
or die "$arg{db_home}: unable to create database home"; |
308 |
|
|
}; |
309 |
|
|
|
310 |
|
|
$arin = new Net::Whois::IP::rwhois "ARIN", server => "rwhois.arin.net:rwhois(4321)"; |
311 |
|
|
|
312 |
|
|
$ripe = new Net::Whois::IP::ripe "RIPE", server => "whois.ripe.net:whois(43)"; |
313 |
|
|
$lacnic = new Net::Whois::IP::whois "LACNIC", server => "whois.lacnic.net:whois(43)"; |
314 |
|
|
$brnic = new Net::Whois::IP::whois "BRNIC", server => "whois.registro.br:whois(43)"; |
315 |
|
|
$apnic = new Net::Whois::IP::ripe "APNIC", server => "whois.apnic.net:whois(43)"; |
316 |
|
|
$jpnic = new Net::Whois::IP::jpnic "JPNIC", server => "whois.nic.ad.jp:whois(43)"; |
317 |
|
|
|
318 |
|
|
$db_referral = new_btree "referral"; |
319 |
|
|
|
320 |
|
|
add_referral "129.13.0.0/16", $ripe; |
321 |
|
|
add_referral "200.0.0.0/8", $lacnic; |
322 |
|
|
add_referral "200.128.0.0/9", $brnic; |
323 |
|
|
|
324 |
|
|
parse_referral "32.0.0.0", $ripe; |
325 |
|
|
parse_referral "202.0.0.0", $apnic; |
326 |
|
|
parse_referral "192.50.0.0", $apnic; |
327 |
|
|
|
328 |
|
|
add_referral "133.0.0.0/8", $jpnic; |
329 |
|
|
} |
330 |
|
|
|
331 |
|
|
### |
332 |
|
|
|
333 |
|
|
init db_home => "/tmp/whois"; |
334 |
|
|
|
335 |
|
|
sub ip_query { |
336 |
|
|
my $ip = shift; |
337 |
|
|
|
338 |
|
|
print "Q?$ip "; |
339 |
|
|
|
340 |
|
|
my $server = $arin; |
341 |
|
|
|
342 |
|
|
my $c = $db_referral->db_cursor; |
343 |
|
|
|
344 |
|
|
my ($k, $v) = (inet_aton $ip); |
345 |
|
|
if (!$c->c_get($k, $v, DB_SET_RANGE)) { |
346 |
|
|
my ($ip1, $ip0) = unpack "N*", $k ^ "\x00\x00\x00\x00\xff\xff\xff\xff"; |
347 |
|
|
my $ipn = unpack "N", inet_aton $ip; |
348 |
|
|
|
349 |
|
|
if ($ip0 <= $ipn && $ipn <= $ip1) { |
350 |
|
|
if ($v =~ s/^S//) { |
351 |
|
|
$server = $server{$v} if $server{$v}; |
352 |
|
|
} elsif ($v =~ s/^I//) { |
353 |
|
|
die; |
354 |
|
|
} else { |
355 |
|
|
# database corrupted or newer |
356 |
|
|
} |
357 |
|
|
} |
358 |
|
|
} |
359 |
|
|
|
360 |
|
|
print "($ip,$server->{name})\n"; |
361 |
|
|
$server->parse_ip_response($server->ip_query($ip)); |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
for (qw(132.64.0.0 129.13.162.91 193.166.90.111 213.189.83.103 161.142.8.76 66.68.103.189 200.149.85.112 210.49.200.207 133.11.128.254)) { |
365 |
|
|
use PApp::Util; |
366 |
|
|
warn PApp::Util::dumpval ip_query($_); |
367 |
|
|
} |
368 |
|
|
|
369 |
|
|
exit; |
370 |
root |
1.1 |
|
371 |
root |
1.2 |
__END__ |
372 |
root |
1.1 |
|
373 |
|
|
1; |
374 |
|
|
|
375 |
|
|
=back |
376 |
|
|
|
377 |
|
|
=head1 AUTHOR |
378 |
|
|
|
379 |
|
|
Marc Lehmann <pcg@goof.com> |
380 |
|
|
http://www.goof.com/pcg/marc/ |
381 |
|
|
|
382 |
|
|
=cut |
383 |
|
|
|