]>
git.ipfire.org Git - location/location-database.git/blob - tools/base.py
2 ###############################################################################
4 # location-database - A database to determine someone's #
5 # location on the Internet #
6 # Copyright (C) 2018 Michael Tremer #
8 # This program is free software: you can redistribute it and/or modify #
9 # it under the terms of the GNU General Public License as published by #
10 # the Free Software Foundation, either version 3 of the License, or #
11 # (at your option) any later version. #
13 # This program is distributed in the hope that it will be useful, #
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
16 # GNU General Public License for more details. #
18 # You should have received a copy of the GNU General Public License #
19 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
21 ###############################################################################
31 from . import downloader
34 FILENAME_ASNUMS
= "asnums.txt"
35 FILENMAE_NETWORKS
= "networks.txt"
39 RE_AS
= re
.compile(r
"^(AS|as)(\d+)")
56 return "%s - %s" % (self
.__class
__.__name
__, self
.name
)
58 return self
.__class
__.__name
__
64 def make_path(self
, path
):
65 return os
.path
.join(self
.__class
__.__name
__, path
)
68 def filename_asnums(self
):
69 return self
.make_path(FILENAME_ASNUMS
)
72 def filename_networks(self
):
73 return self
.make_path(FILENMAE_NETWORKS
)
75 def update(self
, directory
):
78 # Download all data and store it in memory
81 # Write the database to disk
82 p
.export_database(directory
)
85 class RIRParser(object):
86 def __init__(self
, rir
):
89 # Create a downloader to fetch data
90 self
.downloader
= downloader
.Downloader()
92 # Create a database to hold temporary data
93 self
.db
= self
._make
_database
(":memory:")
96 self
.start_time
= datetime
.datetime
.utcnow()
98 def _make_database(self
, filename
):
99 db
= sqlite3
.connect(filename
)
101 # Create database layout
103 cursor
.executescript("""
104 CREATE TABLE IF NOT EXISTS autnums(asn INTEGER, name TEXT, org TEXT);
106 CREATE TABLE IF NOT EXISTS inetnums(network TEXT, netname TEXT, country TEXT, description TEXT);
108 CREATE TABLE IF NOT EXISTS organisations(handle TEXT, name TEXT, country TEXT);
109 CREATE INDEX IF NOT EXISTS organisations_handle ON organisations(handle);
111 CREATE TABLE IF NOT EXISTS routes(route TEXT, asn INTEGER);
112 CREATE INDEX IF NOT EXISTS routes_route ON routes(route);
117 def export_database(self
, directory
):
119 with
open(self
.rir
.filename_asnums
, "w") as f
:
120 self
._export
_asnums
(f
)
123 with
open(self
.rir
.filename_networks
, "w") as f
:
124 self
._export
_networks
(f
)
126 def _export_asnums(self
, f
):
128 self
._write
_header
(f
)
131 res
= c
.execute("""SELECT DISTINCT autnums.asn, autnums.name,
132 organisations.name, organisations.country FROM autnums
133 LEFT JOIN organisations ON autnums.org = organisations.handle
134 WHERE autnums.asn IS NOT NULL ORDER BY autnums.asn""")
137 f
.write(FMT
% ("asnum:", "AS%s" % row
[0]))
140 f
.write(FMT
% ("name:", row
[1]))
143 f
.write(FMT
% ("org:", row
[2]))
146 f
.write(FMT
% ("country:", row
[3]))
151 def _export_networks(self
, f
):
153 self
._write
_header
(f
)
157 res
= c
.execute("""SELECT inetnums.network, routes.asn,
158 inetnums.country, inetnums.netname, inetnums.description
159 FROM inetnums LEFT JOIN routes ON inetnums.network = routes.route
160 ORDER BY routes.asn, inetnums.network""")
163 net
, asn
, country
, name
, description
= row
165 f
.write(FMT
% ("net:", net
))
168 f
.write(FMT
% ("name:", name
))
171 f
.write(FMT
% ("asnum:", "AS%s" % asn
))
174 f
.write(FMT
% ("country:", country
))
177 for line
in description
.splitlines():
178 f
.write(FMT
% ("descr:", line
))
183 def _write_header(self
, f
):
185 f
.write("# %s\n" % self
.rir
)
186 f
.write("# Generated at %s\n" % self
.start_time
)
189 def fetch_data(self
):
190 if not self
.rir
.database_urls
:
191 raise NotImplementedError("Database URLs not set")
193 # Parse entire database in one go
194 for url
in self
.rir
.database_urls
:
199 def parse_url(self
, url
):
200 with self
.downloader
.request(url
) as r
:
202 self
.parse_block(block
)
204 def parse_block(self
, block
):
205 # Get first line to find out what type of block this is
209 if line
.startswith("inet6num:") or line
.startswith("inetnum:"):
210 return self
._parse
_inetnum
_block
(block
)
213 elif line
.startswith("route6:") or line
.startswith("route:"):
214 return self
._parse
_route
_block
(block
)
217 elif line
.startswith("aut-num:"):
218 return self
._parse
_autnum
_block
(block
)
221 elif line
.startswith("organisation:"):
222 return self
._parse
_org
_block
(block
)
225 elif line
.startswith("person:"):
229 elif line
.startswith("domain:"):
233 elif line
.startswith("mntner:"):
237 elif line
.startswith("as-block:"):
241 elif line
.startswith("as-set:"):
244 # route-set (ignored)
245 elif line
.startswith("route-set:"):
249 elif line
.startswith("role:"):
253 elif line
.startswith("key-cert:"):
257 elif line
.startswith("irt:"):
260 # Log any unknown blocks
262 logging
.warning("Unknown block:")
264 logging
.warning(line
)
266 def _parse_inetnum_block(self
, block
):
267 logging
.debug("Parsing inetnum block:")
274 key
, val
= util
.split_line(line
)
277 start_address
, delim
, end_address
= val
.partition("-")
279 # Strip any excess space
280 start_address
, end_address
= start_address
.rstrip(), end_address
.strip()
282 # Skip invalid blocks
283 if start_address
in INVALID_ADDRESSES
:
286 # Convert to IP address
288 start_address
= ipaddress
.ip_address(start_address
)
289 end_address
= ipaddress
.ip_address(end_address
)
291 logging
.warning("Could not parse line: %s" % line
)
294 # Set prefix to default
297 # Count number of addresses in this subnet
298 num_addresses
= int(end_address
) - int(start_address
)
300 prefix
-= math
.log(num_addresses
, 2)
302 inetnum
["inetnum"] = "%s/%.0f" % (start_address
, prefix
)
304 elif key
== "inet6num":
305 # Skip invalid blocks
306 if val
in INVALID_ADDRESSES
:
311 elif key
== "netname":
314 elif key
== "country":
315 if val
== "UNITED STATES":
318 inetnum
[key
] = val
.upper()
322 inetnum
[key
] += "\n%s" % val
332 inetnum
.get("inet6num") or inetnum
.get("inetnum"),
333 inetnum
.get("netname"),
334 inetnum
.get("country"),
335 inetnum
.get("descr"),
338 c
.execute("INSERT INTO inetnums(network, netname, country, description) \
339 VALUES(?, ?, ?, ?)", args
)
341 def _parse_route_block(self
, block
):
342 logging
.debug("Parsing route block:")
349 key
, val
= util
.split_line(line
)
351 # Keep any significant data
352 if key
in ("route6", "route"):
355 elif key
== "origin":
358 route
["asn"] = m
.group(2)
366 route
.get("route6") or route
.get("route"),
370 c
.execute("INSERT INTO routes(route, asn) \
373 def _parse_autnum_block(self
, block
):
374 logging
.debug("Parsing autnum block:")
381 key
, val
= util
.split_line(line
)
386 autnum
["asn"] = m
.group(2)
388 elif key
in ("as-name", "org"):
398 autnum
.get("as-name"),
402 c
.execute("INSERT INTO autnums(asn, name, org) \
403 VALUES(?, ?, ?)", args
)
405 def _parse_org_block(self
, block
):
406 logging
.debug("Parsing org block:")
413 key
, val
= util
.split_line(line
)
415 if key
in ("organisation", "org-name", "country"):
424 org
.get("organisation"),
429 c
.execute("INSERT INTO organisations(handle, name, country) \
430 VALUES(?, ?, ?)", args
)