summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod12
-rw-r--r--go.sum12
-rw-r--r--internal/server/server.go104
-rw-r--r--main.go40
4 files changed, 168 insertions, 0 deletions
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..d4ccd67
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,12 @@
+module sdns
+
+go 1.26.1
+
+require (
+ github.com/miekg/dns v1.1.72 // indirect
+ 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
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..6f633e8
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,12 @@
+github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
+github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
+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/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
+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/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
+golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
diff --git a/internal/server/server.go b/internal/server/server.go
new file mode 100644
index 0000000..01bda09
--- /dev/null
+++ b/internal/server/server.go
@@ -0,0 +1,104 @@
+package server
+
+import (
+ "context"
+ "log/slog"
+ "time"
+
+ "github.com/miekg/dns"
+)
+
+type Server struct {
+ addr string
+ logger *slog.Logger
+ inner *dns.Server
+}
+
+func New(addr string, logger *slog.Logger) (*Server, error) {
+ mux := dns.NewServeMux()
+ mux.HandleFunc(".", handleQuery)
+
+ inner := &dns.Server{
+ Addr: addr,
+ Net: "udp",
+ Handler: mux,
+ UDPSize: 4096,
+ ReadTimeout: 5 * time.Second,
+ WriteTimeout: 5 * time.Second,
+ }
+ return &Server{
+ addr: addr,
+ logger: logger,
+ inner: inner,
+ }, nil
+}
+
+func (s *Server) Run(ctx context.Context) error {
+ errCh := make(chan error, 1)
+ go func() {
+ s.logger.Info("udp listener active", "addr", s.addr)
+ errCh <- s.inner.ListenAndServe()
+ }()
+
+ select {
+ case <-ctx.Done():
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 5 *time.Second)
+ defer cancel()
+ if err := s.inner.ShutdownContext(shutdownCtx); err != nil {
+ s.logger.Error("graceful shutdown failed", "err", err)
+ return err
+ }
+ return ctx.Err()
+ case err := <-errCh:
+ return err
+ }
+}
+
+func (s *Server) Close() error {
+ return s.inner.Shutdown()
+}
+
+func handleQuery(w dns.ResponseWriter, req *dns.Msg) {
+ if len(req.Question) == 0 {
+ m := new(dns.Msg)
+ m.SetRcode(req, dns.RcodeFormatError)
+ _ = w.WriteMsg(m)
+ return
+ }
+
+ q := req.Question[0]
+ resp := new(dns.Msg)
+ resp.SetReply(req)
+ resp.Authoritative = false
+
+ if q.Name == "example.com." && q.Qtype == dns.TypeA {
+ resp.Answer = []dns.RR{
+ &dns.A{
+ Hdr: dns.RR_Header{
+ Name: q.Name,
+ Rrtype: dns.TypeA,
+ Class: dns.ClassINET,
+ Ttl: 60,
+ },
+ A: []byte{127,0,0,1},
+ },
+ }
+ } else {
+ resp.Rcode = dns.RcodeNameError
+ }
+
+ if err := w.WriteMsg(resp); err != nil {
+ slog.Error("write response failed",
+ "err", err,
+ "qname", q.Name,
+ "qtype", dns.TypeToString[q.Qtype],
+ )
+ return
+}
+slog.Info("query served",
+"qname", q.Name,
+"qtype", dns.TypeToString[q.Qtype],
+"rcode", dns.RcodeToString[resp.Rcode],
+"client", w.RemoteAddr().String(),
+)
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..5d9e526
--- /dev/null
+++ b/main.go
@@ -0,0 +1,40 @@
+package main
+
+import (
+ "context"
+ "log/slog"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "sdns/internal/server"
+)
+
+func main() {
+ logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
+ Level: slog.LevelInfo,
+ }))
+
+ addr := os.Getenv("SDNS_LISTEN")
+ if addr == "" {
+ addr = ":5353"
+ }
+
+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
+ defer stop()
+
+ srv, err := server.New(addr, logger)
+ if err != nil {
+ logger.Error("create server failed", "err", err)
+ os.Exit(1)
+ }
+ defer srv.Close()
+
+ logger.Info("sdns starting", "addr", addr)
+
+ 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")
+}