diff --git a/main.go b/main.go index cee8350..7f0577c 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "context" + "encoding/binary" "encoding/json" "expvar" "fmt" @@ -151,31 +152,79 @@ func handleCheck(w http.ResponseWriter, r *http.Request) { }) } +// liefert alle möglichen Präfixe dieser IP, beginnend beim längsten (/32 oder /128) +func supernets(ip netip.Addr) []string { + if ip.Is4() { + a := ip.As4() // Kopie addressierbar machen + u := binary.BigEndian.Uint32(a[:]) // jetzt darf man slicen + + supers := make([]string, 33) // /32 … /0 + for bits := 32; bits >= 0; bits-- { + mask := uint32(0xffffffff) << (32 - bits) + n := u & mask + addr := netip.AddrFrom4([4]byte{ + byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n), + }) + supers[32-bits] = fmt.Sprintf("%s/%d", addr, bits) + } + return supers + } + + a := ip.As16() // Kopie addressierbar + supers := make([]string, 129) // /128 … /0 + for bits := 128; bits >= 0; bits-- { + b := a // Wert-Kopie für Modifikation + + // vollständige Bytes auf 0 setzen + full := (128 - bits) / 8 + for i := 0; i < full; i++ { + b[15-i] = 0 + } + // Restbits maskieren + rem := (128 - bits) % 8 + if rem != 0 { + b[15-full] &= 0xFF << rem + } + + addr := netip.AddrFrom16(b) + supers[128-bits] = fmt.Sprintf("%s/%d", addr, bits) + } + return supers +} + func checkIP(ip netip.Addr, cats []string) ([]string, error) { if res, ok := ipCache.Get(ip.String()); ok { hits.Add(1) return res, nil } - matches := []string{} + supers := supernets(ip) + pipe := rdb.Pipeline() + cmds := make([]*redis.IntCmd, 0, len(cats)*len(supers)) + + // *einen* Redis-Roundtrip bauen for _, cat := range cats { - iter := rdb.Scan(ctx, 0, "bl:"+cat+":*", 0).Iterator() - for iter.Next(ctx) { - key := iter.Val() - parts := strings.SplitN(key, ":", 3) - if len(parts) != 3 { - continue - } - pfx, err := netip.ParsePrefix(parts[2]) - if err != nil { - continue - } - if pfx.Contains(ip) { - matches = append(matches, cat) - break - } + for _, pfx := range supers { + key := "bl:" + cat + ":" + pfx + cmds = append(cmds, pipe.Exists(ctx, key)) } } + if _, err := pipe.Exec(ctx); err != nil && err != redis.Nil { + return nil, err + } + + matches := make([]string, 0, len(cats)) + idx := 0 + for _, cat := range cats { + for range supers { + if cmds[idx].Val() == 1 { + matches = append(matches, cat) + break // Kategorie gefunden → nächste + } + idx++ + } + } + misses.Add(1) ipCache.Add(ip.String(), matches) return matches, nil