Skip to content

Configuration Reference

All configuration is supplied as a single YAML file passed as the first argument to flowwler:

sudo flowwler /etc/flowwler/config.yaml

A complete working example ships with the package at /usr/share/flowwler/config.example.yaml.


Validating the configuration

Before applying a changed config, validate it without starting the daemon:

sudo flowwler validate /etc/flowwler/config.yaml

Exits 0 on success, 1 on error with a description of what failed. IRR WHOIS lookups are performed silently during validation if any group has an irr: block.


Applying configuration changes

Reload (no restart)

Most changes can be applied without restarting the service:

sudo systemctl reload flowwler
sudo journalctl -u flowwler -f

What happens on reload:

  • Config is re-parsed and validated - invalid config is rejected; the running process keeps its current config
  • IRR prefixes are re-resolved and merged into group subnets
  • BGP peers are reconciled live (added, removed, or updated)
  • The group LPM radix tree is rebuilt atomically
  • Active mitigations for groups removed from config are withdrawn
  • The notification manager is rebuilt and swapped atomically
  • Log level is updated live

Restart (required for some BGP changes)

Changes to gobgp.local_asn, gobgp.router_id, or gobgp.grpc_addr cannot be applied via reload. flowwler detects these and exits with code 1 so that systemd restarts it automatically:

sudo systemctl restart flowwler

Active BGP mitigations are withdrawn when the process stops and re-announced after the BGP session re-establishes. The startup cleanup removes any stale paths from the previous run before re-announcing.


logging

Field Type Default Description
level string "info" Log verbosity: debug, info, warn, or error

Log output is structured JSON (zap production format).


netflow

Controls the NetFlow v5 / v9 / IPFIX listener and rate aggregation. See Flow input comparison for how packet-length visibility and detection latency vary across v5, v9/IPFIX, and IPFIX inline monitoring.

Field Type Default Description
listen_addr string "[::]:2055" UDP address to receive NetFlow v5/v9/IPFIX packets. The [::] wildcard accepts both IPv4 and IPv6 exporters.
aggregation_window duration 10s Sliding window depth. Each tick is 1 second; 10s keeps 10 buckets. Set to match your router's active-flow-timeout.
rate_smoothing_alpha_rise float (0,1] 0.9 Smoothing α when the rate is rising (attack onset). Higher = faster detection.
rate_smoothing_alpha_fall float (0,1] 0.2 Smoothing α when the rate is falling (traffic subsiding). Lower = more stable, resists oscillation.
rate_smoothing_alpha float (0,1] - Legacy single-α mode. If set, overrides both rise and fall for backward compatibility. Prefer the asymmetric fields for new configs.
treat_no_output_as_discard bool false Mark flows whose output ifIndex is 0 or 0xFFFFFFFF as router-discarded. Discarded traffic still counts toward BPS/PPS thresholds. See router discard detection.
default_sampling_rate uint32 0 Static fallback 1-in-N sampling rate, used only when the router does not export sampling rate information via Options Data records. 0 and 1 both mean no scaling is applied.

sflow

Controls the sFlow v5 listener. Disabled by default. See Flow input comparison — sFlow exports one sample per packet with no flow cache, but does not carry per-source packet-length data.

Field Type Default Description
listen_addr string "[::]:6343" UDP address to receive sFlow datagrams.
enabled bool false Must be true to open the socket.
default_sampling_rate uint32 1000 Fallback 1-in-N sampling rate used when a flow sample reports sampling_rate = 0.
treat_no_output_as_discard bool false Same semantics as the NetFlow field: samples with output ifIndex 0 or 0xFFFFFFFF are flagged as router-discarded.

sFlow and NetFlow can both be active simultaneously; they share the same aggregation pipeline.


gobgp

Configures the embedded GoBGP BGP daemon. local_asn and router_id are required.

Field Type Default Description
local_asn uint32 - Required. Local AS number for this BGP speaker.
router_id string - Required. BGP Router ID (an IPv4 address).
listen_addresses []string ["0.0.0.0", "::"] IP addresses to bind the BGP listener on (always port 179).
grpc_addr string "" If set, exposes GoBGP's gRPC API at this address for use with the gobgp CLI. Disabled by default.
peers []PeerConfig - List of BGP neighbors. See PeerConfig.

PeerConfig

Field Type Required Description
neighbor_address string yes IP address of the BGP peer.
peer_asn uint32 yes Peer's AS number.
local_asn uint32 no Override the local ASN for this specific session (eBGP with a different local AS). Omit to inherit gobgp.local_asn.
description string no Human-readable label, logged at startup.
password string no MD5 TCP authentication password.
multihop_ttl uint32 no eBGP multihop TTL. 0 = direct peer (default). Set to 2255 when the peer address is not on a connected subnet (e.g. loopback-addressed sessions).

