ginuerzh/gost · warning

empty question

Error message

empty question

What it means

Exchange unpacks a raw DNS query and requires at least one question section to know what to resolve. A well-formed but questionless DNS message (or one whose questions were lost in an upstream transform) cannot be answered, so the error is returned.

Solutions

  1. Drop the packet and reply with FORMERR (or nothing) instead of retrying
  2. Fix the client/handler that produces DNS messages without a Question section
  3. Validate incoming DNS messages at the ingress before calling Exchange

Example fix

// before
resp, err := resolver.Exchange(query)
// after
mq := &dns.Msg{}
if err := mq.Unpack(query); err != nil || len(mq.Question) == 0 {
    return dns.Msg{}
}
resp, err := resolver.Exchange(query)
Defensive patterns

Strategy: validation

Validate before calling

mq := &dns.Msg{}
if err := mq.Unpack(query); err != nil {
    return err
}
if len(mq.Question) == 0 {
    return errors.New("refusing to resolve message with no question")
}

Try / catch

resp, err := resolver.Exchange(query)
if err != nil {
    return newFormErrorResponse(query) // respond FORMERR, don't retry
}

Prevention

When it happens

Trigger: Calling Exchange with a query that Unpacks successfully but has len(mq.Question) == 0 — e.g. an empty QUERY opcode packet or a malformed client request.

Common situations: Buggy or hostile DNS clients sending question-less messages; port-scan probes on the DNS port; a handler stripping the question before forwarding.

Related errors


AI-assisted analysis of ginuerzh/gost@a33fdbf4c9 (2026-09-02). Data as JSON: /api/errors/e8a279b6c1a35288. Report an issue: GitHub.

Appendix: source

Thrown at resolver.go:389

		e.SourceNetmask = 32
		e.Address = ip.To4()
	} else {
		e.Family = 2
		e.SourceNetmask = 128
		e.Address = r.srcIP
	}
	opt.Option = append(opt.Option, e)
	m.Extra = append(m.Extra, opt)
}

func (r *resolver) Exchange(ctx context.Context, query []byte) (reply []byte, err error) {
	mq := &dns.Msg{}
	if err = mq.Unpack(query); err != nil {
		return
	}

	if len(mq.Question) == 0 {
		return nil, errors.New("empty question")
	}

	var mr *dns.Msg
	// Only cache for single question.
	if len(mq.Question) == 1 {
		key := newResolverCacheKey(&mq.Question[0])
		mr = r.cache.loadCache(key)
		if mr != nil {
			log.Logf("[dns] exchange message %d (cached): %s", mq.Id, mq.Question[0].String())
			mr.Id = mq.Id
			return mr.Pack()
		}

		defer func() {
			if mr != nil {
				r.cache.storeCache(key, mr, r.TTL())
			}
		}()

View on GitHub (pinned to a33fdbf4c9)