Open-Source Wikis

/

Consul

/

Systems

/

DNS interface

hashicorp/consul

DNS interface

Consul agents expose a DNS server (default port 8600) that resolves service and node queries against the catalog. Most operators use this as their primary discovery interface — it's what makes "service-name.service.consul" work in any application without integration code.

Purpose

The DNS interface translates standard DNS questions (A, AAAA, SRV, ANY, TXT, PTR) into catalog lookups, applies ACL filtering, and returns answers within strict latency budgets. It supports synthetic record families (addr.<ip>.consul), tag filtering (tag.web.service.consul), datacenter routing (web.service.dc1.consul), prepared queries (web.query.consul), and partition/namespace selectors in Enterprise.

Directory layout

agent/
├── dns.go            # The legacy monolithic DNS server (72 KB)
├── dns_ce.go         # CE-only routing helpers
├── dns/              # Newer DNS subpackage (request context helpers)
│   ├── buffer_response_writer.go
│   ├── context.go
│   └── context_test.go
internal/
└── dnsutil/          # Standalone DNS utilities (parsing, name composition)

The legacy agent/dns.go is still the active server. The new agent/dns/ package and internal/dnsutil/ exist to incrementally factor out the monolith.

Query format

Pattern Resolves to
web.service.consul Healthy web instances in the local DC
web.service.dc1.consul Healthy web instances in DC dc1 (forwarded over WAN if needed)
<tag>.web.service.consul Filter by tag
web.connect.consul Connect-enabled service-mesh proxy endpoints for web
web.virtual.consul The synthetic mesh-internal virtual IP for web
web.query.consul Prepared query named web (with failover semantics)
node-name.node.consul Node addresses
addr.<hex-ip>.consul Synthetic record echoing the IP
_<service>._<protocol>.service.consul RFC 2782 SRV style

The naming machinery is in internal/dnsutil/ and the dispatch logic is in agent/dns.go::dispatch.

How it works

sequenceDiagram
    participant App as Application resolver
    participant Agent as Local agent (dns.Server)
    participant Cache as agent/cache (or submatview)
    participant Server as Consul server (RPC)

    App->>Agent: DNS QUERY web.service.consul A
    Agent->>Agent: parse name → (service=web, dc=local, tag=)
    Agent->>Cache: HealthServices(web)
    alt cache hit (TTL valid)
        Cache-->>Agent: list of nodes/health
    else cache miss
        Cache->>Server: Health.ServiceNodes (blocking)
        Server-->>Cache: nodes
        Cache-->>Agent: list
    end
    Agent->>Agent: filter unhealthy + apply ACL token
    Agent-->>App: A records (deterministic shuffle)

Agents cache catalog responses (agent/cache/ + the health-services cache type) so repeated DNS hits don't always go to the leader. TTLs come from agent config (dns_config.service_ttl, etc., in agent/config/).

ACL handling

The agent's default token (or a query-override token via DNS RFC 1035 isn't possible, so it's always the agent token) is applied:

  • The configured acl.tokens.default token must be allowed to read every node and service that appears in the answer.
  • ACL-filtered services are silently omitted; this is the same behavior as the HTTP catalog endpoints, so DNS clients see only what they're permitted to see.

ACL filtering is implemented in agent/consul/aclfilter/ and called from the catalog reads that DNS performs.

Cross-datacenter resolution

When a DNS query specifies a non-local DC (web.service.dc2.consul), the agent forwards an RPC to the local server, which uses WAN federation to forward across DCs. WAN gossip and the WAN connection pool live in agent/consul/server_serf.go and agent/consul/wanfed/. For the newer cluster peering model, peer-aware DNS uses web.service.peer-name.consul.

Recursors

Configurable upstream DNS servers can be queried for non-*.consul records. Recursor settings come from agent config (recursors, recursor_strategy). Implementation lives in the recursive resolution loop inside agent/dns.go.

Performance and limits

  • A response is built incrementally and truncated to fit UDP unless EDNS0 is in play. The truncation logic and TC-bit handling sit in agent/dns.go.
  • dns_config.enable_truncate and dns_config.udp_answer_limit (in agent/config/) control how many records get squeezed in.
  • The agent applies a deterministic shuffle so two consecutive queries don't return the same first answer.
  • All DNS responses tag the underlying consul.dns.* metrics (latency, query count, error count).

Integration points

  • Cache: the agent/cache-types/health-services.go and friends define how DNS pulls from the agent cache.
  • gRPC DNS service: proto-public/pbdns/ defines a public DNS-over-gRPC service used by Consul Dataplane to query without binding a UDP port. Implementation: agent/grpc-external/services/dns/.
  • Prepared queries: web.query.consul resolves through the prepared-query endpoint (agent/consul/prepared_query_endpoint.go) which can apply failover, near-mode (closest DC by RTT), and templating.

Entry points for modification

  • Add a new query family: extend the dispatch switch at the top of agent/dns.go.
  • Tune cache TTLs: see the per-type stanzas under agent/config/source_default.go and the runtime overlay in agent/config/runtime.go.
  • Add a synthetic record: mirror addr.<ip>.consul handling in dispatchAddr.
  • gRPC DNS: plug the new resolution path into agent/grpc-external/services/dns/.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

DNS interface – Consul wiki | Factory