2 ###############################################################################
4 # libloc - A library to determine the location of someone on the Internet #
6 # Copyright (C) 2020-2021 IPFire Development Team <info@ipfire.org> #
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. #
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. #
18 ###############################################################################
30 log
= logging
.getLogger("location.export")
34 _location
.NETWORK_FLAG_ANONYMOUS_PROXY
: "A1",
35 _location
.NETWORK_FLAG_SATELLITE_PROVIDER
: "A2",
36 _location
.NETWORK_FLAG_ANYCAST
: "A3",
37 _location
.NETWORK_FLAG_DROP
: "XD",
40 class OutputWriter(object):
44 def __init__(self
, f
, family
=None, prefix
=None):
49 # Call any custom initialization
52 # Immediately write the header
57 To be overwritten by anything that inherits from this
62 def open(cls
, filename
, *args
, **kwargs
):
64 Convenience function to open a file
66 f
= open(filename
, cls
.mode
)
68 return cls(f
, *args
, **kwargs
)
71 return "<%s f=%s>" % (self
.__class
__.__name
__, self
.f
)
73 def _write_header(self
):
75 The header of the file
79 def _write_footer(self
):
81 The footer of the file
85 def write(self
, network
):
86 self
.f
.write("%s\n" % network
)
90 Called when all data has been written
98 class IpsetOutputWriter(OutputWriter
):
104 # The value is being used if we don't know any better
105 DEFAULT_HASHSIZE
= 64
107 # We aim for this many networks in a bucket on average. This allows us to choose
108 # how much memory we want to sacrifice to gain better performance. The lower the
109 # factor, the faster a lookup will be, but it will use more memory.
110 # We will aim for only using three quarters of all buckets to avoid any searches
111 # through the linked lists.
112 HASHSIZE_FACTOR
= 0.75
121 Calculates an optimized hashsize
123 # Return the default value if we don't know the size of the set
124 if not self
.networks
:
125 return self
.DEFAULT_HASHSIZE
127 # Find the nearest power of two that is larger than the number of networks
128 # divided by the hashsize factor.
129 exponent
= math
.log(self
.networks
/ self
.HASHSIZE_FACTOR
, 2)
131 # Return the size of the hash
132 return 2 ** math
.ceil(exponent
)
137 Tells ipset how large the set will be.
139 Since these are considered immutable, we will use the total number of networks.
143 def _write_header(self
):
144 # This must have a fixed size, because we will write the header again in the end
145 self
.f
.write("create %s hash:net family inet%s" % (
147 "6" if self
.family
== socket
.AF_INET6
else ""
149 self
.f
.write(" hashsize %8d maxelem %8d -exist\n" % (self
.hashsize
, self
.maxelem
))
150 self
.f
.write("flush %s\n" % self
.prefix
)
152 def write(self
, network
):
153 self
.f
.write("add %s %s\n" % (self
.prefix
, network
))
155 # Increment network counter
158 def _write_footer(self
):
159 # Jump back to the beginning of the file
162 # Rewrite the header with better configuration
166 class NftablesOutputWriter(OutputWriter
):
172 def _write_header(self
):
173 self
.f
.write("define %s = {\n" % self
.prefix
)
175 def _write_footer(self
):
178 def write(self
, network
):
179 self
.f
.write(" %s,\n" % network
)
182 class XTGeoIPOutputWriter(OutputWriter
):
184 Formats the output in that way, that it can be loaded by
185 the xt_geoip kernel module from xtables-addons.
190 def write(self
, network
):
191 self
.f
.write(network
._first
_address
)
192 self
.f
.write(network
._last
_address
)
196 "ipset" : IpsetOutputWriter
,
197 "list" : OutputWriter
,
198 "nftables" : NftablesOutputWriter
,
199 "xt_geoip" : XTGeoIPOutputWriter
,
202 class Exporter(object):
203 def __init__(self
, db
, writer
):
204 self
.db
, self
.writer
= db
, writer
206 def export(self
, directory
, families
, countries
, asns
):
207 for family
in families
:
208 log
.debug("Exporting family %s" % family
)
212 # Create writers for countries
213 for country_code
in countries
:
214 filename
= self
._make
_filename
(
215 directory
, prefix
=country_code
, suffix
=self
.writer
.suffix
, family
=family
,
218 writers
[country_code
] = self
.writer
.open(filename
, family
, prefix
="%s" % country_code
)
220 # Create writers for ASNs
222 filename
= self
._make
_filename
(
223 directory
, "AS%s" % asn
, suffix
=self
.writer
.suffix
, family
=family
,
226 writers
[asn
] = self
.writer
.open(filename
, family
, prefix
="AS%s" % asn
)
228 # Filter countries from special country codes
230 country_code
for country_code
in countries
if not country_code
in FLAGS
.values()
233 # Get all networks that match the family
234 networks
= self
.db
.search_networks(family
=family
,
235 country_codes
=country_codes
, asns
=asns
, flatten
=True)
237 # Walk through all networks
238 for network
in networks
:
239 # Write matching countries
241 writers
[network
.country_code
].write(network
)
245 # Write matching ASNs
247 writers
[network
.asn
].write(network
)
253 if network
.has_flag(flag
):
254 # Fetch the "fake" country code
255 country
= FLAGS
[flag
]
258 writers
[country
].write(network
)
262 # Write everything to the filesystem
263 for writer
in writers
.values():
266 def _make_filename(self
, directory
, prefix
, suffix
, family
):
267 filename
= "%s.%s%s" % (
268 prefix
, suffix
, "6" if family
== socket
.AF_INET6
else "4"
271 return os
.path
.join(directory
, filename
)