DNS Flood: How It Works and How to Test Your Defenses

A tall stack of glowing lookup index rows hit by many separate thin query lines, illustrating how a DNS flood stresses a resolver

· 15 min read

Before a customer’s browser can load your login page, before a payment partner can call your API, before your own staff can open an internal system, something has to turn a name into an address. That lookup happens first, every single time, and nothing downstream begins until it comes back.

Which makes DNS the one dependency where perfect health counts for nothing. A DNS flood never touches your web servers. It goes after the resolver that answers questions about you, and when that resolver stops answering, every server you own keeps running with nobody able to reach it. Your dashboards will say everything is fine, and they are not wrong. Nobody can get there to find out otherwise.

The short version

  • It targets a system you probably do not run. Most banks delegate authoritative DNS to a provider and run recursion on a separate internal platform.
  • Nothing you own is in the traffic path. Queries go to your nameservers, not your website, so they never reach your WAF or your scrubbing centre. The gap comes from your architecture, not from any clever technique.
  • Obsidio sends the queries over TCP, not UDP. Every query pays for a full handshake, which costs the sender more but cannot be spoofed and cannot run away from you.
  • Random subdomains stop the cache from saving you. A fresh label on every query means no cached answer exists, so each one forces real recursion work.
  • The relevant ceilings are small. NSD allows 100 concurrent TCP connections per server by default, Unbound allocates 10 incoming TCP buffers per thread, and response rate limiting ships switched off in both.

This is one in a series covering each of the ten simulation types Obsidio runs, one post per attack. They fail in genuinely different places, which is why a test programme needs coverage across the categories rather than volume in one of them. For how they fit together, start with our guide on how to test your DDoS protection.

The ten simulation types

One post each, grouped the way the platform groups them.

Application-layer floods

Slow attacks

Connection exhaustion

Protocol-specific and scripted

What a DNS flood actually does

Obsidio’s DNS flood builds raw DNS query packets and sends them to the target resolver over TCP, framed the way the protocol requires: a two-byte length field, the twelve-byte DNS header, then the question. That prefix is not an implementation choice. RFC 1035 section 4.2.2 states that over TCP “the message is prefixed with a two byte length field which gives the message length, excluding the two byte length field.”

Each parallel loop then does one small thing on repeat. It opens a fresh TCP connection, writes one query, waits for the first byte of the response, then tears the socket down and starts again. The pressure lands in two places: resolver CPU and memory, which parse the query and do the recursion, and TCP listener resources, meaning file descriptors and accept-queue depth.

Picture a swarm of callers dialling the directory operator, asking for one number, hanging up, then immediately redialling. Every call is a fresh connection, and the switchboard fills up long before anyone runs out of numbers to give out.

Why TCP rather than UDP

Most public writing about DNS floods assumes UDP, because that is what real attackers reach for. UDP needs no handshake, so the source address can be forged, which is what makes reflection and amplification possible. Obsidio runs neither, and TCP is the reason. A completed handshake means every query came from a real, reachable device that agreed to take part, so no source address is forged and no traffic is ever pointed at somebody else.

TCP also buys something UDP cannot. Because the loop waits for the first response byte before closing, every attempt produces an answer or a failure to answer, so what you read is end-to-end resolution latency rather than the cost of a handshake. A handshake per query does make each one heavier, so achievable rates are lower than a spoofed UDP flood would reach. In exchange the load stays naturally rate limited.

None of this makes TCP a corner case. RFC 9210 is explicit that “All DNS resolvers and servers MUST support and service both UDP and TCP queries”. It also notes that inducing clients to retry over TCP, by returning a truncated UDP answer, “has become a common response to source-address-spoofed, DNS denial-of-service attacks.” Under attack conditions your rate limiting pushes legitimate clients onto the TCP path, which is the path this test measures.

Which of your controls should have caught this

Walk the whole chain a query passes through and the honest answer for the first zone is that most of it is not there. Your website sits behind a WAF and a scrubbing service. Your DNS answers on port 53 at your nameservers, frequently at a different company entirely, and traffic aimed at those nameservers never comes near the defenses protecting your site. This front zone is bypassed by architecture, not by technique.

Zone 1

Should any of these have stopped it?

  • DDoS protection and scrubbing. Sits in front of your web traffic. DNS queries go elsewhere, so this never sees a packet.
  • Firewall and WAF. Inspects HTTP for your application. There is no HTTP here, and the destination is not behind it.
  • Rate limiting and bot detection. Both live at your web edge, which is not on the path a DNS query takes.
  • CDN cache. Serves content once a name has resolved. If resolution fails, the CDN is never asked.
  • Your DNS provider’s own protection. The control that actually decides the outcome, configured by somebody else, to limits you were probably never shown.

Zone 2