http

Field Type Default Description
listen_addr string "[::]:9731" Address for the HTTP server (/metrics and REST API).
auth AuthConfig - Optional. REST API authentication. See AuthConfig. The /metrics endpoint is never gated.

AuthConfig

Two authentication methods are available and can be enabled independently. If both are configured, either is accepted.

Field Type Description
api_key string Static token. Clients supply it via Authorization: Bearer <key> or X-API-Key: <key>.
username string HTTP Basic Auth username. Required when password_hash is set.
password_hash string bcrypt hash of the password. Generate with flowwler hash-password.

When neither field is set the API is open. Auth config is re-read on every request, so a SIGHUP reload takes effect immediately.


defaults

Default escalation settings applied to any group that does not define its own escalation block.

Field Type Default Description
hold_time duration 15m How long to remain in HoldDown before returning to Idle. Can be overridden per escalation level via LevelConfig.hold_time.
flowspec_max_rules int 10 Maximum number of FlowSpec NLRI rules to announce per victim (top-N sources by BPS).
max_concurrent_mitigations int 0 Maximum number of simultaneously active auto-mitigations. 0 = unlimited. When the cap is reached, new activations are refused and logged as warnings.
escalation []LevelConfig - Default escalation ladder. See LevelConfig.
whitelist WhitelistSourceConfig - Global source-IP allowlist applied to every group that does not define its own whitelist. See WhitelistSourceConfig.

storage

Controls the persistent attack history database. Storage is always enabled - the database is created automatically at the default path if not specified.

Field Type Default Description
path string /var/lib/flowwler/flowwler.db Path to the SQLite database file. The directory must exist and be writable by the flowwler process.
retention_days int 90 Number of days to retain cleared attacks. Attacks are pruned daily. Active (uncleared) attacks are never pruned regardless of age. 0 uses the default (90 days).

Every state-transition event (activated, escalated, holddown, cleared) is recorded as it happens. The database is queryable via GET /api/v1/attacks and GET /api/v1/attacks/{attack_id}.

Example:

storage:
  path: /var/lib/flowwler/flowwler.db
  retention_days: 180

pcap

Optional. When enabled, flowwler writes a libpcap-format file for each detected attack. The file contains synthesized IP and transport-layer headers derived from flow metadata — source/destination IPs, ports, protocols, and packet-size hints. Actual packet payloads are not available from NetFlow/sFlow and are not present.

Field Type Default Description
enabled bool false Enable PCAP capture. When false (default) the section has no effect and no files are written.
dir string /var/lib/flowwler/pcaps Directory where PCAP files are written. Created automatically if it does not exist.

One file is created per attack session. The file is opened when a victim transitions to Active and closed when the attack transitions to HoldDown or Cleared. Files are named:

{YYYY-MM-DD}T{HH-MM-SS}_{victimIP}_{attackID[:8]}.pcap

For example: 2026-04-25T14-32-00_203.0.113.1_a1b2c3d4.pcap. Colons in the timestamp and IPv6 addresses are replaced with dashes. The timestamp reflects the attack start time.

Files are pruned according to storage.retention_days — PCAP files are deleted on the same daily schedule as attack history entries.

pcap:
  enabled: true
  dir: /var/lib/flowwler/pcaps

!!! note "Content limitations" The PCAP contains one synthesized packet per NetFlow/sFlow record. IP and transport headers are populated from flow metadata (addresses, ports, protocol, packet-length range). There is no payload. The link type is LINKTYPE_RAW (101 — raw IP), compatible with Wireshark, tcpdump, and any standard pcap tool.


telemetry

Optional. Controls anonymous usage reporting. On startup and every 6 hours, flowwler sends a small JSON payload to statistics.flowwler.net containing the version, a random instance UUID, enabled feature flags, and aggregate attack counts. No IP addresses, victim data, routing information, or operator-identifying data are included.

Field Type Default Description
disabled bool false Set to true to opt out of all telemetry pings.
telemetry:
  disabled: true

notifications

Optional top-level block. When absent, the notification subsystem is not started and there is zero overhead.

Field Type Default Description
timeout duration 10s Per-target HTTP request timeout.
retry_count int 0 Number of retries after a failed send (0 = try once).
targets []TargetConfig - List of notification destinations. See TargetConfig.

TargetConfig

