Add error/warn-only Vector filter + 5 new pipeline rules + repetition alert

Analyzed a real 1GB accel-ppp log (2026-07-23): only 2,819 of its lines
were error:/warn:, and one pattern - "can't determine router address" -
repeated 2,746 times over ~4 hours for two specific subscriber interfaces
before self-resolving, completely unalerted since no alert covered it.

- New pipeline rules for 3 previously-unclassified real message types
  (radius:dm_coa session not found, mac change detected, dhcpv4 short
  packet) plus a text-based fallback pair (accelppp_unclassified_error/
  warn) for anything not yet specifically classified - needed because
  Vector-shipped accel-ppp lines have no real syslog PRI header, so the
  numeric-severity generic_critical_severity rule never fires for this
  source.
- New alert7: group by gl2_remote_ip + event_type, fires on >5 occurrences
  in 5 minutes - low enough to have caught the real incident within its
  first cycle, high enough to tolerate a single transient warning.
- vector-accel-ppp-setup.md gained a "keep only error/warn" filter option
  (Step 2b), explicitly documented as a deliberate tradeoff: it also drops
  RADIUS accounting, so the Servers & Sessions dashboard and session
  correlation go empty for any server that applies it. Both the
  with-filter and without-filter full configs are included so the choice
  is per-server, not global.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
byrsapty 2026-07-23 16:20:13 +03:00
parent 624db7510f
commit 31258eee01
10 changed files with 182 additions and 30 deletions

View file

@ -453,6 +453,7 @@ parsed and searchable but never page anyone):
| Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (over temperature, fan, power supply, etc.) - confirmed live against a real `VC.MYRONIVKA` chassis | High | | Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (over temperature, fan, power supply, etc.) - confirmed live against a real `VC.MYRONIVKA` chassis | High |
| Abnormal message volume from one server | A single server in the Servers stream sends more than 150,000 messages in a 10-minute window - see "Message-volume (flood) alerts" below | Medium | | Abnormal message volume from one server | A single server in the Servers stream sends more than 150,000 messages in a 10-minute window - see "Message-volume (flood) alerts" below | Medium |
| Abnormal syslog volume from network equipment | A single device in the Network Equipment stream sends more than 500 messages in a 5-minute window - see "Message-volume (flood) alerts" below | Medium | | Abnormal syslog volume from network equipment | A single device in the Network Equipment stream sends more than 500 messages in a 5-minute window - see "Message-volume (flood) alerts" below | Medium |
| Repeated accel-ppp error/warning from one server | The same `event_type` (from `error:`/`warn:` accel-ppp lines) recurs more than 5 times in 5 minutes from one server - catches a stuck session or ongoing condition rather than a single transient warning. Threshold confirmed live against a real incident: `can't determine router address` repeating 2,746 times over ~4 hours for two specific interfaces | Medium |
That third one is the "universal network equipment problem" catch-all: it That third one is the "universal network equipment problem" catch-all: it
doesn't depend on knowing any vendor's specific message format, just the doesn't depend on knowing any vendor's specific message format, just the

View file

