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 onGET /api/v1/flowspec/rules. POST /api/v1/mitigations(typeflowspec) now acceptsmatch.tcp_flags,match.fragments,match.icmp_types, andmatch.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 bymatch.dst_ports/match.src_ports(array) onPOST /api/v1/mitigations, matching the array shape already used by config-driven rules andGETresponses. - Config-driven rules (
match:in YAML) now supporticmp_types/icmp_codesas a static filter, exposed onGET /api/v1/groupsandGET /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-blackholefor 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 manualblackhole/subnet-blackholeUUID (returned by thePOST /api/v1/mitigationscreate response for those types): it either silently no-op'd while still reporting200 "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/mitigationsnow accepts an explicit CIDR invictim_ipforsubnet-blackhole(e.g."203.0.113.0/24","2001:db8::/44"), announced verbatim instead of always deriving/24//48.blackhole/flowspecaccept a CIDR too, silently masking host bits. Previously, any CIDR invictim_ipwas rejected outright with400 invalid victim_ip. subnet-blackholeconfig gainedipv4_prefix_len/ipv6_prefix_lento change the default auto-derived prefix length (was hardcoded to/24//48) — useful for deployments that aggregate on/44or/36boundaries instead of/48.GET /api/v1/mitigationsnow includesprefixforsubnet-blackholeentries, showing the actually-announced CIDR.DELETE /api/v1/mitigations/{type}/{ip}andGET /api/v1/mitigations/{ip}now accept a CIDR in{ip}as well as a bare IP, so asubnet-blackholecreated with an explicitvictim_ipCIDR can be looked up or withdrawn using that same CIDR — previously both 400'd withinvalid ip.DELETE /api/v1/mitigations/ip/{ip}(bulk withdraw-all) still takes a bare IP only.- The explicit-CIDR override for manual
subnet-blackholerequests 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/statusnow includeslast_reload(trigger, success/failure, error message, completion time) for the most recent config reload. PreviouslyPOST /api/v1/reloadalways returned 202 regardless of outcome, with no way to detect a failed reload outside the daemon log.flowwler validateand 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 of0is now a real match criterion rather than "no port configured." - API shape change:
dst_port/src_port(scalar) is replaced bydst_ports/src_ports(array) onGET /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_bitmaskonGET /api/v1/flowspec/rules(not yet included in chat/ticket notification payloads). Is-fragment is still not exposed on either surface. pkt_lenmatching now falls back to an average derived from bytes/packets when the exporter reports no nativeMIN_PKT_LNGTH/MAX_PKT_LNGTHfields (most NetFlow v9/IPFIX templates, MikroTik NetFlow v5, sFlow) — previously every flow matched regardless of size in that case.- Behavior change: a configured
pkt_lenrange 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'smax_rules; suggestions now match mitigation precision and includesrc_ipper rule when uncollapsed. - No new config for any of the above — behavior is automatic given existing
max_rules/matchsettings.
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, orredirect) alongside the mitigation type — e.g. "flowspec (discard)" instead of "flowspec". Theflowspec_actionfield is present in the webhook JSON payload on all event types when the mitigation type isflowspec.
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: 512would be treated as 0; amax_pkt_len: 1500as 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_setnow accepts bare ASNs: Previouslyirr.as_setrequired 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) andgroups[*].whitelist(per-group override). Each scope supports the same four source types as the prefix import system:static,irr,netbox, andurl. Whitelists are resolved on startup and after everyPOST /api/v1/reload/sourcescall.
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. Setmatch.subnet.ipv4(e.g.24) and/ormatch.subnet.ipv6(e.g.48) to enable. Configuration uses the same escalation list as per-IP rules; any mitigation type is supported includingsubnet-blackhole.min_victims(default3) 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 inGET /api/v1/escalationswith a non-emptysubnet_keyfield;victim_ipis 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/attacksand include apcap_filefield 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 viaDELETE /api/v1/pcap/manual/{id}. Manual captures appear inGET /api/v1/pcap/manualand are downloadable while active and after completion.
FlowSpec enhancements
- Redirect action: FlowSpec rules now support
action: redirectin addition todiscardandrate-limit. Setnext_hopto redirect traffic to a scrubbing center,vrffor a Route Target redirect into a VRF (in"ASN:LocalAdmin"notation), or both simultaneously. - FlowSpec rule suggestions: State-transition notifications now include a
suggested_flowspecobject 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: truewhen 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/mitigationsFlowSpec responses now includesuggested_flowspecwhen 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
tagsfield 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}/testsends 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_timeis now per escalation level: Set it directly under eachlevel:block, the same location asescalate_after. Falls back todefaults.hold_timewhen absent. The old location undermitigation.flowspecis removed; update any config that sethold_timethere.
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=ppsto 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/attacksnow accepts?offset=Nfor cursor-style pagination alongsidelimit.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 samegroup,victim_ip,active,limit, andoffsetquery parameters asGET /api/v1/attacks. ReplacesGET /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. ReplacesGET /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/filesandGET /api/v1/pcap/files/{name}: Replaced by the attack-centric endpoints above. GET /api/v1/configresponse now includes the complete configuration. New fields:pcap,storage,defaults.flowspec_max_rules,defaults.escalation_levels,defaults.whitelist,groups[*].rules, andgroups[*].whitelist.- OpenAPI spec:
assets/openapi.yamlcovers all 25 REST endpoints with full request/response schemas.assets/swagger-ui.htmlis a self-contained, browser-openable UI. GET /api/v1/mitigationsnow includesgroup_nameon every entry ("manual"for API-created mitigations).GET /api/v1/groups,/groups/{group}, and/groups/{group}/rulesactive victim entries now includeattack_id.GET /api/v1/groups/{group}/rulesrule objects now includepkt_len,subnet,tcp_flags, andfragmentsmatch criteria.GET /api/v1/escalationsresponse objects now includesubnet_key.GET /api/v1/groupsandGET /api/v1/groups/{group}exclude subnet-level carpet bomb states fromactive_victims.GET /api/v1/flowspec/rulesandGET /api/v1/mitigationsnow exposeredirect_next_hopandredirect_vrfon FlowSpec rules with a redirect action.GET /api/v1/prefix-sourcesnow covers IRR, NetBox, and URL subnet sources;sourcefield values:irr,netbox,url,url-whitelist.
Prometheus metrics
flowwler_urlsubnets_routes(Gauge, labelsgroup,ip_version): Prefixes resolved from URL subnet imports, by IP version.flowwler_urlsubnets_resolve_duration_seconds(Gauge, labelgroup): Fetch time for URL sources during the last resolution pass.flowwler_urlsubnets_last_refresh_timestamp_seconds(Gauge, labelgroup): Unix timestamp of the last successful URL fetch; use for staleness alerting.flowwler_urlsubnets_list_count(Gauge, labelgroup): 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, labelscope): 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 thevictim_iplabel 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_atrecording 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.
storageconfig section (path,retention_days).GET /api/v1/attacksandGET /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-passwordsubcommand.
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_atadded 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.