DEV Community

IPYE
IPYE

Posted on AI-assisted

Rate Limiting and Banning IPv6 Clients by /64 with nftables

Disclosure up front: I run IPYE, a company that leases IP space. It gets one short paragraph at the end; the rest is plain networking.

Rate limits and ban lists assume that an address stands for a client. In IPv4 that is a decent approximation. In IPv6 it fails: a single customer with a /64 has 2^64 addresses available, and a script that picks a fresh source address for every request will sail past any per-address limit and fill your ban list with entries that never match again. The fix is to aggregate: limit and ban on a prefix, usually the /64. This article shows how to do that with nftables and what to watch out for with fail2ban. Examples use 2001:db8:100::/48 and 203.0.113.0/24; test every rule on a non-production machine, and check syntax against your nftables version.

Choosing the unit

The reasoning is covered in the article on IPv6 logging and abuse handling: a /64 is a normal subnet, and the host inside it can use any address in it. So the default unit for IPv6 limits is the /64. For IPv4 the equivalent is the single address (or a /32), which is a poor stand-in for a person behind carrier-grade NAT, but that is a separate issue.

Some cautions apply before you start:

  • A /64 can be shared between many users on some networks, such as public Wi-Fi or mobile networks. A strict limit on a shared /64 hurts innocent users. Use limits that suit your service, and treat bans as temporary.
  • Some customers receive more than a /64 (a /56 or /48), so a determined client can hop between /64s. Widen the unit when evidence supports it.

Per-/64 rate limiting with a meter

nftables can limit using a dynamic set keyed on a masked address. The following limits new TCP connections to port 443, counting all addresses within the same /64 together:

table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;

    ct state established,related accept
    iif lo accept
    meta l4proto ipv6-icmp accept

    tcp dport 443 ct state new \
      meter flood6 { ip6 saddr & ffff:ffff:ffff:ffff:: \
                     timeout 5m limit rate over 20/second burst 40 packets } \
      counter drop

    tcp dport 443 accept
  }
}
Enter fullscreen mode Exit fullscreen mode

Here ip6 saddr & ffff:ffff:ffff:ffff:: masks the source address to its top 64 bits, so every address in the /64 maps to the same meter entry. The limit rate over clause matches packets above the rate, and these are dropped. The timeout removes idle entries so the meter does not grow without bound. Meter syntax has changed between nftables releases, so check nft -c -f file.nft for validity and the wiki for your version. Add an equivalent rule for IPv4 with ip saddr, whose natural unit is the single address.

Keep ICMPv6 accepted ahead of any rate rule, as the earlier article on ICMPv6 explains: dropping neighbour discovery or Packet Too Big messages causes outages that look like unrelated problems. Rate-limit ICMPv6 separately and generously if you need to, instead of blocking it.

A ban list of prefixes

For persistent offenders, keep a set of prefixes. An interval set holds ranges, and timeout lets entries expire:

table inet filter {
  set banned6 {
    type ipv6_addr
    flags interval, timeout
  }
  chain input {
    type filter hook input priority -10; policy accept;
    ip6 saddr @banned6 drop
  }
}
Enter fullscreen mode Exit fullscreen mode

Add and remove entries at runtime:

nft add element inet filter banned6 { 2001:db8:100:1::/64 timeout 1h }
nft delete element inet filter banned6 { 2001:db8:100:1::/64 }
nft list set inet filter banned6
Enter fullscreen mode Exit fullscreen mode

An entry is one prefix, regardless of how many addresses inside it were used, so the list stays small. Always set a timeout: prefixes get reassigned, and a permanent ban can end up blocking an unrelated user months later. Review hits with a counter on the rule.

fail2ban and IPv6

fail2ban watches logs and bans the addresses it sees. Current versions support IPv6, and ban actions exist for nftables. Two points need attention:

  • It bans individual addresses by default. An attacker rotating inside a /64 will not be stopped by a ban on one address. Options are to use an action that adds the whole /64 to a prefix set, which means a custom action or filter configuration, or to make rate limiting by /64 in nftables your primary defence and use fail2ban for slower, credential-based abuse such as SSH.
  • Matching log lines. The filter must capture IPv6 addresses in the format your service logs. Make sure normalisation and the form of the address are what the regex expects. Test with fail2ban-regex against real log lines containing IPv6 addresses.

Remember ignoreip: put your own management addresses and prefixes in the ignore list, including IPv6 ones, so a typo does not lock you out.

Application-level limits

Reverse proxies and web servers have their own limits, keyed by whatever variable you choose. By default, nginx's limit_req_zone keys on the client address, and this is the per-address problem again. Where the proxy cannot aggregate easily, apply the limit in the firewall in front of it. If the proxy sits behind a load balancer, use the real client address, as the article on the PROXY protocol explains, or all limits would apply to the balancer.

Testing

nft -c -f /etc/nftables.conf     # check syntax without applying
nft list meter inet filter flood6   # inspect the entries (name may differ by version)
Enter fullscreen mode Exit fullscreen mode

Generate traffic from several source addresses in a test /64 and confirm they share a counter, and that traffic from a different /64 does not. Then check that a normal client, with a few connections, is never limited.

Summary

Aggregate on the /64, expire every ban, keep ICMPv6 flowing, and test from a real IPv6 host. A limit that counts addresses in IPv6 is a limit that attackers can ignore with no effort.

About IPYE

IPYE (https://my.ipye.com) leases IPv4, IPv6, and ASN space; a /48 IPv6 block is free for the first year. LOA, ROA, rDNS, and WHOIS are handled by IPYE. Locations: Los Angeles, New York, Ashburn, Salt Lake City, London, Amsterdam, and Frankfurt. Questions: ipye@turnel.com.

Disclosure: I run IPYE

Top comments (0)