Abuse Shield Plugin

The abuse_shield global plugin provides consolidated abuse protection using bounded per-client state and token-bucket rate limiting. It supports:

  • per-IP request, connection, and HTTP/2 error token buckets;

  • trusted IP ranges and rule-specific rate-limit tiers;

  • logging, temporary IP blocking, and connection closing;

  • atomic rule reloads through traffic_ctl; and

  • optional ClientHello fingerprint matching before the TLS handshake continues.

Building and Loading

Enable the experimental plugin and build it with CMake:

cmake -B build -DENABLE_ABUSE_SHIELD=ON
cmake --build build --target abuse_shield

Add the plugin and its YAML configuration to plugin.config:

abuse_shield.so abuse_shield.yaml

A relative configuration path is resolved from the Traffic Server configuration directory.

Fingerprint rules also require global JAx instances before Abuse Shield. Each method publishes to the registry named by global.fingerprint_registry:

jax_fingerprint.so --method JA3 --export abuse_shield.fingerprints
jax_fingerprint.so --method JA4 --export abuse_shield.fingerprints
abuse_shield.so abuse_shield.yaml

Configuration

The following example configures several available abuse controls:

global:
  ip_tracking:
    slots: 50000
  blocking:
    duration_seconds: 300
  trusted_ips_file: /etc/trafficserver/abuse_shield_trusted.yaml
  log_interval_sec: 10
  log_file: abuse_shield
  fingerprint_registry: abuse_shield.fingerprints

rules:
  - name: excessive_requests
    filter:
      max_req_rate: 100
      req_burst_multiplier: 2.0
    action: [log, block, close]

  - name: excessive_connections
    filter:
      max_conn_rate: 20
      conn_burst_multiplier: 1.5
    action: [log, block, close]

  - name: excessive_h2_errors
    filter:
      max_h2_error_rate: 10
    action: [log, block, close]

  - name: blocked_tls_clients
    filter:
      fingerprints:
        JA3:
          - "238bcebdfa16aa0be417a7f7a80063a9"
          - "99c071c5a5e14cc2527c9e8e0dde4a50"
        JA4:
          - "t13d1516h2_8daaf6152771_02713d6af862"
    action: [log, close]

enabled: true

Global Settings

Setting

Description

ip_tracking.slots

Slots in each bounded IP table (default 50000)

blocking.duration_seconds

Duration of a block action (default 300)

trusted_ips_file

Optional YAML file of IP ranges to bypass

log_interval_sec

Minimum log interval per IP (default 10)

log_file

Optional separate Traffic Server text log object

fingerprint_registry

Named JAx registry used by fingerprint rules

Rules are evaluated in file order and the first matching rule wins.

Rule Filters

Filter

Description

max_req_rate

Maximum requests per second

req_burst_multiplier

Request bucket capacity multiplier

max_conn_rate

Maximum connections per second

conn_burst_multiplier

Connection bucket capacity multiplier

max_h2_error_rate

Maximum received/sent HTTP/2 errors per second

h2_burst_multiplier

HTTP/2 error bucket capacity multiplier

rate_limited_ips_file

Optional IP list that selects this rule

fingerprints

Map of method names to fingerprint sequences

HTTP/2 error rates include both stream and connection errors received from or sent to the client. Stream errors are counted at transaction close. Connection errors are counted once per direction at session close, including connections that never created a transaction. Normal NO_ERROR shutdowns are excluded.

Exceeding the HTTP/2 concurrent-stream limit currently closes the connection with COMPRESSION_ERROR because the rejected HEADERS block has not been decoded. This connection error contributes to max_h2_error_rate. A session-close match can log or block the IP for subsequent connections; its close action is a no-op because the connection is already closed.

Unknown keys at the document, global, nested global, rule, and filter levels are rejected. Invalid boolean values are also rejected. An invalid reload leaves the previous configuration active.

