
أداة ps، مع لمسة eBPF وسياق الحاويات
psc (حاوية العمليات) هو ماسح سريع للعمليات يستخدم مكررات eBPF و لغة Google CEL للاستعلام عن حالة النظام بدقة مع سياق حاوية كامل.
يستخدم psc مكررات eBPF لقراءة معلومات العمليات وواصفات الملفات مباشرة من هياكل بيانات النواة. هذا الأسلوب يتميز بـ:
/proc. أخطط أيضًا لإضافة طريقة للوصول إلى بعض حقول task_struct حسب الطلب للاستخدامات المتقدمة، لكن هذه مجرد فكرة/proc بالكامل، مما يوفر رؤية لا يمكن اختراقها بواسطة روتكيت طبقة المستخدم أو حيل LD_PRELOADالأدوات التقليدية في لينكس مثل ps و lsof و ss قوية ولكنها غير مرنة. فهي تُخرج تنسيقات ثابتة تتطلب تمريرًا مكثفًا عبر grep و awk و sed:
# الطريقة التقليدية: البحث عن عمليات nginx المملوكة لـ root
ps aux | grep nginx | grep root | grep -v grep
# psc: التعبير بالضبط عما تريد
psc 'process.name == "nginx" && process.user == "root"'
# الطريقة التقليدية: البحث عن عمليات ذات اتصالات ثابتة على المنفذ 443
ss -tnp | grep ESTAB | grep :443 | awk '{print $6}' | cut -d'"' -f2
# psc: تعبير واحد واضح
psc 'socket.state == established && socket.dstPort == 443'
يستخدم psc لغة التعبير الشائعة (CEL) لتصفية العمليات. تعبيرات CEL تُقرأ تقريبًا مثل اللغة الطبيعية، مما يجعل نصوصك البرمجية موثقة ذاتيًا وقابلة للصيانة. لا مزيد من فك رموز السلاسل المعقدة من grep | awk | sed | xargs.
يسمح لك العلم -o بإخراج الحقول التي تحتاجها بالضبط، مما يلغي المعالجة اللاحقة بالكامل:
psc 'socket.state == listen' -o process.name,socket.srcPort
تتوفر أيضًا إعدادات مسبقة للإخراج لطباعة المعلومات الشائعة بسرعة:
psc 'socket.type == tcp && socket.dstPort == 443' -o sockets
الأدوات التقليدية لا تملك مفهوم الحاويات. يتطلب الحصول على معلومات الحاوية تحليل مسارات cgroup، والاستعلام عن أوقات تشغيل الحاويات، وربط PIDs يدويًا:
# الطريقة التقليدية: البحث عن العمليات داخل الحاويات (هش، غير مكتمل)
ps aux | xargs -I{} sh -c 'cat /proc/{}/cgroup 2>/dev/null | grep -q docker && echo {}'
# psc: دعم أصلي للحاويات
psc 'container.runtime == docker'
يستخرج psc سياق الحاوية (المعرف، الاسم، الصورة، وقت التشغيل، الملصقات) تلقائيًا لـ Docker و containerd و CRI-O و Podman. يمكنك تصحيح أي عمليات حاوية وملفاتها واتصالات الشبكة مباشرة من المضيف:
# عرض جميع العمليات في حاوية معينة
psc 'container.name == "my-app"' --tree
# البحث عن الحاويات التي تعمل كـ root
psc 'container.runtime == docker && process.user == "root"'
# سرد الحاويات مع صورها
psc 'container.id != ""' -o process.pid,process.name,container.name,container.image
على Debian/Ubuntu:
sudo apt-get install clang llvm libbpf-dev linux-headers-$(uname -r) linux-tools-$(uname -r)
على Fedora/RHEL:
sudo dnf install clang llvm libbpf-devel kernel-devel bpftool
# إنشاء vmlinux.h (مطلوب مرة واحدة لكل إصدار نواة)
make vmlinux
# بناء البرنامج الثنائي
make build
أو يدويًا:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > bpf/vmlinux.h
go generate ./...
go build -o psc
sudo make install
يتطلب psc صلاحيات الجذر لتحميل برامج eBPF.
# سرد جميع العمليات
sudo psc
# سرد جميع العمليات على شكل شجرة
sudo psc --tree
مرر تعبير CEL كوسيطة أولى لتصفية العمليات:
# التصفية حسب اسم العملية
psc 'process.name == "nginx"'
# التصفية حسب المستخدم
psc 'process.user == "root"'
# التصفية حسب محتوى سطر الأوامر
psc 'process.cmdline.contains("--config")'
# التصفية حسب نطاق PID
psc 'process.pid > 1000 && process.pid < 2000'
# دمج الشروط
psc 'process.name == "bash" || process.name == "zsh"'
# عرض العمليات داخل الحاويات فقط
psc 'container.id != ""'
# التصفية حسب وقت تشغيل الحاوية (الثوابت: docker, containerd, crio, podman)
psc 'container.runtime == docker'
# التصفية حسب اسم الحاوية
psc 'container.name == "nginx"'
# التصفية حسب صورة الحاوية
psc 'container.image.contains("nginx:latest")'
# العرض كشجرة لرؤية التسلسل الهرمي للعمليات في الحاوية
psc 'container.runtime == docker' --tree
غالبًا ما يتطلب فهم سبب وجود عملية ما النظر إلى واصفات الملفات المفتوحة واتصالات الشبكة:
# البحث عن العمليات ذات مقابس TCP في حالة الاستماع
psc 'socket.type == tcp && socket.state == listen'
# البحث عن العمليات ذات الاتصالات الثابتة
psc 'socket.state == established'
# البحث عن العمليات المتصلة بمنفذ معين
psc 'socket.dstPort == 443'
# البحث عن العمليات التي تستخدم مقابس Unix
psc 'socket.family == unix'
# البحث عن العمليات التي لديها ملفات مفتوحة في /etc
psc 'file.path.startsWith("/etc")'
حقول العملية (process.X):
name - اسم العملية (سلسلة نصية)pid - معرف العملية (عدد صحيح)ppid - معرف العملية الأم (عدد صحيح)tid - معرف الخيط (عدد صحيح)euid - معرف المستخدم الفعال (عدد صحيح)ruid - معرف المستخدم الحقيقي (عدد صحيح)suid - معرف المستخدم المحفوظ (عدد صحيح)user - اسم المستخدم (سلسلة نصية)cmdline - سطر الأوامر الكامل (سلسلة نصية)state - حالة العملية (عدد صحيح غير موقع)حقول الصلاحيات (process.capabilities.X):
effective - قناع الصلاحيات الفعال (عدد صحيح غير موقع)permitted - قناع الصلاحيات المسموح بها (عدد صحيح غير موقع)inheritable - قناع الصلاحيات القابلة للتوريث (عدد صحيح غير موقع)حقول مساحات الأسماء (process.namespaces.X):
net - inode مساحة اسم الشبكة (عدد صحيح)pid - inode مساحة اسم PID (عدد صحيح)mnt - inode مساحة اسم التثبيت (عدد صحيح)uts - inode مساحة اسم UTS (عدد صحيح)ipc - inode مساحة اسم IPC (عدد صحيح)cgroup - inode مساحة اسم Cgroup (عدد صحيح)حقول الحاوية (container.X):
id - معرف الحاوية (سلسلة نصية)name - اسم الحاوية (سلسلة نصية)image - صورة الحاوية (سلسلة نصية)runtime - وقت تشغيل الحاوية (سلسلة نصية)labels - ملصقات الحاوية (خريطة)حقول الملف/المقبس (file.X أو socket.X):
path - مسار الملف (سلسلة نصية)fd - رقم واصف الملف (عدد صحيح)srcPort - منفذ المصدر (عدد صحيح)dstPort - منفذ الوجهة (عدد صحيح)type - نوع المقبس (tcp, udp)state - حالة المقبس (للتصفية، استخدم الثوابت مثل listen, established)family - عائلة العنوان (unix, inet, inet6)unixPath - مسار مقبس Unix (سلسلة نصية)fdType - نوع واصف الملف (file_type, socket_type)استخدم هذه بدون علامات اقتباس في التعبيرات:
docker, containerd, crio, podmantcp, udpunix, inet, inet6established, listen, syn_sent, syn_recv, fin_wait1, fin_wait2, , , , , ملاحظة: يستخدم الإخراج أسماء الحالات بنمط
ss:ESTAB,LISTEN,SYN-SENT، إلخ. بالنسبة لمقابس UDP، يظهر فقطUNCONN(غير متصل) أوESTAB(متصل) لأن UDP غير متصل.
يوفر CEL دوال معالجة السلاسل النصية:
.contains("substr") - للتحقق مما إذا كانت السلسلة تحتوي على سلسلة فرعية.startsWith("prefix") - للتحقق مما إذا كانت السلسلة تبدأ ببادئة.endsWith("suffix") - للتحقق مما إذا كانت السلسلة تنتهي بلاحقة--tree, -t - عرض العمليات كشجرة--no-color - تعطيل الإخراج الملون-o, --output - أعمدة إخراج مخصصة (أسماء الحقول مفصولة بفاصلة أو إعداد مسبق)psc fields - سرد جميع حقول CEL المتاحة والثوابت والإعدادات المسبقة للإخراجpsc version - عرض معلومات الإصدار-oيسمح لك العلم -o بتحديد الحقول التي تريد عرضها بالضبط. يمكنك استخدام الإعدادات المسبقة للاستخدامات الشائعة أو تحديد الحقول الفردية.
الإعدادات المسبقة:
sockets - معلومات العملية + تفاصيل المقبس الكاملة (العائلة، النوع، الحالة، العناوين، المنافذ)files - معلومات العملية + تفاصيل واصف الملف (رقم واصف الملف، النوع، المسار)containers - معلومات العملية + تفاصيل الحاوية (الاسم، الصورة، وقت التشغيل)network - عرض شبكة مضغوط (pid, name, type, state, ports)# استخدام إعداد مسبق
psc 'socket.state == listen' -o sockets
psc 'container.id != ""' -o containers
# أو تحديد الحقول الفردية
psc -o process.pid,process.name,process.user
psc 'socket.state == listen' -o process.pid,process.name,socket.srcPort,socket.state
عندما يتضمن الإخراج حقول ملف/مقبس ويطابق المرشح عدة ملفات لكل عملية، يحصل كل تطابق على صف خاص به:
$ psc 'socket.state == listen' -o network
PID NAME TYPE STATE SRCPORT DSTPORT
1234 nginx tcp LISTEN 80 0
1234 nginx tcp LISTEN 443 0
5678 sshd tcp LISTEN 22 0
البحث عن جميع خوادم الويب:
psc 'process.name == "nginx" || process.name == "apache2" || process.name == "httpd"'
البحث عن العمليات التي تستمع على المنافذ المميزة:
psc 'socket.state == listen && socket.srcPort < 1024'
البحث عن العمليات في مساحة اسم شبكة مختلفة (مفيد لفحص الحاويات/البودات):
psc 'process.namespaces.net != 4026531840' -o process.pid,process.name,process.namespaces.net
عرض الصلاحيات للعمليات المميزة:
psc 'process.euid == 0' -o process.pid,process.name,process.capabilities.effective,process.capabilities.permitted
البحث عن العمليات التي رفعت صلاحياتها عبر SUID binaries (معرف المستخدم الحقيقي يختلف عن الفعال):
psc 'process.ruid != process.euid'
البحث عن العمليات ذات الاتصالات بالخدمات الخارجية:
psc 'socket.state == established && socket.dstPort == 443'
عرض اتصالات الشبكة بأعمدة مخصصة:
psc 'socket.state == established' -o process.pid,process.name,socket.srcPort,socket.dstPort,socket.dstAddr
MIT
time_waitcloseclose_waitlast_ackclosingfile_type, socket_type