تخطَّ إلى المحتوى
netrewind
صندوق أسود للشبكة

البداية

ابْنِه، وشغّله، ثم اكسر شيئًا

البناء والفحص والاختبار تعمل على أيّ نظام — تُشحن المجمّعات ببدائل صامتة لغير لينكس تحديدًا حتى لا تحتاج حلقةُ التطوير القريبة إلى لينكس. أما أن يرصد شيئًا فيحتاج نواة لينكس حقيقية: netlink لحالة الواجهات والجيران والمسارات، وeBPF مع BTF لمآلات الاتصالات.

  1. 01

    ابْنِ

    هدف واحد يتولّى التنسيق والفحص والاختبارات والثنائيَّين معًا. ويبقى CGO مطفأً، فالناتج ثنائي ساكن بلا اعتماديات وقت التشغيل — فمشغّل SQLite مكتوب بـGo خالصة.

    make all
     
  2. 02

    سجِّل

    الخدمة netrewindd هي المسجّل. وقراءةُ إشعارات netlink وإرفاقُ برامج eBPF يحتاجان CAP_NET_ADMIN، وهو في المختبر يعني root.

    وجِّه ‎--rules إلى مجلّد القواعد فيعمل الترابط حيًّا مع وصول الأحداث؛ ومرّر قيمة فارغة فلا يفعل المسجّل إلا التسجيل.

    sudo ./build/netrewindd --db ./var/events.db --rules ./rules
     
  3. 03

    اقرأه

    الأداة netrewind هي أداة الاستعلام. لا تكتب في المخزن أبدًا، فلا بأس بتشغيلها على مسجّل ما زال يعمل.

    ./build/netrewind timeline --last 15m
     

أو شاهده يعمل دون شبكة تكسرها

يبني المختبر التركيبي طوبولوجيا من مساحات أسماء الشبكة وأزواج veth، ويُشغّل المسجّل داخلها، ويحقن أربعة عشر سيناريو — ثلاثة عشر عطلًا ومقطعًا واحدًا من حركة مرور ناجحة عادية — ثم يفتّش السِّجلّ عن كلٍّ منها.

ولا شيء فيه يمسّ الشبكة الحقيقية: فمساحات الأسماء والمسارات وحتى مجموعة قواعد nftables كلُّها خاصّة بكل مساحة أسماء، فلا يستطيع عطلٌ أن يفلت من المختبر.

ويفشل التشغيل إن لم يترك شيءٌ حُقن أثرًا، ويفشل على حدة إن لم يبلغ الترابطُ الاستنتاجَ الصحيح. فامتلاكُ الدليل واستخلاصُ النتيجة إنجازان مختلفان.

sudo make lab
 

ويرفع lab-up وlab-down الطوبولوجيا ويُنزلانها وحدهما، ويحقن inject.sh run <scenario> عطلًا واحدًا في مختبرٍ يعمل.

فخّ واحد يستحقّ المعرفة

الدخول إلى مساحة أسماء شبكة يمنحك مساحة ضمٍّ جديدة أُعيد فيها ضمّ ‎/sys، فيختفي tracefs ولا يمكن إرفاق أيّ tracepoint من eBPF. فلا بدّ من ضمّه داخل الـip netns exec نفسه الذي يُشغّل المسجّل — ولهذا يبدو سكربت المختبر على ما هو عليه.

المسجّل

netrewindd

الخيارالافتراضيالمعنى
--db/var/lib/netrewind/events.dbمسار مخزن الأحداث
--rulesrulesمجلّد قواعد الترابط؛ وتركُه فارغًا يُعطّل الترابط كلّه
--retention168hكم من التاريخ يُحفَظ
--gap-threshold30sغيابٌ أطول من هذا يُسجَّل ثقبًا في السِّجلّ
--observer-idhostnameهويّة هذا المسجّل، تُختم على كل حدث يُنتجه
--metrics-addrمطفأاعرض مقاييس Prometheus هنا، مثل 127.0.0.1:9464
--log-levelinfodebug أو info أو warn أو error

قراءة ما سُجّل