Burst multipliers default to 1.0 and must be at least 1.0. Every rule has an independent token bucket for each configured rate, so thresholds do not depend on rule order. A rate is exceeded when its token bucket becomes negative. Excess events add proportional token debt. Debt is repaid over time at the rule’s rate whether or not the address sends more events, so a quiet address leaves debt once it has earned the tokens back. Entries with current debt are protected from bounded-table eviction. All positive integer rates preserve fractional credit at millisecond resolution, including rates such as 30, 60, and 75. Burst capacity cannot exceed 2,147,483 tokens. Operator-facing token balances are whole tokens, with negative fractions rounded down.

All configured rate filters in a rule use AND logic. Fingerprint values use OR logic across both values and methods, and that fingerprint result is ANDed with the rule’s rate filters. Use separate rules when OR behavior is needed between rate limits.

ClientHello Fingerprints

The supplied JAx methods are JA3 and JA4. Their method names are case-insensitive. JA3 values are validated as 32 hexadecimal characters, while JA4 values are validated against the 36-character JA4 layout. Both are canonicalized to lowercase before matching. Other method names and values are treated as opaque strings and matched exactly, allowing downstream JAx builds to publish site-specific methods.

Only methods derived entirely from a TLS ClientHello can be used. JA4H is derived from an HTTP request and is therefore unavailable at the TS_SSL_CLIENT_HELLO_HOOK where the plugin makes its decision.

JAx computes each fingerprint and publishes it in a versioned, read-only registry held in a named VConn user-argument slot. Abuse Shield consumes that result at its ClientHello hook; it does not link fingerprint algorithms or recompute their values. The registry is an in-process array of length-delimited method/value entries. Its header contains a magic value, ABI version, and structure sizes so a consumer can reject an incompatible layout. JAx owns all registry memory for the VConn lifetime.

Important

Since abuse_shield relies upon the JAx plugin for JA fingerprint values, the JAx plugin lines must precede Abuse Shield in plugin.config. This ensures their hooks publish values before Abuse Shield evaluates them. A missing or incompatible named registry is a startup error.

A matching close action uses TSVConnReenableEx(vconn, TS_EVENT_ERROR), stopping processing before ServerHello and key-exchange work.

Adding Fingerprint Methods

Downstream builds add private or site-specific methods to JAx, following its developer README, then load the method with the same export name:

jax_fingerprint.so --method SITE_METHOD --export abuse_shield.fingerprints

Configure SITE_METHOD as a key under fingerprints. Abuse Shield does not need to know how the method is computed and compares the exported value exactly. No JA-specific addition to ts.h or Abuse Shield provider code is needed.

Actions

Action

Behavior

log

Log the match, rate-limited per client IP

block

Mark the IP blocked for blocking.duration_seconds

close

Close the current connection

For a fingerprint denylist, [log, close] rejects matching TLS connections without changing the IP’s future status. Add block when subsequent connections from that IP should also be denied for the configured duration.

Trusted and Rate-Limited IP Files

A trusted IP file bypasses all Abuse Shield checks:

trusted_ips:
  - 192.0.2.10
  - 198.51.100.0/24
  - 2001:db8::/32

A rule-specific rate tier uses rate_limited_ips_file and a rate_limited_ips sequence:

rate_limited_ips:
  - 203.0.113.0/24
  - 2001:db8:1::/48

Single addresses, ranges, and CIDR blocks are supported. The legacy trusted_ips key is also accepted in a rule-specific rate-limit file.

Runtime Control

Reload the YAML rules without restarting Traffic Server:

traffic_ctl plugin msg abuse_shield.reload

The new configuration is validated before it replaces the active configuration. Existing table data and block expiration times are preserved, as is the state set by abuse_shield.enabled. ip_tracking.slots and log_file and fingerprint_registry are startup-only; a reload that changes any of these settings is rejected. Fingerprint additions and removals apply to the next ClientHello. Buckets for removed or renamed rate rules are pruned on reload and during the next few maintenance passes, releasing obsolete debt.

Other lifecycle messages are:

traffic_ctl plugin msg abuse_shield.dump
traffic_ctl plugin msg abuse_shield.stats
traffic_ctl plugin msg abuse_shield.reset
traffic_ctl plugin msg abuse_shield.clear
traffic_ctl plugin msg abuse_shield.enabled 0
traffic_ctl plugin msg abuse_shield.enabled 1
traffic_ctl plugin msg abuse_shield.trusted

The reset message resets metrics only. The clear message discards all tracking and block state while preserving counters. Hooks already in flight may finish against their existing state. The enabled payload accepts 0/1, false/true, off/on, or no/yes; invalid payloads and unknown plugin messages produce errors.

Metrics

View metrics with traffic_ctl metric get abuse_shield.*.

Metric

Description

abuse_shield.rules.matched

All rule matches

abuse_shield.actions.blocked

Block actions

abuse_shield.actions.block_failed

Block actions not stored

abuse_shield.actions.closed

Close actions

abuse_shield.actions.close_failed

Failed connection closes

abuse_shield.actions.logged

Successful writes or fallback submissions

abuse_shield.actions.log_failed

Failed log object writes

abuse_shield.actions.log_untracked

Log actions without a tracker slot

abuse_shield.connections.rejected

Previously blocked IPs

abuse_shield.connections.reject_failed

Failed blocked-IP rejections

abuse_shield.fingerprints.matched

Fingerprint rule matches

abuse_shield.fingerprints.rejected

ClientHello rejections

abuse_shield.fingerprints.unavailable

Absent or invalid registries

abuse_shield.fingerprints.missing_methods

Registries missing configured methods

abuse_shield.<tracker>.events

Events for a tracker

abuse_shield.<tracker>.events_untracked

Events not admitted to a tracker

abuse_shield.<tracker>.scan_exhausted

Protected-slot scans that hit their bound

abuse_shield.<tracker>.slots_used

Occupied bounded-table slots

abuse_shield.<tracker>.contests

Table contests

abuse_shield.<tracker>.contests_won

Contests won by new IPs

abuse_shield.<tracker>.evictions

Evicted IPs

The tracker name is txn, conn, or h2. Table gauges and counters synchronize once per second and on stats or dump. scan_exhausted is a subset of events_untracked; do not sum them. connections.reject_failed applies only to plain HTTP. Already-disconnected peers do not count as close failures.

Memory Bound

Request, connection, and HTTP/2 state use separate private fixed-size tables with ip_tracking.slots entries each. Block expirations use a separate bounded table of the same capacity that never evicts an unexpired block. This keeps memory usage bounded even when traffic contains many distinct source addresses. These tables use the reusable UdiTable utility with an eviction policy that protects entries carrying token debt. slots must be between 1 and 1,000,000. Per-address memory also grows with the number of applicable rate rules because each rule and metric needs its own bucket.

New addresses compete for tracker slots. An ordinary contest loss increments events_untracked. Entries with token debt are protected from eviction, and the table probes at most min(slots, 1024) candidates before incrementing both scan_exhausted and events_untracked. This bounds work while the table mutex is held even if every probed entry has debt.

Connection rules are evaluated when a connection starts: TLS connections at the TLS virtual-connection hook and plain HTTP connections at the HTTP session hook. Request and HTTP/2 error rules are evaluated when their corresponding event is recorded.

Operational Limitations

Deploy an unscoped JAx instance for each required fingerprint method when enforcement must cover every TLS connection. --servernames limits JAx publication, including omitting connections without SNI; its default is unscoped. An absent registry differs from a registry missing a method in the metrics above. The separate ja4_fingerprint plugin does not export this registry. Export names consume the process-wide budget of four VCONN user argument slots; share an export name across producers for the same consumer.

Runtime hooks fail open when the client address cannot be obtained, when the fingerprint registry is unavailable, or when an exception prevents evaluation. An unavailable fingerprint does not satisfy a fingerprint rule. A full block table cannot retain a new block, although other configured actions for the current event can still run. Monitor failure and unavailable counters. For a narrower HTTP/2 error-blocking policy, see the block_errors plugin.