Documentation
¶
Overview ¶
Package dns implements the per-node embedded DNS server (RUNE-063).
The agent serves an authoritative zone for `.rune` and forwards every other query to host upstream resolvers read from /etc/resolv.conf. Records are A only and resolve `<service>.<namespace>.rune` to the service's stable VIP allocated by the control plane (RUNE-040). TTL is intentionally short (5s) so dropped/replaced services are noticed quickly.
Bind addresses default to 127.0.0.123:53 (UDP+TCP). 127.0.0.123 is chosen rather than 127.0.0.53 because the latter is reserved by systemd-resolved on most Linux distributions and would conflict. Additional bind addresses (typically Docker bridge gateways) can be supplied at construction time so containers reach the DNS server through their own gateway IP.
The Subsystem implements the same Name / Start / Ready / Stop contract as the agent's other subsystems (see internal/agent), but does not import the agent package directly to avoid cycles.
Index ¶
Constants ¶
const ( // DefaultBindAddr is the loopback address the agent binds for // host-side DNS injection. Avoids systemd-resolved on .53. DefaultBindAddr = "127.0.0.123:53" // Zone is the authoritative zone (always trailing dot for miekg). Zone = "rune." // DefaultTTL is the TTL stamped on every answer. DefaultTTL uint32 = 5 // DefaultStaleBudget mirrors the dataplane: after this much time // without a refresh signal, the resolver returns SERVFAIL for // .rune queries instead of stale data. DefaultStaleBudget = 30 * time.Second )
Defaults.
const DevDefaultBindAddr = "127.0.0.1:15353"
DevDefaultBindAddr is the loopback fallback for laptop dev (macOS cannot bind 127.0.0.123 without an interface alias, and port 53 requires root). Host-side .rune access in dev mode goes through the dataplane on 127.0.0.1; this address keeps the embedded resolver available for dig/host testing.
Variables ¶
This section is empty.
Functions ¶
func DiagnoseEmptyBind ¶
func DiagnoseEmptyBind(skipped []BindFailure) string
DiagnoseEmptyBind returns an operator-facing explanation for why the bindable set came back empty, given the skipped candidates. When every candidate failed a privileged port with a permission error it names the likely cause — a missing CAP_NET_BIND_SERVICE, typically a hand-authored systemd unit — and the fix. Otherwise it lists what was tried.
func ResolvConfUpstreams ¶
ResolvConfUpstreams returns an UpstreamProvider that re-reads /etc/resolv.conf on every call. The agent's own bind addresses (typically 127.0.0.123:53) are filtered out so we never register ourselves as an upstream — anything else, including other loopback services like systemd-resolved's 127.0.0.53 stub, is kept. On stock Ubuntu that stub is the only nameserver in /etc/resolv.conf, so skipping it strands the agent without forwarders.
Types ¶
type BindFailure ¶
BindFailure records an address that could not host a DNS listener and why. Returned by FilterBindable so callers can build an actionable diagnostic (see DiagnoseEmptyBind) instead of a bare "no bindable addresses".
func FilterBindable ¶
func FilterBindable(addrs []string, logger log.Logger) (bindable []string, skipped []BindFailure)
FilterBindable returns the subset of addrs that can host both a UDP and TCP listener, in input order with duplicates removed. Addresses that fail the probe are logged at Warn with the underlying error and returned in skipped so callers can explain an empty result — Docker Desktop on macOS reports bridge gateways such as 172.17.0.1 that are not host-bindable, and a runed lacking CAP_NET_BIND_SERVICE fails every privileged :53 bind.
type Config ¶
type Config struct {
// Zone provides the in-zone resolutions. Required.
Zone ZoneProvider
// Freshness gates .rune answers; nil means always fresh.
Freshness Freshness
// BindAddrs are host:port pairs to bind. If empty, defaults to
// just DefaultBindAddr. Each address is bound on both UDP and TCP.
BindAddrs []string
// TTL stamped on answers; defaults to DefaultTTL.
TTL uint32
// StaleBudget; informational, used by callers to align with the
// dataplane budget. The Freshness interface is the actual gate.
StaleBudget time.Duration
// UpstreamProvider, if set, is consulted for the forwarder
// upstream list on every query. If nil, /etc/resolv.conf is
// parsed at Start and on each Refresh() call.
UpstreamProvider func() []string
// ForwardTimeout caps each upstream query; defaults to 2s.
ForwardTimeout time.Duration
// Logger; defaults to the global logger with component "dns".
Logger log.Logger
}
Config bundles the parameters for constructing a Subsystem.
type EndpointPublisher ¶
type EndpointPublisher struct {
// contains filtered or unexported fields
}
EndpointPublisher fans the orchestrator's endpoint + local-instance updates into the OrderedLog-backed publishers from pkg/networking/endpoints and pkg/networking/localinstances.
It implements the pkg/orchestrator/controllers.EndpointPublisher interface (kept loose-coupled to avoid an import cycle: this package only refers to pkg/types).
func NewEndpointPublisher ¶
func NewEndpointPublisher(olog orderedlog.OrderedLog, logger log.Logger) (*EndpointPublisher, error)
NewEndpointPublisher constructs an EndpointPublisher. Both underlying publishers are required.
func (*EndpointPublisher) PublishLocalInstances ¶
func (p *EndpointPublisher) PublishLocalInstances(ctx context.Context, nodeID string, table map[string]types.InstanceIdentity) error
PublishLocalInstances implements controllers.EndpointPublisher.
func (*EndpointPublisher) PublishService ¶
func (p *EndpointPublisher) PublishService(ctx context.Context, service *types.Service, eps []types.Endpoint) error
PublishService implements controllers.EndpointPublisher.
Endpoint sets are keyed by service ID — that is the contract of the endpoints package, of the dataplane Cache, and of the VIP proxy listener (which looks up by svc.ID). Keying by service.Name here meant every VIP lookup missed and the proxy reset the connection.
type Freshness ¶
type Freshness interface {
IsFresh() bool
}
Freshness reports whether the underlying state (orderedlog watch) is fresh enough to answer authoritatively. When false, .rune queries are answered with SERVFAIL.
func AlwaysFresh ¶
func AlwaysFresh() Freshness
AlwaysFresh returns a Freshness that always reports fresh.
func FreshnessFromDataplane ¶
FreshnessFromDataplane builds a Freshness implementation from any type that exposes an IsFresh() bool method (e.g. dataplane.Subsystem once it grows that accessor). When the supplied function is nil, the DNS server treats the data plane as always fresh — appropriate for dev/standalone mode.
type FuncVIPSource ¶
FuncVIPSource adapts a lookup function to ServiceVIPSource.
func (FuncVIPSource) VIPForService ¶
type ServiceVIPSource ¶
type ServiceVIPSource interface {
VIPForService(ctx context.Context, serviceID string) (net.IP, error)
}
ServiceVIPSource resolves the stable cluster VIP for a service ID. The VIP allocator implements this; DNS uses it when the service row is missing Service.Discovery.VIP (legacy drift).
type StoreZone ¶
type StoreZone struct {
// contains filtered or unexported fields
}
StoreZone is a ZoneProvider that resolves <svc>.<ns>.rune queries against the agent's store.Store. The VIP is read from Service.Discovery.VIP when present; otherwise ServiceVIPSource (the cluster allocator) is consulted so DNS never depends on operators hand-setting discovery.vip in cast YAML.
Lookups are memoized for a short TTL to avoid hammering the store on bursts of repeated DNS queries from a single client.
func NewStoreZone ¶
NewStoreZone constructs a StoreZone with a 1s lookup cache. vip may be nil (store-only lookups).
type Subsystem ¶
type Subsystem struct {
// contains filtered or unexported fields
}
Subsystem is the per-node embedded DNS server.
func (*Subsystem) BindAddrs ¶
BindAddrs returns the resolved bind addresses (post-defaulting), formatted as `host:port`. Used by the runed startup glue to wire DNS injection: once this subsystem is Ready, we tell the docker runner to inject these addresses into every subsequently-created container so they can resolve `<service>.<namespace>.rune`.
func (*Subsystem) Ready ¶
func (s *Subsystem) Ready() <-chan struct{}
Ready returns a channel closed when the server has bound and is serving on at least one address.
func (*Subsystem) Refresh ¶
Refresh re-reads /etc/resolv.conf for upstream resolvers. Wire this to SIGHUP at the daemon level. Returns an error if no upstreams could be loaded; the previous list is retained on error.
func (*Subsystem) Start ¶
Start binds the configured addresses on UDP+TCP and begins serving. It blocks until at least one bind succeeds or all binds fail.
func (*Subsystem) Stop ¶
Stop shuts down all listeners. Closing each underlying conn always unblocks its ActivateAndServe loop — unlike dns.Server.Shutdown, which silently no-ops when it races a not-yet-"started" server and leaves the listener (and this wait) hung forever. The wait is also bounded so a stuck listener can never wedge agent shutdown/rollback.
type ZoneProvider ¶
type ZoneProvider interface {
// LookupA returns the IPv4 addresses for service `name` in
// namespace `ns`. ok=false means the service is not known.
LookupA(ns, name string) (ips []net.IP, ok bool)
}
ZoneProvider resolves an in-zone name to one or more A records. LookupA is called for every <service>.<namespace>.rune query the server receives.