ستة أوامر، و‎--db مشترك بينها جميعًا. والذي تريده أثناء العطل هو what-happened، والذي تريده بعده هو incidents.

netrewind events

اسرد الأحداث المسجَّلة بترتيب الزمن

العرض المسطَّح الذي لا يبدي رأيًا. مُدّ يدك إليه حين تعرف أصلًا ما الذي تبحث عنه وتريده بلا تحرير — أو حين تريد JSON لتمرّره إلى مكان آخر.

  • $ netrewind events --last 10m
  • $ netrewind events --last 1h --family link
  • $ netrewind events --last 24h --host 192.168.20.10 -o json
الخيارالافتراضيالمعنى
--last1hانظر إلى الوراء بهذا القدر من الآن
--since / --untilالآننافذة صريحة بصيغة RFC3339؛ و‎--since يَجُبّ ‎--last
--kindالكلاقصره على هذه الأنواع، مثل link.down
--familyالكلاقصره على هذه العائلات، مثل link,l2
--hostالكلاقصره على كيان واحد، بعنوانه أو باسمه
-o, --outputtabletable أو json
--newest-firstمطفأاعكس الترتيب

netrewind timeline

اقرأ التاريخ المسجَّل سردًا

السِّجلّ نفسه، معروضًا جُملًا لا حقولًا. والخطورة رمزٌ مرسوم كي تنجو من طرفيّة أحادية اللون ومن لقطة شاشة، وإلى هذين ينتهي أمر هذه المخرجات.

  • $ netrewind timeline --last 15m
  • $ netrewind timeline --last 1h --min-severity warn
الخيارالافتراضيالمعنى
--last30mإلى أيّ مدى تقرأ إلى الوراء
--familyالكلاقصره على هذه العائلات
--min-severityinfoinfo أو notice أو warn أو error

netrewind what-happened

أعِد بناء ما جرى حول لحظةٍ ما

السؤال الذي لا يمكن طرحه إلا بعد فوات الأوان. أعطِه جهازًا ووقتًا، فيحسم الجهاز أولًا عبر جدول الهويّات، فيجد الأحداث التي سُجّلت وهو يحمل عنوانًا آخر — ولا يجرّ معه أحداث الجهاز الذي يحمل ذاك العنوان الآن.

  • $ netrewind what-happened --host 192.168.20.10 --at 15:00
  • $ netrewind what-happened --at 2026-08-23T10:43:00Z --window 5m
  • $ netrewind what-happened --host 192.168.20.10 --at -2h
الخيارالافتراضيالمعنى
--hostكل شيءالجهاز الذي تتبعه، بعنوانه أو بعنوانه العتادي
--atnowاللحظة التي تنظر حولها: RFC3339 أو HH:MM أو ‎-2h أو now
--window5mكم تنظر على جانبَي تلك اللحظة

netrewind incidents

اسرد ما خلص إليه الترابط، ومعه السلسلة وراء كل استنتاج

استنتاجات لا أرصاد. وكلُّ واحد منها يحمل السلسلة التي يقوم عليها، والعلاقة المدّعاة عند كل خطوة، ونصيحةً بما تنظر فيه بعدُ.

  • $ netrewind incidents --last 24h
  • $ netrewind incidents --last 1h --rule change-broke-a-path
الخيارالافتراضيالمعنى
--last24hإلى أيّ مدى تنظر إلى الوراء
--ruleالكلحوادث هذه القاعدة وحدها
--min-severityالكلinfo أو notice أو warn أو error
-o, --outputtexttext أو json

netrewind serve

اقرأ السِّجلّ في متصفّح

السِّجلّ نفسه الذي يعرضه سطر الأوامر، معروضًا صفحاتٍ: الحوادث، وخط زمني، وعرضٌ لكل مضيف. يُولَّد على الخادم بلا JavaScript، وللقراءة فقط — فالمخزن دليل، وما يعرض الدليل لا شأن له بتعديله. ويرتبط بالعنوان المحلي لأنه بلا استيثاق، ولأن المسجّل لا ينبغي أن يكون بالغًا من الشبكة التي يراقبها.

  • $ netrewind serve
  • $ netrewind serve --db /var/lib/netrewind/events.db