What takes the hit?

  • The resolver’s connection layer. Fills with short-lived sockets. File descriptors and accept-queue depth run short first.
  • The resolver process. This is where it bites. Parsing and recursion cost CPU, and concurrent TCP connections are capped by a number in a config file.
  • Recursion and the servers upstream. Cache-defeating names force real lookups, so pressure travels to whatever answers authoritatively for the zone.
  • Web servers, application, database. Untouched, and that is the cruel part. Healthy and unreachable at the same time.
  • Auto-scaling. Sees no load, because the load is not on your servers. With managed DNS there is nothing for you to scale anyway.

Zone 3

Would you find out?

  • Dashboards. Application graphs improve. Request volume falls and latency drops, because the work that would have arrived never gets sent.
  • A resolution check from outside your network. The one thing that reliably catches this, provided it queries your nameservers rather than reading a cached answer.
  • Logging and your SIEM. Query logging is often left off for performance, and where a provider runs the resolver the logs are theirs.
  • Alerting. Thresholds are set on application metrics, which are all improving.
  • On-call. Somebody eventually gets paged by a customer. Whether the page points anywhere near DNS is the part worth testing.

Purple marks what only a test on your own infrastructure can settle. Everything else follows from how the attack works, not from how well your team configured things.

A blind first zone is not a criticism of anyone’s configuration. It describes where DNS sits. Which makes the coverage question different here: you cannot fix this at your edge, so you have to know your provider’s limits, and you have to have measured them.

The parameters, and who actually runs the resolver

Four settings shape the run:

  • Parallel requests per worker. Default 20, adjustable from 1 to 100. Queries held in flight per worker, which maps directly to concurrent open sockets against the resolver.
  • Port. Default 53. A non-standard port reaches shims that listen elsewhere, with one caveat: this simulation sends plain DNS, so it is not a test of a DoH or DoT endpoint.
  • Random subdomains. On by default. A twelve-character random label is prepended to every query, so no cached answer can exist and the resolver has to do the full lookup. Switch it off and caches absorb nearly everything.
  • Query types. Defaults to A, AAAA, MX and ANY, with TXT, CNAME, NS and SOA available. One is chosen at random per query, because record types exercise different code paths and produce different response sizes. Many resolvers refuse or limit ANY, so seeing it rejected is a result rather than a fault.

The platform estimates 20 queries per second per parallel loop, assuming the resolver answers within 50 ms. Reports carry the count of currently open sockets and a breakdown of how many of each record type went out.

Then there is what you point it at, and this is where DNS testing gets organisationally awkward. Authoritative servers hold your zones and answer the outside world’s questions about you. Recursive resolvers answer your own clients’ questions about everyone else. If the authoritative side goes quiet, customers and partners cannot reach you. If recursion goes quiet, your staff and applications cannot reach anything outward, payment networks and market data feeds included.

In a typical bank those two are run by different people. Authoritative DNS usually sits with a managed provider, sometimes two for redundancy, chosen by whoever owns the domain portfolio. Recursion usually runs internally on the directory platform, owned by infrastructure rather than security. Neither team necessarily thinks of itself as owning a DDoS control. Establish which one you are testing before you scope anything, because the answer decides who has to be in the room.

How to defend against a DNS flood

The controls that matter here are mostly not the ones on your web edge.

  • Response rate limiting, once somebody enables it. ISC states plainly that RRL in BIND 9 “is turned off by default”, and NSD’s rrl-ratelimit is likewise “default is off (with a suggested 200 qps)”. Understand what it does before relying on it: RRL throttles excessive almost identical UDP responses, and the standard behaviour is to nudge the client into retrying over TCP. That makes it a real control against spoofed UDP floods, and it does very little about queries that already arrive over TCP asking for names that are all different.
  • Capacity and anycast. RFC 4786 puts it precisely: a node “can act as a sink for attack traffic originated within its sphere of influence, preventing nodes elsewhere from having to deal with that traffic”, and “the task of dealing with attack traffic whose sources are widely distributed is itself distributed across all the nodes that contribute to the service.” Note the condition in that second sentence. Anycast splits distributed load across nodes, which helps, and it also means a genuinely global attack arrives at many nodes at once rather than being absorbed by one.
  • Resolver limits you have looked at. The defaults are modest. NSD documents tcp-count as “the maximum number of concurrent, active TCP connections by each server. Default is 100”, with tcp-timeout at 120 seconds. Unbound allocates 10 incoming-num-tcp buffers per thread and defaults tcp-idle-timeout to 30 seconds. RFC 7766 permits the obvious mitigation, that servers “MAY impose limits on the number of concurrent TCP connections being handled for any particular client IP address or subnet”, which works against a single source and is much weaker against a distributed one.
  • Recursion restricted to your own clients. A resolver that answers the internet is a far larger target, and it can be abused against others. Unbound gets this right out of the box: “by default only localhost (the 127.0.0.0/8 IP netblock, not the loopback interface) is implicitly allowed, the rest is refused.” Configurations drift, so confirm rather than assume.
  • Provider-side controls, in writing. If somebody else runs your DNS, most of the above is their config file. Ask what the per-source and aggregate limits are, what their anycast footprint looks like, and how you reach a human during an incident. One thing you can do alone is delegate to a second independent provider.

