bugfix for single IP lists
All checks were successful
release-tag / release-image (push) Successful in 1m32s
All checks were successful
release-tag / release-image (push) Successful in 1m32s
This commit is contained in:
66
main.go
66
main.go
@@ -25,33 +25,28 @@ import (
|
|||||||
|
|
||||||
type Source struct {
|
type Source struct {
|
||||||
Category string // e.g. "spam", "tor", "malware"
|
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 {
|
type Config struct {
|
||||||
RedisAddr string
|
RedisAddr string
|
||||||
Sources []Source // each Source now groups URLs per category
|
Sources []Source // grouped by category
|
||||||
TTLHours int // Redis TTL for block entries
|
TTLHours int // TTL for block entries in Redis
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfig() Config {
|
func loadConfig() Config {
|
||||||
// --- default one‑category fallback --------------------------------------
|
// default single source
|
||||||
srcs := []Source{{
|
srcs := []Source{{
|
||||||
Category: "generic",
|
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"
|
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 != "" {
|
if env := os.Getenv("BLOCKLIST_SOURCES"); env != "" {
|
||||||
srcs = nil // override default
|
srcs = nil
|
||||||
for _, spec := range strings.Split(env, ",") {
|
for _, spec := range strings.Split(env, ",") {
|
||||||
spec = strings.TrimSpace(spec)
|
spec = strings.TrimSpace(spec)
|
||||||
if spec == "" {
|
if spec == "" {
|
||||||
@@ -59,12 +54,12 @@ func loadConfig() Config {
|
|||||||
}
|
}
|
||||||
parts := strings.SplitN(spec, ":", 2)
|
parts := strings.SplitN(spec, ":", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue // malformed fragment
|
continue
|
||||||
}
|
}
|
||||||
cat := strings.TrimSpace(parts[0])
|
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
|
var urls []string
|
||||||
for _, u := range rawURLs {
|
for _, u := range raw {
|
||||||
if u = strings.TrimSpace(u); u != "" {
|
if u = strings.TrimSpace(u); u != "" {
|
||||||
urls = append(urls, u)
|
urls = append(urls, u)
|
||||||
}
|
}
|
||||||
@@ -80,10 +75,8 @@ func loadConfig() Config {
|
|||||||
fmt.Sscanf(env, "%d", &ttl)
|
fmt.Sscanf(env, "%d", &ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(getenv("REDIS_ADDR", "localhost:6379"), srcs, ttl)
|
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
RedisAddr: getenv("REDIS_ADDR", "localhost:6379"),
|
RedisAddr: getenv("REDIS_ADDR", "redis:6379"),
|
||||||
Sources: srcs,
|
Sources: srcs,
|
||||||
TTLHours: ttl,
|
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() }
|
func keyWhite(a netip.Addr) string { return "wl:" + a.String() }
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// IN‑MEMORY RANGER – per‑category CIDR map
|
// IN-MEMORY RANGER
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
type Ranger struct {
|
type Ranger struct {
|
||||||
mu sync.RWMutex
|
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{}
|
whites map[netip.Addr]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +125,6 @@ func (r *Ranger) addWhite(a netip.Addr) {
|
|||||||
r.mu.Unlock()
|
r.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// blockedInCats – returns slice of categories in which IP is blocked
|
|
||||||
func (r *Ranger) blockedInCats(a netip.Addr, cats []string) []string {
|
func (r *Ranger) blockedInCats(a netip.Addr, cats []string) []string {
|
||||||
r.mu.RLock()
|
r.mu.RLock()
|
||||||
defer r.mu.RUnlock()
|
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 {
|
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{}{}
|
newBlocks[src.Category][p] = struct{}{}
|
||||||
_ = rdb.Set(ctx, keyBlock(src.Category, p), "1", expiry).Err()
|
_ = rdb.Set(ctx, keyBlock(src.Category, p), "1", expiry).Err()
|
||||||
}); err != nil {
|
}); 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 {
|
if p, err := netip.ParsePrefix(line); err == nil {
|
||||||
cb(p)
|
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()
|
return s.Err()
|
||||||
@@ -227,9 +227,9 @@ type Server struct {
|
|||||||
|
|
||||||
func (s *Server) routes() http.Handler {
|
func (s *Server) routes() http.Handler {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/check/", s.handleCheck) // GET /check/<ip>?cats=spam,tor
|
mux.HandleFunc("/check/", s.handleCheck)
|
||||||
mux.HandleFunc("/whitelist", s.handleAddWhite) // POST {"ip":"1.2.3.4"}
|
mux.HandleFunc("/whitelist", s.handleAddWhite)
|
||||||
mux.HandleFunc("/categories", s.handleCats) // GET all categories
|
mux.HandleFunc("/categories", s.handleCats)
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,18 +240,12 @@ func (s *Server) handleCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "bad ip", http.StatusBadRequest)
|
http.Error(w, "bad ip", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var cats []string
|
var cats []string
|
||||||
if q := strings.TrimSpace(r.URL.Query().Get("cats")); q != "" {
|
if q := strings.TrimSpace(r.URL.Query().Get("cats")); q != "" {
|
||||||
cats = strings.Split(q, ",")
|
cats = strings.Split(q, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
blocked := s.ranger.blockedInCats(addr, cats)
|
blocked := s.ranger.blockedInCats(addr, cats)
|
||||||
writeJSON(w, map[string]any{
|
writeJSON(w, map[string]any{"ip": ipStr, "blocked": len(blocked) > 0, "categories": blocked})
|
||||||
"ip": ipStr,
|
|
||||||
"blocked": len(blocked) > 0,
|
|
||||||
"categories": blocked,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleAddWhite(w http.ResponseWriter, r *http.Request) {
|
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"`
|
IP string `json:"ip"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
http.Error(w, "bad json", 400)
|
http.Error(w, "bad json", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addr, err := netip.ParseAddr(strings.TrimSpace(body.IP))
|
addr, err := netip.ParseAddr(strings.TrimSpace(body.IP))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "bad ip", 400)
|
http.Error(w, "bad ip", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.rdb.Set(r.Context(), keyWhite(addr), "1", 0).Err(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
s.ranger.addWhite(addr)
|
s.ranger.addWhite(addr)
|
||||||
|
Reference in New Issue
Block a user