summaryrefslogtreecommitdiff
path: root/internal/server/doh.go
diff options
context:
space:
mode:
authorradhitya <alif@radhitya.org>2026-06-13 12:46:38 +0700
committerradhitya <alif@radhitya.org>2026-06-13 12:46:38 +0700
commit01e05e8df5f56d605dfd75456a424527e76a2955 (patch)
tree3de06990292982a7c13f9a6edc9fe07e6127ff00 /internal/server/doh.go
parent1d1a15075b93815a2d006167d433c03d1abef419 (diff)
dns codec, udp server (reuseport, 4096 buffers), tcp server, doh listener post get without tls, concurrent, ends0, fuzz)
Diffstat (limited to 'internal/server/doh.go')
-rw-r--r--internal/server/doh.go75
1 files changed, 75 insertions, 0 deletions
diff --git a/internal/server/doh.go b/internal/server/doh.go
new file mode 100644
index 0000000..e9cf466
--- /dev/null
+++ b/internal/server/doh.go
@@ -0,0 +1,75 @@
+package server
+
+import (
+ "encoding/base64"
+ "github.com/miekg/dns"
+ "io"
+ "log/slog"
+ "net/http"
+)
+
+func (s *Server) dohHandler(w http.ResponseWriter, r *http.Request) {
+ var raw []byte
+
+ switch r.Method {
+ case http.MethodPost:
+ ct := r.Header.Get("Content-Type")
+ if ct != "application/dns-message" {
+ http.Error(w, "unsupported content type", http.StatusUnsupportedMediaType)
+ return
+ }
+ body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 65535))
+ if err != nil {
+ http.Error(w, "read body", http.StatusBadRequest)
+ return
+ }
+ raw = body
+ case http.MethodGet:
+ param := r.URL.Query().Get("dns")
+ if param == "" {
+ http.Error(w, "missing dns param", http.StatusBadRequest)
+ return
+ }
+ decoded, err := base64.RawURLEncoding.DecodeString(param)
+ if err != nil {
+ http.Error(w, "invalid base64url", http.StatusBadRequest)
+ return
+ }
+ raw = decoded
+ default:
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ msg := new(dns.Msg)
+ if err := msg.Unpack(raw); err != nil {
+ http.Error(w, "invalid dns message", http.StatusBadRequest)
+ return
+ }
+
+ if len(msg.Question) == 0 {
+ http.Error(w, "no question", http.StatusBadRequest)
+ return
+ }
+
+ resp := buildResponse(msg)
+ packed, err := resp.Pack()
+ if err != nil {
+ http.Error(w, "pack response", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/dns-message")
+ w.Header().Set("Cache-Control", "no-cache, max-age=0")
+ if _, err := w.Write(packed); err != nil {
+ slog.Error("doh write failed", "err", err)
+ return
+ }
+
+ slog.Info("doh query served",
+ "qname", msg.Question[0].Name,
+ "qtype", dns.TypeToString[msg.Question[0].Qtype],
+ "rcode", dns.RcodeToString[resp.Rcode],
+ "client", r.RemoteAddr,
+ )
+}