@ -469,6 +469,7 @@ Graylog відхиляє його з помилкою Jackson-поліморфі
| Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (перегрів, вентилятор, блок живлення тощо) — перевірено наживо на реальному шасі `VC.MYRONIVKA` | High | | Juniper chassis hardware alarm | `CHASSISD_SNMP_TRAP`/`CHASSISD_SNMP_TRAP6` (перегрів, вентилятор, блок живлення тощо) — перевірено наживо на реальному шасі `VC.MYRONIVKA` | High |
| Аномальний обсяг повідомлень від одного сервера | Один сервер у стрімі Servers шле понад 150 000 повідомлень за 10-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium | | Аномальний обсяг повідомлень від одного сервера | Один сервер у стрімі Servers шле понад 150 000 повідомлень за 10-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
| Аномальний обсяг syslog від мережевого обладнання | Один пристрій у стрімі Network Equipment шле понад 500 повідомлень за 5-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium | | Аномальний обсяг syslog від мережевого обладнання | Один пристрій у стрімі Network Equipment шле понад 500 повідомлень за 5-хвилинне вікно — див. "Алерти на обсяг (flood)" нижче | Medium |
| Повторюваний error/warning accel-ppp з одного сервера | Той самий `event_type` (з рядків `error:`/`warn:` accel-ppp) повторюється понад 5 разів за 5 хвилин з одного сервера — ловить завислу сесію чи триваючий стан, а не разове попередження. Поріг підтверджено наживо на реальному інциденті: `can't determine router address` повторився 2 746 разів за ~4 години для двох конкретних інтерфейсів | Medium |
Третій алерт і є тим самим "універсальним" покриттям проблем мережевого Третій алерт і є тим самим "універсальним" покриттям проблем мережевого
обладнання: він не залежить від знання формату повідомлень конкретного обладнання: він не залежить від знання формату повідомлень конкретного

View file

@ -0,0 +1,21 @@
{
"title": "WARNING: repeated accel-ppp error/warning from one server",
"description": "The same category of accel-ppp error/warning is repeating from one server - a single occurrence can be transient (a RADIUS retry, a one-off MAC change), but sustained repetition usually means a stuck session or an ongoing condition. Confirmed live on 2026-07-23 against a real 1GB accel-ppp log: 'can't determine router address' repeated 2,746 times over ~4 hours for two specific subscriber interfaces before self-resolving, with no alert firing at the time since this alert didn't exist yet. Threshold (>5 in 5 minutes) is set low enough to have caught that incident within its first cycle, while still tolerating an occasional single warning.",
"priority": 2,
"alert": true,
"config": {
"type": "aggregation-v1",
"query": "vendor:accel-ppp",
"streams": ["__SERVERS_STREAM_ID__"],
"group_by": ["gl2_remote_ip", "event_type"],
"series": [{"type": "count", "id": "count-", "field": null}],
"conditions": {"expression": {"expr": ">", "left": {"expr": "number-ref", "ref": "count-"}, "right": {"expr": "number", "value": 5.0}}},
"search_within_ms": 300000,
"execute_every_ms": 300000,
"event_limit": 50
},
"field_spec": {},
"key_spec": [],
"notification_settings": {"grace_period_ms": 900000, "backlog_size": 5},
"notifications": [{"notification_id": "__DISCORD_NOTIFICATION_ID__"}]
}

View file

@ -1,5 +1,5 @@
{ {
"title": "Servers Parsing", "title": "Servers Parsing",
"description": "Parses accel-ppp/RADIUS/conntrack syslog", "description": "Parses accel-ppp/RADIUS/conntrack syslog",
"source": "pipeline \"Servers Parsing\"\nstage 0 match either\n rule \"accelppp_router_address_error\";\n rule \"accelppp_radius_accounting\";\n rule \"accelppp_radius_access_request\";\n rule \"accelppp_radius_server_down\";\n rule \"accelppp_no_radius_servers\";\n rule \"accelppp_auth_failed\";\n rule \"conntrack_table_full\";\n rule \"freeradius_login_ok\";\n rule \"freeradius_login_incorrect\";\n rule \"generic_critical_severity\";\n rule \"accelppp_interface_tag\";\nend" "source": "pipeline \"Servers Parsing\"\nstage 0 match either\n rule \"accelppp_router_address_error\";\n rule \"accelppp_radius_accounting\";\n rule \"accelppp_radius_access_request\";\n rule \"accelppp_radius_server_down\";\n rule \"accelppp_no_radius_servers\";\n rule \"accelppp_auth_failed\";\n rule \"accelppp_radius_coa_session_not_found\";\n rule \"accelppp_mac_change_detected\";\n rule \"accelppp_dhcpv4_short_packet\";\n rule \"conntrack_table_full\";\n rule \"freeradius_login_ok\";\n rule \"freeradius_login_incorrect\";\n rule \"generic_critical_severity\";\n rule \"accelppp_unclassified_error\";\n rule \"accelppp_unclassified_warn\";\n rule \"accelppp_interface_tag\";\nend"
} }

