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

  1. base64url-encode (RFC 4648, unpadded) the DNS wire-format message and place it in the ?dns= query parameter
  2. Verify the client uses RawURLEncoding semantics: replace '+' with '-', '/' with '_', and strip '=' padding
  3. Confirm the decoded payload is a complete DNS message (at least the 12-byte header) before sending
  4. 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

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.