Skip to content

Alerter & Telemetry

Overview

OpenShield can dispatch real-time notifications to Discord via webhook when security events occur. The alerter runs in the collector goroutine, processing events from the BPF ring buffer and formatting them as Discord embeds.

Configuration

yaml
alerter:
  enabled: false
  webhook_url: ""
  events: []                # empty = all events
  # events: [attack_start, attack_update, attack_end, ban_triggered, panic_mode]
  graph_enabled: true       # attach labeled traffic graph to attack-end alerts
  show_banned_ips: false    # inline banned IPs in ban alerts (default off: categorized .txt attached instead)
  geo_breakdown: true       # continent/country share of banned IPs (needs GeoIP db)
  attack_updates: true      # progress embeds while an attack is ongoing

telemetry:
  poll_interval: 1
  event_rate_limit: 100
  top_offenders_count: 20
  log_level: info
  snapshot_interval: 1

Alerter Fields

FieldTypeDefaultDescription
alerter.enabledboolfalseMaster toggle for webhook alerts
alerter.webhook_urlstring""Discord webhook URL (must be full https://discord.com/api/webhooks/...)
alerter.events[]string[]Event filter — if not empty, only listed events trigger alerts
alerter.graph_enabledbooltrueAttach the attack traffic graph (incoming vs passed PPS + BPS, labeled axes) to attack-end alerts
alerter.show_banned_ipsboolfalseList banned IPs inline in ban alerts; default off — a categorized .txt (grouped by reason) is attached instead
alerter.geo_breakdownbooltrueContinent/country breakdown (% share + IP counts) in ban-batch alerts; requires the GeoIP database
alerter.attack_updatesbooltrueSend progress embeds while an attack is ongoing

Rate Limiting

All webhook traffic flows through one paced dispatch queue (depth 64). A single dispatcher goroutine delivers messages with a minimum 1.2s spacing, so a burst of events during an attack can never trip Discord's webhook rate limit:

  • HTTP 429 honored: on a rate-limit response the dispatcher sleeps the server-provided Retry-After window and retries once before dropping.
  • Overflow drops, never blocks: when the queue is full, new alerts are dropped and counted — the firewall never blocks on Discord.
  • Coalesced warnings: alerter failures (429s, network errors) reach the log feed through a rate-limited sink (max one per 30s, with "+N similar suppressed"). They are never written to the terminal.
  • Event batching: all per-IP event types (bans, new-source floods, threshold violations, subnet bans, anomaly detections...) accumulate per type and flush every 5 seconds as ONE merged embed per type, with the IP list as a categorized .txt — never one message per IP.

Attack Progress Updates

While an attack is ongoing, progress embeds are dispatched at 30s, 60s, 120s, 240s, 480s, 900s after the start, then every 30 minutes (hard cap) for multi-hour attacks. Each update carries current/peak/avg rates, growth vs the attack's first seconds and vs the previous update, bans, new sources/s, drop rate, and the next update's ETA as a Discord-localized timestamp. Disable with alerter.attack_updates: false.

Attack-End Report Semantics

Since v2.15.0 the attack-end report arrives as two messages:

  1. Attack #N Ended — the operational summary: type/family, timelines, mitigation time, traffic analysis (peak/avg/p95), the traffic graph, and the forensics zip (attached when under 10 MB, otherwise a pointer to the on-disk path).
  2. Attack #N — Sources & Targets — who and what was hit: the targeted IPs on your host (broadcast-looking .255/.0 entries are filtered out), the targeted ports (rendered as mix when more than 10 distinct hot ports were hit), the top attacking countries, and the offender list as a .txt grouped by ban reason (pps_exceeded, syn_pps_exceeded, …). When GeoIP is disabled the country section says so explicitly instead of vanishing. If the offender file can't be attached, the top rows are shown inline — an embed never claims an attachment that didn't ship.

The attack-end embed reports two honest time figures:

  • Duration — how long attack traffic was actually elevated (start → traffic normalized, i.e. 2 sustained samples below the recovery threshold). State Cleared shows the full state-machine duration including the recovery window when it ran longer.
  • Mitigation Time — "blocked in Xs": when the passed rate (traffic getting through mitigation) collapsed. A flood whose sender keeps transmitting after being banned shows a high incoming line with a flat passed line — the graph's green passed line makes this visible.

Event Types

Event KeyTrigger ConditionDiscord Color
attack_startBaseline learner detects attack state (traffic > threshold × spike%)🔴 Red (#FF0000)
attack_updateOngoing-attack progress (30s, growing intervals, 30min cap)🟠 Amber (#E67E22)
attack_endAttack state clears after recovery period🟢 Green (#00FF00)
ban_triggeredIP banned (suspicion score reached threshold)🟠 Orange (#FF8C00)
panic_modePanic circuit breaker activates (per-CPU PPS > panic_pps_rate)🟣 Magenta (#FF00FF)
new_source_floodNew unique IP rate exceeds new_source_limit🟡 Yellow (#FFFF00)
threshold_violationIP exceeded threshold (PPS/BPS/TCP/UDP/ICMP/SYN)⚪ Grey
subnet_banAuto subnet ban triggered (escalation)🔵 Blue
entropy_spoofEntropy-based spoofing detected🟦 Cyan
ttl_anomalyTTL deviation detected🟦 Cyan
packet_size_anomalyAvg packet size outside [min, max] range🟦 Cyan
syn_fin_floodSYN:FIN ratio exceeded threshold🔴 Red
conn_rate_floodConnection rate limit exceeded🟡 Yellow
cluster_suspiciousBehavior cluster crossed the suspicious threshold⚪ Grey
cluster_maliciousBehavior cluster crossed the malicious threshold🔴 Red

Event Rate Limiting (Ring Buffer)

Separate from the webhook rate limiter, the BPF side also enforces an event emission cap:

telemetry.event_rate_limit: 100   # max events/s emitted to ring buffer

This is enforced in the XDP program itself — when the per-second event counter exceeds event_rate_limit, further event emissions to the ring buffer are silently suppressed. This protects both kernel memory and the userspace collector from being overwhelmed during an attack.

Telemetry Fields

FieldDefaultDescription
poll_interval1Seconds between collector reads of global_stats_map
event_rate_limit100Max events/s emitted by BPF to ring buffer
top_offenders_count20Top N IPs displayed in TUI / logged
log_level"info"debug, info, warn, or error
snapshot_interval1Seconds between TUI stat snapshots