diff --git a/CHANGELOG.md b/CHANGELOG.md index a642ebc..cccaca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,25 @@ The release notes on GitHub are taken from this file: the section whose heading is the version number. -## 1.0.4 +## 1.2.2 + +NetFlow and IPFIX sampling +- Sampling rates are found in more of the ways devices declare them: + samplingProbability (IPFIX 311); a rate per interface that is set on the + egress interface of a flow; a single sampler whose id the flow records do + not name; a rate declared under another observation domain of the same + device (line cards export under their own ids); a samplingInterval of 0 + next to samplerRandomInterval. +- A rate can be given by hand for a device that does not declare one: + sampling=N on its device line in Names (Settings). A rate the device + declares still wins; flows waiting for a rate go on with it at once. +- Settings shows the templates a device sent (template and field ids) when + its sampling rate is not declared, to see what it does send. +- Export sessions silent for an hour while the same device sends in another + way (a changed version or domain) are listed apart as stopped, not as + faults. + +## 1.2.1 Pages - Overview: **Total**, **Inbound** and **Outbound** above the bandwidth diff --git a/README.md b/README.md index c53e6e4..e315cd2 100644 --- a/README.md +++ b/README.md @@ -565,9 +565,11 @@ directly (see `inventory.txt.example`). Every line is optional. net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1091,7 +1093,7 @@ traffic66 then restarts instead of the machine running out of memory. | Symptom | Cause and fix | |---|---| | Device missing from **Settings** | Packets do not arrive: see [Check that flows arrive](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | The device has not sent its sampler options yet; most resend within minutes. If it never does, export them (`option sampler-table` on Cisco) or mark it `unsampled` in Names if it really is 1:1 | +| "waiting for the sampling rate" | The device has not sent its sampler options yet; most resend within minutes. If it never does, export them (`option sampler-table` on Cisco) or mark it `unsampled` in Names if it really is 1:1, or give its rate with `sampling=N` on its `device` line. **Settings** then lists the templates the device sent, to see what it declares | | Numbers lower than the interface counters | See **Interface check**: loss on the way, interfaces not sampled, or flows still in the device cache (active timeout longer than 60 s) | | Numbers higher than the interface counters | The same traffic sampled on two interfaces or two devices | | No countries or networks ("Unknown") | No database loaded: upload one on **Settings**, see [Countries](#8-countries-networks-and-threat-lists) | diff --git a/cmd/traffic66/main.go b/cmd/traffic66/main.go index 21ab769..dab6fba 100644 --- a/cmd/traffic66/main.go +++ b/cmd/traffic66/main.go @@ -296,7 +296,7 @@ func serve(args []string, demo bool) { pipe := pipeline.New(pipeline.Config{L2Overhead: f.l2}, st, inv, asn, thr) col := collector.New(pipe) col.NF.HoldFor = f.hold - col.NF.SetUnsampled(inv.Unsampled()) + col.SetSampling(inv.Unsampled(), inv.Sampling()) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() diff --git a/docs/README.ar.md b/docs/README.ar.md index 5f88b60..f7b2213 100644 --- a/docs/README.ar.md +++ b/docs/README.ar.md @@ -586,9 +586,11 @@ NetFlow/IPFIX (عدّادات الواجهات تتضمنه، وعدّ التد net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1109,7 +1111,7 @@ traffic66 الملف `license.json` في دليل البيانات مع رقم | العَرَض | السبب والحل | |---|---| | الجهاز غير ظاهر في **الإعدادات** | الحزم لا تصل: انظر [التحقق من وصول التدفقات](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | لم يرسل الجهاز خيارات أخذ العينات بعد؛ ومعظم الأجهزة تعيد إرسالها خلال دقائق. إن لم يفعل أبدًا، فصدّرها (`option sampler-table` على Cisco) أو علّم الجهاز بـ `unsampled` في الأسماء إن كان يصدّر فعلًا بنسبة 1:1 | +| "waiting for the sampling rate" | لم يرسل الجهاز خيارات أخذ العينات بعد؛ ومعظم الأجهزة تعيد إرسالها خلال دقائق. إن لم يفعل أبدًا، فصدّرها (`option sampler-table` على Cisco) أو علّم الجهاز بـ `unsampled` في الأسماء إن كان يصدّر فعلًا بنسبة 1:1، أو حدّد نسبته بـ `sampling=N` في سطر `device` الخاص به. بعد ذلك تعرض **الإعدادات** القوالب التي أرسلها الجهاز، لمعرفة ما يصرّح به | | الأرقام أقل من عدّادات الواجهات | انظر **مطابقة الواجهات**: فقد في الطريق، أو واجهات لا تؤخذ منها عينات، أو تدفقات ما زالت في ذاكرة الجهاز المؤقتة (مهلة التدفق النشط أطول من 60 ثانية) | | الأرقام أعلى من عدّادات الواجهات | تؤخذ عينات الحركة نفسها على واجهتين أو جهازين | | لا توجد دول أو شبكات ("غير معروف") | لم تُحمَّل أي قاعدة بيانات: ارفع واحدة في **الإعدادات**، وانظر [الدول](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.bn.md b/docs/README.bn.md index eef16e1..c8b1982 100644 --- a/docs/README.bn.md +++ b/docs/README.bn.md @@ -595,9 +595,11 @@ entry। **টেক্সট হিসেবে সম্পাদনা (উ net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1123,7 +1125,7 @@ container-এর memory limit। `-memory`-র ভাগের প্রায | লক্ষণ | কারণ ও সমাধান | |---|---| | ডিভাইস **সেটিংস**-এ নেই | packet পৌঁছাচ্ছে না: [flow পৌঁছাচ্ছে কি না দেখুন](#5-check-that-flows-arrive) দেখুন | -| "waiting for the sampling rate" | ডিভাইস এখনও sampler options পাঠায়নি; বেশিরভাগই কয়েক মিনিটের মধ্যে আবার পাঠায়। কখনো না পাঠালে সেগুলো export করান (Cisco-তে `option sampler-table`) অথবা সত্যিই 1:1 হলে নাম-এ সেটিকে `unsampled` চিহ্নিত করুন | +| "waiting for the sampling rate" | ডিভাইস এখনও sampler options পাঠায়নি; বেশিরভাগই কয়েক মিনিটের মধ্যে আবার পাঠায়। কখনো না পাঠালে সেগুলো export করান (Cisco-তে `option sampler-table`) অথবা সত্যিই 1:1 হলে নাম-এ সেটিকে `unsampled` চিহ্নিত করুন, অথবা তার `device` লাইনে `sampling=N` দিয়ে rate দিন। তারপর **সেটিংস**-এ ডিভাইসের পাঠানো template-গুলো দেখা যায়, যাতে বোঝা যায় সেটি কী ঘোষণা করছে | | সংখ্যা interface counter-এর চেয়ে কম | **ইন্টারফেস মিলানো** দেখুন: পথে loss, interface sample হচ্ছে না, অথবা flow এখনও ডিভাইসের cache-এ (active timeout 60 s-এর বেশি) | | সংখ্যা interface counter-এর চেয়ে বেশি | একই ট্রাফিক দুটি interface বা দুটি ডিভাইসে sample হচ্ছে | | কোনো দেশ বা নেটওয়ার্ক নেই ("অজানা") | কোনো database লোড করা নেই: **সেটিংস**-এ একটি আপলোড করুন, [দেশ](#8-countries-networks-and-threat-lists) দেখুন | diff --git a/docs/README.es.md b/docs/README.es.md index 8ac2f2c..9d18cd4 100644 --- a/docs/README.es.md +++ b/docs/README.es.md @@ -609,9 +609,11 @@ las líneas son opcionales. net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1166,7 +1168,7 @@ máquina sin memoria. | Síntoma | Causa y solución | |---|---| | El equipo no aparece en **Configuración** | Los paquetes no llegan: vea [Comprobar que llegan los flujos](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | El equipo aún no ha enviado sus opciones de sampler; la mayoría las reenvía en pocos minutos. Si no lo hace nunca, expórtelas (`option sampler-table` en Cisco) o márquelo como `unsampled` en Nombres si de verdad es 1:1 | +| "waiting for the sampling rate" | El equipo aún no ha enviado sus opciones de sampler; la mayoría las reenvía en pocos minutos. Si no lo hace nunca, expórtelas (`option sampler-table` en Cisco) o márquelo como `unsampled` en Nombres si de verdad es 1:1, o indique su tasa con `sampling=N` en su línea `device`. **Configuración** muestra entonces las plantillas que envió el equipo, para ver qué declara | | Cifras por debajo de los contadores de interfaz | Vea **Verificación de interfaces**: pérdidas por el camino, interfaces sin muestrear o flujos aún en la caché del equipo (timeout activo superior a 60 s) | | Cifras por encima de los contadores de interfaz | El mismo tráfico se muestrea en dos interfaces o en dos equipos | | No hay países ni redes ("Desconocido") | No hay ninguna base de datos cargada: suba una en **Configuración**; vea [Países](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.fr.md b/docs/README.fr.md index 2b8a160..eb66a16 100644 --- a/docs/README.fr.md +++ b/docs/README.fr.md @@ -625,9 +625,11 @@ fichier, et vous pouvez aussi l'éditer directement (voir net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1202,7 +1204,7 @@ lieu que la machine manque de mémoire. | Symptôme | Cause et solution | |---|---| | Équipement absent de **Paramètres** | Les paquets n'arrivent pas : voir [Vérifier que les flux arrivent](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | L'équipement n'a pas encore envoyé ses options de sampler ; la plupart les renvoient en quelques minutes. S'il ne le fait jamais, exportez-les (`option sampler-table` sur Cisco) ou marquez-le `unsampled` dans Noms s'il est réellement en 1:1 | +| "waiting for the sampling rate" | L'équipement n'a pas encore envoyé ses options de sampler ; la plupart les renvoient en quelques minutes. S'il ne le fait jamais, exportez-les (`option sampler-table` sur Cisco) ou marquez-le `unsampled` dans Noms s'il est réellement en 1:1, ou indiquez son taux avec `sampling=N` sur sa ligne `device`. **Paramètres** liste alors les templates envoyés par l'équipement, pour voir ce qu'il déclare | | Chiffres inférieurs aux compteurs d'interface | Voir **Contrôle des interfaces** : pertes en route, interfaces non échantillonnées, ou flux encore dans le cache de l'équipement (timeout actif supérieur à 60 s) | | Chiffres supérieurs aux compteurs d'interface | Le même trafic est échantillonné sur deux interfaces ou deux équipements | | Pas de pays ni de réseaux ("Inconnu") | Aucune base de données chargée : importez-en une dans **Paramètres**, voir [Pays](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.hi.md b/docs/README.hi.md index f14e762..0e225b1 100644 --- a/docs/README.hi.md +++ b/docs/README.hi.md @@ -595,9 +595,11 @@ edit कर सकते हैं (`inventory.txt.example` देखें)। net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1128,7 +1130,7 @@ restart हो जाता है। | लक्षण | कारण और समाधान | |---|---| | डिवाइस **सेटिंग्स** में नहीं है | Packets नहीं पहुँच रहे: [जाँचें कि flows पहुँच रहे हैं](#5-check-that-flows-arrive) देखें | -| "waiting for the sampling rate" | डिवाइस ने अभी तक अपने sampler options नहीं भेजे; ज़्यादातर कुछ मिनटों में दोबारा भेज देते हैं। अगर कभी न भेजे, तो उन्हें export करवाएँ (Cisco पर `option sampler-table`) या अगर वह सच में 1:1 है तो नाम में उसे `unsampled` लिखें | +| "waiting for the sampling rate" | डिवाइस ने अभी तक अपने sampler options नहीं भेजे; ज़्यादातर कुछ मिनटों में दोबारा भेज देते हैं। अगर कभी न भेजे, तो उन्हें export करवाएँ (Cisco पर `option sampler-table`) या अगर वह सच में 1:1 है तो नाम में उसे `unsampled` लिखें, या उसकी `device` line पर `sampling=N` से rate दें। फिर **सेटिंग्स** में डिवाइस द्वारा भेजे गए templates दिखते हैं, जिससे पता चलता है कि वह क्या घोषित करता है | | आँकड़े interface counters से कम | **इंटरफ़ेस मिलान** देखें: रास्ते में loss, interfaces sample नहीं हो रहे, या flows अभी डिवाइस के cache में हैं (active timeout 60 s से लंबा) | | आँकड़े interface counters से ज़्यादा | वही ट्रैफ़िक दो interfaces या दो डिवाइसों पर sample हो रहा है | | कोई देश या नेटवर्क नहीं ("अज्ञात") | कोई database लोड नहीं है: **सेटिंग्स** पर एक अपलोड करें, [देश](#8-countries-networks-and-threat-lists) देखें | diff --git a/docs/README.id.md b/docs/README.id.md index f43f432..173be24 100644 --- a/docs/README.id.md +++ b/docs/README.id.md @@ -608,9 +608,11 @@ bersifat opsional. net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1159,7 +1161,7 @@ kehabisan memori. | Gejala | Penyebab dan solusi | |---|---| | Perangkat tidak muncul di **Pengaturan** | Paket tidak sampai: lihat [Pastikan flow masuk](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | Perangkat belum mengirim sampler options; kebanyakan mengirim ulang dalam beberapa menit. Jika tidak pernah, ekspor opsi itu (`option sampler-table` di Cisco) atau tandai perangkat sebagai `unsampled` di Nama jika memang 1:1 | +| "waiting for the sampling rate" | Perangkat belum mengirim sampler options; kebanyakan mengirim ulang dalam beberapa menit. Jika tidak pernah, ekspor opsi itu (`option sampler-table` di Cisco) atau tandai perangkat sebagai `unsampled` di Nama jika memang 1:1, atau beri rasionya dengan `sampling=N` di baris `device`-nya. **Pengaturan** lalu menampilkan template yang dikirim perangkat, untuk melihat apa yang dideklarasikannya | | Angka lebih rendah dari counter interface | Lihat **Pencocokan antarmuka**: loss di jalan, interface tidak di-sampling, atau flow masih di cache perangkat (active timeout lebih dari 60 detik) | | Angka lebih tinggi dari counter interface | Trafik yang sama di-sampling di dua interface atau dua perangkat | | Tidak ada negara atau jaringan ("Tidak diketahui") | Tidak ada basis data yang dimuat: unggah di **Pengaturan**, lihat [Negara](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.ja.md b/docs/README.ja.md index a915d48..12a17dd 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -488,9 +488,11 @@ softflowd -i eth0 -n 192.0.2.50:2055 -v 9 -t maxlife=60 net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -873,7 +875,7 @@ traffic66 は [PolyForm Noncommercial License 1.0.0](../LICENSE.md) と [Traffic | 症状 | 原因と対処 | |---|---| | **設定** に機器が表示されない | パケットが届いていません:[フローの受信を確認する](#5-check-that-flows-arrive) を参照 | -| "waiting for the sampling rate" | 機器がまだサンプラーオプションを送っていません。多くの機器は数分以内に再送します。いつまでも送られない場合はエクスポートを設定する(Cisco では `option sampler-table`)か、本当に 1:1 なら名前で `unsampled` を付けます | +| "waiting for the sampling rate" | 機器がまだサンプラーオプションを送っていません。多くの機器は数分以内に再送します。いつまでも送られない場合はエクスポートを設定する(Cisco では `option sampler-table`)か、本当に 1:1 なら名前で `unsampled` を付けるか、`device` 行に `sampling=N` でレートを指定します。その後 **設定** に機器が送ったテンプレートが一覧表示されるので、何を宣言しているか確認できます | | 数値がインターフェースカウンターより小さい | **インターフェース照合** を確認:途中でのロス、サンプリングされていないインターフェース、またはフローがまだ機器のキャッシュ内にある(アクティブタイムアウトが 60 秒より長い) | | 数値がインターフェースカウンターより大きい | 同じトラフィックを 2 つのインターフェースまたは 2 台の機器でサンプリングしています | | 国やネットワークが表示されない("不明") | データベースが読み込まれていません。**設定** でアップロードしてください:[国](#8-countries-networks-and-threat-lists) を参照 | diff --git a/docs/README.ko.md b/docs/README.ko.md index b963fb7..81d824a 100644 --- a/docs/README.ko.md +++ b/docs/README.ko.md @@ -571,9 +571,11 @@ traffic66에 도착하기 전에 유실됨, 샘플링 레이트를 아직 모름 net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1065,7 +1067,7 @@ Top 66은 약 9초이며, 소요 시간은 범위에 비례해 늘고 코어가 | 증상 | 원인과 해결 | |---|---| | **설정**에 장비가 없음 | 패킷이 도착하지 않음: [플로 수신 확인](#5-check-that-flows-arrive) 참조 | -| "waiting for the sampling rate" | 장비가 아직 샘플러 옵션을 보내지 않았습니다. 대부분 몇 분 안에 다시 보냅니다. 끝내 보내지 않으면 익스포트하도록 설정하거나(Cisco는 `option sampler-table`), 정말 1:1이라면 이름에서 `unsampled`로 표시합니다 | +| "waiting for the sampling rate" | 장비가 아직 샘플러 옵션을 보내지 않았습니다. 대부분 몇 분 안에 다시 보냅니다. 끝내 보내지 않으면 익스포트하도록 설정하거나(Cisco는 `option sampler-table`), 정말 1:1이라면 이름에서 `unsampled`로 표시하거나, 해당 `device` 줄에 `sampling=N`으로 비율을 지정합니다. 그러면 **설정**에 장비가 보낸 템플릿이 표시되어 무엇을 선언하는지 확인할 수 있습니다 | | 수치가 인터페이스 카운터보다 낮음 | **인터페이스 대조** 확인: 경로상 유실, 샘플링되지 않은 인터페이스, 또는 플로가 아직 장비 캐시에 있음(액티브 타임아웃이 60초보다 김) | | 수치가 인터페이스 카운터보다 높음 | 같은 트래픽을 두 인터페이스 또는 두 장비에서 샘플링함 | | 국가나 네트워크가 표시되지 않음("알 수 없음") | 로드된 데이터베이스가 없음: **설정**에서 업로드. [국가](#8-countries-networks-and-threat-lists) 참조 | diff --git a/docs/README.pt.md b/docs/README.pt.md index 7cd786a..f2d8987 100644 --- a/docs/README.pt.md +++ b/docs/README.pt.md @@ -609,9 +609,11 @@ Todas as linhas são opcionais. net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1161,7 +1163,7 @@ ficar sem memória. | Sintoma | Causa e solução | |---|---| | Equipamento não aparece em **Configurações** | Os pacotes não chegam: veja [Verificar se os fluxos estão chegando](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | O equipamento ainda não enviou as opções do sampler; a maioria reenvia em poucos minutos. Se nunca enviar, exporte-as (`option sampler-table` no Cisco) ou marque-o como `unsampled` em Nomes se ele for de fato 1:1 | +| "waiting for the sampling rate" | O equipamento ainda não enviou as opções do sampler; a maioria reenvia em poucos minutos. Se nunca enviar, exporte-as (`option sampler-table` no Cisco) ou marque-o como `unsampled` em Nomes se ele for de fato 1:1, ou informe a taxa com `sampling=N` na linha `device` dele. **Configurações** lista então os templates que o equipamento enviou, para ver o que ele declara | | Números abaixo dos contadores de interface | Veja **Conferência de interfaces**: perdas no caminho, interfaces não amostradas ou fluxos ainda no cache do equipamento (timeout ativo acima de 60 s) | | Números acima dos contadores de interface | O mesmo tráfego amostrado em duas interfaces ou dois equipamentos | | Sem países nem redes ("Desconhecido") | Nenhum banco de dados carregado: envie um em **Configurações**; veja [Países](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.ru.md b/docs/README.ru.md index a882934..ed832ff 100644 --- a/docs/README.ru.md +++ b/docs/README.ru.md @@ -603,9 +603,11 @@ Enter; оно сразу сохраняется и показывается ве net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1150,7 +1152,7 @@ traffic66 -data /var/lib/traffic66 -listen "sflow=:6343,netflow=:2055,ipfix=:473 | Симптом | Причина и решение | |---|---| | Устройства нет на странице **Настройки** | Пакеты не доходят: см. [Проверка поступления потоков](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | Устройство ещё не прислало sampler options; большинство устройств повторяют их в течение нескольких минут. Если так и не пришлёт, включите их экспорт (`option sampler-table` на Cisco) или пометьте устройство как `unsampled` в разделе «Названия», если оно действительно экспортирует 1:1 | +| "waiting for the sampling rate" | Устройство ещё не прислало sampler options; большинство устройств повторяют их в течение нескольких минут. Если так и не пришлёт, включите их экспорт (`option sampler-table` на Cisco) или пометьте устройство как `unsampled` в разделе «Названия», если оно действительно экспортирует 1:1, либо укажите его коэффициент через `sampling=N` в строке `device`. На странице **Настройки** затем видны шаблоны, присланные устройством, — по ним понятно, что оно объявляет | | Цифры ниже счётчиков интерфейсов | См. **Сверка интерфейсов**: потери по пути, несэмплируемые интерфейсы или потоки ещё в кеше устройства (active timeout больше 60 с) | | Цифры выше счётчиков интерфейсов | Один и тот же трафик сэмплируется на двух интерфейсах или двух устройствах | | Нет стран и сетей («Неизвестно») | Не загружена база: загрузите её на странице **Настройки**, см. [Страны](#8-countries-networks-and-threat-lists) | diff --git a/docs/README.ur.md b/docs/README.ur.md index 85e3410..d02a052 100644 --- a/docs/README.ur.md +++ b/docs/README.ur.md @@ -596,9 +596,11 @@ entry۔ **متن کے طور پر ترمیم (ایڈوانسڈ)** وہ فائل net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -1124,7 +1126,7 @@ container کی memory limit۔ `-memory` والے حصے کا تقریباً 2.5 | علامت | وجہ اور حل | |---|---| | ڈیوائس **ترتیبات** میں نہیں | packets نہیں پہنچ رہے: [جانچیں کہ flows پہنچ رہے ہیں](#5-check-that-flows-arrive) دیکھیں | -| "waiting for the sampling rate" | ڈیوائس نے ابھی تک اپنے sampler options نہیں بھیجے؛ زیادہ تر چند منٹ میں دوبارہ بھیج دیتی ہیں۔ اگر کبھی نہ بھیجے تو انہیں export کروائیں (Cisco پر `option sampler-table`) یا اگر وہ واقعی 1:1 ہے تو نام میں اسے `unsampled` لکھیں | +| "waiting for the sampling rate" | ڈیوائس نے ابھی تک اپنے sampler options نہیں بھیجے؛ زیادہ تر چند منٹ میں دوبارہ بھیج دیتی ہیں۔ اگر کبھی نہ بھیجے تو انہیں export کروائیں (Cisco پر `option sampler-table`) یا اگر وہ واقعی 1:1 ہے تو نام میں اسے `unsampled` لکھیں، یا اس کی `device` لائن پر `sampling=N` سے rate دیں۔ پھر **ترتیبات** میں ڈیوائس کے بھیجے ہوئے templates نظر آتے ہیں، جن سے پتا چلتا ہے کہ وہ کیا declare کرتی ہے | | اعداد interface counters سے کم | **انٹرفیس جانچ** دیکھیں: راستے میں loss، interfaces sample نہیں ہو رہے، یا flows ابھی ڈیوائس کی cache میں ہیں (active timeout 60 s سے لمبا) | | اعداد interface counters سے زیادہ | ایک ہی ٹریفک دو interfaces یا دو ڈیوائسز پر sample ہو رہی ہے | | کوئی ملک یا نیٹ ورک نہیں ("نامعلوم") | کوئی database لوڈ نہیں: **ترتیبات** پر ایک اپ لوڈ کریں، [ممالک](#8-countries-networks-and-threat-lists) دیکھیں | diff --git a/docs/README.zh.md b/docs/README.zh.md index 27ef965..ea35312 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -488,9 +488,11 @@ softflowd -i eth0 -n 192.0.2.50:2055 -v 9 -t maxlife=60 net 10.10.0.0/16 Office LAN net 203.0.113.0/24 Public servers country=JP -# device names; "unsampled" if it exports every packet (1:1) +# device names; "unsampled" if it exports every packet (1:1), +# sampling=N if it samples 1:N but does not say so in its export device 192.0.2.1 Core router device 192.0.2.9 Branch firewall unsampled +device 192.0.2.20 Edge router sampling=1000 # interface names, by device address and ifIndex; speed in bits per second iface 192.0.2.1 3 ISP uplink speed=1000000000 @@ -873,7 +875,7 @@ traffic66 以 [PolyForm Noncommercial License 1.0.0](../LICENSE.md) 和 [Traffic | 现象 | 原因与解决方法 | |---|---| | **设定** 中看不到设备 | 报文没有到达:见 [确认流数据已到达](#5-check-that-flows-arrive) | -| "waiting for the sampling rate" | 设备尚未发送采样器选项;大多数设备几分钟内会重发。如果一直不发,请配置导出(Cisco 上为 `option sampler-table`);如果确实是 1:1,可在名称中标记为 `unsampled` | +| "waiting for the sampling rate" | 设备尚未发送采样器选项;大多数设备几分钟内会重发。如果一直不发,请配置导出(Cisco 上为 `option sampler-table`);如果确实是 1:1,可在名称中标记为 `unsampled`,或在其 `device` 行用 `sampling=N` 指定采样率。之后 **设定** 会列出该设备发送的模板,可据此查看它声明了什么 | | 统计值低于接口计数器 | 查看 **接口对账**:途中丢包、有接口未采样,或流仍在设备缓存中(活动超时超过 60 秒) | | 统计值高于接口计数器 | 同一流量在两个接口或两台设备上被采样 | | 没有国家或网络信息("未知") | 未加载数据库:在 **设定** 上传一个,见 [国家](#8-countries-networks-and-threat-lists) | diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 5ce1b01..2e6cd01 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -485,7 +485,15 @@ func (s *Server) sources(w http.ResponseWriter, r *http.Request) { now := time.Now() s.mu.Lock() var out []sourceOut - for _, si := range s.Col.Sources() { + var stale []map[string]any + srcs := s.Col.Sources() + active := map[string]bool{} + for _, si := range srcs { + if now.Sub(si.LastSeen) < 2*time.Minute { + active[si.Exporter] = true + } + } + for _, si := range srcs { o := sourceOut{SourceInfo: si, Status: "ok"} o.RecPS = srcRate.ps[srcKey(si)] if a, err := netip.ParseAddr(si.Exporter); err == nil { @@ -521,6 +529,12 @@ func (s *Server) sources(w http.ResponseWriter, r *http.Request) { if si.DecodeErrors > 0 && si.DecodeErrors*100 > si.Packets { issue("warn", "decode_errors") } + // silent for an hour while the same device sends otherwise (a + // changed export version or domain): listed apart, not as a fault + if now.Sub(si.LastSeen) > time.Hour && active[si.Exporter] { + stale = append(stale, map[string]any{"exporter": si.Exporter, "proto": si.Proto, "domain": si.Domain, "last_seen": si.LastSeen, "name": o.Name}) + continue + } out = append(out, o) } s.mu.Unlock() @@ -528,7 +542,7 @@ func (s *Server) sources(w http.ResponseWriter, r *http.Request) { for _, l := range s.Col.Listeners { ls = append(ls, map[string]any{"addr": l.Addr, "proto": l.Proto, "packets": l.Packets.Load(), "undecoded": l.Undecoded.Load(), "rcvbuf": l.RcvBuf}) } - res := map[string]any{"sources": out, "listeners": ls} + res := map[string]any{"sources": out, "listeners": ls, "stale": stale} if s.Capture != nil { res["captures"] = s.Capture() } @@ -638,7 +652,7 @@ func (s *Server) putInventory(w http.ResponseWriter, r *http.Request) { fail(w, err) return } - s.Col.NF.SetUnsampled(s.Inv.Unsampled()) + s.Col.SetSampling(s.Inv.Unsampled(), s.Inv.Sampling()) writeJSON(w, http.StatusOK, map[string]string{"ok": "saved"}) } diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 9509fbc..cbae113 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -308,6 +308,9 @@ type SourceInfo struct { DecodeErrors uint64 `json:"decode_errors"` LastSeen time.Time `json:"last_seen"` ClockSkew time.Duration `json:"clock_skew_ns"` + // NetFlow/IPFIX: the templates received (id and field ids), shown when + // the sampling rate is not declared + TemplateInfo []string `json:"template_info,omitempty"` } // Sources lists every exporter seen. @@ -337,6 +340,7 @@ func (c *Collector) Sources() []SourceInfo { Packets: s.Packets, Records: s.Records, LostPct: lost, LostRecords: s.LostRecords, TransitPct: lost, Sampling: samp, SamplingState: s.SamplingState, Pending: s.Pending, NoTemplate: s.NoTemplate, Templates: s.Templates, LastSeen: s.LastSeen, ClockSkew: s.ClockSkew, LossComp: s.LossComp, + TemplateInfo: s.TemplateInfo, }) } c.mu.Lock() @@ -388,3 +392,13 @@ func (c *Collector) Sources() []SourceInfo { sort.Slice(out, func(i, j int) bool { return out[i].Exporter < out[j].Exporter }) return out } + +// SetSampling applies the names' sampling settings: devices that export +// every packet, and hand-set rates for devices that do not declare one. +// Records that were waiting for a rate go on with it. +func (c *Collector) SetSampling(unsampled map[netip.Addr]bool, manual map[netip.Addr]uint32) { + c.NF.SetUnsampled(unsampled) + if res := c.NF.SetManual(manual); len(res.Records) > 0 { + c.sink.Submit(res.Records) + } +} diff --git a/internal/decode/nf/decoder.go b/internal/decode/nf/decoder.go index a1e3091..9b8f5c0 100644 --- a/internal/decode/nf/decoder.go +++ b/internal/decode/nf/decoder.go @@ -31,6 +31,8 @@ type Stats struct { Sampling uint32 // exporter-wide rate if known Rates []uint32 // every declared rate (exporter, sampler, interface) Templates int + TemplateInfo []string // the templates seen and their field ids + Manual bool // the rate was set by hand (sampling= in the names) // LossComp is the multiplier currently applied to make up for records // lost in transit (1 = no loss). LossComp float64 @@ -74,6 +76,7 @@ type samplerRef struct { samplerID uint32 hasSampler bool inIf uint32 + outIf uint32 } type session struct { @@ -90,6 +93,8 @@ type session struct { pending []pendingRec + tmplDesc map[uint16]string // templates seen, for the sources page + initMs int64 seqInit bool @@ -127,6 +132,11 @@ type Decoder struct { MaxSessions int // Unsampled lists exporters known to export every packet. Unsampled map[netip.Addr]bool + // Manual is the sampling rate of exporters that do not declare it, + // from sampling= in the names; a declared rate takes precedence. + Manual map[netip.Addr]uint32 + // byAddr is the last rate an exporter declared, in any domain. + byAddr map[netip.Addr]uint32 } func NewDecoder() *Decoder { @@ -136,6 +146,8 @@ func NewDecoder() *Decoder { MaxPending: 200000, MaxSessions: 10000, Unsampled: map[netip.Addr]bool{}, + Manual: map[netip.Addr]uint32{}, + byAddr: map[netip.Addr]uint32{}, } } @@ -190,7 +202,17 @@ func (d *Decoder) Stats() []Stats { st := s.stats st.Pending = len(s.pending) st.Templates = len(s.templates) - st.Sampling = s.def + for _, v := range s.tmplDesc { + st.TemplateInfo = append(st.TemplateInfo, v) + } + sort.Strings(st.TemplateInfo) + if s.def == 0 && len(s.bySampler) == 0 && len(s.byIf) == 0 && d.Manual[s.stats.Exporter] > 0 { + st.Manual = true + st.Sampling = d.Manual[s.stats.Exporter] + } + if !st.Manual { + st.Sampling = s.def + } st.LossComp = s.comp seen := map[uint32]bool{} add := func(r uint32) { @@ -200,6 +222,9 @@ func (d *Decoder) Stats() []Stats { } } add(s.def) + if st.Manual { + add(st.Sampling) + } for _, r := range s.bySampler { add(r) } @@ -208,6 +233,8 @@ func (d *Decoder) Stats() []Stats { } sort.Slice(st.Rates, func(i, j int) bool { return st.Rates[i] < st.Rates[j] }) switch { + case st.Manual: + st.SamplingState = "manual" case s.assumed: st.SamplingState = "assumed1" case len(s.pending) > 0: @@ -272,12 +299,43 @@ func (d *Decoder) rateFor(s *session, rec *flow.Record, ref samplerRef, inRecord if rate == 0 { rate = s.byIf[ref.inIf] } + if rate == 0 && ref.outIf != 0 { + // sampling configured on the egress interface + rate = s.byIf[ref.outIf] + } if rate == 0 { rate = s.def } + if rate == 0 { + // one rate declared for a sampler or interface the record does not + // name: it is the only one there is + rate = s.onlyRate() + } + if rate == 0 { + // declared by the same device in another observation domain (line + // cards export under their own ids) + rate = d.byAddr[rec.Exporter] + } + if rate == 0 { + rate = d.Manual[rec.Exporter] + } return rate } +// onlyRate is the session's rate when all its declared rates agree. +func (s *session) onlyRate() uint32 { + var r uint32 + for _, m := range []map[uint32]uint32{s.bySampler, s.byIf} { + for _, x := range m { + if r != 0 && x != r { + return 0 + } + r = x + } + } + return r +} + // resolve fills in the sampling multiplier or queues the record. func (d *Decoder) resolve(s *session, rec flow.Record, ref samplerRef, inRecordRate uint32, now time.Time, out *[]flow.Record) { rate := d.rateFor(s, &rec, ref, inRecordRate) @@ -409,3 +467,16 @@ func (d *Decoder) SetUnsampled(m map[netip.Addr]bool) { d.Unsampled = m d.mu.Unlock() } + +// SetManual replaces the hand-set sampling rates; records waiting for a +// rate are released with them. +func (d *Decoder) SetManual(m map[netip.Addr]uint32) Result { + d.mu.Lock() + defer d.mu.Unlock() + d.Manual = m + var res Result + for _, s := range d.sessions { + d.retryPending(s, &res.Records) + } + return res +} diff --git a/internal/decode/nf/fields.go b/internal/decode/nf/fields.go index 0a433b2..caf8835 100644 --- a/internal/decode/nf/fields.go +++ b/internal/decode/nf/fields.go @@ -2,7 +2,10 @@ package nf import ( "encoding/binary" + "fmt" + "math" "net/netip" + "strings" "time" "github.com/githubflyideas/traffic66/internal/flow" @@ -64,6 +67,7 @@ const ( ieSamplingPktSpace = 306 ieSamplingSize = 309 ieSamplingPop = 310 + ieSamplingProb = 311 // samplingProbability, float64 // NetFlow v9 option scope types (RFC 3954 section 6.1). nf9ScopeSystem = 1 @@ -101,6 +105,7 @@ type acc struct { interval, pktInterval, pktSpace uint32 sampSize, sampPop uint32 + prob float64 samplerID uint32 hasSampler bool @@ -228,7 +233,16 @@ func (a *acc) set(f fieldSpec, v []byte, nf9Scope bool) { case ieSysInitMs: a.sysInitMs = int64(wire.Uint(v)) case ieSamplingInterval, ieSamplerRandom: - a.interval = uint32(wire.Uint(v)) + // both may be present; a zero in one must not hide the other + if x := uint32(wire.Uint(v)); x > 0 { + a.interval = x + } + case ieSamplingProb: + if len(v) == 8 { + if p := math.Float64frombits(binary.BigEndian.Uint64(v)); p > 0 && p <= 1 { + a.prob = p + } + } case ieSamplingPktIntvl: a.pktInterval = uint32(wire.Uint(v)) case ieSamplingPktSpace: @@ -251,6 +265,8 @@ func (a *acc) rate() uint32 { return a.sampPop / a.sampSize case a.interval > 0: return a.interval + case a.prob > 0: + return uint32(math.Round(1 / a.prob)) } return 0 } @@ -319,6 +335,7 @@ func parseTemplateSet(s *session, b []byte, src flow.Source, options bool) []uin break } s.templates[id] = t + s.describe(id, t) ids = append(ids, id) // v9 options template records are padded to 4 bytes per set, // not per record; one record per set is the common case. @@ -357,6 +374,7 @@ func parseTemplateSet(s *session, b []byte, src flow.Source, options bool) []uin break } s.templates[id] = t + s.describe(id, t) ids = append(ids, id) } return ids @@ -426,6 +444,7 @@ func (d *Decoder) decodeData(s *session, t *template, body []byte, h msgHeader, default: s.def = rate } + d.byAddr[addr] = rate sampleChanged = true continue } @@ -459,7 +478,7 @@ func (d *Decoder) emit(s *session, a *acc, h msgHeader, addr netip.Addr, domain default: r.Bytes, r.Packets = a.totBytes, a.totPkts } - ref := samplerRef{samplerID: a.samplerID, hasSampler: a.hasSampler, inIf: r.InIf} + ref := samplerRef{samplerID: a.samplerID, hasSampler: a.hasSampler, inIf: r.InIf, outIf: r.OutIf} inRec := a.rate() s.stats.Records++ if r.Bytes > 0 || r.Packets > 0 { @@ -472,7 +491,7 @@ func (d *Decoder) emit(s *session, a *acc, h msgHeader, addr netip.Addr, domain rev.InIf, rev.OutIf = r.OutIf, r.InIf rev.SrcAS, rev.DstAS = r.DstAS, r.SrcAS rev.Bytes, rev.Packets = a.revBytes, a.revPkts - d.resolve(s, rev, samplerRef{samplerID: a.samplerID, hasSampler: a.hasSampler, inIf: rev.InIf}, inRec, now, out) + d.resolve(s, rev, samplerRef{samplerID: a.samplerID, hasSampler: a.hasSampler, inIf: rev.InIf, outIf: rev.OutIf}, inRec, now, out) } } @@ -503,3 +522,29 @@ func (d *Decoder) replayHeld(s *session, id uint16, addr netip.Addr, domain uint d.decodeData(s, t, h.body, h.hdr, addr, domain, now, out) } } + +// describe keeps a short description of a template for the sources page: +// its id, whether it is an options template, and its field ids (scope +// fields marked with s, enterprise fields as pen:id). +func (s *session) describe(id uint16, t *template) { + var b strings.Builder + if t.options { + fmt.Fprintf(&b, "options %d:", id) + } else { + fmt.Fprintf(&b, "%d:", id) + } + for _, f := range t.fields { + b.WriteByte(' ') + if f.scope { + b.WriteByte('s') + } + if f.ent != 0 { + fmt.Fprintf(&b, "%d:", f.ent) + } + fmt.Fprintf(&b, "%d", f.id) + } + if s.tmplDesc == nil { + s.tmplDesc = map[uint16]string{} + } + s.tmplDesc[id] = b.String() +} diff --git a/internal/decode/nf/sampling_test.go b/internal/decode/nf/sampling_test.go new file mode 100644 index 0000000..afe2c42 --- /dev/null +++ b/internal/decode/nf/sampling_test.go @@ -0,0 +1,103 @@ +package nf + +import ( + "encoding/binary" + "math" + "net/netip" + "testing" + + "github.com/githubflyideas/traffic66/internal/encode" +) + +// a minimal IPFIX data template: addresses, ports, interfaces, counts +var ipfixFlowTmpl = encode.Template{ID: 400, Fields: []encode.Field{ + {ID: ieSrcIPv4, Len: 4}, {ID: ieDstIPv4, Len: 4}, {ID: ieSrcPort, Len: 2}, {ID: ieDstPort, Len: 2}, + {ID: ieProtocol, Len: 1}, {ID: ieInIf, Len: 4}, {ID: ieOutIf, Len: 4}, {ID: ieOctetDelta, Len: 8}, {ID: iePacketDelta, Len: 8}, +}} + +func ipfixFlow(in, out uint32) encode.Values { + return encode.Values{encode.A(cli), encode.A(srv), encode.U(50522, 2), encode.U(443, 2), encode.U(6, 1), + encode.U(uint64(in), 4), encode.U(uint64(out), 4), encode.U(1500, 8), encode.U(1, 8)} +} + +func f64(v float64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, math.Float64bits(v)) + return b +} + +// decodeOne sends options (if any) and one data record, returns its multiplier +func decodeOne(t *testing.T, d *Decoder, domain uint32, opt *encode.Template, optVals encode.Values, rec encode.Values) float64 { + t.Helper() + m := encode.NewIPFIX() + m.AddTemplate(ipfixFlowTmpl) + if opt != nil { + m.AddTemplate(*opt) + m.AddData(opt.ID, []encode.Values{optVals}) + } + m.AddData(400, []encode.Values{rec}) + res, err := d.Decode(m.IPFIX(uint32(base.Unix()), 0, domain), exp, base) + if err != nil { + t.Fatal(err) + } + if len(res.Records) != 1 { + return 0 // held, waiting for a rate + } + return res.Records[0].Mult +} + +func TestSamplingForms(t *testing.T) { + // samplingProbability (311) as a float + opt := encode.Template{ID: 401, Scope: []encode.Field{{ID: ieObsDomainID, Len: 4}}, Fields: []encode.Field{{ID: ieSamplingProb, Len: 8}}} + if m := decodeOne(t, NewDecoder(), 1, &opt, encode.Values{encode.U(1, 4), f64(0.001)}, ipfixFlow(1, 2)); m != 1000 { + t.Fatalf("probability: mult %v", m) + } + // rate per interface, configured on the egress interface of the flow + opt = encode.Template{ID: 402, Scope: []encode.Field{{ID: ieInIf, Len: 4}}, Fields: []encode.Field{{ID: ieSamplingInterval, Len: 4}}} + if m := decodeOne(t, NewDecoder(), 1, &opt, encode.Values{encode.U(5, 4), encode.U(1000, 4)}, ipfixFlow(7, 5)); m != 1000 { + t.Fatalf("egress interface: mult %v", m) + } + // a sampler declared with an id the records never name: the only rate + opt = encode.Template{ID: 403, Scope: []encode.Field{{ID: ieObsDomainID, Len: 4}}, + Fields: []encode.Field{{ID: ieSelectorID, Len: 4}, {ID: ieSamplingPktIntvl, Len: 4}, {ID: ieSamplingPktSpace, Len: 4}}} + if m := decodeOne(t, NewDecoder(), 1, &opt, encode.Values{encode.U(1, 4), encode.U(9, 4), encode.U(1, 4), encode.U(511, 4)}, ipfixFlow(1, 2)); m != 512 { + t.Fatalf("only sampler: mult %v", m) + } + // a zero samplingInterval must not hide samplerRandomInterval + opt = encode.Template{ID: 404, Scope: []encode.Field{{ID: ieObsDomainID, Len: 4}}, + Fields: []encode.Field{{ID: ieSamplerRandom, Len: 4}, {ID: ieSamplingInterval, Len: 4}}} + if m := decodeOne(t, NewDecoder(), 1, &opt, encode.Values{encode.U(1, 4), encode.U(100, 4), encode.U(0, 4)}, ipfixFlow(1, 2)); m != 100 { + t.Fatalf("zero interval: mult %v", m) + } +} + +func TestSamplingOtherDomainAndManual(t *testing.T) { + // the rate is declared in one observation domain, the flows come in another + d := NewDecoder() + opt := encode.Template{ID: 405, Scope: []encode.Field{{ID: ieObsDomainID, Len: 4}}, Fields: []encode.Field{{ID: ieSamplingInterval, Len: 4}}} + m := encode.NewIPFIX() + m.AddTemplate(opt) + m.AddData(405, []encode.Values{{encode.U(9, 4), encode.U(2000, 4)}}) + d.Decode(m.IPFIX(uint32(base.Unix()), 0, 9), exp, base) + if got := decodeOne(t, d, 10, nil, nil, ipfixFlow(1, 2)); got != 2000 { + t.Fatalf("other domain: mult %v", got) + } + + // nothing declared: held; a rate set by hand releases it + d = NewDecoder() + if got := decodeOne(t, d, 1, nil, nil, ipfixFlow(1, 2)); got != 0 { + t.Fatalf("expected held, mult %v", got) + } + res := d.SetManual(map[netip.Addr]uint32{exp: 1000}) + if len(res.Records) != 1 || res.Records[0].Mult != 1000 { + t.Fatalf("manual: %+v", res.Records) + } + st := d.Stats()[0] + if st.SamplingState != "manual" || st.Sampling != 1000 || len(st.TemplateInfo) != 1 { + t.Fatalf("stats %+v", st) + } + // a rate the device declares wins over the hand-set one + if got := decodeOne(t, d, 1, &opt, encode.Values{encode.U(1, 4), encode.U(500, 4)}, ipfixFlow(1, 2)); got != 500 { + t.Fatalf("declared over manual: mult %v", got) + } +} diff --git a/internal/enrich/enrich.go b/internal/enrich/enrich.go index 194307f..05f4667 100644 --- a/internal/enrich/enrich.go +++ b/internal/enrich/enrich.go @@ -60,6 +60,7 @@ type Inventory struct { ifaces map[string]Iface // "exporter/ifindex" hosts map[netip.Addr]string unsampled map[netip.Addr]bool + sampling map[netip.Addr]uint32 // sampling=N on device lines snmp []SNMPTarget // autoIfs are interface names learned from the devices (SNMP ifName); // names written in the inventory always win. @@ -113,6 +114,7 @@ func (inv *Inventory) Parse(text string) error { ifs := map[string]Iface{} hosts := map[netip.Addr]string{} uns := map[netip.Addr]bool{} + samp := map[netip.Addr]uint32{} var snmps []SNMPTarget for n, line := range strings.Split(text, "\n") { line = strings.TrimSpace(line) @@ -161,6 +163,14 @@ func (inv *Inventory) Parse(text string) error { uns[a] = true continue } + if v, ok := strings.CutPrefix(strings.ToLower(w), "sampling="); ok { + n, err := strconv.ParseUint(strings.TrimPrefix(v, "1:"), 10, 32) + if err != nil || n == 0 { + return bad("sampling= needs a number such as sampling=1000") + } + samp[a] = uint32(n) + continue + } words = append(words, w) } devs[a] = strings.Join(words, " ") @@ -222,6 +232,7 @@ func (inv *Inventory) Parse(text string) error { sort.SliceStable(nets, func(i, j int) bool { return nets[i].Prefix.Bits() > nets[j].Prefix.Bits() }) inv.mu.Lock() inv.networks, inv.devices, inv.ifaces, inv.hosts, inv.unsampled, inv.snmp, inv.text = nets, devs, ifs, hosts, uns, snmps, text + inv.sampling = samp inv.mu.Unlock() return nil } @@ -334,6 +345,18 @@ func (inv *Inventory) Unsampled() map[netip.Addr]bool { return out } +// Sampling is the hand-set sampling rate of devices (sampling=N), used +// where a device does not declare its own. +func (inv *Inventory) Sampling() map[netip.Addr]uint32 { + inv.mu.RLock() + defer inv.mu.RUnlock() + out := map[netip.Addr]uint32{} + for a, n := range inv.sampling { + out[a] = n + } + return out +} + // SegmentCountries maps network names to the country set for them. func (inv *Inventory) SegmentCountries() map[string]string { inv.mu.RLock() diff --git a/internal/web/static/app.js b/internal/web/static/app.js index 0a61375..aba7f8f 100644 --- a/internal/web/static/app.js +++ b/internal/web/static/app.js @@ -1297,15 +1297,15 @@ views.sources = async (el) => { ['IPtoASN', 'geo.f_iptoasn', 'PDDL 1.0', 'https://iptoasn.com']]; const freeRows = FREE.map(([n, k, lic, url]) => `
| ${t('col.device')} | ${t('col.proto')} | ${t('col.rate')} | ${t('col.sampling')} | ${t('col.lost')} | ${t('col.last')} | ${t('col.status')} |
|---|---|---|---|---|---|---|
| ${t('empty.first')} | ||||||
${t('src.listeners')} ${lis || '—'} ${caps}
+ ${(d.stale || []).length ? `${t('src.stale')} ${d.stale.map(x => `${esc(x.name || x.exporter)} / ${x.domain} · ${esc(x.proto)} · ${esc(ago(Date.parse(x.last_seen)))}`).join(' ')}
` : ''} ${snmp ? `${t('src.snmp')} ${snmp}
` : ''}`)} ${panel('c12', t('geo.title'), t('geo.sub'), `| ${t('geo.col_holds')} | ${t('geo.col_db')} | ${t('geo.col_built')} | ${t('geo.col_use')} |
|---|