Response Rate Limiting

DNS Response Rate Limiting (RRL)

Nexus GSLB's RRL protects against DNS amplification and DDoS attacks by enforcing a per-source-IP query rate limit. It is independent of the global licensed RPS cap — a single abusive IP exhausts only its own bucket and does not affect other clients.

License: DNS response rate limiting requires the pro tier or above. It is disabled at startup on free and paid installations. Contact licensing@gslb.nexus to upgrade.

How it works

Each source IP gets a token bucket. The bucket refills at perSourceQPS tokens per second up to burstSize. One token is consumed per query. When a source IP's bucket is empty, the query is either truncated (TC=1) or dropped depending on the configured action.

Allowlisted CIDRs bypass rate limiting entirely — no bucket is allocated and no tokens are consumed. Use this for internal networks, monitoring systems, and scheduled jobs that legitimately send high query volumes.

Relationship to the global RPS limit

Inbound query
    │
    ▼
Global RPS check (license gate)   ← SERVFAIL if server over licensed capacity
    │
    ▼
Per-source RRL check              ← TC=1 or drop if this IP is flooding
    │
    ▼
Normal DNS resolution

The two limits compose but are independent. The RRL catches single-source floods long before they push the server over its global capacity.

TCP behaviour

When the action is truncate and the query arrives over TCP, Nexus drops the query silently instead of sending TC=1 (TC on TCP is meaningless and confuses some resolvers). Use action: drop if you want consistent behaviour across both transports.

Configuration

dns:
  rrl:
    enabled: true
    perSourceQPS: 100          # tokens refilled per second per source IP
    burstSize: 200             # bucket depth — allows bursts above perSourceQPS
    action: truncate           # "truncate" (TC=1) or "drop"
    cleanupInterval: 60s       # how often idle source-IP buckets are evicted
    allowlist:
      - 10.0.0.0/8             # internal network — unrestricted
      - 192.168.0.0/16
      - 203.0.113.42/32        # specific monitoring host

Fields

Field Default Description
enabled false Enable RRL
perSourceQPS 100 Token refill rate per source IP (queries/sec)
burstSize 2 × perSourceQPS Maximum bucket depth; allows short bursts
action truncate What to do when a bucket is empty: truncate sends TC=1 (forces TCP retry); drop discards the query silently
cleanupInterval 60s How often the background goroutine evicts buckets from IPs that have gone quiet
allowlist [] CIDR ranges exempt from rate limiting

Sizing guidance

Scenario Suggested settings
Public authoritative server, general use perSourceQPS: 50, burstSize: 100
Internal resolver with scheduled jobs perSourceQPS: 500, burstSize: 1000, allowlist internal ranges
Aggressive DDoS protection perSourceQPS: 20, burstSize: 40, action: drop
Monitoring / canary queries only perSourceQPS: 10, allowlist monitoring CIDRs

Allowlist for scheduled jobs

Any process that starts up and immediately resolves a large number of names (service discovery, config management, CI/CD) can hit the per-source limit during its startup burst. Add its source CIDR to the allowlist:

dns:
  rrl:
    enabled: true
    perSourceQPS: 100
    allowlist:
      - 10.0.0.0/8        # all internal servers — unrestricted
      - 172.16.50.0/24    # CI runner pool

Allowlisted sources do not allocate a token bucket and impose zero overhead on the hot path.

Managing the allowlist at runtime

The allowlist can be managed without editing the config file via the REST API, the gslbctl CLI, or the Web UI. Entries stored this way are persisted in the database and merged with any CIDRs in the config file at daemon startup.

Changes require a daemon restart to take effect. Entries written via the API are saved immediately but the in-memory RRL does not reload mid-flight.

gslbctl

# List current allowlist entries
gslbctl rrl list

# Add a CIDR
gslbctl rrl add 10.0.0.0/8 --description "Internal network"

# Remove an entry by ID (use 'rrl list' to find the ID)
gslbctl rrl remove <id>

REST API

Method Path Permission
GET /api/v1/dns/rrl/allowlist rrl:read
POST /api/v1/dns/rrl/allowlist rrl:write
DELETE /api/v1/dns/rrl/allowlist/{id} rrl:write

POST body:

{ "cidr": "10.0.0.0/8", "description": "Internal network" }

Web UI

Navigate to Admin → RRL Allowlist. Entries are visible to users with the rrl:read permission; rrl:write is required to add or remove entries.

How config-file and DB entries merge

At startup, gslbd loads CIDRs from both sources:

  1. CIDRs in dns.rrl.allowlist (config file)
  2. Entries stored in the database via the API

All CIDRs from both sources are combined and passed to the RRL engine. There is no deduplication concern — a CIDR present in both sources is simply matched twice, with the same effect.

Memory usage

Each unique source IP that has sent at least one query since the last cleanup holds one bucket (~64 bytes). At 1 million unique source IPs between cleanups: ~64 MB. The cleanup goroutine evicts any IP that has been quiet for longer than cleanupInterval, so memory is bounded by the number of active sources, not the total number of clients ever seen.


Was this article helpful?
© 2026