ginuerzh/gost · warning
Bad Request
Error message
Bad Request
What it means
The DoH handler returns HTTP 400 Bad Request when a GET request's 'dns' query parameter is missing, empty, or is not valid base64url (RFC 4648 raw, no padding) encoded DNS wire-format data. The library requires every DoH GET request to carry a validly encoded DNS message in the 'dns' parameter before it will attempt to unpack it.
Source
Thrown at dns.go:260
b, err := m.Pack()
if err != nil {
log.Logf("[dns] %s: %v", l.addr, err)
return
}
if err := l.serve(w, b); err != nil {
log.Logf("[dns] %s: %v", l.addr, err)
}
}
// Based on https://github.com/semihalev/sdns
func (l *dnsListener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var buf []byte
var err error
switch r.Method {
case http.MethodGet:
buf, err = base64.RawURLEncoding.DecodeString(r.URL.Query().Get("dns"))
if len(buf) == 0 || err != nil {
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
case http.MethodPost:
if r.Header.Get("Content-Type") != "application/dns-message" {
http.Error(w, http.StatusText(http.StatusUnsupportedMediaType), http.StatusUnsupportedMediaType)
return
}
buf, err = io.ReadAll(r.Body)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
default:
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
View on GitHub (pinned to a33fdbf4c9)
Solutions
- base64url-encode (RFC 4648, unpadded) the DNS wire-format message and place it in the ?dns= query parameter
- Verify the client uses RawURLEncoding semantics: replace '+' with '-', '/' with '_', and strip '=' padding
- Confirm the decoded payload is a complete DNS message (at least the 12-byte header) before sending
- Test the URL with a known-good DoH client (e.g. curl with a pre-encoded dns param) to rule out URL mangling
Example fix
// before (standard base64 with padding) q := base64.StdEncoding.EncodeToString(wire) url := "https://doh.example/dns-query?dns=" + q // after (raw base64url, no padding) q := base64.RawURLEncoding.EncodeToString(wire) url := "https://doh.example/dns-query?dns=" + q
Defensive patterns
Strategy: validation
Validate before calling
q := r.URL.Query().Get("dns")
buf, err := base64.RawURLEncoding.DecodeString(q)
if err != nil || len(buf) < 12 {
return fmt.Errorf("invalid dns query param: %v", err)
} Type guard
func validDoHGetParam(q string) bool {
buf, err := base64.RawURLEncoding.DecodeString(q)
return err == nil && len(buf) >= 12
} Prevention
- Always use RawURLEncoding (base64url, unpadded) for the ?dns= parameter
- URL-encode the parameter so the client library does not mangle '-' and '_'
- Sanity-check that the decoded payload is at least 12 bytes before sending
When it happens
Trigger: GET /dns-query with no ?dns= parameter; GET with ?dns= containing standard base64 with padding characters; GET with ?dns= containing characters outside the base64url alphabet; GET with ?dns= that decodes to zero bytes.
Common situations: Clients hand-crafting DoH URLs and forgetting to base64url-encode the query; using base64.StdEncoding instead of RawURLEncoding; shell scripts leaving the parameter unencoded so '+'/'/' appear; truncating the URL so the dns parameter is dropped.
Related errors
AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02).
Data as JSON: /api/errors/79d2302066b91ba6.
Report an issue: GitHub.