summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorradhitya <alif@radhitya.org>2026-06-14 17:17:56 +0700
committerradhitya <alif@radhitya.org>2026-06-14 17:17:56 +0700
commitd173554892339e5211020c60d6af610840eef7ed (patch)
tree295ce37851532e6180b47c63ed34146a01adc12c
parent4e6a897a0b55ee533c05f89fa38dbe0704f2798d (diff)
config, rebranding, fix cache
-rw-r--r--.gitignore2
-rw-r--r--Makefile26
-rw-r--r--go.mod26
-rw-r--r--go.sum36
-rw-r--r--internal/cache/cache.go9
-rw-r--r--internal/config/config.go152
-rw-r--r--internal/server/handler.go6
-rw-r--r--internal/server/server.go6
-rw-r--r--internal/server/server_test.go2
-rw-r--r--linum.toml22
-rw-r--r--main.go111
-rw-r--r--readme2
12 files changed, 324 insertions, 76 deletions
diff --git a/.gitignore b/.gitignore
index 514e3b6..842e6ea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
+build/
etc/blocklist/*.txt
-sdns
todo.md
diff --git a/Makefile b/Makefile
index f1f8ac7..daaf3bf 100644
--- a/Makefile
+++ b/Makefile
@@ -1,14 +1,17 @@
-BINARY = sdns
+BINARY = linum
GO = go
-GOMOD = .
+MAIN = .
+OUTPUT = build/$(BINARY)
+LDFLAGS = -ldflags="-s -w -X main.version=$(VERSION)"
-.PHONY: default build test lint fmt clean run install fuzz race
+.PHONY: default build test lint fmt clean run install fuzz race \
+ build-linux
default: test lint build
build:
- $(GO) build -o $(BINARY) $(GOMOD)
+ $(GO) build $(LDFLAGS) -o $(OUTPUT) $(MAIN)
test:
$(GO) test -race -count=1 ./...
@@ -20,16 +23,25 @@ fmt:
gofmt -w .
clean:
- rm -f $(BINARY)
+ rm -f $(OUTPUT)
run: build
- ./$(BINARY)
+ ./$(OUTPUT)
install:
- $(GO) install $(GOMOD)
+ $(GO) install $(LDFLAGS) $(MAIN)
fuzz:
$(GO) test -fuzz=FuzzBuildResponse -fuzztime=30s ./internal/server/
+build-linux:
+ GOOS=linux GOARCH=amd64 $(GO) build $(LDFLAGS) -o build/$(BINARY)-linux-amd64 $(MAIN)
+
+build-darwin:
+ GOOS=darwin GOARCH=amd64 $(GO) build $(LDFLAGS) -o build/$(BINARY)-darwin-amd64 $(MAIN)
+
+build-arm:
+ GOOS=linux GOARCH=arm64 $(GO) build $(LDFLAGS) -o build/$(BINARY)-linux-arm64 $(MAIN)
+
race:
$(GO) test -race -count=1 ./...
diff --git a/go.mod b/go.mod
index 2f9b854..bec9811 100644
--- a/go.mod
+++ b/go.mod
@@ -1,13 +1,25 @@
-module sdns
+module linum
go 1.26.1
-require github.com/miekg/dns v1.1.72
+require (
+ github.com/BurntSushi/toml v1.6.0
+ github.com/miekg/dns v1.1.72
+ modernc.org/sqlite v1.52.0
+)
require (
- golang.org/x/mod v0.31.0 // indirect
- golang.org/x/net v0.48.0 // indirect
- golang.org/x/sync v0.19.0 // indirect
- golang.org/x/sys v0.39.0 // indirect
- golang.org/x/tools v0.40.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/ncruces/go-strftime v1.0.0 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ golang.org/x/mod v0.33.0 // indirect
+ golang.org/x/net v0.50.0 // indirect
+ golang.org/x/sync v0.20.0 // indirect
+ golang.org/x/sys v0.42.0 // indirect
+ golang.org/x/tools v0.42.0 // indirect
+ modernc.org/libc v1.72.3 // indirect
+ modernc.org/mathutil v1.7.1 // indirect
+ modernc.org/memory v1.11.0 // indirect
)
diff --git a/go.sum b/go.sum
index b600bb8..5aacfb5 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,15 @@
+github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
+github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
+github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
@@ -12,32 +18,42 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
-golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
-golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
-golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
-golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
-golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
-golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
-golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
-golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
-golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
+modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
+modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
+modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
+modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
+modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
+modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
+modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
+modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
+modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
+modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo=
modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
+modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index a2d86a0..d6a31f3 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -2,6 +2,7 @@ package cache
import (
"database/sql"
+ "log/slog"
"sync"
"sync/atomic"
"time"
@@ -199,16 +200,22 @@ func (c *Cache) evictLoop() {
}
}
func (c *Cache) writeToDB(key Key, e *entry) {
+ if c.db == nil {
+ return
+ }
data, err := e.msg.Pack()
if err != nil {
return
}
- c.db.Exec(
+ _, err = c.db.Exec(
`INSERT OR REPLACE INTO cache (name, qtype, class, data, stored_at, ttl_ns)
VALUES (?, ?, ?, ?, ?, ?)`,
key.Name, key.Qtype, key.Class, data,
e.storedAt.UnixNano(), int64(e.ttl),
)
+ if err != nil {
+ slog.Warn("cache write to db failed", "err", err)
+ }
}
func (c *Cache) loadFromDB() {
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..f2624c2
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,152 @@
+package config
+
+import (
+ "flag"
+ "fmt"
+
+ "github.com/BurntSushi/toml"
+)
+
+type Config struct {
+ Server ServerConfig `toml:"server"`
+ Cache CacheConfig `toml:"cache"`
+ Resolver ResolverConfig `toml:"resolver"`
+ Blocklist BlocklistConfig `toml:"blocklist"`
+ Log LogConfig `toml:"log"`
+}
+
+type ServerConfig struct {
+ ListenUDP string `toml:"listen_udp"`
+ ListenTCP string `toml:"listen_tcp"`
+ ListenDOH string `toml:"listen_doh"`
+}
+
+type CacheConfig struct {
+ MaxEntries int `toml:"max_entries"`
+ DBPath string `toml:"db_path"`
+}
+
+type ResolverConfig struct {
+ Timeout string `toml:"timeout"`
+ MaxDelegations int `toml:"max_delegations"`
+}
+
+type BlocklistConfig struct {
+ Response string `toml:"response"`
+ Files []string `toml:"files"`
+ URLs []string `toml:"urls"`
+}
+
+type LogConfig struct {
+ Level string `toml:"level"`
+}
+
+type CLIFlags struct {
+ Config string
+ LogLevel string
+ ListenUDP string
+ ListenTCP string
+ ListenDOH string
+}
+
+func ParseFlags() CLIFlags {
+ var f CLIFlags
+ flag.StringVar(&f.Config, "config", "linum.toml", "path to config file")
+ flag.StringVar(&f.LogLevel, "loglevel", "", "log level (debug|info|warn|error)")
+ flag.StringVar(&f.ListenUDP, "udp", "", "UDP listen address")
+ flag.StringVar(&f.ListenTCP, "tcp", "", "TCP listen address")
+ flag.StringVar(&f.ListenDOH, "doh", "", "DoH listen address")
+ flag.Parse()
+ return f
+}
+
+func Default() Config{
+ return Config{
+ Server: ServerConfig{
+ ListenUDP: ":5353",
+ ListenTCP: ":5353",
+ ListenDOH: ":8443",
+ },
+ Cache: CacheConfig{
+ MaxEntries: 100000,
+ },
+ Resolver: ResolverConfig{
+ Timeout: "2s",
+ MaxDelegations: 30,
+ },
+ Blocklist: BlocklistConfig{
+ Response: "zero_ip",
+ },
+ Log: LogConfig{
+ Level: "info",
+ },
+ }
+}
+
+func LoadFile(path string) (Config, error) {
+ var cfg Config
+ _, err := toml.DecodeFile(path, &cfg)
+ return cfg, err
+}
+
+func Merge(dst, src Config) Config {
+ if src.Server.ListenUDP != "" {
+ dst.Server.ListenUDP = src.Server.ListenUDP
+ }
+ if src.Server.ListenTCP != "" {
+ dst.Server.ListenTCP = src.Server.ListenTCP
+ }
+ if src.Server.ListenDOH != "" {
+ dst.Server.ListenDOH = src.Server.ListenDOH
+ }
+ if src.Cache.MaxEntries > 0 {
+ dst.Cache.MaxEntries = src.Cache.MaxEntries
+ }
+ if src.Cache.DBPath != "" {
+ dst.Cache.DBPath = src.Cache.DBPath
+ }
+ if src.Resolver.Timeout != "" {
+ dst.Resolver.Timeout = src.Resolver.Timeout
+ }
+ if src.Resolver.MaxDelegations > 0 {
+ dst.Resolver.MaxDelegations = src.Resolver.MaxDelegations
+ }
+ if src.Blocklist.Response != "" {
+ dst.Blocklist.Response = src.Blocklist.Response
+ }
+ if len(src.Blocklist.Files) > 0 {
+ dst.Blocklist.Files = src.Blocklist.Files
+ }
+ if len(src.Blocklist.URLs) > 0 {
+ dst.Blocklist.URLs = src.Blocklist.URLs
+ }
+ if src.Log.Level != "" {
+ dst.Log.Level = src.Log.Level
+ }
+ return dst
+}
+
+func (f CLIFlags) Apply(cfg Config) Config {
+ if f.ListenUDP != "" {
+ cfg.Server.ListenUDP = f.ListenUDP
+ }
+ if f.ListenTCP != "" {
+ cfg.Server.ListenTCP = f.ListenTCP
+ }
+ if f.ListenDOH != "" {
+ cfg.Server.ListenDOH = f.ListenDOH
+ }
+ if f.LogLevel != "" {
+ cfg.Log.Level = f.LogLevel
+ }
+ return cfg
+}
+
+func (c Config) Validate() error {
+ switch c.Blocklist.Response {
+ case "zero_ip", "nxdomain", "":
+ default:
+ return fmt.Errorf("invalid blocklist response %q (want zero_ip or nxdomain)", c.Blocklist.Response)
+ }
+ return nil
+}
diff --git a/internal/server/handler.go b/internal/server/handler.go
index 4aa771f..406b7ed 100644
--- a/internal/server/handler.go
+++ b/internal/server/handler.go
@@ -7,8 +7,8 @@ import (
"time"
"github.com/miekg/dns"
- "sdns/internal/blocklist"
- "sdns/internal/cache"
+ "linum/internal/blocklist"
+ "linum/internal/cache"
)
func (s *Server) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
@@ -33,7 +33,6 @@ func (s *Server) handleQuery(w dns.ResponseWriter, req *dns.Msg) {
"qname", req.Question[0].Name,
"qtype", dns.TypeToString[req.Question[0].Qtype],
"rcode", dns.RcodeToString[resp.Rcode],
- "client", w.RemoteAddr().String(),
"blocked", blocked,
)
}
@@ -47,6 +46,7 @@ func (s *Server) buildResponse(req *dns.Msg) (*dns.Msg, bool) {
if s.cache != nil {
key := cache.Key{Name: q.Name, Qtype: q.Qtype, Class: q.Qclass}
if cached, ok := s.cache.Get(key); ok {
+ cached.Id = req.Id
return cached, false
}
}
diff --git a/internal/server/server.go b/internal/server/server.go
index e0490bd..ec0dec9 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -7,9 +7,9 @@ import (
"time"
"github.com/miekg/dns"
- "sdns/internal/resolver"
- "sdns/internal/blocklist"
- "sdns/internal/cache"
+ "linum/internal/resolver"
+ "linum/internal/blocklist"
+ "linum/internal/cache"
)
type Server struct {
diff --git a/internal/server/server_test.go b/internal/server/server_test.go
index c49d5f3..e59a131 100644
--- a/internal/server/server_test.go
+++ b/internal/server/server_test.go
@@ -6,7 +6,7 @@ import (
"time"
"github.com/miekg/dns"
- "sdns/internal/resolver"
+ "linum/internal/resolver"
)
func testServer(t *testing.T) *Server {
diff --git a/linum.toml b/linum.toml
new file mode 100644
index 0000000..bdb30ba
--- /dev/null
+++ b/linum.toml
@@ -0,0 +1,22 @@
+[server]
+listen_udp = ":5353"
+listen_tcp = ":5353"
+listen_doh = ":8443"
+
+[cache]
+max_entries = 100000
+db_path = "/tmp/cache.db"
+[resolver]
+timeout = "2s"
+max_delegations = 30
+
+[blocklist]
+response = "zero_ip"
+files = ["etc/blocklist/*.txt"]
+#urls = [
+ # "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
+ # "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt",
+#]
+
+[log]
+level = "info"
diff --git a/main.go b/main.go
index 96c51cb..afb41c2 100644
--- a/main.go
+++ b/main.go
@@ -7,28 +7,47 @@ import (
"os/signal"
"path/filepath"
"syscall"
-
- "sdns/internal/blocklist"
- "sdns/internal/cache"
- "sdns/internal/resolver"
- "sdns/internal/server"
+ "fmt"
+ "linum/internal/blocklist"
+ "linum/internal/cache"
+ "linum/internal/config"
+ "linum/internal/resolver"
+ "linum/internal/server"
)
func main() {
- logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
- Level: slog.LevelInfo,
- }))
- slog.SetDefault(logger)
+ flags := config.ParseFlags()
- r := resolver.New()
+ cfg := config.Default()
+ if _, err := os.Stat(flags.Config); err == nil {
+ fileCfg, err := config.LoadFile(flags.Config)
+ if err != nil {
+ slog.Error("invalid config file", "err", err)
+ os.Exit(1)
+ }
+ cfg = config.Merge(cfg, fileCfg)
+ }
+ cfg = flags.Apply(cfg)
- dbPath := os.Getenv("SDNS_CACHE_DB")
- if dbPath != "" {
- logger.Info("cache using sqlite", "path", dbPath)
- } else {
- logger.Info("cache using in-memory")
+ if err := cfg.Validate(); err != nil {
+ slog.Error("config validation failed", "err", err)
+ os.Exit(1)
}
- c, err := cache.NewCache(100000, dbPath)
+
+ var lvl slog.Level
+ _ = lvl.UnmarshalText([]byte(cfg.Log.Level))
+ logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: lvl}))
+ slog.SetDefault(logger)
+
+ fmt.Println("linum - simple dns recursive")
+ fmt.Printf("github.com/radhityax/linum\n\n")
+
+ logger.Info("config loaded", "file", flags.Config)
+
+ r := resolver.New(
+ resolver.WithTimeout(2 * 1000 * 1000 * 1000),)
+
+ c, err := cache.NewCache(cfg.Cache.MaxEntries, cfg.Cache.DBPath)
if err != nil {
logger.Error("create cache failed", "err", err)
os.Exit(1)
@@ -36,49 +55,57 @@ func main() {
defer c.Stop()
var bl *blocklist.Blocklist
- matches, _ := filepath.Glob("etc/blocklist/*.txt")
- if len(matches) > 0 {
- bl = blocklist.New(blocklist.ResponseZeroIP)
- for _, f := range matches {
- if err := bl.LoadFile(f); err != nil {
- logger.Error("load blocklist failed", "file", f, "err", err)
- os.Exit(1)
+ if len(cfg.Blocklist.Files) > 0 || len(cfg.Blocklist.URLs) > 0 {
+ resp := blocklist.ResponseZeroIP
+ if cfg.Blocklist.Response == "nxdomain" {
+ resp = blocklist.ResponseNXDOMAIN
+ }
+ bl = blocklist.New(resp)
+
+ for _, pattern := range cfg.Blocklist.Files {
+ matches, err := filepath.Glob(pattern)
+ if err != nil {
+ logger.Warn("invalid blocklist glob", "pattern", pattern, "err", err)
+ continue
+ }
+ for _, f := range matches {
+ if err := bl.LoadFile(f); err != nil {
+ logger.Error("load blocklist failed", "file", f, "err", err)
+ os.Exit(1)
+ }
+ logger.Info("blocklist loaded", "file", f, "rules", bl.TotalRules)
+ }
+ }
+ for _, u := range cfg.Blocklist.URLs {
+ if err := bl.LoadURL(u); err != nil {
+ logger.Warn("load blocklist url failed", "url", u, "err", err)
+ continue
}
- logger.Info("blocklist loaded", "file", f, "rules", bl.TotalRules)
+ logger.Info("blocklist url loaded", "url", u)
}
} else {
- logger.Info("no blocklist files in etc/blocklist/, ad-blocking disabled")
- }
- udp := os.Getenv("SDNS_LISTEN_UDP")
- if udp == "" {
- udp = ":5353"
- }
-
- tcp := os.Getenv("SDNS_LISTEN_TCP")
- if tcp == "" {
- tcp = ":5353"
- }
-
- doh := os.Getenv("SDNS_LISTEN_DOH")
- if doh == "" {
- doh = ":8443"
+ logger.Info("no blocklist configured, ad-blocking disabled")
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
- srv, err := server.New(udp, tcp, doh, logger, r, c, bl)
+ srv, err := server.New(cfg.Server.ListenUDP, cfg.Server.ListenTCP, cfg.Server.ListenDOH, logger, r, c, bl)
if err != nil {
logger.Error("create server failed", "err", err)
os.Exit(1)
}
defer srv.Close()
- logger.Info("sdns starting", "udp", udp, "tcp", tcp, "doh", doh)
+ logger.Info("linum starting",
+ "udp", cfg.Server.ListenUDP,
+ "tcp", cfg.Server.ListenTCP,
+ "doh", cfg.Server.ListenDOH,
+ )
if err := srv.Run(ctx); err != nil && err != context.Canceled {
logger.Error("server stopped with error", "err", err)
os.Exit(1)
}
- logger.Info("sdns stopped cleanly")
+ logger.Info("linum stopped cleanly")
}
diff --git a/readme b/readme
index 288a467..39edf5e 100644
--- a/readme
+++ b/readme
@@ -1,4 +1,4 @@
-sdns
+linum
====
simple authoritative dns written in golang