diff options
| author | radhitya <alif@radhitya.org> | 2026-06-07 17:05:59 +0700 |
|---|---|---|
| committer | radhitya <alif@radhitya.org> | 2026-06-07 17:05:59 +0700 |
| commit | 1d1a15075b93815a2d006167d433c03d1abef419 (patch) | |
| tree | 46119368fe984cb90f14d857233fac4ec423c2cb | |
first commit
| -rw-r--r-- | go.mod | 12 | ||||
| -rw-r--r-- | go.sum | 12 | ||||
| -rw-r--r-- | internal/server/server.go | 104 | ||||
| -rw-r--r-- | main.go | 40 |
4 files changed, 168 insertions, 0 deletions
@@ -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 +) @@ -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(), +) +} @@ -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") +} |
