Skip to content

v0.16.2 — a resolver that can defend itself

Choose a tag to compare

@ralyodio ralyodio released this 01 Aug 17:23
· 221 commits to main since this release
3b6bdf0

Two changes, both prerequisites for a resolver that strangers can reach.

The bridge listens on IPv6, not only forwards to it

It picked the socket family per upstream when forwarding and always bound udp4 when listening — a strange shape for a namespace whose targets must be IPv6: the resolver for a v6-only network could not be reached over v6.

The family now follows the address it is asked to bind, so :: serves both from one socket. The default stays loopback IPv4, so upgrading does not widen what a machine answers on — serving anything else is a deliberate act.

The resolver can defend itself

An open forwarding resolver is a DDoS amplifier before it is anything else, and the attack needs no botnet: one host spoofs a victim's source address, sends a small query, and the resolver mails the large answer to the victim.

That shape rules out most defences. The source address is a lie, so blocking "the client" punishes the victim; there is no session to fingerprint and no user agent to read. What is left is bounding the amplification one query can buy, and what one source can extract before we stop answering it:

  • ANY is refused (RFC 8482) — a 30-byte question for every record a name holds, which is the lever the attack pulls
  • A token bucket per source decides what counts as an offence
  • Offences earn a ban that doubles each time, capped, and decaying after a clean spell. A flat limit is a toll an attacker pays and returns from; backoff makes persistence expensive while a client that misbehaves once loses a minute
  • Oversized forwarded answers are truncated with TC, sending a real client to TCP — where the handshake makes a spoofed source useless. Legitimate case retries, abusive case dead-ends

Refusals are REFUSED rather than silence: a dropped packet costs a real client a full resolver timeout before it tries elsewhere, and costs an attacker nothing.

IPv6 clients are grouped by /64, and this is the part that decides whether any of it works. A single v6 address is free to change: any host worth banning holds a /64 at minimum, so a per-address ban is defeated by incrementing — silently, because the bans look like they are being applied. IPv4 stays per-address, because a /24 routinely spans unrelated customers behind carrier NAT.

Both tables are bounded and evict least-recently-seen. The key space is attacker-controlled, so an unbounded map is the vulnerability rather than the mitigation.

Nothing changes for an existing install

The listener default is unchanged and the guards require explicit configuration. This releases the capability for a deployment to use; it is not a behaviour change you would feel.

If Moshpit names still fail to resolve on your machine, v0.16.1 is the release that fixes that.