الخيارالافتراضيالمعنى
--addr127.0.0.1:8464أين يستمع؛ والعنوان المحلي ما لم تُغيّره عامدًا
--observer-idhostnameأيُّ مسجّل يُنسَب إليه هذا العرض

netrewind rules

حمّل قواعد الترابط وافحصها

يتحقّق من المكتبة بالطريقة نفسها التي يتحقّق بها المسجّل، فتُكتشَف القاعدة المكسورة قبل نشرها بدل أن تُكتشَف بأنها لا تُطابق شيئًا أبدًا.

  • $ netrewind rules --dir rules
الخيارالافتراضيالمعنى
--dirrulesمجلّد ملفّات القواعد
netrewind rules --dir rules
19 rules loaded from rules
 
address-changed-hands warn 75% 1 clauses (1 required) window 1m0s
An address moved to a different machine
 
change-broke-a-path error 88% 3 clauses (1 required) window 5m0s
A path that was working stopped working
 
collector-not-watching error 100% 1 clauses (1 required) window 1m0s
The recorder is running but one of its senses is not
 
contested-address error 85% 2 clauses (1 required) window 2m0s
Two machines are claiming the same address
 
default-route-lost error 95% 2 clauses (1 required) window 2m0s
The default route disappeared
 
default-route-moved error 85% 2 clauses (1 required) window 2m0s
The default route was re-pointed
 
gateway-hijack error 90% 3 clauses (1 required) window 3m0s
The default gateway is being answered by a different machine
 
gateway-returned-different error 85% 2 clauses (1 required) window 10m0s
The gateway came back, but as a different machine
 
host-moved-ports warn 70% 1 clauses (1 required) window 1m0s
A machine appeared behind a different interface
 
link-degrading error 90% 2 clauses (1 required) window 10m0s
A link is failing rather than having failed
 
link-down-isolated-hosts warn 80% 2 clauses (2 required) window 1m30s
A link went down and took hosts with it
 
path-mtu-blackhole error 90% 2 clauses (1 required) window 10m0s
A path is silently dropping large packets
 
port-flapping warn 88% 2 clauses (1 required) window 5m0s
A port is flapping
 
reachability-lost error 90% 3 clauses (1 required) window 5m0s
A measured path stopped answering
 
recorder-was-blind warn 100% 1 clauses (1 required) window 1m0s
The record has a hole in it
 
resolver-hijacked warn 80% 3 clauses (1 required) window 5m0s
A machine's resolver changed under it
 
rogue-dhcp-server error 92% 3 clauses (1 required) window 5m0s
A second DHCP server is answering on this segment
 
route-flapping error 85% 2 clauses (1 required) window 5m0s
A route is flapping
 
service-unreachable warn 80% 2 clauses (1 required) window 2m0s
A service stopped answering

المقاييس

المسجّل يُصدّر مقدار ما لم يره

كل أداة رصدٍ تُصدّر أعدادَ ما رأته. تلك هي الأرقام السهلة، وهي مطمئنة على الوجه الخطأ تمامًا: فالمسجّل الذي كفّ عن المراقبة يُبلّغ عن شبكة هادئة هدوءًا بديعًا.

والسلسلتان اللتان تهمّان هنا هما اللتان تعُدّان الثقوب. فـnetrewind_recorder_blind_seconds_total وnetrewind_dropped_events_total تُمكّنان المشغّل من أن يُنذَر بأن السِّجلّ غير جدير بالثقة في مدّةٍ ما، بدل أن يكتشف ذلك أثناء مراجعة الحادثة — حين يكون قد فات أوان فعل أيّ شيء.

وكل سلسلة تُعلَن عند الإقلاع بقيمة صفر، فيعمل الإنذار على الزيادة من أول قراءة بدل أن ينتظر أول سوء يقع.

sudo ./build/netrewindd --db ./var/events.db --metrics-addr 127.0.0.1:9464
 

