1616
1717nftables_geoip_conf = '/run/nftables-geoip.conf'
1818dbip_database_raw = '/usr/share/vyos-geoip/dbip-country-lite.csv.gz'
19+ dbip_asn_database_raw = '/usr/share/vyos-geoip/dbip-asn-lite.csv.gz'
1920mm_database_raw = '/usr/share/vyos-geoip/maxmind-country.zip'
21+ mm_asn_database_raw = '/usr/share/vyos-geoip/maxmind-asn.zip'
2022geoip_database_path = '/var/cache/vyos/geoip-lookup.db'
2123geoip_lock_file = '/var/lock/vyos-geoip.lock'
2224
2325# Raw data
2426
2527def geoip_download_dbip ():
2628 url = 'https://download.db-ip.com/free/dbip-country-lite-{}.csv.gz' .format (strftime ("%Y-%m" ))
29+ asn_url = 'https://download.db-ip.com/free/dbip-asn-lite-{}.csv.gz' .format (strftime ("%Y-%m" ))
2730 try :
2831 dirname = os .path .dirname (dbip_database_raw )
2932 if not os .path .exists (dirname ):
3033 os .mkdir (dirname )
3134
3235 download (dbip_database_raw , url )
36+ download (dbip_asn_database_raw , asn_url )
3337 return True
3438 except :
3539 return False
3640
3741def geoip_download_maxmind (account_id : str , license_key : str , lite : bool ) -> bool :
3842 db_str = 'GeoLite2' if lite else 'GeoIP2'
3943 url = f'https://{ account_id } :{ license_key } @download.maxmind.com/geoip/databases/{ db_str } -Country-CSV/download?suffix=zip'
44+ asn_url = f'https://{ account_id } :{ license_key } @download.maxmind.com/geoip/databases/{ db_str } -ASN-CSV/download?suffix=zip'
4045 try :
4146 dirname = os .path .dirname (mm_database_raw )
4247 if not os .path .exists (dirname ):
4348 os .mkdir (dirname )
4449
4550 download (mm_database_raw , url )
51+ download (mm_asn_database_raw , asn_url )
4652 return True
4753 except :
4854 return False
@@ -68,12 +74,14 @@ def db_initialise():
6874 cur = conn .cursor ()
6975 cur .execute ("""
7076 CREATE TABLE IF NOT EXISTS geoip_ranges (
71- country_code TEXT NOT NULL,
77+ country_code TEXT,
78+ asn INT,
7279 range TEXT NOT NULL,
7380 version INT NOT NULL
7481 )
7582 """ )
7683 cur .execute ('CREATE INDEX IF NOT EXISTS idx_cc_version ON geoip_ranges(country_code, version)' )
84+ cur .execute ('CREATE INDEX IF NOT EXISTS idx_asn_version ON geoip_ranges(asn, version)' )
7785 conn .commit ()
7886
7987def db_import_dbip_ranges (replace = True , delete_file = False ):
@@ -84,22 +92,29 @@ def db_import_dbip_ranges(replace=True, delete_file=False):
8492 return False
8593
8694 try :
87- with gzip . open ( dbip_database_raw , mode = 'rt' ) as csv_fh :
88- reader = csv . reader ( csv_fh )
95+ with sqlite3 . connect ( geoip_database_path ) as conn :
96+ cur = conn . cursor ( )
8997
90- with sqlite3 .connect (geoip_database_path ) as conn :
91- cur = conn .cursor ()
92-
93- if replace :
94- cur .execute ('DELETE FROM geoip_ranges' )
98+ if replace :
99+ cur .execute ('DELETE FROM geoip_ranges' )
95100
101+ with gzip .open (dbip_database_raw , mode = 'rt' ) as csv_fh :
102+ reader = csv .reader (csv_fh )
96103 for start , end , code in reader :
97104 version = 4 if is_ipv4 (start ) else 6
98105 cur .execute ('INSERT INTO geoip_ranges (country_code, range, version) VALUES (?, ?, ?)' , (code .lower (), f'{ start } -{ end } ' , version ))
99- conn .commit ()
106+
107+ with gzip .open (dbip_asn_database_raw , mode = 'rt' ) as csv_fh :
108+ reader = csv .reader (csv_fh )
109+ for start , end , asn , _ in reader :
110+ version = 4 if is_ipv4 (start ) else 6
111+ cur .execute ('INSERT INTO geoip_ranges (asn, range, version) VALUES (?, ?, ?)' , (asn , f'{ start } -{ end } ' , version ))
112+
113+ conn .commit ()
100114
101115 if delete_file :
102116 os .unlink (dbip_database_raw )
117+ os .unlink (dbip_asn_database_raw )
103118
104119 return True
105120 except :
@@ -116,28 +131,28 @@ def db_import_maxmind_ranges(replace=True, delete_file=False):
116131 return False
117132
118133 try :
119- with zipfile .ZipFile (mm_database_raw , mode = 'r' ) as zip_fh :
120- directory = os .path .dirname (zip_fh .namelist ()[0 ])
121- prefix = 'GeoLite2' if any (f .startswith ('GeoLite2' ) for f in zip_fh .namelist ()) else 'GeoIP2'
134+ with sqlite3 .connect (geoip_database_path ) as conn :
135+ cur = conn .cursor ()
122136
123- ipv4_file = f'{ directory } /{ prefix } -Country-Blocks-IPv4.csv'
124- ipv6_file = f'{ directory } /{ prefix } -Country-Blocks-IPv6.csv'
125- locations_file = f'{ directory } /{ prefix } -Country-Locations-en.csv'
126- locations_map = {}
137+ if replace :
138+ cur .execute ('DELETE FROM geoip_ranges' )
127139
128- with zip_fh . open ( locations_file ) as raw_csv_fh :
129- with TextIOWrapper ( raw_csv_fh , encoding = 'utf-8' ) as csv_fh :
130- reader = csv . DictReader ( csv_fh )
140+ with zipfile . ZipFile ( mm_database_raw , mode = 'r' ) as zip_fh :
141+ directory = os . path . dirname ( zip_fh . namelist ()[ 0 ])
142+ prefix = 'GeoLite2' if any ( f . startswith ( 'GeoLite2' ) for f in zip_fh . namelist ()) else 'GeoIP2'
131143
132- for row in reader :
133- id = row ['geoname_id' ]
134- locations_map [id ] = row ['country_iso_code' ]
144+ ipv4_file = f'{ directory } /{ prefix } -Country-Blocks-IPv4.csv'
145+ ipv6_file = f'{ directory } /{ prefix } -Country-Blocks-IPv6.csv'
146+ locations_file = f'{ directory } /{ prefix } -Country-Locations-en.csv'
147+ locations_map = {}
135148
136- with sqlite3 .connect (geoip_database_path ) as conn :
137- cur = conn .cursor ()
149+ with zip_fh .open (locations_file ) as raw_csv_fh :
150+ with TextIOWrapper (raw_csv_fh , encoding = 'utf-8' ) as csv_fh :
151+ reader = csv .DictReader (csv_fh )
138152
139- if replace :
140- cur .execute ('DELETE FROM geoip_ranges' )
153+ for row in reader :
154+ id = row ['geoname_id' ]
155+ locations_map [id ] = row ['country_iso_code' ]
141156
142157 with zip_fh .open (ipv4_file ) as raw_csv_fh :
143158 with TextIOWrapper (raw_csv_fh , encoding = 'utf-8' ) as csv_fh :
@@ -163,16 +178,36 @@ def db_import_maxmind_ranges(replace=True, delete_file=False):
163178 code = locations_map [id ]
164179 cur .execute ('INSERT INTO geoip_ranges (country_code, range, version) VALUES (?, ?, 6)' , (code .lower (), row ['network' ]))
165180
166- conn .commit ()
181+ with zipfile .ZipFile (mm_asn_database_raw , mode = 'r' ) as zip_fh :
182+ directory = os .path .dirname (zip_fh .namelist ()[0 ])
183+ prefix = 'GeoLite2' if any (f .startswith ('GeoLite2' ) for f in zip_fh .namelist ()) else 'GeoIP2'
184+
185+ ipv4_file = f'{ directory } /{ prefix } -ASN-Blocks-IPv4.csv'
186+ ipv6_file = f'{ directory } /{ prefix } -ASN-Blocks-IPv6.csv'
187+
188+ with zip_fh .open (ipv4_file ) as raw_csv_fh :
189+ with TextIOWrapper (raw_csv_fh , encoding = 'utf-8' ) as csv_fh :
190+ reader = csv .DictReader (csv_fh )
191+ for row in reader :
192+ cur .execute ('INSERT INTO geoip_ranges (asn, range, version) VALUES (?, ?, 4)' , (row ['autonomous_system_number' ], row ['network' ]))
193+
194+ with zip_fh .open (ipv6_file ) as raw_csv_fh :
195+ with TextIOWrapper (raw_csv_fh , encoding = 'utf-8' ) as csv_fh :
196+ reader = csv .DictReader (csv_fh )
197+ for row in reader :
198+ cur .execute ('INSERT INTO geoip_ranges (asn, range, version) VALUES (?, ?, 6)' , (row ['autonomous_system_number' ], row ['network' ]))
199+
200+ conn .commit ()
167201
168202 if delete_file :
169203 os .unlink (mm_database_raw )
204+ os .unlink (mm_asn_database_raw )
170205
171206 return True
172207 except :
173208 return False
174209
175- def db_return_ranges (codes , version ):
210+ def db_return_cc_ranges (codes , version ):
176211 out = []
177212 with sqlite3 .connect (geoip_database_path ) as conn :
178213 cur = conn .cursor ()
@@ -181,6 +216,15 @@ def db_return_ranges(codes, version):
181216 out .append (row [0 ])
182217 return out
183218
219+ def db_return_asn_ranges (asn , version ):
220+ out = []
221+ with sqlite3 .connect (geoip_database_path ) as conn :
222+ cur = conn .cursor ()
223+ ph = ',' .join (['?' ] * len (asn ))
224+ for row in cur .execute (f'SELECT range FROM geoip_ranges WHERE version = ? AND asn IN ({ ph } )' , [version , * asn ]):
225+ out .append (row [0 ])
226+ return out
227+
184228# Update
185229
186230def geoip_refresh ():
@@ -223,7 +267,13 @@ def geoip_update(firewall=None, policy=None):
223267 version = 6 if path [0 ] == 'ipv6' else 4
224268 vprefix = '6' if version == 6 else ''
225269 set_name = f'GEOIP_CC{ vprefix } _{ path [1 ]} _{ path [2 ]} _{ path [4 ]} '
226- firewall_sets [f'v{ version } ' ][set_name ] = db_return_ranges (codes , version )
270+ firewall_sets [f'v{ version } ' ][set_name ] = db_return_cc_ranges (codes , version )
271+
272+ for asns , path in dict_search_recursive (firewall , 'asn' ):
273+ version = 6 if path [0 ] == 'ipv6' else 4
274+ vprefix = '6' if version == 6 else ''
275+ set_name = f'GEOIP_ASN{ vprefix } _{ path [1 ]} _{ path [2 ]} _{ path [4 ]} '
276+ firewall_sets [f'v{ version } ' ][set_name ] = db_return_asn_ranges (asns , version )
227277
228278 if policy :
229279 for codes , path in dict_search_recursive (policy , 'country_code' ):
@@ -233,7 +283,13 @@ def geoip_update(firewall=None, policy=None):
233283 version = 6 if path [0 ] == 'route6' else 4
234284 vprefix = '6' if version == 6 else ''
235285 set_name = f'GEOIP_CC{ vprefix } _{ path [0 ]} _{ path [1 ]} _{ path [3 ]} '
236- policy_sets [f'v{ version } ' ][set_name ] = db_return_ranges (codes , version )
286+ policy_sets [f'v{ version } ' ][set_name ] = db_return_cc_ranges (codes , version )
287+
288+ for asns , path in dict_search_recursive (policy , 'asn' ):
289+ version = 6 if path [0 ] == 'route6' else 4
290+ vprefix = '6' if version == 6 else ''
291+ set_name = f'GEOIP_ASN{ vprefix } _{ path [0 ]} _{ path [1 ]} _{ path [3 ]} '
292+ policy_sets [f'v{ version } ' ][set_name ] = db_return_asn_ranges (asns , version )
237293
238294 render (
239295 nftables_geoip_conf ,
0 commit comments