ip user info
This commit is contained in:
75
ip_analyzer_app/services/analysis.py
Normal file
75
ip_analyzer_app/services/analysis.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from collections import Counter
|
||||
|
||||
from .lookups import cymru_lookup, whois_lookup
|
||||
from .parsing import parse_whois
|
||||
|
||||
|
||||
def analyze_ip(ip: str, cymru_data: dict[str, dict[str, str]] | None = None) -> dict[str, str]:
|
||||
info = {
|
||||
'ip': ip,
|
||||
'asn': 'Unknown',
|
||||
'owner': 'Unknown',
|
||||
'user': 'Unknown',
|
||||
'country': 'Unknown',
|
||||
'network': 'Unknown',
|
||||
}
|
||||
|
||||
if cymru_data and ip in cymru_data:
|
||||
data = cymru_data[ip]
|
||||
info['asn'] = data.get('asn', 'Unknown')
|
||||
info['owner'] = data.get('owner', 'Unknown')
|
||||
info['country'] = data.get('country', 'Unknown')
|
||||
info['network'] = data.get('prefix', 'Unknown')
|
||||
|
||||
whois_output = whois_lookup(ip)
|
||||
if whois_output:
|
||||
parsed = parse_whois(whois_output)
|
||||
if info['asn'] == 'Unknown' and parsed['asn'] != 'Unknown':
|
||||
info['asn'] = parsed['asn']
|
||||
if parsed['country'] != 'Unknown':
|
||||
info['country'] = parsed['country']
|
||||
if parsed['cidr'] != 'Unknown':
|
||||
info['network'] = parsed['cidr']
|
||||
info['user'] = parsed['user']
|
||||
if info['owner'] == 'Unknown':
|
||||
info['owner'] = parsed['org'] if parsed['org'] != 'Unknown' else parsed['netname']
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def analyze_ips(ips: list[str]) -> tuple[list[dict[str, str]], dict[str, dict[str, int]]]:
|
||||
print(f'Analyzing {len(ips)} IPs via Team Cymru...')
|
||||
cymru_data = cymru_lookup(ips)
|
||||
|
||||
results = [analyze_ip(ip, cymru_data) for ip in ips]
|
||||
stats = {
|
||||
'total': len(results),
|
||||
'countries': dict(Counter(r['country'] for r in results)),
|
||||
'asns': dict(Counter(r['asn'] for r in results)),
|
||||
'owners': dict(Counter(r['owner'] for r in results)),
|
||||
'users': dict(Counter(r['user'] for r in results)),
|
||||
}
|
||||
|
||||
print(f"Analysis complete: {len(results)} IPs, {len(stats['countries'])} countries")
|
||||
return results, stats
|
||||
|
||||
|
||||
def apply_filters(results: list[dict[str, str]], filters: dict) -> list[dict[str, str]]:
|
||||
countries = set(filters.get('countries', []))
|
||||
asns = set(filters.get('asns', []))
|
||||
owners = set(filters.get('owners', []))
|
||||
users = set(filters.get('users', []))
|
||||
|
||||
if not (countries or asns or owners or users):
|
||||
return results
|
||||
|
||||
filtered: list[dict[str, str]] = []
|
||||
for item in results:
|
||||
if (
|
||||
(not countries or item['country'] in countries)
|
||||
and (not asns or item['asn'] in asns)
|
||||
and (not owners or item['owner'] in owners)
|
||||
and (not users or item.get('user', 'Unknown') in users)
|
||||
):
|
||||
filtered.append(item)
|
||||
return filtered
|
||||
153
ip_analyzer_app/services/exports.py
Normal file
153
ip_analyzer_app/services/exports.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def _ts() -> str:
|
||||
return datetime.now().strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
def generate_ipset(ips: list[str], timeout: int = 86400) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''#!/bin/bash
|
||||
# IPSet Rules - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
# Timeout: {timeout} seconds ({timeout//3600} hours)
|
||||
|
||||
# Create ipset
|
||||
ipset create blocked_ips hash:ip timeout {timeout} maxelem 1000000
|
||||
|
||||
# Add IPs to set
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f'ipset add blocked_ips {ip}\n'
|
||||
rules += '''
|
||||
# Apply iptables rules
|
||||
iptables -I INPUT -m set --match-set blocked_ips src -j DROP
|
||||
iptables -I FORWARD -m set --match-set blocked_ips src -j DROP
|
||||
|
||||
echo "IPSet created and iptables rules applied"
|
||||
echo "To remove: ipset destroy blocked_ips"
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def generate_iptables(ips: list[str]) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''#!/bin/bash
|
||||
# iptables Rules - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
|
||||
# INPUT chain (incoming connections)
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f'iptables -A INPUT -s {ip} -j DROP\n'
|
||||
rules += '\n# FORWARD chain (routed traffic)\n'
|
||||
for ip in ips:
|
||||
rules += f'iptables -A FORWARD -s {ip} -j DROP\n'
|
||||
rules += '''
|
||||
# Save rules
|
||||
iptables-save > /etc/iptables/rules.v4
|
||||
|
||||
echo "iptables rules applied and saved"
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def generate_nginx(ips: list[str]) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''# Nginx Deny Rules - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
#
|
||||
# Usage: Include in http or server block
|
||||
# include /etc/nginx/conf.d/blocked_ips.conf;
|
||||
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f'deny {ip};\n'
|
||||
rules += '\n# After adding rules, reload nginx:\n# nginx -t && nginx -s reload\n'
|
||||
return rules
|
||||
|
||||
|
||||
def generate_apache(ips: list[str]) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''# Apache Deny Rules - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
#
|
||||
# Usage: Add to .htaccess or VirtualHost configuration
|
||||
|
||||
<RequireAll>
|
||||
Require all granted
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f' Require not ip {ip}\n'
|
||||
rules += '''</RequireAll>
|
||||
|
||||
# After adding rules, restart apache:
|
||||
# systemctl restart apache2
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def generate_firewalld(ips: list[str]) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''#!/bin/bash
|
||||
# Firewalld Rules - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f'firewall-cmd --permanent --add-rich-rule="rule family=\'ipv4\' source address=\'{ip}\' reject"\n'
|
||||
rules += '''
|
||||
# Reload firewall
|
||||
firewall-cmd --reload
|
||||
|
||||
echo "Firewalld rules applied"
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def generate_mikrotik(ips: list[str]) -> str:
|
||||
timestamp = _ts()
|
||||
rules = f'''# MikroTik RouterOS Configuration - Generated {timestamp}
|
||||
# Total IPs: {len(ips)}
|
||||
#
|
||||
# Usage: Copy and paste into RouterOS Terminal
|
||||
|
||||
/ip firewall address-list
|
||||
'''
|
||||
for ip in ips:
|
||||
rules += f'add list=blocked_ips address={ip} comment="Auto-blocked {timestamp}"\n'
|
||||
rules += '''
|
||||
# Create firewall filter rules (if not exists)
|
||||
/ip firewall filter
|
||||
add chain=input src-address-list=blocked_ips action=drop comment="Drop blocked IPs - input"
|
||||
add chain=forward src-address-list=blocked_ips action=drop comment="Drop blocked IPs - forward"
|
||||
|
||||
# Verify
|
||||
/ip firewall address-list print where list=blocked_ips
|
||||
'''
|
||||
return rules
|
||||
|
||||
|
||||
def generate_cidr(results: list[dict[str, str]]) -> str:
|
||||
networks = sorted(set(r['network'] for r in results if r['network'] != 'Unknown'))
|
||||
timestamp = _ts()
|
||||
output = f'''# CIDR Networks - Generated {timestamp}
|
||||
# Total unique networks: {len(networks)}
|
||||
#
|
||||
# One network per line
|
||||
|
||||
'''
|
||||
return output + '\n'.join(networks)
|
||||
|
||||
|
||||
def generate_csv(results: list[dict[str, str]]) -> str:
|
||||
csv = 'IP,ASN,Owner,User,Country,Network\n'
|
||||
for item in results:
|
||||
ip = item['ip']
|
||||
asn = item['asn'].replace('"', '""')
|
||||
owner = item['owner'].replace('"', '""')
|
||||
user = item.get('user', 'Unknown').replace('"', '""')
|
||||
country = item['country']
|
||||
network = item['network']
|
||||
csv += f'"{ip}","{asn}","{owner}","{user}","{country}","{network}"\n'
|
||||
return csv
|
||||
57
ip_analyzer_app/services/lookups.py
Normal file
57
ip_analyzer_app/services/lookups.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
|
||||
def whois_lookup(ip: str) -> str:
|
||||
try:
|
||||
result = subprocess.run(['whois', ip], capture_output=True, text=True, timeout=5)
|
||||
return result.stdout
|
||||
except subprocess.TimeoutExpired:
|
||||
return ''
|
||||
except FileNotFoundError:
|
||||
print('WARNING: whois command not found. Install it in the container/image.')
|
||||
return ''
|
||||
except Exception as exc:
|
||||
print(f'WHOIS error for {ip}: {exc}')
|
||||
return ''
|
||||
|
||||
|
||||
def cymru_lookup(ips: list[str]) -> dict[str, dict[str, str]]:
|
||||
results: dict[str, dict[str, str]] = {}
|
||||
if not ips:
|
||||
return results
|
||||
|
||||
try:
|
||||
query = 'begin\nverbose\n' + '\n'.join(ips) + '\nend\n'
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(10)
|
||||
sock.connect(('whois.cymru.com', 43))
|
||||
sock.sendall(query.encode())
|
||||
|
||||
response = b''
|
||||
while True:
|
||||
data = sock.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
response += data
|
||||
sock.close()
|
||||
|
||||
for line in response.decode('utf-8', errors='ignore').split('\n'):
|
||||
if '|' in line and not line.startswith('AS'):
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) >= 5:
|
||||
asn, ip, prefix, cc, owner = parts[0], parts[1], parts[2], parts[3], parts[4]
|
||||
if asn.isdigit():
|
||||
asn = f'AS{asn}'
|
||||
results[ip] = {
|
||||
'asn': asn,
|
||||
'prefix': prefix,
|
||||
'country': cc,
|
||||
'owner': owner,
|
||||
}
|
||||
except socket.timeout:
|
||||
print('WARNING: Team Cymru timeout. Using fallback WHOIS.')
|
||||
except Exception as exc:
|
||||
print(f'Team Cymru lookup error: {exc}')
|
||||
|
||||
return results
|
||||
69
ip_analyzer_app/services/parsing.py
Normal file
69
ip_analyzer_app/services/parsing.py
Normal file
@@ -0,0 +1,69 @@
|
||||
import ipaddress
|
||||
import re
|
||||
|
||||
|
||||
def parse_ip_list(text: str) -> list[str]:
|
||||
"""Parse unique IPv4 addresses from free-form text."""
|
||||
normalized = re.sub(r'[,;|\t]+', '\n', text or '')
|
||||
lines = normalized.strip().split('\n')
|
||||
|
||||
ips: list[str] = []
|
||||
for line in lines:
|
||||
ips.extend(re.findall(r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', line))
|
||||
|
||||
valid_ips: list[str] = []
|
||||
for ip in ips:
|
||||
try:
|
||||
ipaddress.IPv4Address(ip)
|
||||
valid_ips.append(ip)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return sorted(set(valid_ips), key=valid_ips.index)
|
||||
|
||||
|
||||
def extract_field(line: str) -> str:
|
||||
return line.split(':', 1)[1].strip() if ':' in line else ''
|
||||
|
||||
|
||||
def parse_whois(whois_output: str) -> dict[str, str]:
|
||||
"""Extract relevant information from WHOIS output, including user/customer labels."""
|
||||
info = {
|
||||
'org': 'Unknown',
|
||||
'user': 'Unknown',
|
||||
'country': 'Unknown',
|
||||
'netname': 'Unknown',
|
||||
'asn': 'Unknown',
|
||||
'cidr': 'Unknown',
|
||||
}
|
||||
|
||||
for raw_line in whois_output.split('\n'):
|
||||
line = raw_line.strip()
|
||||
lowered = line.lower()
|
||||
|
||||
if line.startswith('Organization:') or line.startswith('org-name:') or line.startswith('OrgName:'):
|
||||
value = extract_field(line)
|
||||
if value:
|
||||
info['org'] = value
|
||||
elif line.startswith('Country:') or line.startswith('country:'):
|
||||
value = extract_field(line)
|
||||
if value:
|
||||
info['country'] = value
|
||||
elif line.startswith('NetName:') or line.startswith('netname:'):
|
||||
value = extract_field(line)
|
||||
if value:
|
||||
info['netname'] = value
|
||||
elif line.startswith('CIDR:') or line.startswith('inetnum:') or line.startswith('route:'):
|
||||
value = extract_field(line)
|
||||
if value:
|
||||
info['cidr'] = value
|
||||
elif line.startswith('OriginAS:') or line.startswith('origin:') or line.startswith('originas:'):
|
||||
asn = re.search(r'AS\d+', line, re.IGNORECASE)
|
||||
if asn:
|
||||
info['asn'] = asn.group().upper()
|
||||
elif re.match(r'^(user|customer|owner|descr):', lowered):
|
||||
value = extract_field(line)
|
||||
if value and info['user'] == 'Unknown':
|
||||
info['user'] = value
|
||||
|
||||
return info
|
||||
Reference in New Issue
Block a user