├── .gitattributes ├── .gitignore ├── GeoLite2-ASN.mmdb ├── GeoLite2-City.mmdb ├── README.md ├── ipfilter.py ├── live_map.py └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Output files 2 | output.txt 3 | fortinet.txt 4 | 5 | # MaxMind GeoIP Database 6 | GeoLite2-City.mmdb 7 | 8 | # Python 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | .DS_Store -------------------------------------------------------------------------------- /GeoLite2-ASN.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vysecurity/IPFilter/9bdf3f5dd08d29f1fd93e1b3301a23a16fe2f889/GeoLite2-ASN.mmdb -------------------------------------------------------------------------------- /GeoLite2-City.mmdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vysecurity/IPFilter/9bdf3f5dd08d29f1fd93e1b3301a23a16fe2f889/GeoLite2-City.mmdb -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IP Filter Tool 2 | 3 | Author: Vincent Yiu (@vysecurity) 4 | 5 | A Python tool for processing and filtering IP addresses from various formats, with geolocation capabilities. 6 | 7 | ## Features 8 | 9 | - Extracts IP addresses from multiple formats: 10 | - Plain IP addresses (e.g., `1.1.1.1`) 11 | - IP:Port combinations (e.g., `1.1.1.1:80`) 12 | - URLs (e.g., `https://1.1.1.1`) 13 | - Removes duplicates and provides unique IPs 14 | - Geolocation support using MaxMind's GeoLite2 database 15 | - ASN (Autonomous System Number) lookup support 16 | - Country-based filtering 17 | - CSV output option with detailed location information 18 | - Ability to split output by country into separate files 19 | - Interactive map visualization with heat maps and statistics 20 | 21 | ## Installation 22 | 23 | 1. Clone the repository: 24 | ```bash 25 | git clone https://github.com/vysecurity/IPFilter 26 | cd IPFilter 27 | ``` 28 | 29 | 2. Install required Python packages: 30 | ```bash 31 | pip3 install -r requirements.txt 32 | ``` 33 | 34 | 3. Download MaxMind GeoLite2 Databases: 35 | - Sign up at [MaxMind GeoLite2](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) 36 | - Download the GeoLite2 City database and GeoLite2 ASN database 37 | - Place both `.mmdb` files in the same directory as the script 38 | - Rename them to `GeoLite2-City.mmdb` and `GeoLite2-ASN.mmdb` respectively 39 | 40 | ## Usage 41 | 42 | ### Input File Format 43 | The tool accepts various formats in the input file. Each line can be in any of these formats: 44 | ``` 45 | 1.1.1.1 46 | 1.1.1.1:80 47 | https://1.1.1.1 48 | http://1.1.1.1:8080 49 | ``` 50 | 51 | ### Basic Operations 52 | 53 | 1. Extract unique IPs from a file: 54 | ```bash 55 | python3 ipfilter.py -i input.txt -o output.txt 56 | ``` 57 | 58 | 2. Filter IPs by country (supports single or multiple countries): 59 | ```bash 60 | # Single country (Hong Kong) 61 | python3 ipfilter.py -i input.txt -o output.txt -f hk 62 | 63 | # Multiple countries (Singapore and Hong Kong) 64 | python3 ipfilter.py -i input.txt -o output.txt -f sg,hk 65 | ``` 66 | 67 | 3. Get detailed location information in CSV format: 68 | ```bash 69 | python3 ipfilter.py -i input.txt -o output.txt -c 70 | ``` 71 | 72 | 4. Include ASN information: 73 | ```bash 74 | python3 ipfilter.py -i input.txt -o output.txt -a 75 | ``` 76 | 77 | 5. Split output by country into separate files: 78 | ```bash 79 | python3 ipfilter.py -i input.txt -o output.txt -s 80 | ``` 81 | 82 | ### Real-World Example 83 | 84 | Process a list of Fortinet IPs with all features enabled (CSV output, ASN information, and country-based splitting): 85 | ```bash 86 | python3 ipfilter.py -i fortinet.txt -o output.txt -c -a -s 87 | ``` 88 | 89 | This command will: 90 | - Read IPs from `fortinet.txt` 91 | - Create CSV files for each country (e.g., `output_us.txt`, `output_sg.txt`) 92 | - Include full geolocation data 93 | - Add ASN information for each IP 94 | - Split results by country 95 | 96 | For example, if `fortinet.txt` contains IPs from US and Singapore, you'll get: 97 | - `output_us.txt` with US-based Fortinet IPs 98 | - `output_sg.txt` with Singapore-based Fortinet IPs 99 | 100 | Each file will be in CSV format with full details: 101 | ```csv 102 | ip,country_code,country_name,city,latitude,longitude,asn,asn_description 103 | 192.0.2.1,us,United States,Sunnyvale,37.3861,-122.0337,12345,Fortinet Inc 104 | 192.0.2.2,sg,Singapore,Singapore,1.3521,103.8198,45678,Fortinet Inc 105 | ``` 106 | 107 | ### Example Input/Output 108 | 109 | Input file (`input.txt`): 110 | ``` 111 | 1.1.1.1 112 | 1.1.1.1:80 113 | https://1.1.1.1 114 | 8.8.8.8 115 | ``` 116 | 117 | Basic output (`output.txt`): 118 | ``` 119 | 1.1.1.1 120 | 8.8.8.8 121 | ``` 122 | 123 | Output with ASN (`-a` flag): 124 | ``` 125 | 1.1.1.1 (ASN: 13335 - Cloudflare, Inc.) 126 | 8.8.8.8 (ASN: 15169 - Google LLC) 127 | ``` 128 | 129 | CSV output with `-c -a` flags: 130 | ```csv 131 | ip,country_code,country_name,city,latitude,longitude,asn,asn_description 132 | 1.1.1.1,au,Australia,Research,-37.7,145.1833,13335,Cloudflare, Inc. 133 | 8.8.8.8,us,United States,Mountain View,37.386,-122.0838,15169,Google LLC 134 | ``` 135 | 136 | Split output with `-s` flag creates separate files for each country: 137 | - `output_au.txt` (Australian IPs) 138 | - `output_us.txt` (US IPs) 139 | 140 | ### Real-World Examples 141 | 142 | 2. Visualize IPs on an interactive threat map: 143 | ```bash 144 | python3 ipfilter.py -i fortinet.txt -o output.txt --live 145 | ``` 146 | 147 | This will: 148 | - Launch an interactive map in your default web browser 149 | - Show IP concentrations as heat maps 150 | - Display detailed statistics 151 | - Show IP counts directly on the map 152 | - Provide clickable markers with IP and ASN information 153 | - Use a cybersecurity-themed dark interface 154 | 155 | The visualization includes: 156 | - Heat map overlay showing IP density 157 | - Circle markers sized by IP count with count labels 158 | - Popup details for each location 159 | - Color-coded intensity based on IP concentration 160 | - Real-time statistics panel 161 | - Dark theme optimized for security operations 162 | 163 | ## Arguments 164 | 165 | - `-i, --input`: Input file path (required) 166 | - `-o, --output`: Output file path (required) 167 | - `-f, --filter`: Filter by country code(s) (e.g., `hk` for Hong Kong, or `sg,hk` for multiple) 168 | - `-c, --country`: Include country information in the output 169 | - `-a, --asn`: Include ASN information in the output 170 | - `-s, --split`: Split output into separate files by country code 171 | - `--live`: Show interactive visualization in web browser 172 | 173 | ### Output Format 174 | 175 | The tool always outputs in CSV format. The columns included depend on the flags used: 176 | 177 | Basic output (no flags): 178 | ```csv 179 | ip 180 | 1.1.1.1 181 | 8.8.8.8 182 | ``` 183 | 184 | With country information (`-c` or `--country` flag): 185 | ```csv 186 | ip,country_code,country_name,city,latitude,longitude 187 | 1.1.1.1,au,Australia,Research,-37.7,145.1833 188 | 8.8.8.8,us,United States,Mountain View,37.386,-122.0838 189 | ``` 190 | 191 | With ASN information (`-a` flag): 192 | ```csv 193 | ip,asn,asn_description 194 | 1.1.1.1,13335,Cloudflare Inc 195 | 8.8.8.8,15169,Google LLC 196 | ``` 197 | 198 | With both country and ASN (`--country -a` flags): 199 | ```csv 200 | ip,country_code,country_name,city,latitude,longitude,asn,asn_description 201 | 1.1.1.1,au,Australia,Research,-37.7,145.1833,13335,Cloudflare Inc 202 | 8.8.8.8,us,United States,Mountain View,37.386,-122.0838,15169,Google LLC 203 | ``` 204 | 205 | When using the `-s` flag, separate CSV files will be created for each country using the format `output_[country_code].csv`. 206 | 207 | ## Licensing 208 | 209 | This tool is released under the MIT License. 210 | 211 | Copyright (c) 2024 Vincent Yiu 212 | 213 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 214 | 215 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 216 | 217 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 218 | 219 | ### Third-Party Licenses 220 | 221 | This project uses GeoLite2 data created by MaxMind, available from [https://www.maxmind.com](https://www.maxmind.com). The GeoLite2 databases are distributed under the Creative Commons Attribution-ShareAlike 4.0 International License. -------------------------------------------------------------------------------- /ipfilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | def print_banner(): 4 | banner = """ 5 | ╔══════════════════════════════════════════╗ 6 | ║ IP Filter Tool ║ 7 | ║ Author: Vincent Yiu (@vysecurity) ║ 8 | ║ ║ 9 | ║ Filters and Geolocates IP Addresses ║ 10 | ║ from various formats including URLs ║ 11 | ╚══════════════════════════════════════════╝ 12 | """ 13 | print(banner) 14 | 15 | import argparse 16 | import re 17 | import csv 18 | from urllib.parse import urlparse 19 | import geoip2.database 20 | import os 21 | import sys 22 | from live_map import serve_map 23 | 24 | def get_asn_info(ip, asn_reader): 25 | try: 26 | response = asn_reader.asn(ip) 27 | return { 28 | 'asn': str(response.autonomous_system_number), 29 | 'asn_description': response.autonomous_system_organization 30 | } 31 | except: 32 | return { 33 | 'asn': 'Unknown', 34 | 'asn_description': 'Unknown' 35 | } 36 | 37 | def get_ip_info(ip, city_reader, asn_reader=None, include_asn=False): 38 | try: 39 | response = city_reader.city(ip) 40 | info = { 41 | 'ip': ip, 42 | 'country_code': response.country.iso_code.lower() if response.country.iso_code else 'unknown', 43 | 'country_name': response.country.name if response.country.name else 'Unknown', 44 | 'city': response.city.name if response.city.name else 'Unknown', 45 | 'latitude': response.location.latitude if response.location else None, 46 | 'longitude': response.location.longitude if response.location else None 47 | } 48 | 49 | if include_asn and asn_reader: 50 | asn_info = get_asn_info(ip, asn_reader) 51 | info.update({ 52 | 'asn': asn_info['asn'], 53 | 'asn_description': asn_info['asn_description'] 54 | }) 55 | return info 56 | except: 57 | info = { 58 | 'ip': ip, 59 | 'country_code': 'unknown', 60 | 'country_name': 'Unknown', 61 | 'city': 'Unknown', 62 | 'latitude': None, 63 | 'longitude': None 64 | } 65 | if include_asn: 66 | info.update({ 67 | 'asn': 'Unknown', 68 | 'asn_description': 'Unknown' 69 | }) 70 | return info 71 | 72 | def extract_ip(line): 73 | # Regular expression for matching IPv4 addresses 74 | ip_pattern = r'(?:\d{1,3}\.){3}\d{1,3}' 75 | 76 | # Remove any whitespace and newlines 77 | line = line.strip() 78 | 79 | # Try to parse as URL first 80 | try: 81 | parsed = urlparse(line) 82 | if parsed.netloc: 83 | # Extract hostname from URL 84 | hostname = parsed.netloc.split(':')[0] 85 | if re.match(ip_pattern, hostname): 86 | return hostname 87 | except: 88 | pass 89 | 90 | # Try to match IP:Port pattern 91 | if ':' in line: 92 | potential_ip = line.split(':')[0] 93 | if re.match(ip_pattern, potential_ip): 94 | return potential_ip 95 | 96 | # Try to match plain IP 97 | if re.match(ip_pattern, line): 98 | return line 99 | 100 | return None 101 | 102 | def main(): 103 | print_banner() 104 | parser = argparse.ArgumentParser(description='Extract and filter IP addresses from a file.') 105 | parser.add_argument('-i', '--input', required=True, help='Input file path') 106 | parser.add_argument('-o', '--output', required=True, help='Output file path') 107 | parser.add_argument('-f', '--filter', help='Filter by country code(s) (e.g., hk for Hong Kong, or sg,hk for multiple)') 108 | parser.add_argument('-c', '--country', action='store_true', help='Include country information in output') 109 | parser.add_argument('-a', '--asn', action='store_true', help='Include ASN information') 110 | parser.add_argument('-s', '--split', action='store_true', help='Split output into separate files by country code') 111 | parser.add_argument('--live', action='store_true', help='Show live visualization in web browser') 112 | 113 | args = parser.parse_args() 114 | 115 | # Check if GeoLite2 databases exist 116 | city_db_path = 'GeoLite2-City.mmdb' 117 | asn_db_path = 'GeoLite2-ASN.mmdb' 118 | 119 | if not os.path.exists(city_db_path): 120 | print("Error: GeoLite2-City database not found. Please download it from MaxMind and place it in the same directory.") 121 | sys.exit(1) 122 | 123 | if args.asn and not os.path.exists(asn_db_path): 124 | print("Error: GeoLite2-ASN database not found. Please download it from MaxMind and place it in the same directory.") 125 | sys.exit(1) 126 | 127 | unique_ips = set() 128 | ip_info_list = [] 129 | 130 | try: 131 | # Initialize GeoIP2 readers 132 | city_reader = geoip2.database.Reader(city_db_path) 133 | asn_reader = geoip2.database.Reader(asn_db_path) if args.asn else None 134 | 135 | # Read and process input file 136 | with open(args.input, 'r') as infile: 137 | for line in infile: 138 | ip = extract_ip(line) 139 | if ip: 140 | unique_ips.add(ip) 141 | 142 | # Process IPs with geolocation 143 | for ip in sorted(unique_ips): 144 | ip_info = get_ip_info(ip, city_reader, asn_reader, args.asn or args.live) 145 | ip_info_list.append(ip_info) 146 | 147 | # Filter by country code if specified 148 | if args.filter: 149 | # Split filter into list of country codes 150 | country_codes = [code.strip().lower() for code in args.filter.split(',')] 151 | ip_info_list = [info for info in ip_info_list if info['country_code'] in country_codes] 152 | 153 | # Launch live visualization if requested 154 | if args.live: 155 | print("Launching live visualization in your web browser...") 156 | serve_map(ip_info_list) 157 | return 158 | 159 | # Write output 160 | if args.split: 161 | # Group IPs by country code 162 | country_groups = {} 163 | for info in ip_info_list: 164 | country_code = info['country_code'] 165 | if country_code not in country_groups: 166 | country_groups[country_code] = [] 167 | country_groups[country_code].append(info) 168 | 169 | # Write separate files for each country 170 | output_base = os.path.splitext(args.output)[0] 171 | for country_code, country_ips in country_groups.items(): 172 | output_file = f"{output_base}_{country_code}.csv" 173 | with open(output_file, 'w', newline='') as outfile: 174 | # Prepare fieldnames based on included information 175 | fieldnames = ['ip'] 176 | if args.country: 177 | fieldnames.extend(['country_code', 'country_name', 'city', 'latitude', 'longitude']) 178 | if args.asn: 179 | fieldnames.extend(['asn', 'asn_description']) 180 | 181 | writer = csv.DictWriter(outfile, fieldnames=fieldnames) 182 | writer.writeheader() 183 | 184 | # Write only selected fields 185 | for info in country_ips: 186 | row = {'ip': info['ip']} 187 | if args.country: 188 | row.update({ 189 | 'country_code': info['country_code'], 190 | 'country_name': info['country_name'], 191 | 'city': info['city'], 192 | 'latitude': info['latitude'], 193 | 'longitude': info['longitude'] 194 | }) 195 | if args.asn: 196 | row.update({ 197 | 'asn': info['asn'], 198 | 'asn_description': info['asn_description'] 199 | }) 200 | writer.writerow(row) 201 | print(f"Successfully processed {len(ip_info_list)} IP addresses into {len(country_groups)} country-specific CSV files.") 202 | else: 203 | with open(args.output, 'w', newline='') as outfile: 204 | # Prepare fieldnames based on included information 205 | fieldnames = ['ip'] 206 | if args.country: 207 | fieldnames.extend(['country_code', 'country_name', 'city', 'latitude', 'longitude']) 208 | if args.asn: 209 | fieldnames.extend(['asn', 'asn_description']) 210 | 211 | writer = csv.DictWriter(outfile, fieldnames=fieldnames) 212 | writer.writeheader() 213 | 214 | # Write only selected fields 215 | for info in ip_info_list: 216 | row = {'ip': info['ip']} 217 | if args.country: 218 | row.update({ 219 | 'country_code': info['country_code'], 220 | 'country_name': info['country_name'], 221 | 'city': info['city'], 222 | 'latitude': info['latitude'], 223 | 'longitude': info['longitude'] 224 | }) 225 | if args.asn: 226 | row.update({ 227 | 'asn': info['asn'], 228 | 'asn_description': info['asn_description'] 229 | }) 230 | writer.writerow(row) 231 | print(f"Successfully processed {len(ip_info_list)} IP addresses.") 232 | 233 | city_reader.close() 234 | if asn_reader: 235 | asn_reader.close() 236 | 237 | except FileNotFoundError: 238 | print(f"Error: Input file '{args.input}' not found.") 239 | except PermissionError: 240 | print("Error: Permission denied when accessing files.") 241 | except Exception as e: 242 | print(f"An error occurred: {str(e)}") 243 | 244 | if __name__ == "__main__": 245 | main() -------------------------------------------------------------------------------- /live_map.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template_string 2 | import folium 3 | from folium import plugins 4 | from collections import defaultdict 5 | import branca.colormap as cm 6 | import webbrowser 7 | import threading 8 | import time 9 | import os 10 | 11 | HTML_TEMPLATE = ''' 12 | 13 | 14 | 15 | IP Filter - Visualization 16 | 46 | 47 | 48 |
49 |

