ASN-Auflösung: dominanter Betreiber statt Offset-0-Treffer
Das anonymisierte /16 (bzw. /32 bei IPv6) gehört oft nicht einem einzigen AS -- gerade im APNIC-Raum ist es in viele kleine Bloecke zerstueckelt. Der bisherige Lookup auf die Basisadresse lieferte dann nur den zufaelligen Inhaber von Offset 0 (z.B. faelschlich "Rakuten Mobile" fuer einen Besucher, der irgendwo im selben /16 sitzt). Jetzt: per NetworksWithin alle ASNs im Netz zaehlen, das mit der groessten Adress-Abdeckung waehlen und bei weiteren "(n weitere Treffer)" anhaengen. Homogene Netze stehen sauber mit Betreibername, zersplitterte tragen den dominanten plus ehrlichen Unsicherheits-Marker. Arbeitet weiter ausschliesslich auf dem maskierten Netz, nie auf der vollen IP. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -70,22 +71,80 @@ func maskIP(ip net.IP) net.IP {
|
||||
}
|
||||
|
||||
// lookupASN löst das bereits anonymisierte Netz zum autonomen System auf. Die
|
||||
// volle IP wird dafür bewusst NICHT verwendet -> die Auflösung ist auf
|
||||
// Netzebene grob (innerhalb eines /16 mit mehreren ASNs nicht immer exakt), aber
|
||||
// es verlässt nie eine vollständige IP diese Funktion. Liefert z.B.
|
||||
// "AS3320 Deutsche Telekom AG" oder "" (keine DB/kein Treffer).
|
||||
// volle IP wird dafür bewusst NICHT verwendet -> wir kennen nur das maskierte
|
||||
// Netz (/16 bei IPv4, /32 bei IPv6).
|
||||
//
|
||||
// Ein solches Netz gehört oft nicht einem einzigen AS: gerade im APNIC-Raum ist
|
||||
// ein /16 in viele kleine Blöcke zerstückelt. Ein Lookup auf die Basisadresse
|
||||
// liefert dann nur den (zufälligen) Inhaber von Offset 0. Stattdessen zählen wir
|
||||
// per NetworksWithin alle ASNs im Netz und wählen das mit der größten
|
||||
// Adress-Abdeckung; gibt es weitere, hängen wir "(n weitere Treffer)" an. So
|
||||
// steht bei einem homogenen Netz schlicht der Betreiber, bei einem
|
||||
// zersplitterten der dominante plus ein ehrlicher Unsicherheits-Hinweis.
|
||||
//
|
||||
// Liefert z.B. "AS3320 Deutsche Telekom AG", "AS8881 1&1 Versatel GmbH
|
||||
// (2 weitere Treffer)" oder "" (keine DB/kein Treffer).
|
||||
func lookupASN(ip net.IP) string {
|
||||
if geoASN == nil || ip == nil {
|
||||
return ""
|
||||
}
|
||||
// Das anonymisierte Netz rekonstruieren — gleiche Maske wie maskIP.
|
||||
var network *net.IPNet
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
network = &net.IPNet{IP: v4, Mask: net.CIDRMask(16, 32)}
|
||||
} else {
|
||||
network = &net.IPNet{IP: ip, Mask: net.CIDRMask(32, 128)}
|
||||
}
|
||||
|
||||
// Adress-Abdeckung je AS aufsummieren. big.Int, weil ein IPv6-Teilnetz im
|
||||
// /32 bis zu 2^96 Adressen umfasst und int64 sprengen würde.
|
||||
type cover struct {
|
||||
org string
|
||||
count *big.Int
|
||||
}
|
||||
covers := map[uint]*cover{}
|
||||
nets := geoASN.NetworksWithin(network)
|
||||
for nets.Next() {
|
||||
var rec asnRecord
|
||||
if err := geoASN.Lookup(ip, &rec); err != nil || rec.Number == 0 {
|
||||
subnet, err := nets.Network(&rec)
|
||||
if err != nil || rec.Number == 0 {
|
||||
continue
|
||||
}
|
||||
ones, bits := subnet.Mask.Size()
|
||||
size := new(big.Int).Lsh(big.NewInt(1), uint(bits-ones))
|
||||
c := covers[rec.Number]
|
||||
if c == nil {
|
||||
c = &cover{org: rec.Org, count: new(big.Int)}
|
||||
covers[rec.Number] = c
|
||||
}
|
||||
c.count.Add(c.count, size)
|
||||
}
|
||||
if len(covers) == 0 {
|
||||
return ""
|
||||
}
|
||||
if rec.Org == "" {
|
||||
return fmt.Sprintf("AS%d", rec.Number)
|
||||
|
||||
// AS mit der größten Abdeckung wählen. Bei Gleichstand das kleinere ASN,
|
||||
// damit das Ergebnis bei zufälliger Map-Reihenfolge stabil bleibt.
|
||||
var bestNum uint
|
||||
var best *cover
|
||||
for num, c := range covers {
|
||||
if best == nil {
|
||||
best, bestNum = c, num
|
||||
continue
|
||||
}
|
||||
return fmt.Sprintf("AS%d %s", rec.Number, rec.Org)
|
||||
if cmp := c.count.Cmp(best.count); cmp > 0 || (cmp == 0 && num < bestNum) {
|
||||
best, bestNum = c, num
|
||||
}
|
||||
}
|
||||
|
||||
label := fmt.Sprintf("AS%d", bestNum)
|
||||
if best.org != "" {
|
||||
label += " " + best.org
|
||||
}
|
||||
if extra := len(covers) - 1; extra > 0 {
|
||||
label += fmt.Sprintf(" (%d weitere Treffer)", extra)
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
// trackImpression zählt einen Seitenaufruf pro (Tag, anonymisiertes Netz, Pfad)
|
||||
|
||||
Reference in New Issue
Block a user