Merge remote-tracking branch 'origin/master' into pypi_package
[blockfinder.git] / block_finder / blockfinder.py
blob8670a53977ff0b0b4bc7d7ed9d9e8f8093b1e337
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 # For the people of Smubworld!
5 import os
6 import time
7 import optparse
8 import sys
9 import sqlite3
10 import hashlib
11 import gzip
12 import zipfile
13 import re
14 import bz2
15 from math import log
17 if sys.version_info[0] >= 3:
18 from configparser import ConfigParser
19 import ipaddress as ipaddr
20 from urllib.request import (urlopen, Request)
21 from urllib.error import URLError
22 long = int
23 else:
24 from ConfigParser import SafeConfigParser as ConfigParser
25 from urllib2 import (urlopen, Request, URLError)
26 try:
27 from embedded_ipaddr import ipaddr
28 ipaddr.ip_address = ipaddr.IPAddress
29 except:
30 import ipaddress as ipaddr
32 is_win32 = (sys.platform == "win32")
34 __program__ = 'blockfinder'
35 __url__ = 'https://github.com/ioerror/blockfinder/'
36 __author__ = 'Jacob Appelbaum <jacob@appelbaum.net>, David <db@d1b.org>'
37 __copyright__ = 'Copyright (c) 2010'
38 __license__ = 'See LICENSE for licensing information'
39 __version__ = '3.14159'
41 try:
42 from future import antigravity
43 except ImportError:
44 antigravity = None
47 class DatabaseCache(object):
49 def __init__(self, cache_dir, verbose=False):
50 self.cache_dir = cache_dir
51 self.verbose = verbose
52 self.cursor = None
53 self.conn = None
54 self.db_version = "0.0.4"
55 self.db_path = os.path.join(self.cache_dir + "sqlitedb")
57 def erase_database(self):
58 """ Erase the database file. """
59 if os.path.exists(self.db_path):
60 os.remove(self.db_path)
62 def connect_to_database(self):
63 """ Connect to the database cache, possibly after creating it if
64 it doesn't exist yet, or after making sure an existing
65 database cache has the correct version. Return True if a
66 connection could be established, False otherwise. """
67 if not os.path.exists(self.cache_dir):
68 if self.verbose:
69 print("Initializing the cache directory...")
70 os.mkdir(self.cache_dir)
71 if os.path.exists(self.db_path):
72 cache_version = self.get_db_version()
73 if not cache_version:
74 cache_version = "0.0.1"
75 if cache_version != self.db_version:
76 print(("The existing database cache uses version %s, "
77 "not the expected %s." % (cache_version,
78 self.db_version)))
79 return False
80 self.conn = sqlite3.connect(self.db_path)
81 self.cursor = self.conn.cursor()
82 self.create_assignments_table()
83 self.create_asn_description_table()
84 self.create_asn_assignments_table()
85 return True
87 def __get_default_config_file_obj(self):
88 open_flags = 'r+'
89 file_path = os.path.join(self.cache_dir, 'db.cfg')
90 if not os.path.exists(file_path):
91 open_flags = 'w+'
92 return open(file_path, open_flags)
94 def _get_db_config(self, file_obj=None):
95 """ Return the database configuration object from the provided
96 file_obj if provided, otherwise from the default database
97 configuration file. """
98 if file_obj is None:
99 file_obj = self.__get_default_config_file_obj()
100 config = ConfigParser()
101 if sys.version_info[0] >= 3:
102 config.read_file(file_obj)
103 else:
104 config.readfp(file_obj)
105 file_obj.close()
106 return config
108 def set_db_version(self, file_obj=None):
109 """ Set the database version string in the config file. """
110 if file_obj is None:
111 file_obj = self.__get_default_config_file_obj()
112 config = self._get_db_config()
113 if not config.has_section('db'):
114 config.add_section('db')
115 config.set('db', 'version', self.db_version)
116 config.write(file_obj)
117 file_obj.close()
119 def get_db_version(self):
120 """ Read and return the database version string from the config
121 file. """
122 config = self._get_db_config()
123 if not config.has_section('db'):
124 return None
125 return config.get('db', 'version')
127 def commit_and_close_database(self):
128 self.conn.commit()
129 self.cursor.close()
131 def create_assignments_table(self):
132 """ Create the assignments table that stores all assignments from
133 IPv4/IPv6/ASN to country code. Blocks are stored as first hex
134 of and first hex after the assignment. Numbers are stored
135 as hex strings, because SQLite's INTEGER type only holds up to
136 63 unsigned bits, which is not enough to store a /64 IPv6
137 block. Hex strings have leading zeros, with IPv6 addresses
138 being 33 hex characters long and IPv4 addresses and ASN being
139 9 hex characters long. The first number after an assignment
140 range is stored instead of the last number in the range to
141 facilitate comparisons with neighboring ranges. """
142 sql = ('CREATE TABLE IF NOT EXISTS assignments(start_hex TEXT, '
143 'next_start_hex TEXT, num_type TEXT, country_code TEXT, '
144 'source_type TEXT, source_name TEXT)')
145 self.cursor.execute(sql)
146 self.conn.commit()
148 def create_asn_description_table(self):
149 """ Create the assignments table that stores all the descriptions
150 associated with ASNs. """
151 sql = ('CREATE TABLE IF NOT EXISTS asn_descriptions(as_num INT, '
152 'source_name TEXT, description TEXT)')
153 self.cursor.execute(sql)
154 sql = ('CREATE INDEX IF NOT EXISTS DescriptionsByASN ON '
155 'asn_descriptions ( as_num )')
156 self.cursor.execute(sql)
157 self.conn.commit()
159 def create_asn_assignments_table(self):
160 """ Create the assignments table that stores the assignments from
161 IPv4 to ASN """
162 # XXX: IPv6 not yet supported. (Not available from routeviews?)
163 sql = ('CREATE TABLE IF NOT EXISTS asn_assignments(start_hex TEXT, '
164 'next_start_hex TEXT, num_type TEXT, as_num INT, '
165 'source_type TEXT, source_name TEXT, PRIMARY KEY(start_hex, '
166 'next_start_hex))')
167 self.cursor.execute(sql)
168 sql = ('CREATE INDEX IF NOT EXISTS ASNEntriesByStartHex on '
169 'asn_assignments ( start_hex )')
170 self.cursor.execute(sql)
171 self.conn.commit()
173 def delete_assignments(self, source_type):
174 """ Delete all assignments from the database cache matching a
175 given source type ("rir", "lir", etc.). """
176 sql = 'DELETE FROM assignments WHERE source_type = ?'
177 self.cursor.execute(sql, (source_type, ))
178 self.conn.commit()
180 def delete_asn_descriptions(self):
181 """ Delete all asn descriptions from the database cache. """
182 sql = 'DELETE FROM asn_descriptions'
183 self.cursor.execute(sql)
184 self.conn.commit()
186 def delete_asn_assignments(self):
187 """ Delete all the bgp netblock to as entries """
188 sql = 'DELETE FROM asn_assignments'
189 self.cursor.execute(sql)
190 self.conn.commit()
192 def insert_assignment(self, start_num, end_num, num_type,
193 country_code, source_type, source_name):
194 """ Insert an assignment into the database cache, without
195 commiting after the insertion. """
196 sql = ('INSERT INTO assignments (start_hex, next_start_hex, '
197 'num_type, country_code, source_type, source_name) '
198 'VALUES (?, ?, ?, ?, ?, ?)')
199 if num_type == 'ipv6':
200 start_hex = '%033x' % start_num
201 next_start_hex = '%033x' % (end_num + 1)
202 else:
203 start_hex = '%09x' % start_num
204 next_start_hex = '%09x' % (end_num + 1)
205 country_code = normalize_country_code(country_code)
206 self.cursor.execute(sql, (start_hex, next_start_hex, num_type,
207 country_code, source_type, source_name))
209 def insert_asn_description(self, asn, source_name, description):
210 sql = ('INSERT INTO asn_descriptions '
211 '(as_num, source_name, description) '
212 'VALUES (?, ?, ?)')
213 self.cursor.execute(sql, (asn, source_name, unicode(description)))
215 def insert_asn_assignment(self, start_num, end_num, num_type, asn,
216 source_type, source_name):
217 # XXX: This is sqlite specific syntax
218 sql = ('INSERT OR IGNORE INTO asn_assignments (start_hex, '
219 'next_start_hex, num_type, as_num, source_type, source_name) '
220 'VALUES (?, ?, ?, ?, ?, ?)')
221 if num_type == 'ipv6':
222 start_hex = '%033x' % start_num
223 next_start_hex = '%033x' % (end_num + 1)
224 else:
225 start_hex = '%09x' % start_num
226 next_start_hex = '%09x' % (end_num + 1)
227 self.cursor.execute(sql, (start_hex, next_start_hex, num_type, asn,
228 source_type, source_name))
230 def commit_changes(self):
231 """ Commit changes, e.g., after inserting assignments into the
232 database cache. """
233 self.conn.commit()
235 def fetch_assignments(self, num_type, country_code):
236 """ Fetch all assignments from the database cache matching the
237 given number type ("asn", "ipv4", or "ipv6") and country code.
238 The result is a sorted list of tuples containing (start_num,
239 end_num). """
240 sql = ('SELECT start_hex, next_start_hex FROM assignments '
241 'WHERE num_type = ? AND country_code = ? '
242 'ORDER BY start_hex')
243 self.cursor.execute(sql, (num_type, country_code))
244 result = []
245 for row in self.cursor:
246 result.append((long(row[0], 16), long(row[1], 16) - 1))
247 return result
249 def fetch_country_code(self, num_type, source_type, lookup_num):
250 """ Fetch the country code from the database cache that is
251 assigned to the given number (e.g., IPv4 address in decimal
252 notation), number type (e.g., "ipv4"), and source type (e.g.,
253 "rir"). """
254 sql = ('SELECT country_code FROM assignments WHERE num_type = ? '
255 'AND source_type = ? AND start_hex <= ? '
256 'AND next_start_hex > ?')
257 if num_type == 'ipv6':
258 lookup_hex = '%033x' % long(lookup_num)
259 else:
260 lookup_hex = '%09x' % long(lookup_num)
261 self.cursor.execute(sql, (num_type, source_type, lookup_hex,
262 lookup_hex))
263 row = self.cursor.fetchone()
264 if row:
265 return row[0]
267 def fetch_country_blocks_in_other_sources(self, first_country_code):
268 """ Fetch all assignments matching the given country code, then look
269 up to which country code(s) the same number ranges are assigned in
270 other source types. Return 8-tuples containing (1) first source
271 type, (2) first and (3) last number of the assignment in the first
272 source type, (4) second source type, (5) first and (6) last number
273 of the assignment in the second source type, (7) country code in
274 the second source type, and (8) number type. """
275 sql = ('SELECT first.source_type, first.start_hex, '
276 'first.next_start_hex, second.source_type, '
277 'second.start_hex, second.next_start_hex, '
278 'second.country_code, first.num_type '
279 'FROM assignments AS first '
280 'JOIN assignments AS second '
281 'WHERE first.country_code = ? '
282 'AND first.start_hex <= second.next_start_hex '
283 'AND first.next_start_hex >= second.start_hex '
284 'AND first.num_type = second.num_type '
285 'ORDER BY first.source_type, first.start_hex, '
286 'second.source_type, second.start_hex')
287 self.cursor.execute(sql, (first_country_code, ))
288 result = []
289 for row in self.cursor:
290 result.append((str(row[0]), long(row[1], 16),
291 long(row[2], 16) - 1, str(row[3]), long(row[4], 16),
292 long(row[5], 16) - 1, str(row[6]), str(row[7])))
293 return result
295 def fetch_org_by_ip_address(self, lookup_str, num_type):
296 if num_type == 'ipv4':
297 lookup_hex = '%09x' % long(int(lookup_str))
298 else:
299 lookup_hex = '%033x' % long(int(lookup_str))
300 sql = ('SELECT asn_descriptions.as_num, asn_descriptions.description, '
301 'asn_assignments.start_hex, asn_assignments.next_start_hex '
302 'FROM asn_descriptions JOIN asn_assignments ON '
303 'asn_assignments.as_num = asn_descriptions.as_num '
304 'WHERE num_type = ? AND start_hex <= ? AND next_start_hex > ?')
305 self.cursor.execute(sql, (num_type, lookup_hex, lookup_hex))
306 row = self.cursor.fetchall()
307 if row:
308 return row
310 def fetch_org_by_ip_range(self, lookup_start, lookup_end, num_type):
311 if num_type == 'ipv4':
312 lookup_start_hex = '%09x' % long(int(lookup_start))
313 lookup_end_hex = '%09x' % long(int(lookup_end))
314 else:
315 lookup_start_hex = '%033x' % long(int(lookup_start))
316 lookup_end_hex = '%033x' % long(int(lookup_end))
318 sql = ('SELECT asn_descriptions.as_num, asn_descriptions.description, '
319 'asn_assignments.start_hex, asn_assignments.next_start_hex '
320 'FROM asn_descriptions JOIN asn_assignments ON '
321 'asn_assignments.as_num = asn_descriptions.as_num '
322 'WHERE num_type = ? AND start_hex >= ? AND next_start_hex <= ?')
323 self.cursor.execute(sql, (num_type, lookup_start_hex, lookup_end_hex))
324 row = self.cursor.fetchall()
325 if row:
326 return row
328 def _concatenate_and_write(
329 self, records, write_function=None, record_filter=None, bits=32):
330 netblocks = []
331 for row in records:
332 try:
333 start_hex, next_start_hex, record = \
334 long(row[0], 16), long(row[1], 16), str(row[2])
335 nb = bits - int(log(next_start_hex - start_hex, 2))
336 net = ipaddr.IPNetwork("%s/%d" %
337 (ipaddr.IPAddress(start_hex), nb))
338 if callable(record_filter):
339 record = record_filter(record)
340 except ValueError:
341 continue
343 # Concatenate adjacent blocks of the same country
344 if netblocks and netblocks[-1][1] == record:
345 pn = netblocks[-1][0]
346 nb = bits - int(log(int(net.network) +
347 int(net.numhosts) - int(pn.network), 2))
348 netblocks[-1] = (ipaddr.IPNetwork("%s/%d" %
349 (pn.network, nb)), record)
351 # if the adjacent blocks aren't the same country,
352 # write the last block out to csv and add the new block
353 # to the list for possible concatenation
354 elif netblocks:
355 prev_n, prev_record = netblocks.pop()
356 if write_function:
357 write_function(prev_n, prev_record)
358 netblocks.append((net, record))
360 # this is the base case
361 else:
362 netblocks.append((net, record))
364 def export_asn(self, filename, num_type):
365 """ Export assignments to the CSV format used to build the
366 geoip-database asn lookup
368 sql = ('SELECT start_hex, next_start_hex, as_num '
369 'FROM asn_assignments WHERE num_type = ? ORDER BY start_hex')
370 self.cursor.execute(sql, (num_type,))
371 try:
372 f = open(filename, 'w')
373 except IOError:
374 print("Unable to open %s" % filename)
375 return
377 def write_csv_line(network, asn):
378 # XXX: wild guess
379 f.write(""""%s","%s","%d","%d","%s"\n""" % (network.network,
380 network.broadcast,
381 int(network.network),
382 int(network.broadcast),
383 asn))
384 if num_type == 'ipv6':
385 ip_bits = 128
386 elif num_type == 'ipv4':
387 ip_bits = 32
388 else:
389 return
391 self._concatenate_and_write(self.cursor, write_function=write_csv_line,
392 bits=ip_bits)
393 f.close()
395 def export_geoip(self, lookup, filename, num_type):
396 """ Export assignments to the CSV format used to build the
397 geoip-database package """
399 sql = ('SELECT start_hex, next_start_hex, country_code '
400 'FROM assignments WHERE num_type = ? ORDER BY start_hex')
401 self.cursor.execute(sql, (num_type,))
403 try:
404 f = open(filename, 'w')
405 except IOError:
406 print("Unable to open %s" % filename)
407 return
409 def write_csv_line(network, country_code):
410 country_name = lookup.get_name_from_country_code(country_code)
411 if country_name:
412 country_name = country_name.split(
413 "#")[0].strip() # Drop comments
414 f.write(""""%s","%s","%d","%d","%s","%s"\n""" % (
415 network.network,
416 network.broadcast,
417 int(network.network),
418 int(network.broadcast),
419 country_code,
420 country_name))
422 if num_type == 'ipv6':
423 ip_bits = 128
424 elif num_type == 'ipv4':
425 ip_bits = 32
426 else:
427 return
429 self._concatenate_and_write(self.cursor, write_function=write_csv_line,
430 record_filter=str.upper, bits=ip_bits)
431 f.close()
434 class DownloaderParser(object):
436 def __init__(self, cache_dir, database_cache, user_agent,
437 verbose=False):
438 self.cache_dir = cache_dir
439 self.database_cache = database_cache
440 self.user_agent = user_agent
441 self.verbose = verbose
443 MAXMIND_URLS = """
444 http://geolite.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip
445 http://geolite.maxmind.com/download/geoip/database/GeoIPv6.csv.gz
448 RIR_URLS = """
449 ftp://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
450 ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest
451 ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest
452 ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest
453 ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest
456 LIR_URLS = """
457 ftp://ftp.ripe.net/ripe/dbase/split/ripe.db.inetnum.gz
458 ftp://ftp.ripe.net/ripe/dbase/split/ripe.db.inet6num.gz
461 COUNTRY_CODE_URL = ("http://www.iso.org/iso/home/standards/country_codes/"
462 "country_names_and_code_elements_txt-temp.htm")
464 ASN_DESCRIPTION_URL = "http://www.cidr-report.org/as2.0/autnums.html"
466 ASN_ASSIGNMENT_URLS = [
467 ('http://archive.routeviews.org/oix-route-views/'
468 'oix-full-snapshot-latest.dat.bz2'),
471 def download_maxmind_files(self):
472 """ Download all LIR delegation urls. """
473 for maxmind_url in self.MAXMIND_URLS.split():
474 self._download_to_cache_dir(maxmind_url)
476 def download_rir_files(self):
477 """ Download all RIR delegation files including md5 checksum. """
478 for rir_url in self.RIR_URLS.split():
479 rir_md5_url = rir_url + '.md5'
480 self._download_to_cache_dir(rir_url)
481 self._download_to_cache_dir(rir_md5_url)
483 def download_lir_files(self):
484 """ Download all LIR delegation urls. """
485 for lir_url in self.LIR_URLS.split():
486 self._download_to_cache_dir(lir_url)
488 def download_country_code_file(self):
489 """ Download and save the latest semicolon-separated open country
490 codes file. """
491 self._download_to_cache_dir(self.COUNTRY_CODE_URL)
493 def download_asn_description_file(self):
494 """ Download and save the latest ASN to Name report from
495 cidr-report.org"""
496 self._download_to_cache_dir(self.ASN_DESCRIPTION_URL)
498 def download_asn_assignment_files(self):
499 """ Download and save the latest routing snapshots. """
500 for assignment_url in self.ASN_ASSIGNMENT_URLS:
501 self._download_to_cache_dir(assignment_url)
503 def _download_to_cache_dir(self, url):
504 """ Fetch a resource (with progress bar) and store contents to the
505 local cache directory under the file name given in the URL. """
506 if not os.path.exists(self.cache_dir):
507 if self.verbose:
508 print("Initializing the cache directory...")
509 os.mkdir(self.cache_dir)
510 filename = url.split('/')[-1]
511 if self.verbose:
512 print(url)
513 req = Request(url)
514 if self.user_agent:
515 req.add_header('User-Agent', self.user_agent)
516 # TODO Allow use of a proxy.
517 # req.set_proxy(host, type)
518 try:
519 fetcher = urlopen(req)
520 except URLError as err:
521 msg = "An error occurred while attempting to cache file from:"
522 print(("%s\n\t%s\n\t%s" % (msg, url, str(err))))
523 return
524 length_header = fetcher.headers.get("Content-Length")
525 expected_bytes = -1
526 if length_header:
527 expected_bytes = int(length_header)
528 print(("Fetching %d kilobytes" %
529 round(float(expected_bytes / 1024), 2)))
530 download_started = time.time()
531 output_file = open(os.path.join(self.cache_dir, filename), "wb")
532 received_bytes, seconds_elapsed = 0, 0
533 while True:
534 seconds_elapsed = time.time() - download_started
535 if expected_bytes >= 0:
536 self._update_progress_bar(received_bytes, expected_bytes,
537 seconds_elapsed)
538 chunk = fetcher.read(1024)
539 if len(chunk) == 0:
540 if expected_bytes >= 0 and received_bytes != expected_bytes:
541 print(("Expected %s bytes, only received %s" %
542 (expected_bytes, received_bytes)))
543 print("")
544 break
545 received_bytes += len(chunk)
546 output_file.write(chunk)
547 output_file.close()
549 def _update_progress_bar(self, received_bytes, expected_bytes,
550 seconds_elapsed):
551 """ Write a progress bar to the console. """
552 if is_win32:
553 rows = 100 # use some WinCon function for these?
554 columns = 80 # but not really important.
555 EOL = "\r"
556 else:
557 rows, columns = list(map(int, os.popen('stty size', 'r'
558 ).read().split()))
559 EOL = "\x1b[G"
560 if seconds_elapsed == 0:
561 seconds_elapsed = 1
562 percent_done = float(received_bytes) / float(expected_bytes)
563 caption = "%.2f K/s" % (received_bytes / 1024 / seconds_elapsed)
564 width = columns - 4 - len(caption)
565 sys.stdout.write("[%s>%s] %s%s" % (
566 "=" * int(percent_done * width),
567 "." * (width - int(percent_done * width)), caption, EOL))
568 sys.stdout.flush()
570 def check_rir_file_mtimes(self):
571 """ Return True if the mtime of any RIR file in our cache directory
572 is > 24 hours, False otherwise. """
573 if not os.path.exists(self.cache_dir):
574 return False
575 for rir_url in self.RIR_URLS.split():
576 rir_path = os.path.join(self.cache_dir,
577 rir_url.split('/')[-1])
578 if os.path.exists(rir_path):
579 rir_stat = os.stat(rir_path)
580 if (time.time() - rir_stat.st_mtime) > 86400:
581 return True
582 return False
584 def verify_rir_files(self):
585 """ Compute md5 checksums of all RIR files, compare them to the
586 provided .md5 files, and return True if the two checksums match,
587 or False otherwise. """
588 for rir_url in self.RIR_URLS.split():
589 rir_path = os.path.join(self.cache_dir,
590 rir_url.split('/')[-1])
591 rir_md5_path = os.path.join(self.cache_dir,
592 rir_url.split('/')[-1] + '.md5')
593 if not os.path.exists(rir_md5_path) or \
594 not os.path.exists(rir_path):
595 continue
596 rir_md5_file = open(rir_md5_path, 'r')
597 expected_checksum = rir_md5_file.read()
598 rir_md5_file.close()
599 if "=" in expected_checksum:
600 expected_checksum = expected_checksum.split("=")[-1].strip()
601 elif expected_checksum == "":
602 if self.verbose:
603 print("No checksum... skipping verification...")
604 continue
605 else:
606 regex = re.compile("[a-f0-9]{32}")
607 regres = regex.findall(expected_checksum)
608 if len(regres) > 1:
609 print("Error: mutiple checksum found")
610 elif len(regres) < 1:
611 print("Error: no checksum found")
612 else:
613 expected_checksum = regres[0]
614 computed_checksum = ""
615 rir_file = open(rir_path, 'rb')
616 rir_data = rir_file.read()
617 rir_file.close()
618 computed_checksum = str(hashlib.md5(rir_data).hexdigest())
619 if expected_checksum != computed_checksum:
620 print(("The computed md5 checksum of %s, %s, does *not* "
621 "match the provided checksum %s!" %
622 (rir_path, computed_checksum, expected_checksum)))
624 def parse_maxmind_files(self, maxmind_urls=None):
625 """ Parse locally cached MaxMind files and insert assignments to the
626 local database cache, overwriting any existing MaxMind
627 assignments. """
628 if not maxmind_urls:
629 maxmind_urls = self.MAXMIND_URLS.split()
630 self.database_cache.delete_assignments('maxmind')
631 for maxmind_url in maxmind_urls:
632 maxmind_path = os.path.join(self.cache_dir,
633 maxmind_url.split('/')[-1])
634 if not os.path.exists(maxmind_path):
635 print("Unable to find %s." % maxmind_path)
636 continue
637 if maxmind_path.endswith('.zip'):
638 maxmind_zip_path = zipfile.ZipFile(maxmind_path)
639 for contained_filename in maxmind_zip_path.namelist():
640 content = maxmind_zip_path.read(contained_filename)
641 self._parse_maxmind_content(content, 'maxmind',
642 'maxmind')
643 maxmind_zip_path.close()
644 elif maxmind_path.endswith('.gz'):
645 gzip_file = gzip.open(maxmind_path)
646 content = gzip_file.read()
647 self._parse_maxmind_content(content, 'maxmind', 'maxmind')
648 gzip_file.close()
649 self.database_cache.commit_changes()
651 def import_maxmind_file(self, maxmind_path):
652 self.database_cache.delete_assignments(maxmind_path)
653 if not os.path.exists(maxmind_path):
654 print("Unable to find %s." % maxmind_path)
655 return
656 with open(maxmind_path, 'r') as f:
657 content = f.read()
658 self._parse_maxmind_content(content, maxmind_path, maxmind_path)
659 self.database_cache.commit_changes()
661 def _parse_maxmind_content(self, content, source_type, source_name):
662 keys = ['start_str', 'end_str', 'start_num', 'end_num',
663 'country_code', 'country_name']
664 for line in content.decode('utf-8').split('\n'):
665 if len(line.strip()) == 0 or line.startswith("#"):
666 continue
667 line = line.replace('"', '').replace(' ', '').strip()
668 parts = line.split(',')
669 entry = dict((k, v) for k, v in zip(keys, parts))
670 start_num = int(entry['start_num'])
671 end_num = int(entry['end_num'])
672 country_code = str(entry['country_code'])
673 start_ipaddr = ipaddr.ip_address(entry['start_str'])
674 if isinstance(start_ipaddr, ipaddr.IPv4Address):
675 num_type = 'ipv4'
676 else:
677 num_type = 'ipv6'
678 self.database_cache.insert_assignment(
679 start_num, end_num,
680 num_type,
681 country_code,
682 source_type,
683 source_name)
685 def parse_rir_files(self, rir_urls=None):
686 """ Parse locally cached RIR files and insert assignments to the local
687 database cache, overwriting any existing RIR assignments. """
688 if not rir_urls:
689 rir_urls = self.RIR_URLS.split()
690 self.database_cache.delete_assignments('rir')
691 keys = "registry country_code type start value date status"
692 for rir_url in rir_urls:
693 rir_path = os.path.join(self.cache_dir,
694 rir_url.split('/')[-1])
695 if not os.path.exists(rir_path):
696 print("Unable to find %s." % rir_path)
697 continue
698 rir_file = open(rir_path, 'r')
699 for line in rir_file:
700 if line.startswith("#"):
701 continue
702 entry = dict((k, v) for k, v in
703 zip(keys.split(), line.strip().split("|")))
704 source_name = str(entry['registry'])
705 country_code = str(entry['country_code'])
706 if source_name.replace(
707 ".", "", 1).isdigit() or country_code == "*":
708 continue
709 num_type = entry['type']
710 if num_type == 'asn':
711 start_num = end_num = int(entry['start'])
712 elif num_type == 'ipv4':
713 start_num = int(ipaddr.IPv4Address(entry['start']))
714 end_num = start_num + int(entry['value']) - 1
715 elif num_type == 'ipv6':
716 network_str = entry['start'] + '/' + entry['value']
717 network_ipaddr = ipaddr.IPv6Network(network_str)
718 start_num = int(network_ipaddr.network_address)
719 end_num = int(network_ipaddr.broadcast_address)
720 self.database_cache.insert_assignment(
721 start_num,
722 end_num,
723 num_type,
724 country_code,
725 'rir',
726 source_name)
727 rir_file.close()
728 self.database_cache.commit_changes()
730 def parse_lir_files(self, lir_urls=None):
731 """ Parse locally cached LIR files and insert assignments to the local
732 database cache, overwriting any existing LIR assignments. """
733 if not lir_urls:
734 lir_urls = self.LIR_URLS.split()
735 self.database_cache.delete_assignments('lir')
736 for lir_url in lir_urls:
737 lir_path = os.path.join(self.cache_dir,
738 lir_url.split('/')[-1])
739 if not os.path.exists(lir_path):
740 print("Unable to find %s." % lir_path)
741 continue
742 if lir_path.endswith('.gz'):
743 lir_file = gzip.open(lir_path)
744 else:
745 lir_file = open(lir_path)
746 start_num = 0
747 end_num = 0
748 country_code = ""
749 entry = False
750 num_type = ""
751 for line in lir_file:
752 line = line.decode('utf-8', 'ignore').replace("\n", "")
753 if line == "":
754 entry = False
755 start_num, end_num, country_code, num_type = 0, 0, "", ""
756 elif not entry and "inetnum:" in line:
757 try:
758 line = line.replace("inetnum:", "").strip()
759 start_str = line.split("-")[0].strip()
760 end_str = line.split("-")[1].strip()
761 start_num = int(ipaddr.IPv4Address(start_str))
762 end_num = int(ipaddr.IPv4Address(end_str))
763 entry = True
764 num_type = 'ipv4'
765 except Exception as e:
766 if self.verbose:
767 print(repr(e), line)
768 elif not entry and "inet6num:" in line:
769 try:
770 network_str = line.replace("inet6num:", "").strip()
771 network_ipaddr = ipaddr.IPv6Network(network_str)
772 start_num = int(network_ipaddr.network_address)
773 end_num = int(network_ipaddr.broadcast_address)
774 entry = True
775 num_type = 'ipv6'
776 except Exception as e:
777 if self.verbose:
778 print(repr(e), line)
779 elif entry and "country:" in line:
780 country_code = line.replace("country:", "").strip()
781 self.database_cache.insert_assignment(
782 start_num,
783 end_num,
784 num_type,
785 country_code,
786 'lir',
787 'ripencc')
788 lir_file.close()
789 self.database_cache.commit_changes()
791 def parse_asn_description_file(self, asn_description_url=None):
792 """ Parse locally cached ASN to Description mappings and insert
793 mappings to the local database cache, overwriting any existing ASN
794 to Name assignments. """
795 if not asn_description_url:
796 asn_description_url = self.ASN_DESCRIPTION_URL
797 self.database_cache.delete_asn_descriptions()
798 asn_description_path = os.path.join(self.cache_dir,
799 asn_description_url.split('/')[-1])
800 asn_descriptions = open(asn_description_path)
801 source_name = 'cidr_report'
802 skiplen = len('<a href="/cgi-bin/as-report?as=AS')
803 for line in asn_descriptions:
804 try:
805 asn, _name = line[skiplen:].split('&view=2.0')
806 description = _name.split('</a>')[1].strip()
807 self.database_cache.insert_asn_description(asn, source_name,
808 description)
809 except ValueError:
810 pass
811 self.database_cache.commit_changes()
812 asn_descriptions.close()
814 def parse_asn_assignment_files(self, asn_assignment_urls=None):
815 if not asn_assignment_urls:
816 asn_assignment_urls = self.ASN_ASSIGNMENT_URLS
817 self.database_cache.delete_asn_assignments()
818 for asn_assignment_url in asn_assignment_urls:
819 asn_assignment_path = os.path.join(
820 self.cache_dir,
821 asn_assignment_url.split('/')[-1])
822 if not os.path.exists(asn_assignment_path):
823 print("Unable to find %s." % asn_assignment_path)
824 continue
825 if asn_assignment_path.endswith('.bz2'):
826 b = bz2.BZ2File(asn_assignment_path)
827 for line in b:
828 if line.startswith("*"):
829 l = line.split()
830 netblock, path = l[1], l[6:-1]
831 nexthop, metric, locprf, weight = l[
832 2], l[3], l[4], l[5]
834 network = ipaddr.IPNetwork(netblock)
835 # XXX add support for other sources too
836 source_type = 'bgp_snapshot'
837 source_name = 'routeviews'
839 if isinstance(network, ipaddr.IPv4Network):
840 num_type = "ipv4"
841 else:
842 num_type = "ivp6"
844 self.database_cache.insert_asn_assignment(
845 int(network.network),
846 int(network.broadcast),
847 num_type,
848 path[-1],
849 source_type,
850 source_name)
853 class Lookup(object):
855 def __init__(self, cache_dir, database_cache, verbose=False):
856 self.cache_dir = cache_dir
857 self.database_cache = database_cache
858 self.verbose = verbose
859 self.map_co = None
860 self.build_country_code_dictionary()
862 def build_country_code_dictionary(self):
863 """ Return a dictionary mapping country name to the country
864 code. """
865 country_code_path = os.path.join(
866 self.cache_dir,
867 'country_names_and_code_elements_txt-temp.htm')
868 if not os.path.exists(country_code_path):
869 return
870 self.map_co = {}
871 country_code_file = open(country_code_path, 'r')
872 for line in country_code_file:
873 if line == "" or line.startswith("Country ") or ";" not in line:
874 continue
875 country_name, country_code = line.strip().split(";")
876 country_name = ' '.join([part.capitalize() for part in
877 country_name.split(" ")])
878 self.map_co[country_name] = country_code
879 country_code_file.close()
881 def knows_country_names(self):
882 return self.map_co is not None
884 def get_name_from_country_code(self, cc_code):
885 if not self.knows_country_names():
886 return
887 country_name = [(key, value) for (key, value) in
888 list(self.map_co.items()) if value == cc_code]
889 if len(country_name) > 0:
890 return country_name[0][0]
892 def get_country_code_from_name(self, country_name):
893 """ Return the country code for a given country name. """
894 if not self.knows_country_names():
895 return
896 cc_code = [self.map_co[key] for key in list(self.map_co.keys()) if
897 key.upper().startswith(country_name.upper())]
898 if len(cc_code) > 0:
899 return cc_code[0]
901 def lookup_ipv6_address(self, lookup_ipaddr):
902 print("Reverse lookup for: " + str(lookup_ipaddr))
903 for source_type in ['maxmind', 'rir', 'lir']:
904 cc = self.database_cache.fetch_country_code(
905 'ipv6',
906 source_type,
907 int(lookup_ipaddr))
908 if cc:
909 print(source_type.upper(), "country code:", cc)
910 cn = self.get_name_from_country_code(cc)
911 if cn:
912 print(source_type.upper(), "country name:", cn)
914 def lookup_ipv4_address(self, lookup_ipaddr):
915 print("Reverse lookup for: " + str(lookup_ipaddr))
916 maxmind_cc = self.database_cache.fetch_country_code('ipv4', 'maxmind',
917 int(lookup_ipaddr))
918 if maxmind_cc:
919 print('MaxMind country code:', maxmind_cc)
920 maxmind_cn = self.get_name_from_country_code(maxmind_cc)
921 if maxmind_cn:
922 print('MaxMind country name:', maxmind_cn)
923 rir_cc = self.database_cache.fetch_country_code('ipv4', 'rir',
924 int(lookup_ipaddr))
925 if rir_cc:
926 print('RIR country code:', rir_cc)
927 rir_cn = self.get_name_from_country_code(rir_cc)
928 if rir_cn:
929 print('RIR country name:', rir_cn)
930 else:
931 print('Not found in RIR db')
932 lir_cc = self.database_cache.fetch_country_code('ipv4', 'lir',
933 int(lookup_ipaddr))
934 if lir_cc:
935 print('LIR country code:', lir_cc)
936 lir_cn = self.get_name_from_country_code(lir_cc)
937 if lir_cn:
938 print('LIR country name:', lir_cn)
939 if maxmind_cc and maxmind_cc != rir_cc:
940 print("It appears that the RIR data conflicts with MaxMind's "
941 "data. MaxMind's data is likely closer to being "
942 "correct due to sub-delegation issues with LIR databases.")
944 def lookup_ip_address(self, lookup_str):
945 """ Return the country code and name for a given ip address. """
946 try:
947 lookup_ipaddr = ipaddr.ip_address(lookup_str)
948 if isinstance(lookup_ipaddr, ipaddr.IPv4Address):
949 self.lookup_ipv4_address(lookup_ipaddr)
950 elif isinstance(lookup_ipaddr, ipaddr.IPv6Address):
951 self.lookup_ipv6_address(lookup_ipaddr)
952 else:
953 print(("Did not recognize '%s' as either IPv4 or IPv6 "
954 "address." % lookup_str))
955 except ValueError as e:
956 print("'%s' is not a valid IP address." % lookup_str)
958 def asn_lookup(self, asn):
959 asn_cc = self.database_cache.fetch_country_code('asn', 'rir', asn)
960 if asn_cc:
961 print("AS country code: %s" % asn_cc)
962 asn_cn = self.get_name_from_country_code(asn_cc)
963 if asn_cn:
964 print("AS country name: %s" % asn_cn)
965 else:
966 print("AS%s not found!" % asn)
968 def fetch_rir_blocks_by_country(self, request, country):
969 if request == "asn":
970 return [str(start_num) for (start_num, end_num) in
971 self.database_cache.fetch_assignments(request, country)]
972 if request != "ipv4" and request != "ipv6":
973 return []
974 seen = set()
975 result = []
976 for (start_num, end_num) in \
977 self.database_cache.fetch_assignments(request, country):
978 start_ipaddr = ipaddr.ip_address(start_num)
979 end_ipaddr = ipaddr.ip_address(end_num)
980 for block in (str(x) for x in
981 ipaddr.summarize_address_range(start_ipaddr,
982 end_ipaddr)):
983 if block in seen:
984 continue
985 seen.add(block)
986 result.append(block)
987 return result
989 def lookup_countries_in_different_source(self, first_country_code):
990 """ Look up all assignments matching the given country code, then
991 look up to which country code(s) the same number ranges are
992 assigned in other source types. Print out the result showing
993 similarities and differences. """
994 print(("\nLegend:\n"
995 " '<' = found assignment range with country code '%s'\n"
996 " '>' = overlapping assignment range with same country code\n"
997 " '*' = overlapping assignment range, first conflict\n"
998 " '#' = overlapping assignment range, second conflict and "
999 "beyond\n ' ' = neighboring assignment range") % (
1000 first_country_code, ))
1001 results = self.database_cache.fetch_country_blocks_in_other_sources(
1002 first_country_code)
1003 prev_first_source_type = ''
1004 prev_first_start_num = -1
1005 cur_second_country_codes = []
1006 for (first_source_type, first_start_num, first_end_num,
1007 second_source_type, second_start_num, second_end_num,
1008 second_country_code, num_type) in results:
1009 if first_source_type != prev_first_source_type:
1010 print("\nAssignments in '%s':" % (first_source_type, ))
1011 prev_first_source_type = first_source_type
1012 if first_start_num != prev_first_start_num:
1013 cur_second_country_codes = []
1014 print("")
1015 prev_first_start_num = first_start_num
1016 marker = ''
1017 if second_end_num >= first_start_num and \
1018 second_start_num <= first_end_num:
1019 if first_country_code != second_country_code and \
1020 second_country_code not in cur_second_country_codes:
1021 cur_second_country_codes.append(second_country_code)
1022 if first_source_type == second_source_type:
1023 marker = '<'
1024 elif len(cur_second_country_codes) == 0:
1025 marker = '>'
1026 elif len(cur_second_country_codes) == 1:
1027 marker = '*'
1028 else:
1029 marker = '#'
1030 if num_type.startswith("ip") and \
1031 second_start_num == second_end_num:
1032 second_range = "%s" % (ipaddr.ip_address(second_start_num), )
1033 elif num_type.startswith("ip") and \
1034 second_start_num < second_end_num:
1035 second_range = "%s-%s" % (ipaddr.ip_address(second_start_num),
1036 ipaddr.ip_address(second_end_num))
1037 elif second_start_num < second_end_num:
1038 second_range = "AS%d-%d" % (second_start_num, second_end_num)
1039 else:
1040 second_range = "AS%d" % (second_start_num, )
1041 print("%1s %s %s %s" % (marker, second_country_code, second_range,
1042 second_source_type, ))
1044 def _get_network_string_from_range(self, end, start, bits=32):
1045 start, end = int(start, 16), int(end, 16)
1046 netbits = bits - int(log(end - start, 2))
1047 return ipaddr.IPNetwork("%s/%d" % (ipaddr.IPAddress(start), netbits))
1049 def lookup_org_by_ip(self, lookup_str):
1050 """ Return the ASN and AS Description by IP """
1051 try:
1052 lookup_ipaddr = ipaddr.IPAddress(lookup_str)
1053 if isinstance(lookup_ipaddr, ipaddr.IPv4Address):
1054 num_type = 'ipv4'
1055 len_bits = 32
1056 elif isinstance(lookup_ipaddr, ipaddr.IPv6Address):
1057 num_type = 'ipv6'
1058 len_bits = 128
1059 else:
1060 raise ValueError
1061 rs = self.database_cache.fetch_org_by_ip_address(
1062 lookup_ipaddr, num_type)
1063 for r in rs:
1064 network = self._get_network_string_from_range(
1065 r[3], r[2], bits=len_bits)
1066 print("%s in %s announced by AS%s - %s" %
1067 (lookup_str, network, r[0], r[1]))
1068 except ValueError:
1069 print("'%s' is not a valid IP address." % lookup_str)
1070 except TypeError:
1071 print("Did not find any matching announcements containing %s." %
1072 lookup_str)
1074 def lookup_org_by_range(self, start_range, end_range):
1075 output_str = "%s announced by AS%s - %s"
1076 try:
1077 a = ipaddr.IPAddress(start_range)
1078 b = ipaddr.IPAddress(end_range)
1079 if isinstance(a, ipaddr.IPv4Address) and isinstance(
1080 b, ipaddr.IPv4Address):
1081 num_type = 'ipv4'
1082 len_bits = 32
1083 elif isinstance(a, ipaddr.IPv6Address) and (
1084 isinstance(b, ipaddr.IPv6Address)):
1085 num_type = 'ipv6'
1086 len_bits = 128
1087 else:
1088 raise ValueError
1089 rs = self.database_cache.fetch_org_by_ip_range(
1090 min(a, b), max(a, b), num_type)
1091 for r in rs:
1092 network = self._get_network_string_from_range(
1093 r[3], r[2], bits=len_bits)
1094 print(output_str % (network, r[0], r[1]))
1095 except ValueError:
1096 print("%s %s is not a valid IP range." % (start_range, end_range))
1097 except TypeError:
1098 print("Did not find any matching announcements in range %s %s." %
1099 (start_range, end_range))
1102 def split_callback(option, opt, value, parser):
1103 split_value = value.split(':')
1104 setattr(parser.values, option.dest, split_value[0])
1105 if len(split_value) > 1 and split_value[1] != '':
1106 setattr(parser.values, 'type_filter', split_value[1])
1109 def normalize_country_code(country_code):
1110 """ Normalize country codes a bit by making capitalization consistent and
1111 removing trailing comments (and other words). """
1112 if not country_code:
1113 return country_code
1114 country_code = re.match(r'^(\w+)', country_code).group(1)
1115 return country_code.upper()
1118 def main():
1119 """ Where the magic starts. """
1120 usage = ("Usage: %prog [options]\n\n"
1121 "Example: %prog -v -t mm")
1122 parser = optparse.OptionParser(usage)
1123 parser.add_option("-v", "--verbose", action="store_true",
1124 dest="verbose", help="be verbose", default=False)
1125 parser.add_option("-c", "--cache-dir", action="store", dest="dir",
1126 help="set cache directory [default: %default]",
1127 default=str(os.path.expanduser('~')) + "/.blockfinder/")
1128 parser.add_option("--user-agent", action="store", dest="ua",
1129 help=('provide a User-Agent which will be used when '
1130 'fetching delegation files [default: "%default"]'),
1131 default=("Mozilla/5.0 (Windows NT 6.1; rv:17.0) "
1132 "Gecko/20100101 Firefox/17.0"))
1133 parser.add_option("-x", "--hack-the-internet", action="store_true",
1134 dest="hack_the_internet", help=optparse.SUPPRESS_HELP)
1135 group = optparse.OptionGroup(
1136 parser,
1137 "Cache modes",
1138 "Pick at most one of these modes to initialize or update "
1139 "the local cache. May not be combined with lookup modes.")
1140 group.add_option(
1141 "-m",
1142 "--init-maxmind",
1143 action="store_true",
1144 dest="init_maxmind",
1145 help="initialize or update MaxMind GeoIP database")
1146 group.add_option(
1147 "-g",
1148 "--reload-maxmind",
1149 action="store_true",
1150 dest="reload_maxmind",
1151 help=("update cache from existing MaxMind GeoIP database"))
1152 group.add_option(
1153 "-r",
1154 "--import-maxmind",
1155 action="store",
1156 dest="import_maxmind",
1157 metavar="FILE",
1158 help=("import the specified MaxMind GeoIP database file into "
1159 "the database cache using its file name as source "
1160 "name"))
1161 group.add_option("-i", "--init-rir",
1162 action="store_true", dest="init_del",
1163 help="initialize or update delegation information")
1164 group.add_option(
1165 "-d",
1166 "--reload-rir",
1167 action="store_true",
1168 dest="reload_del",
1169 help="use existing delegation files to update the database")
1170 group.add_option(
1171 "-l",
1172 "--init-lir",
1173 action="store_true",
1174 dest="init_lir",
1175 help=("initialize or update lir information; can take up to "
1176 "5 minutes"))
1177 group.add_option(
1178 "-z",
1179 "--reload-lir",
1180 action="store_true",
1181 dest="reload_lir",
1182 help=("use existing lir files to update the database; can "
1183 "take up to 5 minutes"))
1184 group.add_option(
1185 "-o",
1186 "--download-cc",
1187 action="store_true",
1188 dest="download_cc",
1189 help="download country codes file")
1190 group.add_option(
1191 "-e",
1192 "--erase-cache",
1193 action="store_true",
1194 dest="erase_cache",
1195 help="erase the local database cache")
1196 group.add_option(
1197 "-j",
1198 "--init-asn-descriptions",
1199 action="store_true",
1200 dest="init_asn_descriptions",
1201 help=("initialize or update asn description information"))
1202 group.add_option(
1203 "-k",
1204 "--reload-asn-descriptions",
1205 action="store_true",
1206 dest="reload_asn_descriptions",
1207 help=("Use existing asn descriptions to update database"))
1208 group.add_option(
1209 "-y",
1210 "--init-asn-assignments",
1211 action="store_true",
1212 dest="init_asn_assignments",
1213 help=("initialize or update asn assignment information"))
1214 group.add_option(
1215 "-u",
1216 "--reload-asn-assignments",
1217 action="store_true",
1218 dest="reload_asn_assignments",
1219 help=("Use existing asn assignments to update database"))
1220 parser.add_option_group(group)
1221 group = optparse.OptionGroup(
1222 parser, "Lookup modes",
1223 "Pick at most one of these modes to look up data in the "
1224 "local cache. May not be combined with cache modes.")
1225 group.add_option(
1226 "-4",
1227 "--ipv4",
1228 action="store",
1229 dest="ipv4",
1230 help=("look up country code and name for the specified IPv4 "
1231 "address"))
1232 group.add_option(
1233 "-6",
1234 "--ipv6",
1235 action="store",
1236 dest="ipv6",
1237 help=("look up country code and name for the specified IPv6 "
1238 "address"))
1239 group.add_option(
1240 "-a",
1241 "--asn",
1242 action="store",
1243 dest="asn",
1244 help="look up country code and name for the specified ASN")
1245 group.add_option(
1246 "-t",
1247 "--code",
1248 action="callback",
1249 dest="cc",
1250 callback=split_callback,
1251 metavar="CC[:type]",
1252 type="str",
1253 help=("look up all allocations (or only those for number "
1254 "type 'ipv4', 'ipv6', or 'asn' if provided) in the "
1255 "delegation cache for the specified two-letter country "
1256 "code"))
1257 group.add_option(
1258 "-n",
1259 "--name",
1260 action="callback",
1261 dest="cn",
1262 callback=split_callback,
1263 metavar="CN[:type]",
1264 type="str",
1265 help=("look up all allocations (or only those for number "
1266 "type 'ipv4', 'ipv6', or 'asn' if provided) in the "
1267 "delegation cache for the specified full country "
1268 "name"))
1269 group.add_option(
1270 "-p",
1271 "--compare",
1272 action="store",
1273 dest="compare",
1274 metavar="CC",
1275 help=("compare assignments to the specified country code "
1276 "with overlapping assignments in other data "
1277 "sources; can take some time and produce some "
1278 "long output"))
1279 group.add_option(
1280 "-w",
1281 "--what-country",
1282 action="store",
1283 dest="what_cc",
1284 help=("look up country name for specified country code"))
1285 group.add_option(
1286 "--lookup-org-by-ip",
1287 "--lookup-org-by-ip",
1288 action="store",
1289 dest="lookup_org_by_ip",
1290 help=("look up ASN and AS Description for an IP address"))
1291 group.add_option(
1292 "--lookup-org-by-range",
1293 "--lookup-org-by-range",
1294 action="store_true",
1295 dest="lookup_org_by_range",
1296 help=("look up announced networks in a range of addresses; "
1297 "requires --range-start and --range-end to be set"))
1298 group.add_option(
1299 "--range-start",
1300 "--range-start",
1301 action="store",
1302 dest="range_start",
1303 help=("Specify the start of a range of addresses"))
1304 group.add_option(
1305 "--range-end", "--range-end",
1306 action="store",
1307 dest="range_end",
1308 help=("Specify the end of a range of addresses"))
1309 parser.add_option_group(group)
1310 group = optparse.OptionGroup(parser, "Export modes")
1311 group.add_option(
1312 "--export-geoip",
1313 "--export-geoip",
1314 action="store_true",
1315 dest="export",
1316 help=("export the lookup database to GeoIPCountryWhois.csv and "
1317 "v6.csv files in the format used to build the debian "
1318 "package geoip-database"))
1319 group.add_option(
1320 "--geoip-v4-file",
1321 "--geoip-v4-file",
1322 action="store",
1323 dest="geoip_v4_filename",
1324 help=("The filename to write the IPv4 GeoIP dataset to"))
1325 group.add_option(
1326 "--geoip-v6-file",
1327 "--geoip-v6-file",
1328 action="store",
1329 dest="geoip_v6_filename",
1330 help=("The filename to write the IPv6 GeoIP dataset to"))
1331 group.add_option(
1332 "--geoip-asn-file",
1333 "--geoip-asn-file",
1334 action="store",
1335 dest="geoip_asn_filename",
1336 help=("The filename to write the IPv4 GeoIP ASNum dataset to"))
1337 parser.add_option_group(group)
1339 group = optparse.OptionGroup(parser, "Network modes")
1340 (options, args) = parser.parse_args()
1341 if options.hack_the_internet:
1342 print("all your bases are belong to us!")
1343 sys.exit(0)
1344 options_dict = vars(options)
1345 modes = 0
1346 for mode in ["init_maxmind", "reload_maxmind", "import_maxmind",
1347 "init_del", "init_lir", "reload_del", "reload_lir",
1348 "download_cc", "erase_cache", "ipv4", "ipv6", "asn",
1349 "cc", "cn", "compare", "what_cc", "init_asn_descriptions",
1350 "reload_asn_descriptions", "init_asn_assignments",
1351 "reload_asn_assignments", "lookup_org_by_ip",
1352 "lookup_org_by_range", "export"]:
1353 if mode in options_dict and options_dict.get(mode):
1354 modes += 1
1355 if modes > 1:
1356 parser.error("only 1 cache or lookup mode allowed")
1357 elif modes == 0:
1358 parser.error("must provide 1 cache or lookup mode")
1359 database_cache = DatabaseCache(options.dir, options.verbose)
1360 if options.erase_cache:
1361 database_cache.erase_database()
1362 sys.exit(0)
1363 if not database_cache.connect_to_database():
1364 print("Could not connect to database.")
1365 print("You may need to erase it using -e and then reload it "
1366 "using -d/-z. Exiting.")
1367 sys.exit(1)
1368 database_cache.set_db_version()
1369 downloader_parser = DownloaderParser(options.dir, database_cache,
1370 options.ua)
1371 lookup = Lookup(options.dir, database_cache)
1372 if options.ipv4 or options.ipv6 or options.asn or options.cc \
1373 or options.cn or options.compare:
1374 if downloader_parser.check_rir_file_mtimes():
1375 print("Your cached RIR files are older than 24 hours; you "
1376 "probably want to update them.")
1377 if options.asn:
1378 lookup.asn_lookup(options.asn)
1379 elif options.lookup_org_by_ip:
1380 lookup.lookup_org_by_ip(options.lookup_org_by_ip)
1381 elif options.lookup_org_by_range:
1382 if not (options.range_start and options.range_end):
1383 print("You must specify the start and end addresses; "
1384 "see --range-start and --range-end")
1385 else:
1386 lookup.lookup_org_by_range(options.range_start, options.range_end)
1387 elif options.ipv4:
1388 lookup.lookup_ip_address(options.ipv4)
1389 elif options.ipv6:
1390 lookup.lookup_ip_address(options.ipv6)
1391 elif options.cc or options.cn or options.what_cc:
1392 country = None
1393 if options.cc:
1394 country = options.cc.upper()
1395 elif not lookup.knows_country_names():
1396 print("Need to download country codes first before looking "
1397 "up countries by name.")
1398 elif options.what_cc:
1399 country = options.what_cc.upper()
1400 country_name = lookup.get_name_from_country_code(country)
1401 if country_name:
1402 print(("Hmm...%s? That would be %s."
1403 % (options.what_cc, country_name)))
1404 sys.exit(0)
1405 else:
1406 print(("Hmm, %s? We're not sure either. Are you sure that's "
1407 "a country code?" % options.what_cc))
1408 sys.exit(1)
1409 else:
1410 country = lookup.get_country_code_from_name(options.cn)
1411 if not country:
1412 print("It appears your search did not match a country.")
1413 if country:
1414 types = ["ipv4", "ipv6", "asn"]
1415 if hasattr(options, 'type_filter') and \
1416 options.type_filter.lower() in types:
1417 types = [options.type_filter.lower()]
1418 for request in types:
1419 print("\n".join(lookup.fetch_rir_blocks_by_country(
1420 request, country)))
1421 elif options.compare:
1422 print("Comparing assignments with overlapping assignments in other "
1423 "data sources...")
1424 lookup.lookup_countries_in_different_source(options.compare)
1425 elif options.init_maxmind or options.reload_maxmind:
1426 if options.init_maxmind:
1427 print("Downloading Maxmind GeoIP files...")
1428 downloader_parser.download_maxmind_files()
1429 print("Importing Maxmind GeoIP files...")
1430 downloader_parser.parse_maxmind_files()
1431 elif options.import_maxmind:
1432 print("Importing Maxmind GeoIP files...")
1433 downloader_parser.import_maxmind_file(options.import_maxmind)
1434 elif options.init_del or options.reload_del:
1435 if options.init_del:
1436 print("Downloading RIR files...")
1437 downloader_parser.download_rir_files()
1438 print("Verifying RIR files...")
1439 downloader_parser.verify_rir_files()
1440 print("Importing RIR files...")
1441 downloader_parser.parse_rir_files()
1442 elif options.init_lir or options.reload_lir:
1443 if options.init_lir:
1444 print("Downloading LIR delegation files...")
1445 downloader_parser.download_lir_files()
1446 print("Importing LIR files...")
1447 downloader_parser.parse_lir_files()
1448 elif options.download_cc:
1449 print("Downloading country code file...")
1450 downloader_parser.download_country_code_file()
1451 elif options.init_asn_descriptions or options.reload_asn_descriptions:
1452 if options.init_asn_descriptions:
1453 print("Downloading ASN Descriptions...")
1454 downloader_parser.download_asn_description_file()
1455 print("Importing ASN Descriptions...")
1456 downloader_parser.parse_asn_description_file()
1457 elif options.init_asn_assignments or options.reload_asn_assignments:
1458 if options.init_asn_assignments:
1459 print("Downloading ASN Assignments...")
1460 downloader_parser.download_asn_assignment_files()
1461 print("Importing ASN Assignments...")
1462 downloader_parser.parse_asn_assignment_files()
1463 elif options.export:
1464 v4_file = options.geoip_v4_filename or "GeoIPCountryWhois.csv"
1465 v6_file = options.geoip_v6_filename or "v6.csv"
1466 asn_file = options.geoip_asn_filename or "GeoIPASNum.csv"
1467 print("Exporting GeoIP IPv4 to %s" % v4_file)
1468 database_cache.export_geoip(lookup, v4_file, 'ipv4')
1469 print("Exporting GeoIP IPv6 to %s" % v6_file)
1470 database_cache.export_geoip(lookup, v6_file, 'ipv6')
1471 print("Exporting GeoIP IPv4 ASNum to %s" % asn_file)
1472 database_cache.export_asn(asn_file, 'ipv4')
1473 # XXX: Unsupported
1474 # print("Exporting GeoIP IPv6 ASNum to %s" % asn_file)
1475 # database_cache.export_geoip(asn_file, 'ipv6')
1476 database_cache.commit_and_close_database()
1478 if __name__ == "__main__":
1479 main()