تُعرَض على ‎/metrics بصيغة Prometheus النصّية. وتركُها فارغة يُعطّلها.

السلسلةالنوعما تَعُدّه
netrewind_recorder_blind_seconds_totalcounterثوانٍ لم يكن المسجّل فيها ناظرًا. وأيّ زيادة تعني أن في السِّجلّ ثقبًا.
netrewind_dropped_events_totalcounterأحداث ضاعت لأنها وصلت أسرع مما يمكن قراءتها، موسومةً بمصدرها.
netrewind_clock_steps_totalcounterقفزات رُصدت في الساعة المدنية. وكلُّ قفزة تُعيد ترتيب الخط الزمني على من يقرؤه بعدُ.
netrewind_events_totalcounterأحداث سُجّلت، بحسب النوع والخطورة. والأحداث المطويّة تَعُدّ وقوعاتها لا أسطرها — فمقياسٌ يعُدّ الأسطر كان سيُبخِس العدد في أسوأ الأوقات بالضبط.
netrewind_incidents_totalcounterحوادث خُلص إليها، بحسب القاعدة والخطورة.
netrewind_collector_upgauge1 ما دام المُجمِّع يعمل، و0 متى توقّف.
netrewind_stored_eventsgaugeالأحداث المحفوظة في المخزن الآن.
netrewind_build_infogaugeنسخة المسجّل العامل ومُعرّف الراصد.

كتابة قاعدة

القاعدة تصف شكلًا واحدًا معروفًا للعطل

القواعد ملفّات لا شيفرة. والمكتبة هي الجزء من هذا المشروع الذي لا يمكن استنساخه — إذ يتراكم من أعطال حقيقية — ومَن قضى ليلةً في تفتيش عطل شبكة يستطيع أن يُسهم فيها دون أن يمسّ Go.

وفي الأسفل ملفّ rules/change-broke-a-path.yaml كاملًا: أطرف قاعدة في المكتبة المشحونة، لأنها التي تطرح السؤال إلى الوراء.

مخرَج مسجَّل
id: change-broke-a-path
title: A path that was working stopped working
severity: error
confidence: 88
window: 300s
root_cause: change
advice: >-
The change named here is the nearest one in time, not a proven cause. Confirm
it against your change record before acting: if it was intended, the breakage
is a side effect nobody planned; if it was not, something changed the network
without going through you.
match:
- as: change
kinds:
- l2.arp_binding_changed
- l2.duplicate_ip
- l3.route_changed
- l3.default_route_changed
- l3.route_removed
- link.down
- policy.rule_changed
- change.config_applied
optional: true
why: >-
This is the last thing that changed on the path before it broke.
- as: breakage
kinds: [flow.first_failure_for_pair]
relation: causes
why: >-
Two machines that had been connecting successfully can no longer complete
a handshake. Whatever else is true, something between them changed.
- as: spread
kinds: [flow.first_failure_for_pair, flow.handshake_fail, l2.neighbor_failed]
optional: true
relation: correlates
why: Other connections started failing in the same window.
rules/change-broke-a-path.yaml
match[0] — as: change, optional: true
هذا البند قبل المرساة، فيُبحث عنه إلى الوراء في الزمن، الأقربُ فالأقرب. وهو ما يجعل القاعدة تسأل «ما الذي تغيّر قُبَيل أن ينكسر هذا» لا «ما الذي تلاه». وهو اختياري، فإن لم يكن في النافذة تغيّرٌ سابق أطلقت القاعدة مع ذلك بسلسلةٍ من حلقة واحدة: يقف العَرَض وحده بدل أن يُعلَّق على تخمين.
match[1] — as: breakage, required
المرساة: البند اللازم الوحيد، والحدث الذي يدفع المحرّك إلى تقييم القاعدة من الأساس. وflow.first_failure_for_pair أقوى إشارة في النظام، ولهذا بُنيت القاعدة حول وصولها.
relation: causes
دعوى في الآلية، وأقوى ما يستطيع بندٌ أن يقوله عمّا قبله. فلا تمدّ يدك إليها إلا حين تستطيع الدفاع عنها أمام زميل مرتاب. وكل بند بعد الأول عليه أن يُصرّح بعلاقته، والمدقّق يرفض ما لا يفعل.
relation: correlates
البند spread لا يدّعي شيئًا في الآلية. اتصالات أخرى أخفقت في النافذة نفسها، وهذا كلّ ما يُقرَّر، ويطبعه سطر الأوامر «وفي الوقت نفسه» لا سهمًا.
root_cause: change
أيَّ بندٍ تُحمّله القاعدةُ التبعة، مُسمًّى بـas الخاص به. والمدقّق يرفض قاعدةً تُحمّل التبعة بندًا ليس فيها.
window: 300s
كم يجوز أن يتباعد البند الأول والأخير. والقاعدة بلا نافذة مرفوضة: فبدونها ستَصِل يومًا حدثين لا صلة بينهما.
advice
ما تنظر فيه بعدُ، لا ما تُغيّره أبدًا. فالمسجّل يرصد ولا يمسّ الشبكة، ولا يأمر أحدًا بأن يمسّها. و«أعد تشغيل الواجهة» كانت ستُتلف الدليل على منفذ يرتجف. وثمّة اختبار يفرض أن لكل قاعدة مشحونة نصيحة.

