2 # -*- coding: utf-8 -*-
4 # For the people of Smubworld!
10 from math
import floor
, log
13 from xml
.dom
import minidom
14 __program__
= 'blockfinder'
15 __url__
= 'http://github.com/ioerror/blockfinder/'
16 __author__
= 'Jacob Appelbaum <jacob@appelbaum.net>'
17 __copyright__
= 'Copyright (c) 2009'
18 __license__
= 'See LICENSE for licensing information'
22 from future
import antigravity
26 def update_progress_bar(percent_done
, caption
=""):
27 """Write a progress bar to the console"""
28 rows
, columns
= map(int, os
.popen('stty size', 'r').read().split())
29 width
= columns
- 4 - len(caption
)
30 sys
.stdout
.write("[%s>%s] %s\x1b[G" % (
31 "=" * int(percent_done
*width
),
32 "." * (width
- int(percent_done
* width
)),
36 # XXX TODO:allow the use of a proxy
37 # Set up a proper Request object, set the user agent and if desired, a proxy
38 def fetch(url
,useragent
):
39 """ Fetch (with progress meter) and return the contents of a url. """
40 req
= urllib2
.Request(url
)
41 req
.add_header('User-agent', useragent
)
42 #req.set_proxy(host, type)
43 fetcher
= urllib2
.urlopen(req
)
44 length_header
= fetcher
.headers
.get("content-length")
45 if length_header
== None:
46 raise Exception("Missing content-length header in reply from server.")
47 length
= int(length_header
)
48 print "Fetching ", str (round(float(length
/1024),2)) , " kilobytes"
52 t_delta
= time
.time() - t_start
54 float(len(ret
)) / length
,
55 "%.2f K/s" % (len(ret
) / 1024 / t_delta
) )
56 tmp
= fetcher
.read(1024)
58 if len(ret
) != length
:
59 raise Exception("Expected %s bytes, only received %s" % (
65 def cache_delegation(cache_dir
, delegation_url
, useragent
):
66 """ Attempt to cache the contents of a delegation url in our cache dir. """
72 print "Initializing the cache directory..."
77 print "Fetching " + delegation_url
78 delegation
= fetch(delegation_url
,useragent
)
79 tmp
= delegation_url
.split('/')
80 delegation_file
= str(cache_dir
) + str(tmp
[-1])
82 f
= open(delegation_file
, 'w')
90 def cache_is_dated(cache_dir
, cached_files
):
91 """ Returns True if the mtime of any files in cache dir is > 24hrs."""
95 print "\nDid you initialize the cache directory?\n"
97 for file in cached_files
:
98 fstat
= os
.stat(cache_dir
+ file)
99 if (time
.time() - fstat
.st_mtime
) > 86400:
103 def create_sql_database(cache_dir
):
104 """creates a sqlite database and if there is already an existing one it deletes it.
105 ftp://ftp.arin.net/pub/stats/arin/README"""
107 os
.remove(cache_dir
+"sqlitedb")
110 conn
= sqlite3
.connect(cache_dir
+"sqlitedb")
111 cursor
= conn
.cursor()
112 cursor
.execute("""create table asn(registry text, cc text, start text, value INTEGER, date text, status text)""")
113 cursor
.execute("""create table ipv4(registry text, cc text, start text, value INTEGER, date text, status text)""")
114 cursor
.execute("""create table ipv6(registry text, cc text, start text, value INTEGER, date text, status text)""")
118 def insert_into_sql_database(delegations
,cache_dir
):
119 """ inserts delegations into the sqlite database"""
120 conn
= sqlite3
.connect(cache_dir
+"sqlitedb")
121 cursor
= conn
.cursor()
123 for delegation
in delegations
:
124 for entry
in delegation
:
125 registry
= str(entry
['registry'])
126 if not registry
.isdigit() and str (entry
['cc']) !="*":
127 if entry
['type'] == "ipv6":
129 if entry
['type'] == "ipv4":
131 if entry
['type'] == "asn":
133 text
= """INSERT INTO """ + table
+ """ ( registry, cc, start, value, date,status) VALUES (?,?,?,?,?,?)"""
134 data
= [entry
['registry'], entry
['cc'], entry
['start'], entry
['value'], entry
['date'], entry
['status'] ]
135 cursor
.execute(text
, data
)
139 def get_total_delegations_from_db(cache_dir
):
140 """returns count of the n.o. of entries in the ipv4 +ipv6 + asn tables"""
141 conn
= sqlite3
.connect(cache_dir
+"sqlitedb")
142 cursor
= conn
.cursor()
144 table_names
= ["ipv4", "ipv6", "asn"]
145 for table
in table_names
:
146 cursor
.execute("""select count (*) from """ + table
)
147 count
+= int (cursor
.fetchone()[0] )
151 def get_possible_match_entries(cc
,cache_dir
):
152 """ get the count of 'possible' matching delegation entries"""
153 conn
= sqlite3
.connect(cache_dir
+"sqlitedb")
154 cursor
= conn
.cursor()
156 table_names
=["ipv4", "ipv6", "asn"]
157 for table
in table_names
:
158 cursor
.execute("""select count (*) from """ + table
+ """ where cc=?""",cc
)
159 count
+= int (cursor
.fetchone()[0] )
163 def use_sql_database(request
, cc
, cache_dir
):
164 """now with added magic!"""
165 conn
= sqlite3
.connect(cache_dir
+"sqlitedb")
166 cursor
= conn
.cursor()
168 print "We have %d entries in our delegation cache." %get
_total
_delegations
_from
_db
(cache_dir
)
169 text
="""select start,value from """ + request
+ """ where cc=?"""
171 cursor
.execute(text
,cc
)
173 if request
== "ipv4":
174 print str(row
[0]) + "/" + str(calculate_ipv4_subnet(int(row
[1])))
175 elif request
== "ipv6":
176 print str(row
[0]) + "/" + str(int(row
[1]))
178 print str(int(row
[0]) )
180 print "We found %d possible entries in our delegation cache." % get_possible_match_entries(cc
, cache_dir
)
181 cursor
.execute("""select count(*) from """ + request
+ """ where cc=?""", cc
)
182 print "We found %d matching entries in our delegation cache." % int (cursor
.fetchone()[0] )
185 def get_md5_from_delegation_md5_file(cache_dir
, delegation_file
):
186 """ Returns the md5sum from the delegation md5 file (if it exists)"""
189 f
= open(cache_dir
+ delegation_file
+".md5", "r")
192 if delegation_file
== "delegated-afrinic-latest":
193 pos
= checksum
.find(" ")
194 assert pos
< len(checksum
)
195 checksum
= str ( checksum
[:pos
] )
197 pos
= checksum
.find("=") +2
198 assert pos
< len(checksum
)
199 checksum
= str ( checksum
[pos
:-1] )
204 def verify_delegation_file(cache_dir
, delegation_file
):
205 """compares the delegation file md5sum to that of the provided md5sum
206 returns True if they match otherwise returns False"""
208 checksum_of_file
= ""
210 f
= open(cache_dir
+ delegation_file
, "rb")
211 checksum_of_file
= str (hashlib
.md5(f
.read()).hexdigest() )
215 checksum
= get_md5_from_delegation_md5_file(cache_dir
,delegation_file
)
216 if checksum
!= checksum_of_file
:
218 if checksum
== checksum_of_file
and checksum
!= "":
221 def verify_cache(cache_dir
, delegation_files
):
222 """ if in verbose mode prints the result of checking the checksum of the
224 for file in delegation_files
:
226 print "verifying " + file
227 if verify_delegation_file(cache_dir
,file):
229 print "the md5 checksum of " + file + " *matches* the provided checksum"
232 print "the md5 checksum of " + file + " does *not* match the provided checksum"
234 def update_delegation_cache(cache_dir
, delegation_urls
, useragent
):
235 """ Fetch multiple delegation urls and cache the contents. """
236 print "Updating delegation cache..."
237 for url
in delegation_urls
.split():
238 cache_delegation(cache_dir
, url
+".md5",useragent
)
239 if verify_delegation_file(cache_dir
, url
.rpartition('/')[-1]):
242 cache_delegation(cache_dir
, url
,useragent
)
245 def load_delegation(delegation_file
):
246 """ Load, parse and store the delegation file contents as a list. """
247 keys
= "registry cc type start value date status"
249 f
= open(delegation_file
, "r")
250 delegations
= [ dict((k
,v
) for k
,v
in zip(keys
.split(), line
.split("|")))
251 for line
in f
.readlines() if not line
.startswith("#")]
257 def load_all_delegations(cache_dir
, delegation_urls
):
258 """ Load all delegations into memory. """
260 for url
in delegation_urls
.split():
261 filename
= url
.rpartition('/')[-1]
263 print "Attempting to load delegation file into memory: " + filename
264 delegations
.append(load_delegation(cache_dir
+ filename
))
267 def calculate_ipv4_subnet(host_count
):
268 return 32 - int(floor(log(host_count
,2)))
270 def download_country_code_file(cache_dir
, useragent
):
271 """ Download and save the latest opencountrycode XML file """
272 # Google frontend will not return content-length for some reason...
273 url
= "http://opencountrycodes.appspot.com/xml"
274 ul
= urllib2
.urlopen(url
)
277 f
= open(cache_dir
+ "countrycodes.xml",'w')
285 def build_country_code_dictionary(cache_dir
):
286 """ Return a dictionary mapping country name to the country code"""
288 xml_file
= str(cache_dir
) + "countrycodes.xml"
289 clist
= minidom
.parse(xml_file
)
290 for country
in clist
.getElementsByTagName("country"):
291 code
= country
.attributes
["code"]
292 name
= country
.attributes
["name"]
293 map[name
.value
] = code
.value
296 def get_country_code_from_name(cache_dir
,country_name
):
297 """ Return the country code for a given country name. """
298 map = build_country_code_dictionary(cache_dir
)
299 for name
,code
in map.iteritems():
300 if name
.lower() == country_name
.lower():
306 """ Print usage information. """
307 print >> sys
.stderr
, """
308 blockfinder [-c DIR] -i
309 blockfinder [options] -t COUNTRY
311 The first form initializes the local cache. The second form queries it.
313 Understood options (not all of which are implemented yet):
314 -h, --help Show this help and exit
316 -c, --cachedir DIR Set the cache directory
320 -4, --ipv4 Search IPv4 allocations
321 -6, --ipv6 Search IPv6 allocation
322 -a, --asn Search ASN allocations
323 -t, --nation-state CC Set the country to search (given as a two-letter code)
324 -n, --country-name "Costa Rica" Set country to search (full name)
326 At least one of -t or -i is required, and when in -t mode, at least one of -4,
327 -6, and -a is required in order to do anything sensible.
331 """ Where the magic starts. """
333 opts
, args
= getopt
.getopt(sys
.argv
[1:],
334 "vhc:u:pso:46at:n:i",
335 ["verbose", "help", "cachedir=", "useragent=", "progress",
336 "silent", "output=", "ipv4", "ipv6", "asn", "nation-state=",
337 "country-name", "initialize-delegation"])
338 except getopt
.GetoptError
, err
:
347 cache_dir
= str(os
.path
.expanduser('~')) + "/.blockfinder/"
348 update_delegations
= False
349 delegation_urls
= """
350 ftp://ftp.arin.net/pub/stats/arin/delegated-arin-latest
351 ftp://ftp.ripe.net/ripe/stats/delegated-ripencc-latest
352 ftp://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-latest
353 ftp://ftp.apnic.net/pub/stats/apnic/delegated-apnic-latest
354 ftp://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-latest
356 delegation_files
= []
357 for url
in delegation_urls
.split():
358 filename
= url
.rpartition('/')
359 delegation_files
.append(filename
[-1])
360 update_delegations
= False
363 useragent
= "Mozilla/5.0"
365 if not os
.path
.exists(cache_dir
+ "countrycodes.xml"):
366 download_country_code_file(cache_dir
,useragent
)
371 elif o
in ("-h", "--help"):
374 elif o
in ("-c", "--cachedir"):
376 elif o
in ("-u", "--useragent"):
378 elif o
in ("-p", "--progress"):
380 elif o
in ("-s", "--silent"):
382 elif o
in ("-o", "--output"):
384 elif o
in ("-4", "--ipv4"):
385 requests
.append("ipv4")
386 elif o
in ("-6", "--ipv6"):
387 requests
.append("ipv6")
388 elif o
in ("-a", "--asn"):
389 requests
.append("asn")
390 # XXX TODO: This should be a positional argument as it's the only manditory one...
391 elif o
in ("-t", "--nation-state"):
393 elif o
in ("-n", "--country-name"):
394 country
= get_country_code_from_name(cache_dir
,a
)
395 elif o
in ("-i", "--initialize-delegations"):
396 update_delegations
= True
398 assert False, "Unhandled option; Sorry!"
401 if update_delegations
:
402 update_delegation_cache(cache_dir
,delegation_urls
,useragent
)
404 verify_cache(cache_dir
, delegation_files
)
405 delegations
= load_all_delegations(cache_dir
, delegation_urls
)
406 create_sql_database(cache_dir
)
407 insert_into_sql_database(delegations
, cache_dir
)
410 print "Nothing to do. Have you requested anything?"
411 print "Example usage: blockfinder -v --ipv4 -t mm"
413 # Check our cache age and warn if it's aged
414 if cache_is_dated(cache_dir
, delegation_files
) and verbose
:
415 print "Your delegation cache is older than 24 hours; you probably want to update it."
417 print "Using country code %s" % country
418 for request
in requests
:
419 use_sql_database(request
, country
, cache_dir
)
421 if __name__
== "__main__":