]>
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
:
197 def parse_url(self
, url
):
198 with self
.downloader
.request(url
) as r
:
200 self
.parse_block(block
)
202 def parse_block(self
, block
):
203 # Get first line to find out what type of block this is
207 if line
.startswith("inet6num:") or line
.startswith("inetnum:"):
208 return self
._parse
_inetnum
_block
(block
)
211 elif line
.startswith("route6:") or line
.startswith("route:"):
212 return self
._parse
_route
_block
(block
)
215 elif line
.startswith("aut-num:"):
216 return self
._parse
_autnum
_block
(block
)
219 elif line
.startswith("organisation:"):
220 return self
._parse
_org
_block
(block
)
223 elif line
.startswith("person:"):
227 elif line
.startswith("domain:"):
231 elif line
.startswith("mntner:"):
234 # Log any unknown blocks
236 logging
.warning("Unknown block:")
238 logging
.warning(line
)
240 def _parse_inetnum_block(self
, block
):
241 logging
.debug("Parsing inetnum block:")
248 key
, val
= util
.split_line(line
)
251 start_address
, delim
, end_address
= val
.partition("-")
253 # Strip any excess space
254 start_address
, end_address
= start_address
.rstrip(), end_address
.strip()
256 # Skip invalid blocks
257 if start_address
in INVALID_ADDRESSES
:
260 # Convert to IP address
262 start_address
= ipaddress
.ip_address(start_address
)
263 end_address
= ipaddress
.ip_address(end_address
)
265 logging
.warning("Could not parse line: %s" % line
)
268 # Set prefix to default
271 # Count number of addresses in this subnet
272 num_addresses
= int(end_address
) - int(start_address
)
274 prefix
-= math
.log(num_addresses
, 2)
276 inetnum
["inetnum"] = "%s/%.0f" % (start_address
, prefix
)
278 elif key
== "inet6num":
279 # Skip invalid blocks
280 if val
in INVALID_ADDRESSES
:
285 elif key
== "netname":
288 elif key
== "country":
289 if val
== "UNITED STATES":
292 inetnum
[key
] = val
.upper()
296 inetnum
[key
] += "\n%s" % val
306 inetnum
.get("inet6num") or inetnum
.get("inetnum"),
307 inetnum
.get("netname"),
308 inetnum
.get("country"),
309 inetnum
.get("descr"),
312 c
.execute("INSERT INTO inetnums(network, netname, country, description) \
313 VALUES(?, ?, ?, ?)", args
)
315 def _parse_route_block(self
, block
):
316 logging
.debug("Parsing route block:")
323 key
, val
= util
.split_line(line
)
325 # Keep any significant data
326 if key
in ("route6", "route"):
329 elif key
== "origin":
332 route
["asn"] = m
.group(2)
340 route
.get("route6") or route
.get("route"),
344 c
.execute("INSERT INTO routes(route, asn) \
347 def _parse_autnum_block(self
, block
):
348 logging
.debug("Parsing autnum block:")
355 key
, val
= util
.split_line(line
)
360 autnum
["asn"] = m
.group(2)
362 elif key
in ("as-name", "org"):
372 autnum
.get("as-name"),
376 c
.execute("INSERT INTO autnums(asn, name, org) \
377 VALUES(?, ?, ?)", args
)
379 def _parse_org_block(self
, block
):
380 logging
.debug("Parsing org block:")
387 key
, val
= util
.split_line(line
)
389 if key
in ("organisation", "org-name", "country"):
398 org
.get("organisation"),
403 c
.execute("INSERT INTO organisations(handle, name, country) \
404 VALUES(?, ?, ?)", args
)