Field Type Required for Description
type string all Backend type: alertmanager, jira, pagerduty, pushover, slack, teams, telegram, webhook, zammad.
name string all Human-readable label used in log output.
url string webhook, alertmanager, teams, slack, jira, zammad HTTP endpoint URL. For jira, the Atlassian base URL (e.g. https://mycompany.atlassian.net). For zammad, the Zammad instance base URL.
headers map[string]string - Extra HTTP headers (webhook only, e.g. Authorization).
events []string - Event filter. Omit to receive all events. Valid values: activated, escalated, holddown, cleared.
token string telegram Bot API token.
chat_id string telegram Telegram chat or channel ID.
integration_key string pagerduty PagerDuty Events v2 integration key.
user_key string pushover Pushover user or group key.
app_token string pushover Pushover application API token.
priority map[int]int - Pushover only. Maps escalation level to Pushover message priority (-2..2). Key 0 applies to cleared and holddown events. Defaults: 0-1 (quiet), 10 (normal), 21 (high), 3+2 (emergency). Emergency priority automatically includes the required retry and expire parameters.
critical_level int - PagerDuty and Alertmanager only. Escalation levels ≥ this value map to "critical" severity; below maps to "warning". Default: 2.
email string jira Jira user email address for Basic auth (paired with api_token).
api_token string jira, zammad Atlassian API token (Jira) or Zammad API token (Zammad).
project_key string jira Jira project key (e.g. NOC).
issue_type string - Jira issue type name. Default: Task.
resolve_transition string - Transition name used to close the issue on cleared. Default: Done. Must match exactly (case-insensitive).
issue_priority string - Jira priority name (e.g. High). Omit to use the project default.
group string zammad Zammad ticket group name (e.g. Users).
customer string zammad Customer email address for new Zammad tickets. Must be an existing Zammad user.
close_state string - Zammad state name set when the attack clears. Default: closed. Must match the state name exactly (case-sensitive).
tags []string - Optional tags/labels. Defaults to [flowwler, ddos] when not set. When set, the configured list replaces the defaults entirely.

Notification events

Event Fired when
activated Idle → Active (first level triggered)
escalated Active level increases
holddown Active → HoldDown (traffic subsided)
cleared HoldDown → Idle (hold-down timer expired)

Each event carries: type, attack_id (hex, stable across the full attack lifecycle), timestamp, victim_ip, group_name, rule_name, level, prev_level, mitigation_type, notification_only, bps, pps, discard_bps.

The attack_id is a 16-byte crypto/rand hex string generated at Idle→Active and reused for all subsequent events of the same attack. PagerDuty uses it as dedup_key; Alertmanager includes it as a label.

Per-group notifications

Each group can define its own notifications: block independent of the global one. Targets from both scopes receive events for the group.

groups:
  - name: datacenter-a
    subnets:
      - "203.0.113.0/24"
    notifications:
      targets:
        - type: pagerduty
          name: datacenter-a-pd
          integration_key: "abc123"
          events: [activated, cleared]

groups

A list of named groups of subnets. The first matching group (longest prefix match) is used for each victim IP.

Field Type Description
name string Unique name, used in log output and Prometheus labels.
subnets []string CIDR prefixes (IPv4 or IPv6). Multiple prefixes can share one group.
irr IRRConfig Optional. Auto-resolve prefixes from IRR by AS-SET. Resolved prefixes are merged with static subnets. See IRRConfig.
netbox NetBoxConfig Optional. Auto-resolve prefixes from a NetBox IPAM instance by tag or tenant. Resolved prefixes are merged with static subnets. See NetBoxConfig.
url_subnets URLImportConfig Optional. Fetch additional victim subnets from an HTTP/HTTPS URL. Resolved prefixes are merged with static subnets. See URLImportConfig.
whitelist WhitelistSourceConfig Optional. Per-group source-IP allowlist. Overrides defaults.whitelist entirely for this group (not additive). See WhitelistSourceConfig.
bgp GroupBGPConfig Optional. Per-group BGP route attributes applied to all mitigations. See GroupBGPConfig.
escalation []LevelConfig Optional. Overrides defaults.escalation for this group.
rules []RuleConfig Optional. Per-protocol/port sub-rules with independent escalation.
notifications NotificationsConfig Optional. Per-group notification targets, independent of the global block.

GroupBGPConfig

Per-group BGP route attributes applied to all mitigations triggered for the group. Useful when performing mitigations on behalf of downstream customers - set their ASN and community preferences without affecting other groups.

Field Type Default Description
asn uint32 0 When non-zero, appended as the last entry in the AS_SEQUENCE of the AS-PATH attribute. Applies to unicast routes only (blackhole, subnet-blackhole). Has no effect on FlowSpec (the AS-PATH attribute is required by GoBGP but ignored by receivers).
no_export bool false When true, attaches the well-known NO_EXPORT community (65535:65281, RFC 1997). Applies to all mitigation types.
no_advertise bool false When true, attaches the well-known NO_ADVERTISE community (65535:65282, RFC 1997). Applies to all mitigation types.
groups:
  - name: "customer-a"
    subnets:
      - "203.0.113.0/24"
    bgp:
      asn: 64500          # append customer's ASN as last AS-PATH entry (unicast only)
      no_export: true     # do not propagate the route beyond the immediate peer
      no_advertise: true  # do not advertise the route to any peer

no_export and no_advertise can be used independently or together. When combined with a per-mitigation community (set under blackhole: or subnet-blackhole:), all communities are merged into a single COMMUNITIES attribute.

IRRConfig

Configures automatic prefix discovery from the Internet Routing Registry. Resolved prefixes are merged with any static subnets entries. Resolution runs at startup, on SIGHUP, and every 12 hours in the background.

Field Type Default Description
as_set string - Required. AS-SET object to expand (e.g. AS64500:AS-CUSTOMERS).
server string rr.ntt.net IRR WHOIS server, host or host:port.
sources []string - Filter queries to specific IRR databases (e.g. ["RADB", "RIPE"]). When set, the query becomes RADB,RIPE::AS-EXAMPLE. Omit to query all.
ipv4 bool true Resolve IPv4 prefixes.
ipv6 bool true Resolve IPv6 prefixes.
concurrency int 10 Number of parallel per-ASN WHOIS connections used during resolution. Increase to speed up large AS-SETs.
groups:
  - name: "customers"
    irr:
      as_set: "AS64500:AS-CUSTOMERS"
      server: "rr.ntt.net"         # optional, default: rr.ntt.net
      sources: ["RADB", "RIPE"]    # optional
      ipv4: true
      ipv6: true
      concurrency: 10              # optional, default: 10

Resolved prefixes are logged at INFO level on each resolution alongside the ASN count, route counts per IP version, and wall-clock duration. See flowwler_irr_* metrics for Prometheus observability.

NetBoxConfig

Configures automatic prefix discovery from a NetBox IPAM instance. Only prefixes with status=active are returned. Resolved prefixes are merged with any static subnets entries and any IRR-resolved prefixes. Resolution runs at startup, on SIGHUP, and every 12 hours in the background.

Field Type Default Description
url string - Required. Base URL of the NetBox instance (e.g. https://netbox.example.com).
token string - Required. NetBox API token. Sent as Authorization: Token <token>.
tag string - Filter to prefixes carrying this tag slug. At least one of tag or tenant is required.
tenant string - Filter to prefixes belonging to this tenant slug. At least one of tag or tenant is required.
ipv4 bool true Include IPv4 prefixes.
ipv6 bool true Include IPv6 prefixes.

Both tag and tenant may be set simultaneously - NetBox applies AND logic.

groups:
  - name: "customers"
    netbox:
      url: "https://netbox.example.com"
      token: "abc123def456"
      tag: "flowwler-protect"   # at least one of tag/tenant required
      tenant: "acme-corp"       # optional, combined with tag as AND
      ipv4: true
      ipv6: true

See flowwler_netbox_* metrics for Prometheus observability.

URLImportConfig

Fetches CIDR prefixes from an HTTP/HTTPS URL. Used as a single entry under groups[].url_subnets and as a list entry under whitelist.url_subnets.

Field Type Default Description
url string - Required. HTTP or HTTPS URL to fetch.
json_field string "" When empty, the response body is decoded as a JSON []string of CIDRs. When set, the body is decoded as a JSON object and this key must name a top-level []string of CIDRs. For plain-text responses, omit this field entirely.
ipv4 bool true Include IPv4 prefixes.
ipv6 bool true Include IPv6 prefixes.

For plain-text URLs: one CIDR per line; #-prefixed lines and blank lines are ignored.

groups:
  - name: "customers"
    url_subnets:
      url: "https://example.com/prefixes.txt"
      ipv4: true
      ipv6: true

WhitelistSourceConfig

Defines a set of trusted source IPs. Traffic from whitelisted sources is excluded from BPS/PPS threshold calculations and those IPs are never selected as FlowSpec mitigation targets.

All populated source fields are resolved and union-merged into a single prefix set. You may use any combination of static CIDRs, IRR-derived routes, NetBox IPAM prefixes, and URL-fetched lists.

Override semantics: a per-group whitelist block replaces defaults.whitelist entirely for that group — it is not additive. Groups without a whitelist block fall back to defaults.whitelist.

Field Type Default Description
subnets []string - Static list of CIDR prefixes (IPv4 or IPv6).
irr []IRRConfig - One or more IRR AS-SET queries. Each entry uses the same fields as IRRConfig. Multiple AS-SETs are union-merged.
netbox []NetBoxConfig - One or more NetBox IPAM instances. Each entry uses the same fields as NetBoxConfig.
url_subnets []URLImportConfig - One or more URL-fetched prefix lists. Each entry uses the same fields as URLImportConfig.

Resolution runs at startup, on SIGHUP, on POST /api/v1/reload, and on POST /api/v1/reload/sources. The resolved count per scope is exposed via the flowwler_whitelist_prefixes metric and the GET /api/v1/whitelist endpoint.

Global whitelist (applied to all groups unless overridden):

defaults:
  whitelist:
    subnets:
      - "192.0.2.0/24"       # static trusted range
      - "2001:db8::/32"
    irr:
      - as_set: "AS64500:AS-TRUSTED"
        server: "rr.ntt.net"
    url_subnets:
      - url: "https://example.com/trusted-sources.txt"

Per-group whitelist (overrides the global list for this group only):

groups:
  - name: "customer-a"
    subnets:
      - "203.0.113.0/24"
    whitelist:
      subnets:
        - "10.0.0.0/8"       # customer's internal ranges — never blocked
      netbox:
        - url: "https://netbox.example.com"
          token: "abc123def456"
          tag: "customer-a-trusted"

LevelConfig

Defines one step in an escalation ladder.

Field Type Required Description
level int yes Level index. Levels are evaluated in ascending order.
condition ConditionConfig yes Threshold condition that must be met to activate this level.
mitigation MitigationConfig no What action to take when this level activates. Omitting this field (or leaving type empty) creates an alert-only level: the state machine runs normally and notifications fire, but no BGP announcement is made.
escalate_after duration no Minimum time to remain at this level before ascending to the next. 0s = ascend immediately.
hold_time duration no Override defaults.hold_time for this level only. When set, the HoldDown timer uses this value instead of the global default when this level triggers the Active → HoldDown transition. Applies to all mitigation types.

Alert-only levels are useful for soft-warning thresholds that notify operators without triggering BGP, for groups you want to observe before committing to auto-mitigation, and for safe threshold tuning during initial deployment:

escalation:
  - level: 1
    condition:
      bps: 500m        # notify at 500 Mbps - no BGP action
    escalate_after: 2m

  - level: 2
    condition:
      bps: 2g
    mitigation:
      type: flowspec
      flowspec:
        action: discard

ConditionConfig

At least one of bps or pps must be set. If both are set, both must be met (AND logic).

Field Type Description
bps BitRate Bits-per-second threshold. Accepts raw integers or suffixed strings: 1g = 1 Gbps, 500m = 500 Mbps, 100k = 100 kbps.
pps uint64 Packets-per-second threshold.

MitigationConfig

Field Type Description
type string One of: blackhole, subnet-blackhole, flowspec, or omitted/empty for alert-only (no BGP action).
blackhole BlackholeConfig Required when type: blackhole.
subnet-blackhole SubnetBlackholeConfig Required when type: subnet-blackhole.
flowspec FlowspecConfig Required when type: flowspec.

BlackholeConfig

Field Type Default Description
community string - BGP community string (e.g. "65535:666"). Sent as a standard BGP community attribute.
next_hop string 192.0.2.1 (IPv4) / 100::1 (IPv6) BGP next-hop for the announced route. The router maps this to a null/discard interface for blackhole routing.

SubnetBlackholeConfig

The prefix is automatically derived from the victim IP: IPv4 → /24, IPv6 → /48 by default. These lengths are configurable below. A manual POST /api/v1/mitigations request can also supply an explicit CIDR in victim_ip, which is announced verbatim regardless of these settings — see REST API.

Field Type Default Description
community string - BGP community string.
next_hop string 192.0.2.1 (IPv4) / 100::1 (IPv6) BGP next-hop.
ipv4_prefix_len uint8 24 Prefix length used when auto-deriving a subnet from a bare victim IP (8-32). No effect when a manual API request supplies an explicit CIDR.
ipv6_prefix_len uint8 48 Prefix length used when auto-deriving a subnet from a bare victim IP (8-128). Set to your actual allocation size (e.g. 44 or 36) if you don't aggregate on /48 boundaries. No effect when a manual API request supplies an explicit CIDR.

Scrubbing center redirection

subnet-blackhole can redirect attack traffic to a scrubbing center instead of blackholing it. The announced /24 (or /48) is more specific than normal prefix announcements, so it wins by BGP longest-prefix match. Routers export it to scrubbing center peers, who attract the attack traffic, filter it, and re-inject clean traffic via a return path (typically a GRE tunnel or MPLS VPN with a static route for the /24 at lower local-preference).

Set next_hop to the scrubbing center's IP (reachable via IGP or tunnel) and community to a value your routers recognize as a scrubbing trigger. Leave no_export and no_advertise unset so the route propagates to scrubbing peers.

mitigation:
  type: subnet-blackhole
  subnet-blackhole:
    community: "65001:100"   # scrubbing trigger - matched by router import/export policy
    next_hop: "10.255.0.1"   # scrubbing center next-hop, reachable via IGP or tunnel

When flowwler withdraws the route at the end of the attack, the covering aggregate takes over and traffic reverts to its normal path.

FlowspecConfig

Field Type Default Description
action string "discard" "discard" (traffic rate = 0), "rate-limit", or "redirect".
rate_limit_bps BitRate - Required when action: rate-limit. Rate in bits/sec; converted to bytes/sec internally per RFC 5575.
next_hop string - Scrubbing-center next-hop IP for action: redirect (RFC 7674 IPv4-address-specific redirect). May be combined with vrf.
vrf string - Route Target in "ASN:LocalAdmin" notation for VRF redirect (RFC 5575 §7). ASN ≤ 65535 encodes as a 2-octet AS-specific extended community; ASN > 65535 as a 4-octet extended community. May be combined with next_hop. At least one of next_hop or vrf is required when action: redirect.
community string - Optional BGP community to attach to announced FlowSpec routes. Standard format: "AS:VALUE" (RFC 1997, each part 0–65535). Large community format: "ASN:Local1:Local2" (RFC 8092, each part 0–4294967295). Useful for upstream policy control: set this on a higher escalation level so your upstream only receives FlowSpec routes once the attack crosses that threshold.
max_rules int defaults.flowspec_max_rules Maximum number of FlowSpec rules to announce. Controls both per-source-IP mode and consolidated mode (see below).

Rule selection strategy - flowwler inspects all unique source IPs visible in the current snapshot before deciding which mode to use. A source only counts toward either mode if it's currently active — one that stopped sending in the last couple of seconds is excluded from selection even if its bytes still linger in the windowed rate average (see materiality in How It Works).

  • Per-source-IP mode (sources ≤ max_rules, and cross-tick spoofing not yet confirmed for this mitigation — see below): one FlowSpec rule per top-N source IP, sorted by BPS. The NLRI includes destination IP, source IP, and any non-zero/non-empty match fields (protocol, destination port(s), source port(s), TCP flags, ICMP type/code, packet-length range) extracted from the flow data.
  • Consolidated mode (sources > max_rules, or cross-tick spoofing confirmed): flowwler searches over every droppable match dimension — protocol, destination port, source port, packet-length range, TCP flags, ICMP type, ICMP code — for whichever combination of kept dimensions covers the most traffic within max_rules groups (never relaxing all the way to a protocol-only, blackhole-equivalent rule). A kept port dimension is no longer strictly "one value or dropped": it groups by the bounded set of distinct ports actually observed, so a rule can carry a short OR'd list of ports (e.g. dst_ports: [53, 5353]) instead of collapsing straight to a wildcard. ICMP type/code are populated the same bounded-value-set way for ICMP traffic. TCP flags are narrowed automatically too, but as a single value rather than a set: it's kept only when the flags actually observed are exactly consistent across the group (or fall within a narrower, operator-configured tcp_flags), and dropped otherwise. None of this needs config beyond what the rule already has. BPS/PPS are summed across all IPs in each group and the top-N patterns by aggregate BPS are announced. A group's source IP is dropped only when more than one distinct IP shares that pattern (they're already indistinguishable from each other) or when the overall traffic looks spoofed (many sources, each contributing negligible average BPS); otherwise a group that traces back to exactly one IP keeps it. This keeps the announced set stable and broadly effective when an attack uses many rotating source IPs, while still pinning a rule to a specific attacker's address whenever one is genuinely identifiable.

Cross-tick spoofing detection: even when a single snapshot shows fewer unique sources than max_rules, flowwler tracks the visible source-IP set across successive refreshes (every 5 seconds) of the same active FlowSpec mitigation. Once at least 80% of a tick's sources are new versus the previous tick for 3 consecutive refreshes, the sources are considered confirmed-spoofed for the rest of that mitigation's lifetime, and consolidated mode is used from then on regardless of the per-tick source count. This is zero-config — there is no flowspec.spoofing (or similarly named) setting to tune it.

The mitigation audit: flowspec rules announced log line includes "consolidated": true when the pattern-grouping path is taken.

Redirect actions

action: redirect steers matched traffic to a scrubbing center rather than dropping it. Two redirect mechanisms are available and can be combined:

IP redirect (next_hop) - encodes an RT redirect IPv4 extended community (RFC 7674). Routers that implement RFC 7674 replace the BGP next-hop of matched flows with the specified IP, forwarding traffic to the scrubbing center. The receiving router must have a route to the next_hop IP.

VRF redirect (vrf) - encodes a Route Target redirect community (RFC 5575 §7). Routers that support this redirect the matched flows into the VRF whose import policy matches the Route Target. The VRF typically has a static or BGP route pointing to the scrubbing center.

When both are set, both extended communities are announced simultaneously. Behaviour is router-dependent, but most implementations apply the VRF redirect and use the VRF's routing table to reach the next-hop.

# IP redirect only - steer attack traffic to scrubbing center at 10.255.0.1
mitigation:
  type: flowspec
  flowspec:
    action: redirect
    next_hop: "10.255.0.1"

# VRF redirect only - steer into scrubbing VRF identified by RT 65001:100
mitigation:
  type: flowspec
  flowspec:
    action: redirect
    vrf: "65001:100"

# Combined - VRF redirect with explicit next-hop override
mitigation:
  type: flowspec
  flowspec:
    action: redirect
    vrf: "65001:100"
    next_hop: "10.255.0.1"

Community-based upstream propagation

Use community to control which FlowSpec routes your upstream receives. Set it on a higher escalation level and configure your upstream's BGP import policy to accept FlowSpec routes only when that community is present. Routes from lower levels (no community) stay local; routes from the higher level propagate upstream.

escalation:
  - level: 1
    condition:
      bps: 500m
    mitigation:
      type: flowspec
      flowspec:
        action: discard
        # no community — stays local, upstream does not see this

  - level: 2
    condition:
      bps: 2g
    mitigation:
      type: flowspec
      flowspec:
        action: discard
        community: "65000:100"   # upstream import policy accepts this

Both standard (AS:VALUE) and large (ASN:Local1:Local2, RFC 8092) formats are accepted.


RuleConfig

Per-protocol/port sub-rules within a group. Each rule maintains an independent escalation state per victim.

Field Type Description
name string Unique name within the group. Used in log output and Prometheus labels.
match MatchConfig Traffic matching criteria.
escalation []LevelConfig Escalation ladder for matching traffic.

MatchConfig

All fields are optional; unset fields match any value.

Field Type Description
protocol uint8 IP protocol number. 6 = TCP, 17 = UDP, 1 = ICMP. 0 = any.
dst_ports []uint16 Match if destination port is in this list.
src_ports []uint16 Match if source port is in this list.
icmp_types []uint16 Match if ICMP type is in this list (0-255). Only meaningful with protocol: 1 (ICMP). Example: [8] matches echo request.
icmp_codes []uint16 Match if ICMP code is in this list (0-255).
tcp_flags []string Match packets with these TCP flags set. Adds a FlowSpec type-9 component to announced rules. Valid flag names (case-insensitive): FIN, SYN, RST, PSH, ACK, URG, ECE, CWR. Example: [SYN] matches SYN-flood packets.
fragments bool When true, adds a FlowSpec type-12 is-fragment component to announced rules. Matches any fragmented IP packet.
pkt_len PktLenRange Optional. Packet-length range filter. See PktLenRange. Adds a FlowSpec type-10 packet-length NLRI component to announced rules. Also constrains FlowSpec source selection: sources are excluded when all their observed (or, absent native exporter data, bytes/packets-derived approximate) packet sizes fall outside the configured range.
subnet SubnetMatchConfig Optional. Enables carpet bomb detection — aggregate BPS/PPS across all victim IPs within a subnet prefix rather than per-IP. See SubnetMatchConfig.

protocol, dst_ports, src_ports, icmp_types, and icmp_codes also constrain FlowSpec source selection. When a rule with these criteria triggers a flowspec mitigation, only sources whose flows satisfy the match are eligible for the top-N ranking. A rule with match: protocol: 17 will only announce FlowSpec NLRIs for UDP sources - TCP sources for the same victim are excluded entirely. icmp_types/icmp_codes similarly exclude sources whose observed ICMP type/code isn't in the configured list. pkt_len further constrains selection: sources are excluded if their packet sizes — observed natively, or an approximation derived from bytes/packets when the exporter reports no native data (see PktLenRange) — fall entirely outside the configured range. tcp_flags and fragments affect the NLRI components added to each announced rule but do not filter source selection.

SubnetMatchConfig

Enables carpet bomb detection: BPS/PPS is aggregated across all distinct victim IPs within the same subnet bucket rather than evaluated per-IP. When subnet: is set in a rule's match: block, the rule triggers on the subnet aggregate, not on any individual victim.

Field Type Default Description
ipv4 uint8 0 Prefix length for IPv4 aggregation. 0 uses the group's own containing subnet size. Valid range: 8–32.
ipv6 uint8 0 Prefix length for IPv6 aggregation. 0 uses the group's own containing subnet size. Valid range: 8–128.
min_victims int 3 Minimum number of distinct victim IPs that must be hit within the subnet window before the rule can trigger. Prevents a single high-rate host from activating subnet-level mitigation when per-IP escalation is the correct response.

The StateKey for a carpet bomb state has VictimIP = "" and SubnetKey = "10.1.2.0/24" (the derived prefix CIDR). All standard escalation mechanics (levels, hold-down, notifications) apply unchanged.

rules:
  - name: "carpet-bomb"
    match:
      subnet:
        ipv4: 24
        ipv6: 48
        min_victims: 3
    escalation:
      - level: 1
        condition:
          bps: 20g
        mitigation:
          type: subnet-blackhole
          subnet-blackhole:
            community: "65535:666"
        escalate_after: 0s

PktLenRange

Field Type Description
min uint16 Minimum packet length in bytes. 0 = no lower bound.
max uint16 Maximum packet length in bytes. 0 = no upper bound.

At least one of min or max must be non-zero. When both are set, min must be ≤ max.

Source filtering: sources are excluded from FlowSpec rule selection if all their observed or derived packets fall outside the configured range — specifically if EffectivePktLen().max < min (all packets too small) or EffectivePktLen().min > max (all packets too large). A source with no data at all (neither native fields nor a computable BPS/PPS ratio) is always included.

FlowSpec NLRI: adds a type-10 packet-length NLRI component. A range (min and max both set) encodes GE and LE operators; a single bound encodes one operator only.

Configured range takes precedence over observed native sizes in the NLRI. The operator-configured bounds are used for all announced rules, overriding per-source native packet-size data, so the announced set remains stable across snapshot ticks.

A purely derived value (no native data, no configured range) is never attached to a live route's NLRI. EffectivePktLen's fallback can only ever produce a single point (min == max, an average, not a true range), and a single-point min/max pair encodes as an exact-match filter (RFC 8955 type-10 EQ operator) rather than a range. Announcing that on an auto-managed rule with no operator opt-in could make the router match only packets of that one computed byte length, missing most of a size-varying real attack — so a route with no native data and no configured pkt_len simply carries no type-10 component at all, exactly as if the exporter reported nothing. The derived value still does its job everywhere else: FlowSpec source selection/exclusion, detection matching, and rule suggestions (see below) — only the announced NLRI itself withholds it.

min/max are IP-layer (L3) lengths — the value RFC 8955's FlowSpec type-10 component matches on, and the same semantic as NetFlow/IPFIX's own MIN_PKT_LNGTH/MAX_PKT_LNGTH fields. Set them accordingly, not as Ethernet-frame sizes. Which exporters populate native per-source min_pkt_len/max_pkt_len, and what happens when they don't, varies:

Exporter / field Populates native pkt len? Layer Fallback when native data is absent
NetFlow v5 No — fixed 12-tuple record, no length field Approximate, derived from dOctets/dPkts (both L3, always present)
NetFlow v9 / IPFIX, fields 25/26 (MIN_PKT_LNGTH/MAX_PKT_LNGTH) Yes, when the exporter's template includes them L3 (IP total length, by IANA definition) Approximate, derived from IN_BYTES/IN_PKTS (fields 1/2, both L3, present in essentially every template) when 25/26 are absent
IPFIX, Junos inline flow-monitoring (fields 312/315, dataLinkFrameSize/dataLinkFrameSection) Yes — derived from the captured frame's own IP header L3 (converted from the L2 frame size) Not applicable — this path always has native data when 315 is present
sFlow v5 raw packet header No — frame_length is only used to compute Bytes, not per-source packet-length — (would be L2 if ever added) Approximate, derived from Bytes/Packets — but since sFlow's Bytes is itself frame_length (L2), the derived average inherits the same L2-vs-L3 overestimate (~18–22 bytes) as a raw frame size, unlike the NetFlow/IPFIX fallback rows above

Every fallback row above is a single-point approximation (min == max, an average bytes-per-packet, not a true observed range) computed from BPS/PPS — see RateUpdate.EffectivePktLen in internal/aggregator/keys.go. It never overrides native data. A live FlowSpec route's pkt_len_approximate is therefore always false/omitted — a route only ever carries native or operator-configured packet-length data. A FlowSpec suggestion's pkt_len_approximate can be true, since a suggestion is advisory only and an operator reviews it before ever applying it to a router (see REST API and Notifications).

See Flow input comparison for the same exporters compared on detection latency, not just packet-length support.


BitRate values

The bps field and rate_limit_bps accept either raw integers or human-readable suffixed strings:

Input Value
1000000000 1 000 000 000 bps (1 Gbps)
1g 1 000 000 000 bps (1 Gbps)
500m 500 000 000 bps (500 Mbps)
100k 100 000 bps (100 kbps)

Suffixes are case-insensitive (1G and 1g are equivalent).


Durations

All time.Duration fields accept Go duration strings: 10s, 5m, 1h, 15m30s, 0s.