bugfix for single IP lists
All checks were successful
release-tag / release-image (push) Successful in 1m32s

This commit is contained in:
2025-06-09 18:32:34 +02:00
parent c8d3dc66b1
commit 969c25ce9a

66
main.go
View File

@@ -25,33 +25,28 @@ import (
type Source struct {
Category string // e.g. "spam", "tor", "malware"
URL []string // one or many URLs that belong to the same category
URL []string // one or many URLs belonging to this category
}
type Config struct {
RedisAddr string
Sources []Source // each Source now groups URLs per category
TTLHours int // Redis TTL for block entries
Sources []Source // grouped by category
TTLHours int // TTL for block entries in Redis
}
func loadConfig() Config {
// --- default onecategory fallback --------------------------------------
// default single source
srcs := []Source{{
Category: "generic",
URL: []string{"https://ipv64.net/blocklists/ipv64_blocklist_firehole_l1.txt"},
URL: []string{"https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset"},
}}
/*
ENV syntax supporting multiple URLs per category:
ENV format supporting many URLs per category:
BLOCKLIST_SOURCES="spam:https://a.net|https://b.net,tor:https://c.net;https://d.net"
categories separated by comma
URLs inside category separated by | or ;
*/
if env := os.Getenv("BLOCKLIST_SOURCES"); env != "" {
srcs = nil // override default
srcs = nil
for _, spec := range strings.Split(env, ",") {
spec = strings.TrimSpace(spec)
if spec == "" {
@@ -59,12 +54,12 @@ func loadConfig() Config {
}
parts := strings.SplitN(spec, ":", 2)
if len(parts) != 2 {
continue // malformed fragment
continue
}
cat := strings.TrimSpace(parts[0])
rawURLs := strings.FieldsFunc(parts[1], func(r rune) bool { return r == '|' || r == ';' })
raw := strings.FieldsFunc(parts[1], func(r rune) bool { return r == '|' || r == ';' })
var urls []string
for _, u := range rawURLs {
for _, u := range raw {
if u = strings.TrimSpace(u); u != "" {
urls = append(urls, u)
}
@@ -80,10 +75,8 @@ func loadConfig() Config {
fmt.Sscanf(env, "%d", &ttl)
}
fmt.Println(getenv("REDIS_ADDR", "localhost:6379"), srcs, ttl)
return Config{
RedisAddr: getenv("REDIS_ADDR", "localhost:6379"),
RedisAddr: getenv("REDIS_ADDR", "redis:6379"),
Sources: srcs,
TTLHours: ttl,
}
@@ -104,12 +97,12 @@ func keyBlock(cat string, p netip.Prefix) string { return "bl:" + cat + ":" + p.
func keyWhite(a netip.Addr) string { return "wl:" + a.String() }
// -----------------------------------------------------------------------------
// INMEMORY RANGER percategory CIDR map
// IN-MEMORY RANGER
// -----------------------------------------------------------------------------
type Ranger struct {
mu sync.RWMutex
blocks map[string]map[netip.Prefix]struct{} // cat → set(prefix)
blocks map[string]map[netip.Prefix]struct{}
whites map[netip.Addr]struct{}
}
@@ -132,7 +125,6 @@ func (r *Ranger) addWhite(a netip.Addr) {
r.mu.Unlock()
}
// blockedInCats returns slice of categories in which IP is blocked
func (r *Ranger) blockedInCats(a netip.Addr, cats []string) []string {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -163,7 +155,7 @@ func (r *Ranger) blockedInCats(a netip.Addr, cats []string) []string {
}
// -----------------------------------------------------------------------------
// SYNC WORKER fetch lists → Redis + Ranger
// SYNC WORKER
// -----------------------------------------------------------------------------
func syncOnce(ctx context.Context, cfg Config, rdb *redis.Client, ranger *Ranger) error {
@@ -179,7 +171,7 @@ func syncOnce(ctx context.Context, cfg Config, rdb *redis.Client, ranger *Ranger
newBlocks[src.Category][p] = struct{}{}
_ = rdb.Set(ctx, keyBlock(src.Category, p), "1", expiry).Err()
}); err != nil {
fmt.Println("Error initialising List:", err, src.Category, src.URL)
return err
}
}
}
@@ -210,6 +202,14 @@ func parseStream(r io.Reader, cb func(netip.Prefix)) error {
}
if p, err := netip.ParsePrefix(line); err == nil {
cb(p)
continue
}
if addr, err := netip.ParseAddr(line); err == nil {
plen := 32
if addr.Is6() {
plen = 128
}
cb(netip.PrefixFrom(addr, plen))
}
}
return s.Err()
@@ -227,9 +227,9 @@ type Server struct {
func (s *Server) routes() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/check/", s.handleCheck) // GET /check/<ip>?cats=spam,tor
mux.HandleFunc("/whitelist", s.handleAddWhite) // POST {"ip":"1.2.3.4"}
mux.HandleFunc("/categories", s.handleCats) // GET all categories
mux.HandleFunc("/check/", s.handleCheck)
mux.HandleFunc("/whitelist", s.handleAddWhite)
mux.HandleFunc("/categories", s.handleCats)
return mux
}
@@ -240,18 +240,12 @@ func (s *Server) handleCheck(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad ip", http.StatusBadRequest)
return
}
var cats []string
if q := strings.TrimSpace(r.URL.Query().Get("cats")); q != "" {
cats = strings.Split(q, ",")
}
blocked := s.ranger.blockedInCats(addr, cats)
writeJSON(w, map[string]any{
"ip": ipStr,
"blocked": len(blocked) > 0,
"categories": blocked,
})
writeJSON(w, map[string]any{"ip": ipStr, "blocked": len(blocked) > 0, "categories": blocked})
}
func (s *Server) handleAddWhite(w http.ResponseWriter, r *http.Request) {
@@ -263,16 +257,16 @@ func (s *Server) handleAddWhite(w http.ResponseWriter, r *http.Request) {
IP string `json:"ip"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
http.Error(w, "bad json", 400)
http.Error(w, "bad json", http.StatusBadRequest)
return
}
addr, err := netip.ParseAddr(strings.TrimSpace(body.IP))
if err != nil {
http.Error(w, "bad ip", 400)
http.Error(w, "bad ip", http.StatusBadRequest)
return
}
if err := s.rdb.Set(r.Context(), keyWhite(addr), "1", 0).Err(); err != nil {
http.Error(w, "redis", 500)
http.Error(w, "redis", http.StatusInternalServerError)
return
}
s.ranger.addWhite(addr)