diff --git a/activation.go b/activation.go index 5539bb8..8aeb60e 100644 --- a/activation.go +++ b/activation.go @@ -1,9 +1,5 @@ package main -import ( - "time" -) - // ToggleData type type ToggleData struct { Mode uint @@ -17,9 +13,7 @@ type ActivationHandler struct { setChannel chan bool } -func startActivation(actChannel chan *ActivationHandler, quit chan bool, reactivationDelay uint) { - var reactivate time.Time - var reactivatePending bool +func startActivation(actChannel chan *ActivationHandler, quit chan bool) { a := &ActivationHandler{} a.queryChannel = make(chan bool) @@ -30,10 +24,6 @@ func startActivation(actChannel chan *ActivationHandler, quit chan bool, reactiv // then continue to the loop actChannel <- a - ticker := time.Tick(1 * time.Second) - - var nextToggleTime = time.Now() - forever: for { select { @@ -42,35 +32,15 @@ forever: case <-a.queryChannel: a.queryChannel <- lengActive case v := <-a.toggleChannel: - // Firefox is sending 2 queries in a row, so debouncing is needed. - if v.Mode == 1 && nextToggleTime.After(time.Now()) { - logger.Warning("Toggle is too close: wait 10 seconds\n") + if v.Mode == 1 { + lengActive = !lengActive } else { - if v.Mode == 1 { - lengActive = !lengActive - } else { - lengActive = false - } - nextToggleTime = time.Now().Add(time.Duration(10) * time.Second) - if !lengActive && reactivationDelay > 0 { - reactivate = time.Now().Add(time.Duration(v.Data) * time.Second) - reactivatePending = true - } else { - reactivatePending = false - } - a.queryChannel <- lengActive + lengActive = false } + a.queryChannel <- lengActive case v := <-a.setChannel: lengActive = v - reactivatePending = false a.setChannel <- lengActive - case <-ticker: - now := time.Now() - if reactivatePending && now.After(reactivate) { - logger.Notice("Reactivating leng (timer)") - lengActive = true - reactivatePending = false - } } } logger.Debugf("Activation goroutine exiting") diff --git a/config.go b/config.go index 47af460..a77f2f6 100644 --- a/config.go +++ b/config.go @@ -22,33 +22,40 @@ var ConfigVersion = "1.3.0" // Config holds the configuration parameters type Config struct { Version string - Sources []string - SourceDirs []string LogConfig string Bind string API string - NXDomain bool - Nullroute string - Nullroutev6 string - Nameservers []string Interval int Timeout int - Expire uint32 - Maxcount int QuestionCacheCap int TTL uint32 - Blocklist []string - Whitelist []string CustomDNSRecords []string - ToggleName string - ReactivationDelay uint APIDebug bool - DoH string + Blocking Blocking + Upstream Upstream Metrics Metrics `toml:"metrics"` DnsOverHttpServer DnsOverHttpServer FollowCnameDepth uint32 } +type Blocking struct { + Sources []string + SourceDirs []string + Blocklist []string + Whitelist []string + NXDomain bool + Nullroute string + Nullroutev6 string +} + +type Upstream struct { + DoH string + Nameservers []string + TimeoutS int `toml:"timeout_s"` + Expire uint32 + Maxcount int +} + type Metrics struct { Enabled bool Path string @@ -78,21 +85,6 @@ var defaultConfig = ` # version this config was generated from version = "%s" -# list of sources to pull blocklists from, stores them in ./sources -sources = [ - "https://mirror1.malwaredomains.com/files/justdomains", - "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", - "https://sysctl.org/cameleon/hosts", - "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", - "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", - "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" -] - -# list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) -sourcedirs = [ - "sources" -] - # log configuration # format: comma separated list of options, where options is one of # file:@ @@ -111,36 +103,12 @@ bind = "0.0.0.0:53" # address to bind to for the API server api = "127.0.0.1:8080" -# response to blocked queries with a NXDOMAIN -nxdomain = false - -# ipv4 address to forward blocked queries to -nullroute = "0.0.0.0" - -# ipv6 address to forward blocked queries to -nullroutev6 = "0:0:0:0:0:0:0:0" - -# nameservers to forward queries to -nameservers = ["1.1.1.1:53", "1.0.0.1:53"] - -# concurrency interval for lookups in miliseconds +# concurrency interval for lookups in milliseconds interval = 200 -# query timeout for dns lookups in seconds -timeout = 5 - -# cache entry lifespan in seconds -expire = 600 - -# cache capacity, 0 for infinite -maxcount = 0 - # question cache capacity, 0 for infinite but not recommended (this is used for storing logs) questioncachecap = 5000 -# manual blocklist entries -blocklist = [] - # manual whitelist entries - comments for reference whitelist = [ # "getsentry.com", @@ -153,26 +121,50 @@ customdnsrecords = [ # "example.other.tld IN CNAME wikipedia.org" ] -# When this string is queried, toggle leng on and off -togglename = "" - -# If not zero, the delay in seconds before leng automaticall reactivates after -# having been turned off. -reactivationdelay = 300 - -# Dns over HTTPS upstream provider to use -DoH = "https://cloudflare-dns.com/dns-query" - # How deep to follow chains of CNAME records # set to 0 to disable CNAME-following entirely # (anything more than 10 should be more than plenty) # see https://github.com/Cottand/leng/wiki/CNAME%E2%80%90following-DNS followCnameDepth = 12 +[Blocking] + # response to blocked queries with a NXDOMAIN + nxdomain = false + # ipv4 address to forward blocked queries to + nullroute = "0.0.0.0" + # ipv6 address to forward blocked queries to + nullroutev6 = "0:0:0:0:0:0:0:0" + # manual blocklist entries + blocklist = [] + # list of sources to pull blocklists from, stores them in ./sources + sources = [ + "https://mirror1.malwaredomains.com/files/justdomains", + "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", + "https://sysctl.org/cameleon/hosts", + "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", + "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", + "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" + ] + # list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) + sourcedirs = ["sources"] + + +[Upstream] + # Dns over HTTPS provider to use. + DoH = "https://cloudflare-dns.com/dns-query" + # nameservers to forward queries to + nameservers = ["1.1.1.1:53", "1.0.0.1:53"] + # query timeout for dns lookups in seconds + timeout_s = 5 + # cache entry lifespan in seconds + expire = 600 + # cache capacity, 0 for infinite + maxcount = 0 + # Prometheus metrics - disabled by default [Metrics] - enabled = false - path = "/metrics" + enabled = false + path = "/metrics" [DnsOverHttpServer] enabled = false diff --git a/doc/src/Configuration.md b/doc/src/Configuration.md index 21c19ab..1549f5d 100644 --- a/doc/src/Configuration.md +++ b/doc/src/Configuration.md @@ -3,21 +3,6 @@ If leng.toml is not found the default configuration will be used. If it is found Here is the default configuration: ```toml -# list of sources to pull blocklists from, stores them in ./sources -sources = [ - "https://mirror1.malwaredomains.com/files/justdomains", - "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", - "https://sysctl.org/cameleon/hosts", - "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", - "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", - "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" -] - -# list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) -sourcedirs = [ - "sources" -] - # log configuration # format: comma separated list of options, where options is one of # file:@ @@ -36,69 +21,77 @@ bind = "0.0.0.0:53" # address to bind to for the API server api = "127.0.0.1:8080" -# response to blocked queries with a NXDOMAIN -nxdomain = false - -# ipv4 address to forward blocked queries to -nullroute = "0.0.0.0" - -# ipv6 address to forward blocked queries to -nullroutev6 = "0:0:0:0:0:0:0:0" - -# nameservers to forward queries to -nameservers = ["1.1.1.1:53", "1.0.0.1:53"] - # concurrency interval for lookups in miliseconds interval = 200 -# query timeout for dns lookups in seconds -timeout = 5 - -# cache entry lifespan in seconds -expire = 600 - -# cache capacity, 0 for infinite -maxcount = 0 - # question cache capacity, 0 for infinite but not recommended (this is used for storing logs) questioncachecap = 5000 -# manual blocklist entries -blocklist = [] - -# Drbl related settings -usedrbl = 0 -drblpeersfilename = "drblpeers.yaml" -drblblockweight = 128 -drbltimeout = 30 -drbldebug = 0 - -# manual whitelist entries - comments for reference -whitelist = [ - # "getsentry.com", - # "www.getsentry.com" -] - # manual custom dns entries - comments for reference customdnsrecords = [ # "example.mywebsite.tld IN A 10.0.0.1" # "example.other.tld IN CNAME wikipedia.org" ] -# When this string is queried, toggle leng on and off -togglename = "" - -# If not zero, the delay in seconds before leng automaticall reactivates after -# having been turned off. -reactivationdelay = 300 - -#Dns over HTTPS provider to use. -DoH = "https://cloudflare-dns.com/dns-query" +[Blocking] + # response to blocked queries with a NXDOMAIN + nxdomain = false + # ipv4 address to forward blocked queries to + nullroute = "0.0.0.0" + # ipv6 address to forward blocked queries to + nullroutev6 = "0:0:0:0:0:0:0:0" + # manual blocklist entries + blocklist = [] + # list of sources to pull blocklists from, stores them in ./sources + sources = [ + "https://mirror1.malwaredomains.com/files/justdomains", + "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", + "https://sysctl.org/cameleon/hosts", + "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", + "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", + "https://gitlab.com/quidsup/notrack-blocklists/raw/master/notrack-blocklist.txt" + ] + # list of locations to recursively read blocklists from (warning, every file found is assumed to be a hosts-file or domain list) + sourcedirs = ["sources"] + # manual blocklist entries + blocklist = [] + # manual whitelist entries - comments for reference + whitelist = [ + # "getsentry.com", + # "www.getsentry.com" + ] + + + +[Upstream] + # Dns over HTTPS provider to use. + DoH = "https://cloudflare-dns.com/dns-query" + # nameservers to forward queries to + nameservers = ["1.1.1.1:53", "1.0.0.1:53"] + # query timeout for dns lookups in seconds + timeout_s = 5 + # cache entry lifespan in seconds + expire = 600 + # cache capacity, 0 for infinite + maxcount = 0 # Prometheus metrics - enable [Metrics] - enabled = false - path = "/metrics" + enabled = false + path = "/metrics" + +[DnsOverHttpServer] + enabled = false + bind = "0.0.0.0:80" + timeoutMs = 5000 + +# TLS config is not required for DoH if you have some proxy (ie, caddy, nginx, traefik...) manage HTTPS for you + [DnsOverHttpServer.TLS] + enabled = false + certPath = "" + keyPath = "" + # if empty, system CAs will be used + caPath = "" ``` The most up-to-date version can be found on [config.go](https://github.com/Cottand/leng/blob/master/config.go) \ No newline at end of file diff --git a/grimd_test.go b/grimd_test.go index faedf48..5f1a97e 100644 --- a/grimd_test.go +++ b/grimd_test.go @@ -39,7 +39,7 @@ func integrationTest(changeConfig func(c *Config), test func(client *dns.Client, quitActivation := make(chan bool) actChannel := make(chan *ActivationHandler) - go startActivation(actChannel, quitActivation, config.ReactivationDelay) + go startActivation(actChannel, quitActivation) lengActivation = <-actChannel lengActive = true close(actChannel) @@ -53,7 +53,7 @@ func integrationTest(changeConfig func(c *Config), test func(client *dns.Client, // BlockCache contains all blocked domains blockCache := &MemoryBlockCache{Backend: make(map[string]bool)} - for _, blocked := range config.Blocklist { + for _, blocked := range config.Blocking.Blocklist { _ = blockCache.Set(blocked, true) } // QuestionCache contains all queries to the dns server @@ -200,7 +200,7 @@ func TestCnameFollowWithBlocked(t *testing.T) { "first.com IN CNAME second.com ", "second.com IN CNAME example.com ", } - c.Blocklist = []string{"example.com"} + c.Blocking.Blocklist = []string{"example.com"} }, func(client *dns.Client, target string) { @@ -289,7 +289,7 @@ func TestConfigReloadForCustomRecords(t *testing.T) { quitActivation := make(chan bool) actChannel := make(chan *ActivationHandler) - go startActivation(actChannel, quitActivation, config.ReactivationDelay) + go startActivation(actChannel, quitActivation) lengActivation = <-actChannel close(actChannel) diff --git a/handler.go b/handler.go index 0b2ba8a..9d6da8f 100644 --- a/handler.go +++ b/handler.go @@ -4,7 +4,6 @@ import ( "github.com/cottand/leng/internal/metric" "net" "slices" - "strings" "sync" "time" @@ -71,12 +70,12 @@ func NewEventLoop(config *Config, blockCache *MemoryBlockCache, questionCache *M resolver = &Resolver{clientConfig} cache = &MemoryCache{ - Backend: make(map[string]*Mesg, config.Maxcount), - Maxcount: config.Maxcount, + Backend: make(map[string]*Mesg, config.Upstream.Maxcount), + Maxcount: config.Upstream.Maxcount, } negCache = &MemoryCache{ Backend: make(map[string]*Mesg), - Maxcount: config.Maxcount, + Maxcount: config.Upstream.Maxcount, } handler := &EventLoop{ @@ -125,16 +124,6 @@ func (h *EventLoop) responseFor(Net string, req *dns.Msg, _local net.Addr, _remo Q := Question{UnFqdn(q.Name), dns.TypeToString[q.Qtype], dns.ClassToString[q.Qclass]} logger.Infof("%s lookup %s\n", remote, Q.String()) var lengActive = lengActivation.query() - if len(h.config.ToggleName) > 0 && strings.Contains(Q.Qname, h.config.ToggleName) { - logger.Noticef("Found ToggleName! (%s)\n", Q.Qname) - lengActive = lengActivation.toggle(h.config.ReactivationDelay) - - if lengActive { - logger.Notice("Leng Activated") - } else { - logger.Notice("Leng Deactivated") - } - } IPQuery := h.isIPQuery(q) @@ -174,11 +163,11 @@ func (h *EventLoop) responseFor(Net string, req *dns.Msg, _local net.Addr, _remo m := new(dns.Msg) m.SetReply(req) - if h.config.NXDomain { + if h.config.Blocking.NXDomain { m.SetRcode(req, dns.RcodeNameError) } else { - nullroute := net.ParseIP(h.config.Nullroute) - nullroutev6 := net.ParseIP(h.config.Nullroutev6) + nullroute := net.ParseIP(h.config.Blocking.Nullroute) + nullroutev6 := net.ParseIP(h.config.Blocking.Nullroutev6) switch IPQuery { case _IP4Query: @@ -225,7 +214,7 @@ func (h *EventLoop) responseFor(Net string, req *dns.Msg, _local net.Addr, _remo NewEntry := QuestionCacheEntry{Date: time.Now().Unix(), Remote: remote.String(), Query: Q, Blocked: false} go h.questionCache.Add(NewEntry) - mesg, err := h.resolver.Lookup(Net, req, h.config.Timeout, h.config.Interval, h.config.Nameservers, h.config.DoH) + mesg, err := h.resolver.Lookup(Net, req, h.config.Timeout, h.config.Interval, h.config.Upstream.Nameservers, h.config.Upstream.DoH) if err != nil { logger.Errorf("resolve query error %s\n", err) @@ -238,7 +227,7 @@ func (h *EventLoop) responseFor(Net string, req *dns.Msg, _local net.Addr, _remo } if mesg.Truncated && Net == "udp" { - mesg, err = h.resolver.Lookup("tcp", req, h.config.Timeout, h.config.Interval, h.config.Nameservers, h.config.DoH) + mesg, err = h.resolver.Lookup("tcp", req, h.config.Timeout, h.config.Interval, h.config.Upstream.Nameservers, h.config.Upstream.DoH) if err != nil { logger.Errorf("resolve tcp query error %s\n", err) @@ -251,7 +240,7 @@ func (h *EventLoop) responseFor(Net string, req *dns.Msg, _local net.Addr, _remo } //find the smallest ttl - ttl := h.config.Expire + ttl := h.config.Upstream.Expire var candidateTTL uint32 for index, answer := range mesg.Answer { diff --git a/main.go b/main.go index 2080108..8b0b855 100644 --- a/main.go +++ b/main.go @@ -63,7 +63,7 @@ func main() { quitActivation := make(chan bool) actChannel := make(chan *ActivationHandler) - go startActivation(actChannel, quitActivation, config.ReactivationDelay) + go startActivation(actChannel, quitActivation) lengActivation = <-actChannel close(actChannel) diff --git a/updater.go b/updater.go index d1731a4..6e2a56a 100644 --- a/updater.go +++ b/updater.go @@ -179,11 +179,11 @@ func parseHostFile(fileName string, blockCache *MemoryBlockCache) error { func PerformUpdate(config *Config, forceUpdate bool) *MemoryBlockCache { newBlockCache := &MemoryBlockCache{Backend: make(map[string]bool), Special: make(map[string]*regexp.Regexp)} if _, err := os.Stat("lists"); os.IsNotExist(err) || forceUpdate { - if err := update(newBlockCache, config.Whitelist, config.Blocklist, config.Sources); err != nil { + if err := update(newBlockCache, config.Blocking.Whitelist, config.Blocking.Blocklist, config.Blocking.Sources); err != nil { logger.Fatal(err) } } - if err := updateBlockCache(newBlockCache, config.SourceDirs); err != nil { + if err := updateBlockCache(newBlockCache, config.Blocking.SourceDirs); err != nil { logger.Fatal(err) }