Skip to content

Changelog

All notable changes to flowwler are documented here, newest first.


August 2026

ICMP/TCP-flags/fragments FlowSpec matching parity

  • TCP flags, is-fragment, and ICMP type/code matched by a FlowSpec rule are now also surfaced on chat/ticket notification payloads (tcp_flags_bitmask, fragments, icmp_types, icmp_codes) — previously these were only visible on GET /api/v1/flowspec/rules.
  • POST /api/v1/mitigations (type flowspec) now accepts match.tcp_flags, match.fragments, match.icmp_types, and match.icmp_codes, bringing manually-created rules to feature parity with config-driven ones.
  • API shape change: match.dst_port/match.src_port (scalar) is replaced by match.dst_ports/match.src_ports (array) on POST /api/v1/mitigations, matching the array shape already used by config-driven rules and GET responses.
  • Config-driven rules (match: in YAML) now support icmp_types/icmp_codes as a static filter, exposed on GET /api/v1/groups and GET /api/v1/groups/{group}/rules — previously ICMP type/code was only ever auto-derived from observed traffic, with no way for an operator to pin a rule to a specific ICMP type (e.g. echo request only).

Manual mitigations no longer clobbered by auto-escalation, or by DELETE-by-UUID

  • Fixed: a manually-created static blackhole/subnet-blackhole for a victim IP was silently overwritten in BGP the moment the escalation engine activated its own mitigation for the same victim, and then withdrawn along with it when the attack ended - deleting the operator's manual mitigation without any indication it had happened. The internal active-mitigation map is now scoped per owning group, so a manual activation and an auto-escalation activation for the same victim never collide.
  • Fixed: DELETE /api/v1/mitigations/uuid/{uuid} - previously effectively FlowSpec-only despite accepting any UUID - mishandled a manual blackhole/subnet-blackhole UUID (returned by the POST /api/v1/mitigations create response for those types): it either silently no-op'd while still reporting 200 "withdrawn", or, if an unrelated automatic mitigation happened to share the same victim IP and type, withdrew that one instead. It now correctly withdraws the intended manual mitigation. See DELETE /api/v1/mitigations/uuid/{uuid}.

July 2026

Configurable subnet-blackhole prefix, manual CIDR input

  • Manual POST /api/v1/mitigations now accepts an explicit CIDR in victim_ip for subnet-blackhole (e.g. "203.0.113.0/24", "2001:db8::/44"), announced verbatim instead of always deriving /24//48. blackhole/flowspec accept a CIDR too, silently masking host bits. Previously, any CIDR in victim_ip was rejected outright with 400 invalid victim_ip.
  • subnet-blackhole config gained ipv4_prefix_len/ipv6_prefix_len to change the default auto-derived prefix length (was hardcoded to /24//48) — useful for deployments that aggregate on /44 or /36 boundaries instead of /48.
  • GET /api/v1/mitigations now includes prefix for subnet-blackhole entries, showing the actually-announced CIDR.
  • DELETE /api/v1/mitigations/{type}/{ip} and GET /api/v1/mitigations/{ip} now accept a CIDR in {ip} as well as a bare IP, so a subnet-blackhole created with an explicit victim_ip CIDR can be looked up or withdrawn using that same CIDR — previously both 400'd with invalid ip. DELETE /api/v1/mitigations/ip/{ip} (bulk withdraw-all) still takes a bare IP only.
  • The explicit-CIDR override for manual subnet-blackhole requests is now bounds-checked (prefix length 8-32 for IPv4, 8-128 for IPv6) — previously an unbounded value like "0.0.0.0/0" would have been announced verbatim.

Config reload visibility and validation

  • GET /api/v1/status now includes last_reload (trigger, success/failure, error message, completion time) for the most recent config reload. Previously POST /api/v1/reload always returned 202 regardless of outcome, with no way to detect a failed reload outside the daemon log.
  • flowwler validate and reload-time validation now report every config error in one pass instead of stopping at the first.

FlowSpec matching: value-set ports, TCP flags, ICMP, and packet length

  • Destination/source ports are now matched as bounded value-sets (dst_ports/src_ports, arrays) instead of a single value or full wildcard, matched as an OR'd set (RFC 8955 §4.2.1.1). A single explicit port value of 0 is now a real match criterion rather than "no port configured."
  • API shape change: dst_port/src_port (scalar) is replaced by dst_ports/src_ports (array) on GET /api/v1/flowspec/rules, GET /api/v1/flowspec/suggest/{ip}, and notification payloads.
  • TCP flags and ICMP type/code are now automatic FlowSpec matching dimensions alongside protocol/port/packet-length, exposed as icmp_types/icmp_codes/tcp_flags_bitmask on GET /api/v1/flowspec/rules (not yet included in chat/ticket notification payloads). Is-fragment is still not exposed on either surface.
  • pkt_len matching now falls back to an average derived from bytes/packets when the exporter reports no native MIN_PKT_LNGTH/MAX_PKT_LNGTH fields (most NetFlow v9/IPFIX templates, MikroTik NetFlow v5, sFlow) — previously every flow matched regardless of size in that case.
  • Behavior change: a configured pkt_len range now actually excludes non-matching FlowSpec sources on such exporters, which it previously didn't despite the docs saying so; mitigation coverage may narrow for the first time.
  • The derived value is never attached to a live route's own announced filter (it's a single average, not a true range) but is used for detection matching and source selection, and shown on suggestions as pkt_len_approximate: true. New metric: flowwler_pktlen_derived_total.
  • Junos inline-monitoring packet-capture IPFIX export is now decoded, extracting real per-packet length and 5-tuple data instead of dropping these records.