حقول البند

as
يُسمّي البند ليشير إليه root_cause
kinds
أنواع الأحداث التي تُحقّق هذا البند؛ أيٌّ منها
where
يُضيّق بـattrs.<name> أو min_severity أو subject_kind. وتُقارَن الصفات نصًّا، لأن المُجمِّع قد يكتب قيمة منطقية بينما يُعيد المخزن json.Number
min_count
يشترط عدّة مطابقات. فسقوطُ منفذٍ مرّةً حدَث، وسقوطُ المنفذ نفسه خمس مرّات في خمس دقائق عطل
optional
يتيح للقاعدة أن تُطلق بغير هذا البند. وقبل المرساة يجعله أيضًا بحثًا إلى الوراء
relation
causes أو correlates أو precedes. لازم على كل بند بعد الأول
why
الجملة التي تُسهم بها هذه الخطوة في الرواية. وهي ما يقرؤه المشغّل بدل أسماء الحقول

ما يرفضه المدقّق

لن يُحمّل المحرّك قاعدةً لا يمكن أن تعمل، والفحص نفسه متاح من سطر الأوامر، فتفشل القاعدة على مكتبك بدل أن تفشل بألّا تُطابق شيئًا إلى الأبد.

  • قاعدة بلا نافذة، أو بثقةٍ خارج المدى 1–100.
  • بندٌ لا يُطابق أيّ نوع.
  • بندٌ بعد الأول لا يقول كيف يتّصل بما قبله.
  • قاعدةٌ تُحمّل التبعة بندًا ليس فيها.
  • قاعدةٌ كلُّ بنودها اختيارية — ستُطابق كل شيء، فليست قاعدة.

وقد أمسك آخرُ هذه الفحوص خطأً حقيقيًّا في إحدى القواعد المشحونة هنا، ولولاه لبقيت في المكتبة لا تُطابق شيئًا إلى الأبد.

  • $ netrewind rules --dir rules
  • $ go test ./internal/correlate/...
  • $ sudo lab/inject.sh all

مفاتيح الربط تُبقي الأعطال غير المترابطة متفرّقة

يُسمّي حقولًا يجب أن تتّفق عليها كل الأحداث المطابِقة: subject أو subject.id أو attrs.<name> أو observer. وبدونه يُنسَج عطلان وقعا في اللحظة نفسها على جهازين مختلفين في حادثةٍ واحدة لم تقع قط. ومع correlate_on: [subject] لا يمكن أن يُوصَل منفذٌ يرتجف على سويتش بجارٍ يسقط على سويتش آخر.

كل ما في هذه الصفحة موجود في المستودع.

  • عقد المخطّطdocs/schema.md
  • كتابة القواعدdocs/rules.md
  • تشغيلهdocs/runbook.md
المصدر