|
import ipaddress |
|
import maxminddb |
|
from fastapi import FastAPI, Request |
|
import json |
|
import datetime |
|
import logging |
|
import sys |
|
import os |
|
from logging.handlers import RotatingFileHandler |
|
|
|
LOG_FILE = os.path.join('/code', 'ip_query.log') |
|
|
|
try: |
|
formatter = logging.Formatter('%(message)s') |
|
log_handler = RotatingFileHandler( |
|
LOG_FILE, |
|
maxBytes=10*1024*1024, |
|
backupCount=5, |
|
encoding='utf-8' |
|
) |
|
log_handler.setFormatter(formatter) |
|
logger = logging.getLogger('ip_query') |
|
logger.setLevel(logging.INFO) |
|
logger.addHandler(log_handler) |
|
startup_log = { |
|
"时间": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
"事件": "系统启动", |
|
"状态": "成功" |
|
} |
|
logger.info(json.dumps(startup_log, ensure_ascii=False)) |
|
except Exception as e: |
|
print(f"日志初始化失败: {e}") |
|
sys.exit(1) |
|
|
|
city_reader = maxminddb.open_database('GeoLite2-City.mmdb') |
|
asn_reader = maxminddb.open_database('GeoLite2-ASN.mmdb') |
|
cn_reader = maxminddb.open_database('GeoCN.mmdb') |
|
lang = ["zh-CN", "en"] |
|
asn_map = { |
|
9812: "东方有线", |
|
9389: "中国长城", |
|
17962: "天威视讯", |
|
17429: "歌华有线", |
|
7497: "科技网", |
|
24139: "华数", |
|
9801: "中关村", |
|
4538: "教育网", |
|
24151: "CNNIC", |
|
38019: "中国移动", 139080: "中国移动", 9808: "中国移动", 24400: "中国移动", 134810: "中国移动", 24547: "中国移动", |
|
56040: "中国移动", 56041: "中国移动", 56042: "中国移动", 56044: "中国移动", 132525: "中国移动", 56046: "中国移动", |
|
56047: "中国移动", 56048: "中国移动", 59257: "中国移动", 24444: "中国移动", |
|
24445: "中国移动", 137872: "中国移动", 9231: "中国移动", 58453: "中国移动", |
|
4134: "中国电信", 4812: "中国电信", 23724: "中国电信", 136188: "中国电信", 137693: "中国电信", 17638: "中国电信", |
|
140553: "中国电信", 4847: "中国电信", 140061: "中国电信", 136195: "中国电信", 17799: "中国电信", 139018: "中国电信", |
|
134764: "中国电信", 4837: "中国联通", 4808: "中国联通", 134542: "中国联通", 134543: "中国联通", |
|
59019: "金山云", |
|
135377: "优刻云", |
|
45062: "网易云", |
|
37963: "阿里云", 45102: "阿里云国际", |
|
45090: "腾讯云", 132203: "腾讯云国际", |
|
55967: "百度云", 38365: "百度云", |
|
58519: "华为云", 55990: "华为云", 136907: "华为云", |
|
4609: "澳門電訊", |
|
13335: "Cloudflare", |
|
55960: "亚马逊云", 14618: "亚马逊云", 16509: "亚马逊云", |
|
15169: "谷歌云", 396982: "谷歌云", 36492: "谷歌云", |
|
} |
|
|
|
def get_as_info(number): |
|
r = asn_map.get(number) |
|
if r: |
|
return r |
|
|
|
def get_des(d): |
|
for i in lang: |
|
if i in d['names']: |
|
return d['names'][i] |
|
return d['names']['en'] |
|
|
|
def get_country(d): |
|
r = get_des(d) |
|
if r in ["香港", "澳门", "台湾"]: |
|
return "中国" + r |
|
return r |
|
|
|
def province_match(s): |
|
arr = ['内蒙古', '黑龙江', '河北', '山西', '吉林', '辽宁', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '海南', '四川', '贵州', '云南', '陕西', '甘肃', '青海', '广西', '西藏', '宁夏', '新疆', '北京', '天津', '上海', '重庆'] |
|
for i in arr: |
|
if i in s: |
|
return i |
|
return '' |
|
|
|
def de_duplicate(regions): |
|
regions = filter(bool, regions) |
|
ret = [] |
|
[ret.append(i) for i in regions if i not in ret] |
|
return ret |
|
|
|
def get_addr(ip, mask): |
|
network = ipaddress.ip_network(f"{ip}/{mask}", strict=False) |
|
first_ip = network.network_address |
|
return f"{first_ip}/{mask}" |
|
|
|
def get_maxmind(ip: str): |
|
ret = {"ip": ip} |
|
asn_info = asn_reader.get(ip) |
|
if asn_info: |
|
as_ = {"number": asn_info["autonomous_system_number"], "name": asn_info["autonomous_system_organization"]} |
|
info = get_as_info(as_["number"]) |
|
if info: |
|
as_["info"] = info |
|
ret["as"] = as_ |
|
|
|
city_info, prefix = city_reader.get_with_prefix_len(ip) |
|
ret["addr"] = get_addr(ip, prefix) |
|
if not city_info: |
|
return ret |
|
|
|
if "location" in city_info: |
|
location = city_info["location"] |
|
ret["location"] = { |
|
"latitude": location.get("latitude"), |
|
"longitude": location.get("longitude") |
|
} |
|
|
|
if "country" in city_info: |
|
country_code = city_info["country"]["iso_code"] |
|
country_name = get_country(city_info["country"]) |
|
ret["country"] = {"code": country_code, "name": country_name} |
|
|
|
if "registered_country" in city_info: |
|
registered_country_code = city_info["registered_country"]["iso_code"] |
|
ret["registered_country"] = {"code": registered_country_code, "name": get_country(city_info["registered_country"])} |
|
|
|
regions = [get_des(i) for i in city_info.get('subdivisions', [])] |
|
|
|
if "city" in city_info: |
|
c = get_des(city_info["city"]) |
|
if (not regions or c not in regions[-1]) and c not in country_name: |
|
regions.append(c) |
|
|
|
regions = de_duplicate(regions) |
|
if regions: |
|
ret["regions"] = regions |
|
|
|
return ret |
|
|
|
def get_cn(ip: str, info={}): |
|
ret, prefix = cn_reader.get_with_prefix_len(ip) |
|
if not ret: |
|
return |
|
info["addr"] = get_addr(ip, prefix) |
|
regions = de_duplicate([ret["province"], ret["city"], ret["districts"]]) |
|
if regions: |
|
info["regions"] = regions |
|
info["regions_short"] = de_duplicate([province_match(ret["province"]), ret["city"].replace('市', ''), ret["districts"]]) |
|
if "as" not in info: |
|
info["as"] = {} |
|
info["as"]["info"] = ret['isp'] |
|
if ret['net']: |
|
info["type"] = ret['net'] |
|
return ret |
|
|
|
def get_ip_info(ip): |
|
info = get_maxmind(ip) |
|
if "country" in info and info["country"]["code"] == "CN" and ("registered_country" not in info or info["registered_country"]["code"] == "CN"): |
|
get_cn(ip, info) |
|
return info |
|
|
|
def query(): |
|
while True: |
|
try: |
|
ip = input('IP: \t').strip() |
|
info = get_ip_info(ip) |
|
print(f"网段:\t{info['addr']}") |
|
if "location" in info: |
|
print(f"经纬度:\t{info['location']['latitude']}, {info['location']['longitude']}") |
|
if "as" in info: |
|
print(f"ISP:\t", end=' ') |
|
if "info" in info["as"]: |
|
print(info["as"]["info"], end=' ') |
|
else: |
|
print(info["as"]["name"], end=' ') |
|
if "type" in info: |
|
print(f"({info['type']})", end=' ') |
|
print(f"ASN{info['as']['number']}", end=' ') |
|
print(info['as']["name"]) |
|
if "registered_country" in info and ("country" not in info or info["country"]["code"] != info["registered_country"]["code"]): |
|
print(f"注册地:\t{info['registered_country']['name']}") |
|
if "country" in info: |
|
print(f"使用地:\t{info['country']['name']}") |
|
if "regions" in info: |
|
print(f"位置: \t{' '.join(info['regions'])}") |
|
except Exception as e: |
|
print(e) |
|
raise e |
|
finally: |
|
print("\n") |
|
|
|
app = FastAPI() |
|
|
|
@app.get("/") |
|
async def api(request: Request, ip: str = None): |
|
client_ip = request.headers.get("x-forwarded-for") or request.headers.get("x-real-ip") or request.client.host |
|
query_ip = ip.strip() if ip else client_ip |
|
result = get_ip_info(query_ip) |
|
log_data = { |
|
"时间": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
"访问IP": client_ip, |
|
"查询IP": query_ip, |
|
"请求头": dict(request.headers), |
|
"查询结果": result |
|
} |
|
logger.info(json.dumps(log_data, ensure_ascii=False)) |
|
return result |
|
|
|
@app.get("/{ip}") |
|
async def path_api(request: Request, ip: str): |
|
client_ip = request.headers.get("x-forwarded-for") or request.headers.get("x-real-ip") or request.client.host |
|
result = get_ip_info(ip) |
|
log_data = { |
|
"时间": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), |
|
"访问IP": client_ip, |
|
"查询IP": ip, |
|
"请求头": dict(request.headers), |
|
"查询结果": result |
|
} |
|
logger.info(json.dumps(log_data, ensure_ascii=False)) |
|
return result |
|
|
|
if __name__ == '__main__': |
|
query() |
|
import uvicorn |
|
uvicorn.run(app, host="0.0.0.0", port=8080, server_header=False, proxy_headers=True) |
|
|