FlowSpec source consolidation and spoofing detection

  • Consolidation now tracks the source-IP set across ticks: sources that mostly turn over across several consecutive refreshes are treated as confirmed-spoofed and consolidated, even if any single tick stays under max_rules.
  • Consolidation now searches all four match dimensions (protocol, dst port, src port, packet-length) and picks whichever combination covers the most BPS/PPS traffic, superseding the earlier packet-length-only preference and source-port-randomization-only detection logic.
  • Sources that stop sending are now excluded from live selection/consolidation after a couple seconds of silence, independent of their smoothed historical rate.
  • A source IP is now preserved on the announced rule when traffic traces back to exactly one IP, instead of always being dropped once the rule budget is exceeded; spoofed/reflected patterns (many low-volume sources) are unaffected.
  • Fixed FlowSpec suggestions (GET /api/v1/flowspec/suggest/{ip}, suggested_flowspec) consolidating against a fixed internal budget instead of the triggering level's max_rules; suggestions now match mitigation precision and include src_ip per rule when uncollapsed.
  • No new config for any of the above — behavior is automatic given existing max_rules/match settings.

Carpet-bomb detection fixes

  • Fixed false carpet-bomb classifications from incidental background traffic: a per-victim materiality floor (1% of the rule's escalation threshold) now excludes noise from unrelated hosts in the same subnet.
  • Fixed carpet-bomb rules with match criteria beyond subnet (protocol, ports, packet length, TCP flags, fragments) ignoring that criteria and counting a victim's total traffic instead of only matching traffic.
  • Victims are now clustered by protocol before counting toward min_victims, so unrelated attacks sharing a subnet (e.g. a UDP flood and a separate TCP flood) are tracked as independent incidents instead of one false combined classification.

June 2026

Anonymous usage telemetry

flowwler now sends an anonymous ping to statistics.flowwler.net on startup and every 6 hours. The payload includes the version, a randomly generated instance UUID, enabled feature flags, and aggregate attack counts. No IP addresses, victim data, or operator-identifying information are included. To opt out, set telemetry.disabled: true in your config file.

FlowSpec BGP community support

FlowSpec routes now accept an optional community field in both standard (AS:VALUE, RFC 1997) and large (ASN:Local1:Local2, RFC 8092) formats. Configure per-level via mitigation.flowspec.community in YAML, or pass "community" in the POST /api/v1/mitigations body for manual announcements. When set at a higher escalation level, your upstream's BGP policy can use the community to control whether FlowSpec routes are propagated further.

PCAP capture

  • Rule-level attacks now captured: PCAP capture previously only started for group-level escalations. Named rule triggers (e.g. udp-amplification, syn-flood) now also start a capture. When a group-level and one or more rule-level attacks are active at the same time for the same victim, all traffic is written to a single shared file. The file stays open until every concurrent attack session has cleared.

Notifications

  • FlowSpec action in notifications. All notification backends now show the active FlowSpec action (discard, rate-limit, or redirect) alongside the mitigation type — e.g. "flowspec (discard)" instead of "flowspec". The flowspec_action field is present in the webhook JSON payload on all event types when the mitigation type is flowspec.

Bug fixes

  • Fixed FlowSpec port matching: Destination port rules were incorrectly matching flows on either source or destination port instead of destination only, and vice versa for source port rules. A rule targeting destination port 53 would match any flow with port 53 on either side.
  • Fixed FlowSpec rules being rejected by JunOS: When a rule contained both TCP flags and a packet-length range, JunOS silently discarded the route due to incorrect field ordering.
  • Fixed packet-length range encoding: Packet-length values above 255 were silently truncated, producing wrong values on the wire. A min_pkt_len: 512 would be treated as 0; a max_pkt_len: 1500 as 220.
  • Fixed manual withdrawal of automated FlowSpec routes: Deleting an automated FlowSpec mitigation via the API returned success but left the BGP route announced when the attack was already in hold-down. The route is now correctly withdrawn.
  • Fixed rule-triggered PCAP captures not appearing in /api/v1/pcap/attacks: In certain situations, captures started by rule-level attacks were not linked to the attack record and therefore did not appear in the listing.
  • Fixed rule-level escalation states stuck in Active after attack traffic stopped: When a named rule stopped matching but the victim still had other traffic, the escalation state remained active at 0 bps/0 pps indefinitely. The hold-down transition now fires correctly. The Force Clear action was also broken for these stuck states and is now fixed.

May 2026

IRR bare ASN support

  • irr.as_set now accepts bare ASNs: Previously irr.as_set required an AS-SET name (e.g. AS-EXAMPLE). It now also accepts a bare ASN (e.g. AS64496), which fetches all routes originated by that AS directly. Useful when a peer does not maintain a formal AS-SET in the IRR.

Performance

  • Escalation engine no longer stalls under large DDoS events: Processing large attack events could take several seconds during high-volume attacks, causing delays and log warnings about a full queue. Processing speed has been significantly improved.

Bug fixes

  • Fixed abrupt HTTP listener close on shutdown: The daemon now waits up to 5 seconds for active API connections to finish before shutting down, instead of closing them immediately.
  • Fixed a resource leak in NetFlow and sFlow listeners on hot-reload: Background tasks started by the listeners were not stopped during hot-reload, accumulating over time.
  • Fixed a crash that could occur when router data was accessed concurrently: Concurrent access to router sampling rate data could cause crashes or data corruption under load.

April 2026

Source IP whitelists

  • Per-scope whitelists: Source IPs matching a whitelist entry are excluded from attack-rate calculations and never selected as FlowSpec mitigation targets. Configure under defaults.whitelist (global) and groups[*].whitelist (per-group override). Each scope supports the same four source types as the prefix import system: static, irr, netbox, and url. Whitelists are resolved on startup and after every POST /api/v1/reload/sources call.

Carpet bomb detection

  • Subnet-level escalation (match.subnet): Rules can now aggregate traffic across all victim IPs within a subnet rather than evaluating each IP individually. Set match.subnet.ipv4 (e.g. 24) and/or match.subnet.ipv6 (e.g. 48) to enable. Configuration uses the same escalation list as per-IP rules; any mitigation type is supported including subnet-blackhole. min_victims (default 3) prevents a single high-rate victim from triggering subnet mitigation; the rule only fires when traffic is spread across at least that many distinct IPs. Subnet states appear in GET /api/v1/escalations with a non-empty subnet_key field; victim_ip is empty for these states.

PCAP capture

  • Attack traffic capture: flowwler can now write a libpcap-format file for each detected attack. Enable with pcap: { enabled: true } in the top-level config. One file is created per attack session and closed when the attack moves to hold-down or clears. Files are stored in a configurable directory (dir, default /var/lib/flowwler/pcaps) and pruned on the same retention schedule as attack history. Filename format: {date}T{time}_{victimIP}_{attackID}.pcap. Since flowwler receives sampled NetFlow/sFlow rather than raw packets, captures contain synthesized headers derived from flow metadata and carry no payload. See Configuration: pcap.
  • PCAP filename stored in attack history: When a capture opens, the filename is written to the attack history. Attacks with a capture appear in GET /api/v1/pcap/attacks and include a pcap_file field in attack history responses.
  • Manual PCAP captures: Operators can start an ad-hoc capture for any victim IP without an active attack using POST /api/v1/pcap/manual. The capture runs until the requested duration expires (default 60 s, max 5 min) or is stopped via DELETE /api/v1/pcap/manual/{id}. Manual captures appear in GET /api/v1/pcap/manual and are downloadable while active and after completion.

FlowSpec enhancements

  • Redirect action: FlowSpec rules now support action: redirect in addition to discard and rate-limit. Set next_hop to redirect traffic to a scrubbing center, vrf for a Route Target redirect into a VRF (in "ASN:LocalAdmin" notation), or both simultaneously.
  • FlowSpec rule suggestions: State-transition notifications now include a suggested_flowspec object when the active mitigation type is not FlowSpec. Suggestions are derived from live flow data and ranked by whichever metric (BPS or PPS) exceeded its threshold by a larger margin. The dominant pattern must cover at least 20% of total attack traffic for a suggestion to be emitted.
  • Spoofed-source detection: Suggestions flag spoofed_sources: true when many unique source IPs are each contributing a small amount of traffic, indicating randomised source flooding where per-source FlowSpec rules would be ineffective.
  • Manual POST /api/v1/mitigations FlowSpec responses now include suggested_flowspec when live traffic data is available.

Notifications

  • Zammad backend: Opens, updates, and closes tickets on attack events. Required fields: url, api_token, group, customer. See Notifications: zammad.
  • Optional tags field for Jira and Zammad targets (default: [flowwler, ddos]).
  • Dynamic ticket priority: normal on creation, updated to high on escalation.
  • Human-readable event labels across all backends: Attack Detected, Attack Escalated, Attack Subsiding, Attack Cleared.
  • New POST /api/v1/notifications/targets/{name}/test sends a synthetic test event to a named target, bypassing event-type filters.

Packet-length match condition

  • match.pkt_len: Optional inclusive packet-length range (bytes) for named rules. When the rule triggers a FlowSpec mitigation, the range is included in the FlowSpec rule. Attacker source selection is also filtered to flows whose reported packet size falls within the range. Fields: min (0 = no lower bound), max (0 = no upper bound).

Config change: hold_time moved to per-level config

  • hold_time is now per escalation level: Set it directly under each level: block, the same location as escalate_after. Falls back to defaults.hold_time when absent. The old location under mitigation.flowspec is removed; update any config that set hold_time there.

REST API

  • GET /api/v1/ip/{ip}: Returns the matching group, most-specific matched subnet, all subnets, and effective escalation config for any IP.
  • GET /api/v1/flowspec/suggest/{ip}: Returns live FlowSpec rule suggestions for a victim IP. Accepts ?rank_by=pps to rank by packets instead of bits. Returns 404 when no traffic data is available.
  • POST /api/v1/escalations/{group}/{ip}/clear: Force-clears all escalation states for a victim immediately, withdrawing active BGP mitigations and firing cleared notifications. Skips the hold-down timer entirely.
  • GET /api/v1/attacks/stats: Aggregate statistics across all stored attacks: total and active count, mean duration, all-time peak BPS/PPS, and top-10 victims and groups by attack count.
  • GET /api/v1/attacks now accepts ?offset=N for cursor-style pagination alongside limit.
  • DELETE /api/v1/mitigations/uuid/{uuid} now works for auto-managed FlowSpec rules, not just manually created ones. Withdraws all rules for the affected victim and transitions the escalation state to hold-down.
  • POST /api/v1/reload/sources: Re-fetches all external prefix sources (IRR, NetBox, URL subnets, whitelists) without touching BGP peers, escalation config, or notification targets. Returns HTTP 202 immediately. Useful when upstream routing data has changed and you do not want to wait for the 12-hour background refresh.
  • GET /api/v1/whitelist: Returns all configured whitelist scopes with prefix counts. Returns HTTP 501 when no whitelist is configured.
  • GET /api/v1/pcap/captures: Returns all currently open per-attack PCAP capture sessions. Returns HTTP 501 when PCAP is disabled.
  • GET /api/v1/pcap/attacks: Lists attacks that have an associated PCAP capture, with file metadata and a decoded preview of the first 10 packets. Supports the same group, victim_ip, active, limit, and offset query parameters as GET /api/v1/attacks. Replaces GET /api/v1/pcap/files.
  • GET /api/v1/pcap/attacks/{attack_id}: Downloads the PCAP file for a specific attack. Returns 404 when no capture exists or the file is no longer on disk. Replaces GET /api/v1/pcap/files/{name}.
  • POST /api/v1/pcap/manual: Starts an ad-hoc PCAP capture. Body: {"victim_ip": "...", "duration": "60s"}. Returns HTTP 201 with capture metadata. Returns HTTP 409 when a capture for that IP is already active.
  • GET /api/v1/pcap/manual: Lists active manual captures.
  • GET /api/v1/pcap/manual/{id}: Downloads the PCAP file for a manual capture (active or completed).
  • DELETE /api/v1/pcap/manual/{id}: Stops a manual capture early; the file is kept on disk.
  • Removed GET /api/v1/pcap/files and GET /api/v1/pcap/files/{name}: Replaced by the attack-centric endpoints above.
  • GET /api/v1/config response now includes the complete configuration. New fields: pcap, storage, defaults.flowspec_max_rules, defaults.escalation_levels, defaults.whitelist, groups[*].rules, and groups[*].whitelist.
  • OpenAPI spec: assets/openapi.yaml covers all 25 REST endpoints with full request/response schemas. assets/swagger-ui.html is a self-contained, browser-openable UI.
  • GET /api/v1/mitigations now includes group_name on every entry ("manual" for API-created mitigations).
  • GET /api/v1/groups, /groups/{group}, and /groups/{group}/rules active victim entries now include attack_id.
  • GET /api/v1/groups/{group}/rules rule objects now include pkt_len, subnet, tcp_flags, and fragments match criteria.
  • GET /api/v1/escalations response objects now include subnet_key.
  • GET /api/v1/groups and GET /api/v1/groups/{group} exclude subnet-level carpet bomb states from active_victims.
  • GET /api/v1/flowspec/rules and GET /api/v1/mitigations now expose redirect_next_hop and redirect_vrf on FlowSpec rules with a redirect action.
  • GET /api/v1/prefix-sources now covers IRR, NetBox, and URL subnet sources; source field values: irr, netbox, url, url-whitelist.

Prometheus metrics

  • flowwler_urlsubnets_routes (Gauge, labels group, ip_version): Prefixes resolved from URL subnet imports, by IP version.
  • flowwler_urlsubnets_resolve_duration_seconds (Gauge, label group): Fetch time for URL sources during the last resolution pass.
  • flowwler_urlsubnets_last_refresh_timestamp_seconds (Gauge, label group): Unix timestamp of the last successful URL fetch; use for staleness alerting.
  • flowwler_urlsubnets_list_count (Gauge, label group): Number of URL import sources configured for the group.
  • flowwler_pcap_captures_active (Gauge): Number of currently open PCAP capture files.
  • flowwler_pcap_files_created_total (Counter): Total PCAP files opened since startup.
  • flowwler_pcap_records_written_total (Counter): Total flow records written to PCAP files.
  • flowwler_pcap_tap_dropped_total (Counter): Flow records dropped when the internal buffer is full.
  • flowwler_whitelist_prefixes (Gauge, label scope): Number of prefixes loaded in each whitelist scope; updated on startup and after every source reload.
  • Attack and mitigation metrics for carpet bomb states now use the subnet CIDR (e.g. 10.1.2.0/24) as the victim_ip label value, making subnet states unambiguously distinguishable in PromQL.

Bug fixes

  • Fixed notification flapping when traffic oscillates near threshold: The transition to hold-down now requires the rate to stay below threshold continuously for 15 seconds before firing. Previously, short dips below threshold immediately triggered hold-down and the next spike re-triggered an attack event, causing a notification storm across all backends.
  • Fixed hold-down never firing for high-volume attacks: Under certain high-volume conditions, attacks could remain stuck in Active state and never transition to hold-down.
  • Fixed Jira and Zammad creating duplicate tickets on hold-down to active re-activation: Both backends now append a comment to the existing ticket instead of opening a new one.
  • Fixed mitigations being re-announced after manual deletion via the API.
  • Fixed manual mitigation started_at recording as Unix epoch 1970.
  • Fixed stale metric entries not being cleaned up when a group is removed during hot-reload.

March 2026

Initial release (v1.1.0-v1.1.7), before the YY.MM.XX version scheme was adopted.

Storage

  • Persistent attack history. storage config section (path, retention_days). GET /api/v1/attacks and GET /api/v1/attacks/{attack_id} endpoints.

REST API

  • Full REST API: /status, /routers, /prefix-sources, /reload, /config, group detail, subnets, per-IP escalation/mitigation filters, notification target health.
  • Optional REST API auth (API key and HTTP Basic Auth). flowwler hash-password subcommand.

Escalation and mitigation

  • Alert-only escalation levels (mitigation.type: ""). Outbound traffic visibility (direction="outbound" metrics).
  • FlowSpec source selection filtered by the triggering rule's protocol and ports. FlowSpec rules refresh every 5 seconds during active mitigation.

Notifications

  • Jira and Slack notification backends.
  • attack_started_at added to all notification payloads.

Metrics

  • Per-rule and per-victim Prometheus metrics; stale series cleanup on group removal.
  • Per-rule attack level metrics independently visible in Prometheus; effectiveness gauges kept while any mitigation for the same victim remains active.

Bug fixes

  • Fixed a ~10% traffic undercount under sustained high-volume attacks.
  • Fixed multiple rules for the same victim silently overwriting each other's BGP paths.
  • Fixed a mitigation start timestamp metric being deleted prematurely when another rule for the same victim was still active.