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