Files
myip/app.py
2025-12-03 17:20:30 +00:00

387 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from flask import Flask, request, render_template_string
import requests
from werkzeug.middleware.proxy_fix import ProxyFix
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
RIPESTAT_BASE = "https://stat.ripe.net/data"
HTML_TEMPLATE = """<!doctype html>
<html lang="it">
<head>
<meta charset="utf-8">
<title>SolidData IP & RIPE Lookup</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="icon" type="image/png" href="/static/favicon.png">
<link rel="stylesheet" href="/static/myip.css">
</head>
<body>
<div class="card">
{% if show_logo %}
<a href="https://www.soliddata.cloud" class="logo-link" target="_blank" rel="noopener">
<img src="/static/logo.png" class="logo" alt="Logo">
</a>
{% endif %}
<div class="badge">
<span class="badge-dot"></span>
<span>IP &amp; RIPE Lookup</span>
</div>
<h1 class="title">Il tuo IP pubblico è:</h1>
<div class="ip-wrapper">
<span id="ip-value" class="ip">{{ ip }}</span>
</div>
<button id="copy-btn" class="copy-btn copy-btn-below" type="button">
<span class="copy-icon">📋</span>
<span class="copy-label">Copia IP</span>
</button>
<div class="divider"></div>
{% if ripe %}
<div class="ripe-title">Informazioni RIPE</div>
{% if ripe.holder %}
<div class="ripe-item"><strong>Provider:</strong> {{ ripe.holder }}</div>
{% endif %}
{% if ripe.routing_status %}
<div class="ripe-item"><strong>Routing Status:</strong> {{ ripe.routing_status }}</div>
{% endif %}
{% if ripe.reverse_dns %}
<div class="ripe-item"><strong>Reverse DNS:</strong> {{ ripe.reverse_dns }}</div>
{% endif %}
{% if ripe.geoloc %}
<div class="ripe-item">
<strong>GeoLocation:</strong>
{% if ripe.geoloc.code %}
<img src="https://flagsapi.com/{{ ripe.geoloc.code }}/flat/24.png" class="flag-icon" alt="{{ ripe.geoloc.code }}">
{% endif %}
{{ ripe.geoloc.country }}
</div>
{% endif %}
{% if ripe.abuse %}
<div class="ripe-item"><strong>Abuse Contact:</strong> {{ ripe.abuse }}</div>
{% endif %}
{% if ripe.netname %}
<div class="ripe-item"><strong>Netname:</strong> {{ ripe.netname }}</div>
{% endif %}
{% if ripe.org_name %}
<div class="ripe-item"><strong>Organisation:</strong> {{ ripe.org_name }}</div>
{% endif %}
{% if ripe.rir %}
<div class="ripe-item"><strong>RIR:</strong> {{ ripe.rir }}</div>
{% endif %}
{% if ripe.block %}
<div class="ripe-item"><strong>IP Block:</strong> {{ ripe.block }}</div>
{% endif %}
{% if ripe.rir %}
<div class="ripe-item"><strong>RIR:</strong> {{ ripe.rir }}</div>
{% endif %}
<div class="ripe-muted">Dati ottenuti tramite RIPEstat Data API.</div>
{% else %}
<div class="ripe-title">Informazioni RIPE</div>
<div class="ripe-muted">
Non è stato possibile recuperare i dettagli RIPE per questo indirizzo.
</div>
{% endif %}
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
var card = document.querySelector(".card");
if (card) card.classList.add("card-visible");
var btn = document.getElementById("copy-btn");
var ipSpan = document.getElementById("ip-value");
if (!btn || !ipSpan) return;
btn.addEventListener("click", function () {
var ip = ipSpan.textContent.trim();
if (!ip) return;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(ip).then(function () {
var labelEl = btn.querySelector(".copy-label");
var original = labelEl.textContent;
btn.classList.add("copy-btn-success");
labelEl.textContent = "Copiato!";
setTimeout(function () {
btn.classList.remove("copy-btn-success");
labelEl.textContent = original;
}, 1500);
}).catch(console.error);
}
});
});
</script>
</body>
</html>
"""
import ipaddress
from flask import Flask, request, render_template_string
# ...
def _is_private_ip(ip: str) -> bool:
try:
return ipaddress.ip_address(ip).is_private
except ValueError:
# se non è un IP valido, lo consideriamo "non utilizzabile"
return True
def get_client_ip():
"""
Prova a determinare l'IP pubblico del client:
- prende la lista da X-Forwarded-For
- sceglie il primo IP NON privato (non 10.x/192.168/172.16/127.x ecc.)
- fallback su X-Real-IP
- fallback su request.remote_addr
"""
# 1) X-Forwarded-For: "client, proxy1, proxy2..."
xff = request.headers.get("X-Forwarded-For", "")
if xff:
parts = [p.strip() for p in xff.split(",") if p.strip()]
# cerco il primo IP pubblico nella lista
for ip in parts:
if not _is_private_ip(ip):
return ip
# se proprio non trovo nulla di pubblico, prendo il primo
if parts:
return parts[0]
# 2) X-Real-IP
xri = request.headers.get("X-Real-IP")
if xri and not _is_private_ip(xri):
return xri.strip()
# 3) Fallback: remote_addr così com'è
return request.remote_addr or "Sconosciuto"
def get_effective_ip():
"""
Restituisce (ip, overridden)
- ip: indirizzo IP da usare per la lookup
- overridden: True se l'IP è stato forzato via ?checkip=
"""
override = request.args.get("checkip")
if override:
override = override.strip()
try:
# valida che sia un IP v4/v6
ipaddress.ip_address(override)
return override, True
except ValueError:
# se non è valido, ignora e usa il flusso normale
pass
# default: uso l'IP reale del client
return get_client_ip(), False
def fetch_ripe_info(ip: str):
"""Recupera informazioni RIPEstat estese per l'IP, compatibili col template."""
info = {
"prefix": None,
"asns": [],
"holder": None,
"routing_status": None,
"reverse_dns": None,
"geoloc": None,
"abuse": None,
# campi extra che stai già usando in pagina
"block": None,
"rir": None,
"country": None, # non usato ora in UI
"country_whois": None, # usato in "Country (WHOIS)"
"netname": None, # usato in "Netname"
"org_name": None,
"raw": {}, # solo per debug
}
try:
# 0) PREFIX-OVERVIEW: blocco IP + RIR + stato announced/routed
po_resp = requests.get(
f"{RIPESTAT_BASE}/prefix-overview/data.json",
params={"resource": ip},
timeout=2,
)
po = po_resp.json().get("data", {})
info["raw"]["prefix_overview"] = po
if po:
# es: 77.43.64.0/18
info["block"] = po.get("resource")
# es: block.desc = "RIPE NCC (Status: ALLOCATED)"
block = po.get("block") or {}
desc = block.get("desc")
if desc:
# prendiamo la parte prima della parentesi
info["rir"] = desc.split("(")[0].strip()
# announced / routed (bool)
info["routing_status"] = None
if po.get("announced") is not None:
info["routing_status"] = "Announced" if po.get("announced") else "Not announced"
# prefix principale
if not info["prefix"]:
info["prefix"] = po.get("resource")
# 1) NETWORK-INFO: prefisso + ASNs
ni_resp = requests.get(
f"{RIPESTAT_BASE}/network-info/data.json",
params={"resource": ip},
timeout=2,
)
ni = ni_resp.json().get("data", {})
info["raw"]["network_info"] = ni
if ni:
if not info["prefix"]:
info["prefix"] = ni.get("prefix")
asns = ni.get("asns") or []
info["asns"] = [f"AS{a}" for a in asns]
# 2) AS-OVERVIEW: holder/provider (es: "AS-IRIDEOS - Retelit Digital Services S.p.A.")
if ni and ni.get("asns"):
ao_resp = requests.get(
f"{RIPESTAT_BASE}/as-overview/data.json",
params={"resource": ni["asns"][0]},
timeout=2,
)
ao = ao_resp.json().get("data", {})
info["raw"]["as_overview"] = ao
if ao:
info["holder"] = ao.get("holder") or info["holder"]
# 3) ROUTING-STATUS: origin AS + visibilità
rs_resp = requests.get(
f"{RIPESTAT_BASE}/routing-status/data.json",
params={"resource": info["prefix"] or ip},
timeout=2,
)
rs = rs_resp.json().get("data", {})
info["raw"]["routing_status"] = rs
if rs:
origins = rs.get("origins") or []
origin_as = None
if origins:
origin_as = origins[0].get("origin")
v4 = (rs.get("visibility") or {}).get("v4") or {}
peers = v4.get("ris_peers_seeing")
total = v4.get("total_ris_peers")
if origin_as and peers is not None and total is not None:
info["routing_status"] = f"Origin AS{origin_as}, visibility {peers}/{total} peers"
elif origin_as:
info["routing_status"] = f"Origin AS{origin_as}"
# 4) REVERSE-DNS
rd_resp = requests.get(
f"{RIPESTAT_BASE}/reverse-dns/data.json",
params={"resource": ip},
timeout=2,
)
rd = rd_resp.json().get("data", {}).get("result", [])
info["raw"]["reverse_dns"] = rd
if rd:
info["reverse_dns"] = rd[0].get("name")
# 5) GEOLOC (usa located_resources → locations)
gl_resp = requests.get(
f"{RIPESTAT_BASE}/geoloc/data.json",
params={"resource": ip},
timeout=2,
)
gl = gl_resp.json().get("data", {})
info["raw"]["geoloc"] = gl
located_resources = gl.get("located_resources") or []
if located_resources:
first_res = located_resources[0]
locs = first_res.get("locations") or []
if locs:
loc0 = locs[0]
city = loc0.get("city")
country_code = loc0.get("country")
# flagsapi vuole il codice (es: IT)
label = country_code
if city and country_code:
label = f"{city}, {country_code}"
info["geoloc"] = {
"country": label,
"code": country_code,
}
# 6) ABUSE CONTACT (abuse_contacts, non emails)
ac_resp = requests.get(
f"{RIPESTAT_BASE}/abuse-contact-finder/data.json",
params={"resource": ip},
timeout=2,
)
ac = ac_resp.json().get("data", {})
info["raw"]["abuse"] = ac
abuse_contacts = ac.get("abuse_contacts") or []
if abuse_contacts:
info["abuse"] = abuse_contacts[0]
# 7) WHOIS: netname, country (WHOIS), org / descr
whois_resp = requests.get(
f"{RIPESTAT_BASE}/whois/data.json",
params={"resource": ip},
timeout=3,
)
whois = whois_resp.json().get("data", {})
info["raw"]["whois"] = whois
records = whois.get("records") or []
for block in records:
for entry in block:
k = entry.get("key", "").lower()
v = entry.get("value", "")
if k == "netname" and not info["netname"]:
info["netname"] = v
elif k == "country" and not info["country_whois"]:
info["country_whois"] = v
elif k in ("org-name", "organisation", "descr") and not info["org_name"]:
info["org_name"] = v
except Exception:
pass
return info
@app.route("/")
def index():
ip, overridden = get_effective_ip()
ripe_info = fetch_ripe_info(ip) if ip != "Sconosciuto" else None
return render_template_string(
HTML_TEMPLATE,
ip=ip,
ripe=ripe_info,
show_logo=True,
override=overridden,
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)