]> git.ipfire.org Git - location/libloc.git/blame - src/python/location-query.in
location-query: Allow exporting data for nftables
[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
2bb7d64e 21import gettext
4439e317
MT
22import ipaddress
23import os
24import socket
5118a4b8
MT
25import sys
26import syslog
27
28# Load our location module
29import location
30
31# i18n
2bb7d64e
MT
32def _(singular, plural=None, n=None):
33 if plural:
34 return gettext.dngettext("libloc", singular, plural, n)
35
36 return gettext.dgettext("libloc", singular)
5118a4b8 37
4439e317
MT
38# Output formatters
39
40class OutputFormatter(object):
71e0ad0b
MT
41 def __init__(self, ns):
42 self.ns = ns
43
4439e317
MT
44 def __enter__(self):
45 # Open the output
46 self.open()
47
48 return self
49
50 def __exit__(self, type, value, tb):
51 if tb is None:
52 self.close()
53
71e0ad0b
MT
54 @property
55 def name(self):
56 if "country_code" in self.ns:
57 return "networks_country_%s" % self.ns.country_code[0]
58
59 elif "asn" in self.ns:
60 return "networks_AS%s" % self.ns.asn[0]
61
4439e317
MT
62 def open(self):
63 pass
64
65 def close(self):
66 pass
67
68 def network(self, network):
69 print(network)
70
71
71e0ad0b
MT
72class NftablesOutputFormatter(OutputFormatter):
73 """
74 For nftables
75 """
76 def open(self):
77 print("define %s = {" % self.name)
78
79 def close(self):
80 print("}")
81
82 def network(self, network):
83 print(" %s," % network)
84
85
4439e317
MT
86class XTGeoIPOutputFormatter(OutputFormatter):
87 """
88 Formats the output in that way, that it can be loaded by
89 the xt_geoip kernel module from xtables-addons.
90 """
91 def network(self, network):
92 n = ipaddress.ip_network("%s" % network)
93
94 for address in (n.network_address, n.broadcast_address):
95 bytes = socket.inet_pton(
96 socket.AF_INET6 if address.version == 6 else socket.AF_INET,
97 "%s" % address,
98 )
99
100 os.write(1, bytes)
101
102
5118a4b8 103class CLI(object):
4439e317
MT
104 output_formats = {
105 "list" : OutputFormatter,
71e0ad0b 106 "nftables" : NftablesOutputFormatter,
4439e317
MT
107 "xt_geoip" : XTGeoIPOutputFormatter,
108 }
109
5118a4b8
MT
110 def parse_cli(self):
111 parser = argparse.ArgumentParser(
112 description=_("Location Database Command Line Interface"),
113 )
114 subparsers = parser.add_subparsers()
115
116 # Global configuration flags
117 parser.add_argument("--debug", action="store_true",
118 help=_("Enable debug output"))
119
ddb184be
MT
120 # version
121 parser.add_argument("--version", action="version",
122 version="%%(prog)s %s" % location.__version__)
123
2538ed9a
MT
124 # database
125 parser.add_argument("--database", "-d",
126 default="@databasedir@/database.db", help=_("Path to database"),
127 )
128
5118a4b8
MT
129 # lookup an IP address
130 lookup = subparsers.add_parser("lookup",
131 help=_("Lookup one or multiple IP addresses"),
132 )
133 lookup.add_argument("address", nargs="+")
134 lookup.set_defaults(func=self.handle_lookup)
135
fadc1af0
MT
136 # Get AS
137 get_as = subparsers.add_parser("get-as",
138 help=_("Get information about one or multiple Autonomous Systems"),
139 )
140 get_as.add_argument("asn", nargs="+")
141 get_as.set_defaults(func=self.handle_get_as)
142
da3e360e
MT
143 # Search for AS
144 search_as = subparsers.add_parser("search-as",
145 help=_("Search for Autonomous Systems that match the string"),
146 )
147 search_as.add_argument("query", nargs=1)
148 search_as.set_defaults(func=self.handle_search_as)
149
43154ed7
MT
150 # List all networks in an AS
151 list_networks_by_as = subparsers.add_parser("list-networks-by-as",
152 help=_("Lists all networks in an AS"),
153 )
154 list_networks_by_as.add_argument("asn", nargs=1, type=int)
4439e317
MT
155 list_networks_by_as.add_argument("--output-format",
156 choices=self.output_formats.keys(), default="list")
43154ed7
MT
157 list_networks_by_as.set_defaults(func=self.handle_list_networks_by_as)
158
ccc7ab4e 159 # List all networks in a country
b5cdfad7 160 list_networks_by_cc = subparsers.add_parser("list-networks-by-cc",
ccc7ab4e
MT
161 help=_("Lists all networks in a country"),
162 )
b5cdfad7 163 list_networks_by_cc.add_argument("country_code", nargs=1)
4439e317
MT
164 list_networks_by_cc.add_argument("--output-format",
165 choices=self.output_formats.keys(), default="list")
b5cdfad7 166 list_networks_by_cc.set_defaults(func=self.handle_list_networks_by_cc)
ccc7ab4e 167
78f37815
MT
168 args = parser.parse_args()
169
170 # Print usage if no action was given
171 if not "func" in args:
172 parser.print_usage()
173 sys.exit(2)
174
175 return args
5118a4b8
MT
176
177 def run(self):
178 # Parse command line arguments
179 args = self.parse_cli()
180
2538ed9a
MT
181 # Open database
182 try:
183 db = location.Database(args.database)
184 except FileNotFoundError as e:
185 sys.stderr.write("location-query: Could not open database %s: %s\n" \
186 % (args.database, e))
187 sys.exit(1)
188
5118a4b8 189 # Call function
2538ed9a 190 ret = args.func(db, args)
5118a4b8
MT
191
192 # Return with exit code
193 if ret:
194 sys.exit(ret)
195
196 # Otherwise just exit
197 sys.exit(0)
198
2538ed9a 199 def handle_lookup(self, db, ns):
5118a4b8
MT
200 ret = 0
201
202 for address in ns.address:
203 try:
2538ed9a 204 n = db.lookup(address)
5118a4b8 205 except ValueError:
9f2f5d13 206 print(_("Invalid IP address: %s") % address, file=sys.stderr)
5118a4b8
MT
207
208 args = {
209 "address" : address,
210 "network" : n,
211 }
212
213 # Nothing found?
214 if not n:
9f2f5d13 215 print(_("Nothing found for %(address)s") % args, file=sys.stderr)
5118a4b8
MT
216 ret = 1
217 continue
218
219 # Try to retrieve the AS if we have an AS number
220 if n.asn:
2538ed9a 221 a = db.get_as(n.asn)
5118a4b8
MT
222
223 # If we have found an AS we will print it in the message
224 if a:
225 args.update({
226 "as" : a,
227 })
228
229 print(_("%(address)s belongs to %(network)s which is a part of %(as)s") % args)
230 continue
231
232 print(_("%(address)s belongs to %(network)s") % args)
233
234 return ret
235
2538ed9a 236 def handle_get_as(self, db, ns):
fadc1af0
MT
237 """
238 Gets information about Autonomous Systems
239 """
240 ret = 0
241
242 for asn in ns.asn:
243 try:
244 asn = int(asn)
245 except ValueError:
9f2f5d13 246 print(_("Invalid ASN: %s") % asn, file=sys.stderr)
fadc1af0
MT
247 ret = 1
248 continue
249
250 # Fetch AS from database
2538ed9a 251 a = db.get_as(asn)
fadc1af0
MT
252
253 # Nothing found
254 if not a:
9f2f5d13 255 print(_("Could not find AS%s") % asn, file=sys.stderr)
fadc1af0
MT
256 ret = 1
257 continue
258
259 print(_("AS%(asn)s belongs to %(name)s") % { "asn" : a.number, "name" : a.name })
260
261 return ret
5118a4b8 262
2538ed9a 263 def handle_search_as(self, db, ns):
da3e360e
MT
264 for query in ns.query:
265 # Print all matches ASes
2538ed9a 266 for a in db.search_as(query):
da3e360e
MT
267 print(a)
268
4439e317
MT
269 def __get_output_formatter(self, ns):
270 try:
271 cls = self.output_formats[ns.output_format]
272 except KeyError:
273 cls = OutputFormatter
274
71e0ad0b 275 return cls(ns)
4439e317 276
43154ed7 277 def handle_list_networks_by_as(self, db, ns):
4439e317
MT
278 with self.__get_output_formatter(ns) as f:
279 for asn in ns.asn:
280 # Print all matching networks
281 for n in db.search_networks(asn=asn):
282 f.network(n)
43154ed7 283
ccc7ab4e 284 def handle_list_networks_by_cc(self, db, ns):
4439e317
MT
285 with self.__get_output_formatter(ns) as f:
286 for country_code in ns.country_code:
287 # Print all matching networks
288 for n in db.search_networks(country_code=country_code):
289 f.network(n)
290
ccc7ab4e 291
5118a4b8
MT
292def main():
293 # Run the command line interface
294 c = CLI()
295 c.run()
296
297main()