🌐 IP Filter - Threat Intelligence Map 🔒

50 | Visualization of infrastructure 51 |
52 |
53 |
Total IPs: {{ total_ips }}
54 |
Countries: {{ total_countries }}
55 |
ASNs: {{ total_asns }}
56 |
57 | {{ map_html|safe }} 58 | 59 | 60 | ''' 61 | 62 | def create_map(ip_data): 63 | # Create a map centered on the world 64 | m = folium.Map( 65 | location=[20, 0], 66 | zoom_start=2, 67 | tiles='CartoDB dark_matter', # Dark theme for cybersecurity feel 68 | prefer_canvas=True 69 | ) 70 | 71 | # Group data by country 72 | country_data = defaultdict(lambda: {'count': 0, 'asns': defaultdict(set), 'ips': set(), 'cities': set()}) 73 | 74 | # First pass: collect all data 75 | for ip in ip_data: 76 | if ip['latitude'] and ip['longitude']: 77 | key = (ip['country_code'], ip['country_name']) 78 | country_data[key]['ips'].add(ip['ip']) 79 | if ip['city'] != 'Unknown': 80 | country_data[key]['cities'].add(ip['city']) 81 | # Only add ASN if it exists and is not Unknown 82 | if 'asn' in ip and ip['asn'] != 'Unknown' and ip['asn_description'] != 'Unknown': 83 | asn_key = ip['asn'] 84 | country_data[key]['asns'][asn_key].add(ip['asn_description']) 85 | 86 | # Create heat data 87 | heat_data = [] 88 | 89 | # Calculate max count for scaling 90 | max_count = max([len(data['ips']) for data in country_data.values()]) if country_data else 1 91 | 92 | # Create color map for intensity 93 | colormap = cm.LinearColormap( 94 | colors=['#00ff00', '#ffff00', '#ff0000'], 95 | vmin=0, 96 | vmax=max_count 97 | ) 98 | 99 | # Add markers and heat data 100 | for country_key, data in country_data.items(): 101 | country_code, country_name = country_key 102 | unique_ips = len(data['ips']) 103 | 104 | # Get a representative location for the country 105 | country_location = None 106 | for ip in ip_data: 107 | if ip['country_code'] == country_code and ip['latitude'] and ip['longitude']: 108 | country_location = (ip['latitude'], ip['longitude']) 109 | break 110 | 111 | if not country_location: 112 | continue 113 | 114 | lat, lon = country_location 115 | 116 | # Add to heat map data 117 | heat_data.append([lat, lon, unique_ips]) 118 | 119 | # Format ASN information 120 | asn_info = [] 121 | for asn, descriptions in sorted(data['asns'].items()): 122 | desc = next(iter(descriptions)) # Get the first description (should be same for same ASN) 123 | asn_info.append(f"ASN {asn} - {desc}") 124 | 125 | # Create popup content with improved styling 126 | cities_list = sorted(list(data['cities'])) 127 | cities_display = '
'.join(cities_list[:5]) 128 | if len(cities_list) > 5: 129 | cities_display += f'
... ({len(cities_list)} total cities)' 130 | 131 | popup_content = f""" 132 |
141 |

{country_name} ({country_code.upper()})

142 |
143 | Unique IPs: {unique_ips} 144 |
145 |
146 | Unique ASNs: {len(data['asns'])} 147 |
148 |
149 | Cities:
{cities_display} 150 |
151 |
160 | IPs:
161 | {('
'.join(sorted(list(data['ips'])[:10])) + f'
... ({len(data["ips"])} total IPs)' if len(data['ips']) > 10 else '
'.join(sorted(data['ips'])))} 162 |
163 |
172 | ASNs:
173 | {('
'.join(asn_info[:10]) + f'
... ({len(asn_info)} total ASNs)' if len(asn_info) > 10 else '
'.join(asn_info))} 174 |
175 |
176 | """ 177 | 178 | # Create a div with the count for the circle marker 179 | count_div = folium.DivIcon( 180 | html=f'
{unique_ips}
', 185 | icon_size=(40, 40) 186 | ) 187 | 188 | # Add circle marker 189 | folium.CircleMarker( 190 | location=[lat, lon], 191 | radius=min(unique_ips * 3, 20), 192 | popup=folium.Popup(popup_content, max_width=400), 193 | color='#00ff00', 194 | fill=True, 195 | fillColor=colormap(unique_ips), 196 | fillOpacity=0.7 197 | ).add_to(m) 198 | 199 | # Add count label 200 | folium.Marker( 201 | location=[lat, lon], 202 | icon=count_div, 203 | popup=folium.Popup(popup_content, max_width=400) 204 | ).add_to(m) 205 | 206 | # Add heat layer 207 | plugins.HeatMap(heat_data, min_opacity=0.3, radius=25).add_to(m) 208 | 209 | # Add legend 210 | colormap.add_to(m) 211 | colormap.caption = 'Number of Unique IPs per country' 212 | 213 | return m 214 | 215 | def serve_map(ip_data): 216 | app = Flask(__name__) 217 | 218 | @app.route('/') 219 | def home(): 220 | m = create_map(ip_data) 221 | 222 | # Calculate statistics more accurately 223 | total_ips = len(set(ip['ip'] for ip in ip_data)) 224 | total_countries = len(set(ip['country_code'] for ip in ip_data)) 225 | 226 | # Count unique ASNs globally, excluding Unknown values 227 | asns = set() 228 | for ip in ip_data: 229 | if ('asn' in ip and 230 | ip['asn'] != 'Unknown' and 231 | 'asn_description' in ip and 232 | ip['asn_description'] != 'Unknown'): 233 | asns.add(ip['asn']) 234 | total_asns = len(asns) 235 | 236 | # Debug print for ASN counting 237 | print(f"Debug - Total ASNs found: {total_asns}") 238 | print(f"Debug - Unique ASNs: {asns}") 239 | 240 | return render_template_string( 241 | HTML_TEMPLATE, 242 | map_html=m.get_root().render(), 243 | total_ips=total_ips, 244 | total_countries=total_countries, 245 | total_asns=total_asns 246 | ) 247 | 248 | # Open browser after a short delay 249 | def open_browser(): 250 | time.sleep(1.5) 251 | webbrowser.open('http://127.0.0.1:5000/') 252 | 253 | threading.Thread(target=open_browser).start() 254 | 255 | # Run the Flask app 256 | app.run(debug=False, use_reloader=False) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | geoip2==4.8.0 2 | ipwhois==1.2.0 3 | flask==3.0.0 4 | folium==0.15.1 5 | branca==0.7.0 --------------------------------------------------------------------------------