Do not block DNS over TCP as a shortcut. RFC 9210 is unambiguous that “the filtering of DNS over TCP is harmful in the general case”, and networks that block TCP port 53 “risk losing access to significant or important pieces of the DNS namespace.”

What you can check yourself, and what it will get wrong

Some of this you can answer today. It is worth knowing which parts, and where a cheap check misleads you.

Read the configuration, or ask for it. Write down the concurrent connection cap, the timeouts, whether recursion is restricted, whether rate limiting is enabled at all. Where a provider runs the service, that conversation is the review. This tells you what the limits are. It does not tell you what happens as you approach them, and resolvers do not degrade in a straight line.

Test alert routing on its own. Fire a synthetic alert and see whether it reaches the right rotation and whether anyone acts. That verifies the plumbing and needs no attack traffic. It says nothing about whether a DNS failure would generate an alert in the first place, which is the actual gap in the third zone.

Query your own resolver from a single machine, with sign-off, against infrastructure you own. Useful for a latency baseline and for confirming which record types get answered. Also where the trap is.

One source does not merely fail to answer the questions above. It answers several of them wrongly, in a reassuring direction. A per-source connection limit catches one address instantly. Your provider may filter it as obvious abuse before it registers as load. Nothing gets near a capacity ceiling, so the run completes, latency looks flat, and the conclusion is that DNS holds. A real distributed attack does not present that way. Thousands of separate residential addresses each open a modest number of connections, no single source crosses any threshold, and the aggregate arrives at the resolver anyway.

How Obsidio tests a DNS flood

Authorization comes first, and this simulation has a wrinkle the other nine do not.

Obsidio verifies domain ownership by DNS TXT record before any traffic flows, runs ramp up gradually, and any run can be aborted live. That covers infrastructure you own. It does not by itself cover a resolver somebody else operates. If your authoritative DNS is delegated to a provider, their nameservers are shared infrastructure serving other customers, and controlling the domain does not entitle you to authorize load against machines you do not own. Scope it explicitly with your provider and get their agreement in writing before the run is booked. Some providers support authorized testing on request, and some will run it themselves on your behalf. Both are legitimate routes. What you must not do is assume. Where you run recursion on your own resolvers, the usual authorization path applies and scoping is straightforward.

With that settled, you choose the target, the concurrency per worker, the record types and whether random subdomains defeat the cache. Load comes from 100,000+ globally distributed real devices rather than a handful of datacenter instances, so per-source connection limits get tested the way a distributed attack would test them.

Two things to watch during the run:

  • Resolution latency and failure rate, in that order. Because every query waits for a real answer, you are reading the number your customers experience. A resolver under pressure gets slow well before it starts refusing, and the slow phase is where the damage to users begins.
  • Open connections against the documented cap. If concurrency plateaus at a round number while latency climbs, you have found the ceiling, and you now know it as a measurement rather than as a default value in a manual.

Be honest about the split before you start. Obsidio can measure what is visible from outside: resolution latency, failure rate, how many connections were held, what the edge did with them. Whether your provider’s team noticed, whether your SIEM recorded anything, and whether anybody was paged are answers only your consoles and theirs hold. Have someone watching both during the window.

Strength is proven, not promised. A DNS flood does not ask how much bandwidth you bought. It asks whether the one service every other service depends on was ever tested by anybody, including the company you pay to run it.

Where this fits in a testing programme

DNS Flood is the protocol-specific category. Every other simulation points at your application stack, so their findings are about your servers, your timeouts and your thresholds. This one points at a dependency, which is why it tends to be the run that surfaces an owner nobody had identified. The closest relative is the TCP Connection Flood, which also works through connection churn, but that one holds sockets idle against a server you control. Here each socket carries a real query to a resolver that may not be yours, and the answer it waits for is the measurement.

Ten simulation types, failing in genuinely different places. Slowloris tests whether anyone has checked your connection timeouts. An HTTP flood tests where the smallest number in your stack sits. A DNS flood tests something you may not have realised was in scope. Coverage across the categories is the point, not volume in any one of them.

For regulated institutions, the test also has to leave a record. Obsidio produces cryptographically attested, tamper-evident reports mapped to FINMA, DORA and NIS2, generated inside Trusted Execution Environments, so the result of a run is an artifact you can file rather than a screenshot you have to vouch for.

The DNS simulation is available on request rather than as a default option, partly because the scoping conversation above needs to happen first. To arrange one against infrastructure you are entitled to test, see the Obsidio platform or get in touch with the team. Independent, neutral, verifiable: resilience you can prove.

← Back to Blog