1 ###############################################################################
3 # libloc - A library to determine the location of someone on the Internet #
5 # Copyright (C) 2020-2021 IPFire Development Team <info@ipfire.org> #
7 # This library is free software; you can redistribute it and/or #
8 # modify it under the terms of the GNU Lesser General Public #
9 # License as published by the Free Software Foundation; either #
10 # version 2.1 of the License, or (at your option) any later version. #
12 # This library is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU #
15 # Lesser General Public License for more details. #
17 ###############################################################################
31 log
= logging
.getLogger("location.export")
35 _location
.NETWORK_FLAG_ANONYMOUS_PROXY
: "A1",
36 _location
.NETWORK_FLAG_SATELLITE_PROVIDER
: "A2",
37 _location
.NETWORK_FLAG_ANYCAST
: "A3",
38 _location
.NETWORK_FLAG_DROP
: "XD",
41 class OutputWriter(object):
45 def __init__(self
, name
, family
=None, directory
=None, f
=None):
48 self
.directory
= directory
51 self
.tag
= self
._make
_tag
()
57 self
.f
= open(self
.filename
, self
.mode
)
58 elif "b" in self
.mode
:
61 self
.f
= io
.StringIO()
63 # Call any custom initialization
66 # Immediately write the header
71 To be overwritten by anything that inherits from this
76 return "<%s %s f=%s>" % (self
.__class
__.__name
__, self
, self
.f
)
80 socket
.AF_INET6
: "6",
84 return "%sv%s" % (self
.name
, families
.get(self
.family
, "?"))
89 return os
.path
.join(self
.directory
, "%s.%s" % (self
.tag
, self
.suffix
))
91 def _write_header(self
):
93 The header of the file
97 def _write_footer(self
):
99 The footer of the file
103 def write(self
, network
):
104 self
.f
.write("%s\n" % network
)
108 Called when all data has been written
117 Prints the entire output line by line
119 if isinstance(self
.f
, io
.BytesIO
):
120 raise TypeError(_("Won't write binary output to stdout"))
122 # Go back to the beginning
125 # Iterate over everything line by line
127 sys
.stdout
.write(line
)
130 class IpsetOutputWriter(OutputWriter
):
136 # The value is being used if we don't know any better
137 DEFAULT_HASHSIZE
= 64
139 # We aim for this many networks in a bucket on average. This allows us to choose
140 # how much memory we want to sacrifice to gain better performance. The lower the
141 # factor, the faster a lookup will be, but it will use more memory.
142 # We will aim for only using three quarters of all buckets to avoid any searches
143 # through the linked lists.
144 HASHSIZE_FACTOR
= 0.75
150 # Check that family is being set
152 raise ValueError("%s requires family being set" % self
.__class
__.__name
__)
157 Calculates an optimized hashsize
159 # Return the default value if we don't know the size of the set
160 if not self
.networks
:
161 return self
.DEFAULT_HASHSIZE
163 # Find the nearest power of two that is larger than the number of networks
164 # divided by the hashsize factor.
165 exponent
= math
.log(self
.networks
/ self
.HASHSIZE_FACTOR
, 2)
167 # Return the size of the hash (the minimum is 64)
168 return max(2 ** math
.ceil(exponent
), 64)
170 def _write_header(self
):
171 # This must have a fixed size, because we will write the header again in the end
172 self
.f
.write("create %s hash:net family inet%s" % (
174 "6" if self
.family
== socket
.AF_INET6
else ""
176 self
.f
.write(" hashsize %8d maxelem 1048576 -exist\n" % self
.hashsize
)
177 self
.f
.write("flush %s\n" % self
.tag
)
179 def write(self
, network
):
180 self
.f
.write("add %s %s\n" % (self
.tag
, network
))
182 # Increment network counter
185 def _write_footer(self
):
186 # Jump back to the beginning of the file
190 # If the output stream isn't seekable, we won't try writing the header again
191 except io
.UnsupportedOperation
:
194 # Rewrite the header with better configuration
198 class NftablesOutputWriter(OutputWriter
):
204 def _write_header(self
):
205 self
.f
.write("define %s = {\n" % self
.tag
)
207 def _write_footer(self
):
210 def write(self
, network
):
211 self
.f
.write(" %s,\n" % network
)
214 class XTGeoIPOutputWriter(OutputWriter
):
216 Formats the output in that way, that it can be loaded by
217 the xt_geoip kernel module from xtables-addons.
227 return "iv%s" % ("6" if self
.family
== socket
.AF_INET6
else "4")
229 def write(self
, network
):
230 self
.f
.write(network
._first
_address
)
231 self
.f
.write(network
._last
_address
)
235 "ipset" : IpsetOutputWriter
,
236 "list" : OutputWriter
,
237 "nftables" : NftablesOutputWriter
,
238 "xt_geoip" : XTGeoIPOutputWriter
,
241 class Exporter(object):
242 def __init__(self
, db
, writer
):
243 self
.db
, self
.writer
= db
, writer
245 def export(self
, directory
, families
, countries
, asns
):
246 for family
in families
:
247 log
.debug("Exporting family %s" % family
)
251 # Create writers for countries
252 for country_code
in countries
:
253 writers
[country_code
] = self
.writer(country_code
, family
=family
, directory
=directory
)
255 # Create writers for ASNs
257 writers
[asn
] = self
.writer("AS%s" % asn
, family
=family
, directory
=directory
)
259 # Filter countries from special country codes
261 country_code
for country_code
in countries
if not country_code
in FLAGS
.values()
264 # Get all networks that match the family
265 networks
= self
.db
.search_networks(family
=family
,
266 country_codes
=country_codes
, asns
=asns
, flatten
=True)
268 # Walk through all networks
269 for network
in networks
:
270 # Write matching countries
272 writers
[network
.country_code
].write(network
)
276 # Write matching ASNs
278 writers
[network
.asn
].write(network
)
284 if network
.has_flag(flag
):
285 # Fetch the "fake" country code
286 country
= FLAGS
[flag
]
289 writers
[country
].write(network
)
293 # Write everything to the filesystem
294 for writer
in writers
.values():
299 for writer
in writers
.values():