View file

@ -0,0 +1,5 @@
{
"title": "accelppp_radius_coa_session_not_found",
"description": "accel-ppp: RADIUS CoA/Disconnect-Message arrived for a session that no longer exists - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: radius:dm_coa: session not found'",
"source": "rule \"accelppp_radius_coa_session_not_found\"\nwhen\n contains(to_string($message.message), \"radius:dm_coa: session not found\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"radius_coa_session_not_found\");\n set_field(\"severity_tag\", \"warning\");\nend"
}

View file

@ -0,0 +1,5 @@
{
"title": "accelppp_mac_change_detected",
"description": "accel-ppp: a subscriber's MAC address changed mid-session - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: <interface>: mac change detected'. accelppp_interface is filled in by the accelppp_interface_tag fallback rule, not here.",
"source": "rule \"accelppp_mac_change_detected\"\nwhen\n contains(to_string($message.message), \"mac change detected\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"mac_change_detected\");\n set_field(\"severity_tag\", \"warning\");\nend"
}

View file

@ -0,0 +1,5 @@
{
"title": "accelppp_dhcpv4_short_packet",
"description": "accel-ppp: malformed/truncated DHCPv4 packet received - found live in a real 1GB accel-ppp log sample (2026-07-23), format: 'warn: dhcpv4: short packet received' (no interface in the text itself, unlike most other accel-ppp warnings)",
"source": "rule \"accelppp_dhcpv4_short_packet\"\nwhen\n contains(to_string($message.message), \"dhcpv4: short packet received\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"dhcpv4_short_packet\");\n set_field(\"severity_tag\", \"warning\");\nend"
}

View file

@ -0,0 +1,5 @@
{
"title": "accelppp_unclassified_error",
"description": "accel-ppp: fallback for any 'error:' line that no specific rule already classified - same catch-all philosophy as generic_critical_severity, but text-based (Vector-shipped accel-ppp lines have no real syslog PRI header, so the numeric severity field is always -1/unparseable and generic_critical_severity never fires for this source).",
"source": "rule \"accelppp_unclassified_error\"\nwhen\n !has_field(\"event_type\") && contains(to_string($message.message), \"error:\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"accelppp_unclassified_error\");\n set_field(\"severity_tag\", \"critical\");\nend"
}

View file

@ -0,0 +1,5 @@
{
"title": "accelppp_unclassified_warn",
"description": "accel-ppp: fallback for any 'warn:' line that no specific rule already classified - see accelppp_unclassified_error for why a text-based fallback is needed instead of the numeric-severity one.",
"source": "rule \"accelppp_unclassified_warn\"\nwhen\n !has_field(\"event_type\") && contains(to_string($message.message), \"warn:\")\nthen\n set_field(\"vendor\", \"accel-ppp\");\n set_field(\"event_type\", \"accelppp_unclassified_warn\");\n set_field(\"severity_tag\", \"warning\");\nend"
}

View file

