]> git.ipfire.org Git - location/libloc.git/blame - src/python/location-query.in
python: Use override flags when exporting the database
[location/libloc.git] / src / python / location-query.in
CommitLineData
5118a4b8
MT
1#!/usr/bin/python3
2###############################################################################
3# #
4# libloc - A library to determine the location of someone on the Internet #
5# #
6# Copyright (C) 2017 IPFire Development Team <info@ipfire.org> #
7# #
8# This library is free software; you can redistribute it and/or #
9# modify it under the terms of the GNU Lesser General Public #
10# License as published by the Free Software Foundation; either #
11# version 2.1 of the License, or (at your option) any later version. #
12# #
13# This library 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 GNU #
16# Lesser General Public License for more details. #
17# #
18###############################################################################
19
20import argparse
4439e317
MT
21import ipaddress
22import os
23import socket
5118a4b8 24import sys
a68a46f5 25import time
5118a4b8
MT
26
27# Load our location module
28import location
7dccb767 29from location.i18n import _
5118a4b8 30
4439e317
MT
31# Output formatters
32
33class OutputFormatter(object):
71e0ad0b
MT
34 def __init__(self, ns):
35 self.ns = ns
36
4439e317
MT
37 def __enter__(self):
38 # Open the output
39 self.open()
40
41 return self
42
43 def __exit__(self, type, value, tb):
44 if tb is None:
45 self.close()
46
71e0ad0b
MT
47 @property
48 def name(self):
49 if "country_code" in self.ns:
50 return "networks_country_%s" % self.ns.country_code[0]
51
52 elif "asn" in self.ns:
53 return "networks_AS%s" % self.ns.asn[0]
54
4439e317
MT
55 def open(self):
56 pass
57
58 def close(self):
59 pass
60
61 def network(self, network):
62 print(network)
63
64
6da14cc1
MT
65class IpsetOutputFormatter(OutputFormatter):
66 """
67 For nftables
68 """
69 def open(self):
70 print("create %s hash:net family inet hashsize 1024 maxelem 65536" % self.name)
71
72 def network(self, network):
73 print("add %s %s" % (self.name, network))
74
75
71e0ad0b
MT
76class NftablesOutputFormatter(OutputFormatter):
77 """
78 For nftables
79 """
80 def open(self):
81 print("define %s = {" % self.name)
82
83 def close(self):
84 print("}")
85
86 def network(self, network):
87 print(" %s," % network)
88
89
4439e317
MT
90class XTGeoIPOutputFormatter(OutputFormatter):
91 """
92 Formats the output in that way, that it can be loaded by
93 the xt_geoip kernel module from xtables-addons.
94 """
95 def network(self, network):
96 n = ipaddress.ip_network("%s" % network)
97
98 for address in (n.network_address, n.broadcast_address):
99 bytes = socket.inet_pton(
100 socket.AF_INET6 if address.version == 6 else socket.AF_INET,
101 "%s" % address,
102 )
103
104 os.write(1, bytes)
105
106
5118a4b8 107class CLI(object):
4439e317 108 output_formats = {
6da14cc1 109 "ipset" : IpsetOutputFormatter,
4439e317 110 "list" : OutputFormatter,
71e0ad0b 111 "nftables" : NftablesOutputFormatter,
4439e317
MT
112 "xt_geoip" : XTGeoIPOutputFormatter,
113 }
114
5118a4b8
MT
115 def parse_cli(self):
116 parser = argparse.ArgumentParser(
117 description=_("Location Database Command Line Interface"),
118 )
119 subparsers = parser.add_subparsers()
120
121 # Global configuration flags
122 parser.add_argument("--debug", action="store_true",
123 help=_("Enable debug output"))
124
ddb184be
MT
125 # version
126 parser.add_argument("--version", action="version",
d2714e4a 127 version="%(prog)s @VERSION@")
ddb184be 128
2538ed9a
MT
129 # database
130 parser.add_argument("--database", "-d",
131 default="@databasedir@/database.db", help=_("Path to database"),
132 )
133
726f9984
MT
134 # public key
135 parser.add_argument("--public-key", "-k",
136 default="@databasedir@/signing-key.pem", help=_("Public Signing Key"),
137 )
138
5118a4b8
MT
139 # lookup an IP address
140 lookup = subparsers.add_parser("lookup",
141 help=_("Lookup one or multiple IP addresses"),
142 )
143 lookup.add_argument("address", nargs="+")
144 lookup.set_defaults(func=self.handle_lookup)
145
a68a46f5
MT
146 # Dump the whole database
147 dump = subparsers.add_parser("dump",
148 help=_("Dump the entire database"),
149 )
150 dump.add_argument("output", nargs="?", type=argparse.FileType("w"))
151 dump.set_defaults(func=self.handle_dump)
152
fadc1af0
MT
153 # Get AS
154 get_as = subparsers.add_parser("get-as",
155 help=_("Get information about one or multiple Autonomous Systems"),
156 )
157 get_as.add_argument("asn", nargs="+")
158 get_as.set_defaults(func=self.handle_get_as)
159
da3e360e
MT
160 # Search for AS
161 search_as = subparsers.add_parser("search-as",
162 help=_("Search for Autonomous Systems that match the string"),
163 )
164 search_as.add_argument("query", nargs=1)
165 search_as.set_defaults(func=self.handle_search_as)
166
43154ed7
MT
167 # List all networks in an AS
168 list_networks_by_as = subparsers.add_parser("list-networks-by-as",
169 help=_("Lists all networks in an AS"),
170 )
171 list_networks_by_as.add_argument("asn", nargs=1, type=int)
44e5ef71 172 list_networks_by_as.add_argument("--family", choices=("ipv6", "ipv4"))
4439e317
MT
173 list_networks_by_as.add_argument("--output-format",
174 choices=self.output_formats.keys(), default="list")
43154ed7
MT
175 list_networks_by_as.set_defaults(func=self.handle_list_networks_by_as)
176
ccc7ab4e 177 # List all networks in a country
b5cdfad7 178 list_networks_by_cc = subparsers.add_parser("list-networks-by-cc",
ccc7ab4e
MT
179 help=_("Lists all networks in a country"),
180 )
b5cdfad7 181 list_networks_by_cc.add_argument("country_code", nargs=1)
44e5ef71 182 list_networks_by_cc.add_argument("--family", choices=("ipv6", "ipv4"))
4439e317
MT
183 list_networks_by_cc.add_argument("--output-format",
184 choices=self.output_formats.keys(), default="list")
b5cdfad7 185 list_networks_by_cc.set_defaults(func=self.handle_list_networks_by_cc)
ccc7ab4e 186
bbdb2e0a
MT
187 # List all networks with flags
188 list_networks_by_flags = subparsers.add_parser("list-networks-by-flags",
189 help=_("Lists all networks with flags"),
190 )
191 list_networks_by_flags.add_argument("--anonymous-proxy",
192 action="store_true", help=_("Anonymous Proxies"),
193 )
194 list_networks_by_flags.add_argument("--satellite-provider",
195 action="store_true", help=_("Satellite Providers"),
196 )
197 list_networks_by_flags.add_argument("--anycast",
198 action="store_true", help=_("Anycasts"),
199 )
44e5ef71 200 list_networks_by_flags.add_argument("--family", choices=("ipv6", "ipv4"))
bbdb2e0a
MT
201 list_networks_by_flags.add_argument("--output-format",
202 choices=self.output_formats.keys(), default="list")
203 list_networks_by_flags.set_defaults(func=self.handle_list_networks_by_flags)
204
78f37815
MT
205 args = parser.parse_args()
206
207 # Print usage if no action was given
208 if not "func" in args:
209 parser.print_usage()
210 sys.exit(2)
211
212 return args
5118a4b8
MT
213
214 def run(self):
215 # Parse command line arguments
216 args = self.parse_cli()
217
2538ed9a
MT
218 # Open database
219 try:
220 db = location.Database(args.database)
221 except FileNotFoundError as e:
222 sys.stderr.write("location-query: Could not open database %s: %s\n" \
223 % (args.database, e))
224 sys.exit(1)
225
6961aaf3
MT
226 # Translate family (if present)
227 if "family" in args:
228 if args.family == "ipv6":
229 args.family = socket.AF_INET6
230 elif args.family == "ipv4":
231 args.family = socket.AF_INET
232 else:
233 args.family = 0
44e5ef71 234
5118a4b8 235 # Call function
2538ed9a 236 ret = args.func(db, args)
5118a4b8
MT
237
238 # Return with exit code
239 if ret:
240 sys.exit(ret)
241
242 # Otherwise just exit
243 sys.exit(0)
244
2538ed9a 245 def handle_lookup(self, db, ns):
5118a4b8
MT
246 ret = 0
247
248 for address in ns.address:
249 try:
2538ed9a 250 n = db.lookup(address)
5118a4b8 251 except ValueError:
9f2f5d13 252 print(_("Invalid IP address: %s") % address, file=sys.stderr)
5118a4b8
MT
253
254 args = {
255 "address" : address,
256 "network" : n,
257 }
258
259 # Nothing found?
260 if not n:
9f2f5d13 261 print(_("Nothing found for %(address)s") % args, file=sys.stderr)
5118a4b8
MT
262 ret = 1
263 continue
264
265 # Try to retrieve the AS if we have an AS number
266 if n.asn:
2538ed9a 267 a = db.get_as(n.asn)
5118a4b8
MT
268
269 # If we have found an AS we will print it in the message
270 if a:
271 args.update({
272 "as" : a,
273 })
274
275 print(_("%(address)s belongs to %(network)s which is a part of %(as)s") % args)
276 continue
277
278 print(_("%(address)s belongs to %(network)s") % args)
279
280 return ret
281
a68a46f5
MT
282 def handle_dump(self, db, ns):
283 # Use output file or write to stdout
284 f = ns.output or sys.stdout
285
286 # Write metadata
287 f.write("#\n# Location Database Export\n#\n")
288
289 f.write("# Generated: %s\n" % time.strftime(
290 "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(db.created_at),
291 ))
292
293 if db.vendor:
294 f.write("# Vendor: %s\n" % db.vendor)
295
296 if db.license:
297 f.write("# License: %s\n" % db.license)
298
299 f.write("#\n")
300
301 if db.description:
302 for line in db.description.splitlines():
303 f.write("# %s\n" % line)
304
305 f.write("#\n")
306
307 # Iterate over all ASes
308 for a in db.ases:
309 f.write("\n")
310 f.write("aut-num: AS%s\n" % a.number)
311 f.write("name: %s\n" % a.name)
312
313 # Iterate over all networks
314 for n in db.networks:
315 f.write("\n")
316 f.write("net: %s\n" % n)
317
318 if n.country_code:
319 f.write("country: %s\n" % n.country_code)
320
321 if n.asn:
322 f.write("autnum: %s\n" % n.asn)
323
2538ed9a 324 def handle_get_as(self, db, ns):
fadc1af0
MT
325 """
326 Gets information about Autonomous Systems
327 """
328 ret = 0
329
330 for asn in ns.asn:
331 try:
332 asn = int(asn)
333 except ValueError:
9f2f5d13 334 print(_("Invalid ASN: %s") % asn, file=sys.stderr)
fadc1af0
MT
335 ret = 1
336 continue
337
338 # Fetch AS from database
2538ed9a 339 a = db.get_as(asn)
fadc1af0
MT
340
341 # Nothing found
342 if not a:
9f2f5d13 343 print(_("Could not find AS%s") % asn, file=sys.stderr)
fadc1af0
MT
344 ret = 1
345 continue
346
347 print(_("AS%(asn)s belongs to %(name)s") % { "asn" : a.number, "name" : a.name })
348
349 return ret
5118a4b8 350
2538ed9a 351 def handle_search_as(self, db, ns):
da3e360e
MT
352 for query in ns.query:
353 # Print all matches ASes
2538ed9a 354 for a in db.search_as(query):
da3e360e
MT
355 print(a)
356
4439e317
MT
357 def __get_output_formatter(self, ns):
358 try:
359 cls = self.output_formats[ns.output_format]
360 except KeyError:
361 cls = OutputFormatter
362
71e0ad0b 363 return cls(ns)
4439e317 364
43154ed7 365 def handle_list_networks_by_as(self, db, ns):
4439e317
MT
366 with self.__get_output_formatter(ns) as f:
367 for asn in ns.asn:
368 # Print all matching networks
44e5ef71 369 for n in db.search_networks(asn=asn, family=ns.family):
4439e317 370 f.network(n)
43154ed7 371
ccc7ab4e 372 def handle_list_networks_by_cc(self, db, ns):
4439e317
MT
373 with self.__get_output_formatter(ns) as f:
374 for country_code in ns.country_code:
375 # Print all matching networks
44e5ef71 376 for n in db.search_networks(country_code=country_code, family=ns.family):
4439e317
MT
377 f.network(n)
378
bbdb2e0a
MT
379 def handle_list_networks_by_flags(self, db, ns):
380 flags = 0
381
382 if ns.anonymous_proxy:
383 flags |= location.NETWORK_FLAG_ANONYMOUS_PROXY
384
385 if ns.satellite_provider:
386 flags |= location.NETWORK_FLAG_SATELLITE_PROVIDER
387
388 if ns.anycast:
389 flags |= location.NETWORK_FLAG_ANYCAST
390
391 with self.__get_output_formatter(ns) as f:
44e5ef71 392 for n in db.search_networks(flags=flags, family=ns.family):
bbdb2e0a
MT
393 f.network(n)
394
ccc7ab4e 395
5118a4b8
MT
396def main():
397 # Run the command line interface
398 c = CLI()
399 c.run()
400
401main()