@ -139,45 +139,46 @@
Якщо є помилка — `vector validate` вкаже точний рядок і причину; не Якщо є помилка — `vector validate` вкаже точний рядок і причину; не
запускайте службу, поки ця команда не відповість без помилок. запускайте службу, поки ця команда не відповість без помилок.
## Крок 2b. (Опційно) Фільтрація DHCP-шуму ## Крок 2b. (Опційно) Фільтрація — лишити тільки error/warn
**Навіщо:** `accel-ppp` при кожній оренді/оновленні DHCP пише повний **Навіщо:** `accel-ppp` на кожну оренду DHCP, кожен RADIUS
обмін (`recv [DHCPv4 Discover...]`, `send [DHCPv4 Ack...]` тощо) — це accounting-цикл і кожен ipoe-хендшейк пише повний `info`/`debug` обмін.
основна частина обсягу логів, а не RADIUS/accounting-дані, заради яких Порахували наживо на реальних цифрах (2 активні NAS-сервери, ~634
власне і збирається моніторинг. Порахували наживо на реальних цифрах байти/повідомлення в Graylog): при масштабуванні до 10+ таких серверів
(2 активні NAS-сервери, ~634 байти/повідомлення в Graylog): при це ~38.5 GiB/добу лише "сирих" даних — диск, розрахований на 14-21 день
масштабуванні до 10+ таких серверів це ~38.5 GiB/добу лише "сирих" даних retention, заповниться за лічені дні. У реальному 1GB лозі одного
— диск, розрахований на 14-21 день retention, заповниться за лічені дні. сервера за добу лише **2,819 рядків** виявились `error:`/`warn:` — решта
Прибирання DHCP на рівні Vector (до того, як пакет узагалі покине цей (мільйони рядків) це info/debug-шум.
сервер) — найдешевший спосіб скоротити обсяг, бо не витрачається ні
мережевий трафік, ні дисковий IO/CPU Graylog на індексацію того, що
однаково ніхто не шукає.
Додайте `filter`-transform між `format_for_graylog` і `sinks`. Умова **Свідомий компроміс:** цей фільтр лишає **тільки** `error:` і `warn:`,
навмисно **не** дропає DHCP-рядки, якщо вони позначені як `error:` чи відкидаючи все інше — включно з RADIUS Accounting (сесії, трафік
`warn:` — щоб реальна проблема (наприклад, вичерпання DHCP-пулу чи Input/Output-Octets). Якщо для вас важлива аналітика сесій/трафіку
таймаут оренди) не загубилась разом зі звичайним шумом: (дашборд "Servers & Sessions", кореляція за `accelppp_interface`/
`radius_session_id` у Graylog) — **не застосовуйте цей фільтр**, або
тримайте один сервер без нього для порівняння. Це рішення per-server: у
`vector.yaml` кожного сервера обирається окремо.
Додайте `filter`-transform між `format_for_graylog` і `sinks`:
```yaml ```yaml
transforms: transforms:
format_for_graylog: format_for_graylog:
# ... як у кроці 2, без змін ... # ... як у кроці 2, без змін ...
# Прибирає рутинний DHCP-обмін (Discover/Request/Offer/Ack), АЛЕ # Лишає тільки error:/warn: - усе info/debug (RADIUS accounting, DHCP,
# залишає DHCP-рядки, позначені error:/warn: - щоб не втратити реальну # ipoe housekeeping) відкидається. Умова true = повідомлення проходить
# проблему (вичерпання пулу, таймаут оренди тощо) разом зі звичайним # далі.
# шумом. Умова true = повідомлення проходить далі. keep_error_warn_only:
drop_dhcp_noise:
type: "filter" type: "filter"
inputs: inputs:
- "format_for_graylog" - "format_for_graylog"
condition: '!contains(string!(.message), "[DHCPv4 ") || contains(string!(.message), "error:") || contains(string!(.message), "warn:")' condition: 'contains(string!(.message), "error:") || contains(string!(.message), "warn:")'
sinks: sinks:
graylog_out: graylog_out:
type: "socket" type: "socket"
inputs: inputs:
- "drop_dhcp_noise" # було "format_for_graylog" - тепер через фільтр - "keep_error_warn_only" # було "format_for_graylog" - тепер через фільтр
address: "udp://10.254.254.202:5140" address: "udp://10.254.254.202:5140"
mode: "udp" mode: "udp"
encoding: encoding:
@ -189,13 +190,116 @@ sinks:
```bash ```bash
vector validate /etc/vector/vector.yaml vector validate /etc/vector/vector.yaml
sudo systemctl restart vector sudo systemctl restart vector
sudo journalctl -u vector -f # переконайтесь, що йдуть RADIUS-рядки, а DHCPv4 - ні sudo journalctl -u vector -f # переконайтесь, що йдуть лише error:/warn: рядки
``` ```
Якщо для якогось джерела DHCP-дані навпаки потрібні (наприклад, окремий **Знайдені наживо типи `error:`/`warn:`** (з реального 1GB логу,
DHCP-сервер, а не accel-ppp) — просто не додавайте `drop_dhcp_noise` у 2026-07-23), усі вже покриті pipeline rules у Graylog:
ланцюжок цього джерела; фільтр застосовується лише до того `transform`, - `error: <interface>: can't determine router address` — стабільно
який у нього `inputs`. повторювався 2,746 разів поспіль ~4 години для двох конкретних
інтерфейсів, перш ніж саморозв'язатись; саме цей випадок і привів до
появи алерту на повторюваність нижче
- `warn: <interface>: radius: server(N) not responding` — чергування між
серверами кожні ~30с
- `warn: radius:dm_coa: session not found` — CoA/DM-запит на вже
завершену сесію
- `warn: <interface>: mac change detected` — зміна MAC-адреси абонента
під час сесії
- `warn: dhcpv4: short packet received` — пошкоджений/обрізаний
DHCP-пакет
Для будь-якого нового типу `error:`/`warn:`, якого ще нема серед
специфічних правил, спрацює fallback-правило
(`accelppp_unclassified_error`/`accelppp_unclassified_warn`) — воно
тегує `event_type` і `severity_tag`, тож нічого не губиться навіть без
власного правила.
### Алерт на повторюваність
Одне повідомлення `error:`/`warn:` часто нормальне (одна невдала спроба
RADIUS, разова зміна MAC) — тривожний сигнал саме **повторюваність**.
Алерт `alerts/alert7_accelppp_repeated_error_warn.json` групує за
`gl2_remote_ip` + `event_type` і спрацьовує, якщо той самий тип
повідомлення з того самого сервера трапляється **>5 разів за 5 хвилин**
— поріг підібраний так, щоб гарантовано зловити знайдений
"router address"-інцидент у перший же цикл, але не спамити на поодинокі
попередження.
### Повний конфіг — без фільтра
Базовий варіант (усе: RADIUS accounting, DHCP, ipoe housekeeping,
error/warn — усе разом):
```yaml
sources:
accel_ppp_log:
type: "file"
include:
- "/var/log/accel-ppp/accel-ppp.log"
read_from: "beginning"
transforms:
format_for_graylog:
type: "remap"
inputs:
- "accel_ppp_log"
source: |
.message = "[" + get_hostname!() + "] " + to_string!(.message)
sinks:
graylog_out:
type: "socket"
inputs:
- "format_for_graylog"
address: "udp://10.254.254.202:5140"
mode: "udp"
encoding:
codec: "text"
```
### Повний конфіг — з фільтром "тільки error/warn"
Той самий конфіг плюс один `transform` і зміна `inputs` у `sinks`:
```yaml
sources:
accel_ppp_log:
type: "file"
include:
- "/var/log/accel-ppp/accel-ppp.log"
read_from: "beginning"
transforms:
format_for_graylog:
type: "remap"
inputs:
- "accel_ppp_log"
source: |
.message = "[" + get_hostname!() + "] " + to_string!(.message)
keep_error_warn_only:
type: "filter"
inputs:
- "format_for_graylog"
condition: 'contains(string!(.message), "error:") || contains(string!(.message), "warn:")'
sinks:
graylog_out:
type: "socket"
inputs:
- "keep_error_warn_only"
address: "udp://10.254.254.202:5140"
mode: "udp"
encoding:
codec: "text"
```
**Коли який обирати:** без фільтра — коли потрібна аналітика сесій/трафіку
(дашборд "Servers & Sessions", кореляція сесій); з фільтром — коли
головне економія диска/мережі і головний інтерес саме в проблемах
(error/warn + алерт на повторюваність). Рішення приймається окремо для
кожного сервера — нічого не заважає тримати частину серверів з фільтром,
частину без.
## Крок 3. Права доступу (запуск від root) ## Крок 3. Права доступу (запуск від root)
@ -383,7 +487,7 @@ Graylog двічі з різними форматами.
| Vector запускається, але не бачить оновлень файлу | Файл ротується (logrotate) інакше, ніж очікує Vector | Перевірте `include`/`ignore_older_secs`; за потреби додайте `fingerprint` налаштування у `source`, щоб Vector коректно стежив за файлом після ротації | | Vector запускається, але не бачить оновлень файлу | Файл ротується (logrotate) інакше, ніж очікує Vector | Перевірте `include`/`ignore_older_secs`; за потреби додайте `fingerprint` налаштування у `source`, щоб Vector коректно стежив за файлом після ротації |
| `vector validate` видає `error[E103]: unhandled fallible assignment` у `transform` | VRL вимагає явно гарантувати тип операнда перед конкатенацією/операцією (наприклад, `.message` без `to_string!()`) | Обгорніть проблемне поле в `to_string!(...)` (або іншу `!`-функцію відповідного типу) — саме так це вирішено в базовому конфізі кроку 2 | | `vector validate` видає `error[E103]: unhandled fallible assignment` у `transform` | VRL вимагає явно гарантувати тип операнда перед конкатенацією/операцією (наприклад, `.message` без `to_string!()`) | Обгорніть проблемне поле в `to_string!(...)` (або іншу `!`-функцію відповідного типу) — саме так це вирішено в базовому конфізі кроку 2 |
| Критичні алерти в Discord на рутинні `info`-повідомлення від Vector-джерела | Syslog UDP input не бачить валідного `<PRI>` заголовка в сирому тексті Vector, тож Graylog проставляє severity `-1` ("невідомо") — а не 0-2 (справді критично) | Це вже виправлено на стороні Graylog (правило `generic_critical_severity` тепер явно виключає `level < 0`); якщо побачите знову — перевірте, чи не відкотилось це правило | | Критичні алерти в Discord на рутинні `info`-повідомлення від Vector-джерела | Syslog UDP input не бачить валідного `<PRI>` заголовка в сирому тексті Vector, тож Graylog проставляє severity `-1` ("невідомо") — а не 0-2 (справді критично) | Це вже виправлено на стороні Graylog (правило `generic_critical_severity` тепер явно виключає `level < 0`); якщо побачите знову — перевірте, чи не відкотилось це правило |
| DHCP-рядки більше не з'являються в Graylog | Це очікувано, якщо додано `drop_dhcp_noise` з кроку 2b | Перевірте `vector.yaml` — якщо DHCP-дані все ж потрібні, приберіть цей transform з ланцюжка `inputs` sink'у | | RADIUS accounting/DHCP/ipoe-рядки більше не з'являються в Graylog | Це очікувано, якщо додано `keep_error_warn_only` з кроку 2b — фільтр лишає тільки error:/warn: | Перевірте `vector.yaml` — якщо потрібна аналітика сесій/трафіку, приберіть цей transform з ланцюжка `inputs` sink'у (див. "Повний конфіг без фільтра" у кроці 2b) |
| Алерт "abnormal message volume" щойно після першого запуску Vector на новому сервері | `read_from: "beginning"` (крок 2) означає, що при першому старті Vector вичитує весь наявний лог-файл одразу, а не тільки нові рядки — одноразовий сплеск, не триваюча проблема | Перевірте обсяг від цього джерела через кілька хвилин (`gl2_remote_ip:<ip>` у Search) — має впасти до звичайного рівня | | Алерт "abnormal message volume" щойно після першого запуску Vector на новому сервері | `read_from: "beginning"` (крок 2) означає, що при першому старті Vector вичитує весь наявний лог-файл одразу, а не тільки нові рядки — одноразовий сплеск, не триваюча проблема | Перевірте обсяг від цього джерела через кілька хвилин (`gl2_remote_ip:<ip>` у Search) — має впасти до звичайного рівня |
## Довідка: структура конфігурації ## Довідка: структура конфігурації