عندما يصل إلى مكتبك صندوق فيه ثلاثون هاتفاً مكتبياً جديداً، فأمامك طريقان: أن تفتح واجهة الويب لكل هاتف وتكتب فيه رقم التحويلة وكلمة المرور وعنوان المقسم وتعيد ذلك ثلاثين مرة، أو أن تصل الهواتف بالشبكة فتجد إعداداتها وحدها وتسجل في المقسم قبل أن تنتهي من ترتيب الكراتين. والطريق الثاني له اسم هو التهيئة التلقائية Auto Provisioning، وهو ما تفعله كل شركة تدير أكثر من عشرة هواتف، وفيه أيضاً أكثر خطأ أمني يتكرر في هذا المجال: ملف فيه كلمة مرور كل تحويلة، على سيرفر يقرأ منه أي جهاز في الشبكة.
المواضيع في السلسلة:
- مصطلحات الاتصالات الهاتفية VoIP للمبتدئين
- تتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟
- لماذا يتقطع الصوت في مكالمات VoIP؟ قياس Jitter وPacket Loss
- ربط المقسم PBX بشبكة الهاتف عبر SIP Trunk والأرقام DID
- دليل Asterisk العملي: من التحويلة Extension إلى الرد الآلي IVR
- FreePBX: مقسم هاتف PBX كامل تديره من المتصفح
- FreeSWITCH للمنصات الكبيرة ومراكز الاتصال Contact Center
- 3CX أم FreePBX أم FreeSWITCH؟ كيف تختار المقسم PBX
- هواتف SIP المكتبية والتطبيقات: لماذا لا يرن الجوال النائم؟ (الموضوع الحالي)
- مركز اتصال Call Center على Asterisk: الطابور وتقرير SLA
- تأمين المقسم من الاحتيال على المكالمات Toll Fraud
- موازنة الحمل Load Balancing أمام خوادم Asterisk مع Kamailio
وقد شرحنا في مقال المصطلحات ما هو SIP وRTP والمقسم PBX، وفي تتبع مكالمة SIP عبر الشبكة كيف تمر المكالمة عبر NAT، فلن نعيد ذلك هنا، وإنما سوف ننتقل من المقسم إلى الطرف الآخر: الجهاز الذي يمسكه الموظف. وسوف نتناول في هذا المقال ما يلي:
- أنواع الهواتف: الجهاز المكتبي Desk Phone، والهاتف البرمجي Softphone، وتطبيق الجوال، ومتى تختار كل واحد.
- التهيئة التلقائية: كيف يجد الهاتف ملف إعداده عبر DHCP Option 66، وكيف تسمي Yealink وGrandstream وFanvil ملفاتها بحسب وثائقها.
- تجربة عملية في Docker: سيرفر DHCP وسيرفر HTTP للتهيئة ومقسم Asterisk وهاتف يجد إعداده وحده، ثم نسرق كلمة مروره من حاسوب آخر في الشبكة ونغلق الثغرة.
- الهواتف البرمجية: Linphone وZoiper وMicroSIP وbaresip، وتسجيل Linphone من سطر الأوامر.
- أزرار المراقبة BLF وكيف تعمل من الداخل برسائل SUBSCRIBE وNOTIFY.
- مشكلة الجوال النائم: لماذا لا يرن التطبيق عندما تكون الشاشة مطفأة، وكيف تحلها الإشعارات Push Notifications وخوادم مثل Flexisip.
ثلاثة أنواع من الهواتف ومتى تختار كلاً منها
بالنسبة للمقسم لا يوجد فرق بين الأنواع الثلاثة، فكلها عملاء SIP تسجل بتحويلة وكلمة مرور وتستقبل INVITE، والفرق كله في المكان الذي يعيش فيه الهاتف ومن يتحكم في تشغيله، وفيما يلي المقارنة:
| النوع | متى يناسب | ما يجب أن تنتبه له |
|---|---|---|
| الهاتف المكتبي Desk Phone | الاستقبال، ومراكز الاتصال، وأي مكتب يرد على مكالمات كثيرة طوال اليوم | يحتاج كهرباء من الشبكة PoE، ويحتاج التهيئة التلقائية إذا زاد العدد، وتحديث البرنامج الثابت Firmware مسؤوليتك |
| الهاتف البرمجي Softphone | الموظف الذي يعمل على الحاسوب بسماعة رأس Headset، والعمل عن بعد | جودة الصوت تعتمد على السماعة وشبكة البيت، والحاسوب النائم لا يرن |
| تطبيق الجوال | المبيعات والفنيون في الميدان، ومن يريد تحويلته معه خارج المكتب | نظام الجوال يوقف التطبيق في الخلفية، فلا يرن دون إشعارات Push، كما سنرى في آخر المقال |
والهاتف المكتبي يأخذ الكهرباء غالباً من كابل الشبكة نفسه عبر Power over Ethernet، واختصاراً PoE، فلا يحتاج محولاً على المكتب، والمعيار الأول 802.3af يعطي من ال Switch حتى 15.4 واط للمنفذ، وهو يكفي أغلب الهواتف المكتبية، أما الهواتف ذات الشاشات الكبيرة والكاميرات فقد تحتاج 802.3at. لذلك قبل أن تشتري ال Switch اجمع استهلاك الهواتف من ورقة مواصفات كل طراز Datasheet، وقارن المجموع بميزانية الطاقة PoE Budget في ال Switch، وليس بعدد منافذه، والسبب أن Switch فيه 24 منفذ PoE قد لا تكفي ميزانيته لتشغيل 24 هاتفاً معاً.
وبما أن الهاتف أصبح جهازاً على الشبكة، فالأفضل أن تضع الهواتف في شبكة صوت منفصلة Voice VLAN، فتعطيها أولوية في ال Switch، ولا يصل إليها حاسوب الضيف في الشبكة نفسها، وقد شرحنا ذلك في تقسيم الشبكة Network Segmentation، وسوف ترى في التجربة لماذا يهم هذا الفصل.
التهيئة التلقائية Auto Provisioning: كيف يجد الهاتف إعداده؟
الحل البسيط أن تدخل واجهة الويب لكل هاتف وتكتب إعداده بيدك، وهذا يصلح لخمسة هواتف، ولكنه غير مناسب إطلاقاً لخمسين، والسبب ليس الوقت فقط: فعندما تريد تغيير الترميز Codec أو عنوان المقسم أو كلمة مرور الإدارة في كل الهواتف، يجب أن تمر عليها واحداً واحداً، وستجد بعد شهر أن كل هاتف في المكتب يعمل بإعداد مختلف قليلاً. والطريقة الصحيحة أن يكون الإعداد ملفات نصية على سيرفر واحد، والهاتف يقرأها عند كل تشغيل، فتغير الملف مرة واحدة وتعيد تشغيل الهواتف.
ويبقى سؤال واحد: كيف يعرف هاتف خرج للتو من الكرتونة عنوان هذا السيرفر؟ والإجابة أن الهاتف يسأل عنه في أول خطوة يقوم بها على الشبكة، أي طلب العنوان من DHCP. فسيرفر DHCP يستطيع أن يرسل مع العنوان خيارات إضافية، والخيار 66 عرفه RFC 2132 في الأصل باسم «اسم سيرفر TFTP»، ولكن الهواتف اليوم تقبل فيه رابطاً كاملاً بعنوان HTTP أو HTTPS. والصورة التالية تبين الخطوات:

ولاحظ أن الهاتف يطلب ملفين وليس ملفاً واحداً: ملف مشترك فيه ما يتشابه في كل الهواتف، مثل الترميز والتوقيت واللغة وكلمة مرور الإدارة، وملف يحمل اسم عنوان MAC الخاص بالهاتف، فيه التحويلة وكلمة مرورها. وهذا النمط تجده عند الشركات الثلاث الأكثر انتشاراً، مع اختلاف الأسماء، كما في وثائقها الرسمية:
| الشركة | الملف المشترك | ملف الهاتف | كيف يجد الهاتف السيرفر |
|---|---|---|---|
| Yealink | y0000000000XX.cfg، و XX رقمان بحسب الطراز | <MAC>.cfg مثل 00156574b150.cfg | بالترتيب: Zero Touch ثم PnP ثم خيارات DHCP (خيار مخصص ثم 66 ثم 43)، بحسب دليل التهيئة التلقائية من Yealink |
| Grandstream | ملف للطراز مثل cfggrp2614.xml ثم cfg.xml | cfg<MAC>.xml مثل cfg000b820102ab.xml | DHCP 66 أو 43، والإعدادات بأرقام تسمى P-values، بحسب دليل التهيئة من Grandstream |
| Fanvil | ملف للطراز مثل F0V00X600000.cfg | <MAC>.cfg مثل 001565113af8.cfg | بالترتيب: MDNS ثم FDPS ثم DHCP ثم TR069 ثم SIP، بحسب وصف التهيئة من Fanvil |
والكلمات Zero Touch وFDPS في الجدول هي خدمات سحابية تديرها الشركات المصنعة نفسها، وتسمى خدمات إعادة التوجيه Redirection Services: يسجل المورد رقم الهاتف التسلسلي أو عنوان MAC في حسابه عند الشركة، فيسأل الهاتف الجديد سيرفر الشركة عند أول تشغيل، فيوجهه إلى سيرفر التهيئة الخاص بك. وهي مفيدة للهاتف الذي يرسل إلى بيت موظف لا تتحكم في شبكته، ولكن تذكر أنها تضيف طرفاً ثالثاً في طريق إعداد هواتفك.
التجربة: هاتف يجد إعداده وحده
سوف نبني في Docker ما يحدث في المكتب بالضبط: سيرفر DHCP بالبرنامج dnsmasq يرسل الخيار 66، وسيرفر Nginx يقدم ملفات التهيئة، ومقسم Asterisk، ثم «هاتف مكتبي» يبدأ من الصفر. ولأننا لا نملك هاتف Yealink داخل Container، سوف نستخدم الهاتف البرمجي baresip، ونكتب له سكربت تشغيل صغير يقوم بما يقوم به البرنامج الثابت في الهاتف الحقيقي: يطلب العنوان من DHCP، ويقرأ الخيار 66، ويطلب الملف المشترك وملف MAC، ثم يسجل. والملفات نفسها سوف تكون بصيغة baresip وليس بصيغة Yealink، ولكن الخطوات والأسماء والمخاطر هي نفسها.

ال Image وملفات المقسم
لا توجد Image رسمية لهذه الأدوات معاً، لذلك نبني Image واحدة من Ubuntu 24.04 ونثبت إصدار كل حزمة، كما فعلنا في مقال تتبع المكالمة، ونضيف إليها Linphone لسطر الأوامر، وSIPp لتقليد أزرار BLF، وNginx وdnsmasq وعميل DHCP:
FROM ubuntu:24.04
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
asterisk=1:20.6.0~dfsg+~cs6.13.40431414-2build5 \
asterisk-moh-opsound-gsm=2.03-1.1 \
baresip-core=1.0.0-4build14 \
linphone-cli=5.2.0-4.3ubuntu2 \
sip-tester=1:3.7.2-1build3 \
nginx=1.24.0-2ubuntu7.18 \
dnsmasq-base=2.91-0ubuntu0.24.04.2 \
isc-dhcp-client=4.4.3-P1-4ubuntu2 \
tcpdump sngrep sox curl python3 iproute2 iptables iputils-ping procps \
&& rm -rf /var/lib/apt/lists/*docker build -t ard-voip-tools:1 .والمقسم فيه ثلاث تحويلات: 2001 للهاتف المكتبي، و2002 لـ Linphone، و2003 لتطبيق الجوال، وكلها ترث من قالب واحد فيه خيارات NAT التي شرحناها في المقال السابق، والملف asterisk/pjsip.conf كاملاً:
[global]
type=global
user_agent=ArabRoot-Lab-PBX
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
[phone](!)
type=endpoint
context=office
disallow=all
allow=ulaw,alaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
subscribe_context=office
[aor](!)
type=aor
max_contacts=1
remove_existing=yes
qualify_frequency=15
[2001](phone)
auth=2001
aors=2001
callerid="Reception" <2001>
[2001](aor)
[2001]
type=auth
auth_type=userpass
username=2001
password=Prov-2001-x9Kq
[2002](phone)
auth=2002
aors=2002
callerid="Linphone" <2002>
[2002](aor)
[2002]
type=auth
auth_type=userpass
username=2002
password=Lin-2002-p4Rt
[2003](phone)
auth=2003
aors=2003
callerid="Mobile" <2003>
[2003](aor)
[2003]
type=auth
auth_type=userpass
username=2003
password=Mob-2003-z7Wdوخطة الاتصال في asterisk/extensions.conf قصيرة، والسطر الأول فيها هو التلميح Hint الذي سنحتاجه لأزرار BLF لاحقاً:
[office]
exten => _200X,hint,PJSIP/${EXTEN}
exten => _200X,1,NoOp(Call from ${CALLERID(num)} to ${EXTEN})
same => n,Dial(PJSIP/${EXTEN},20)
same => n,Hangup()أما rtp.conf وlogger.conf فهما كما في المقال السابق.
سيرفر التهيئة وسيرفر DHCP
سيرفر التهيئة مجلد عادي على Nginx، فيه ملف مشترك common.cfg بصيغة إعداد baresip:
# Shared by every phone in the office
poll_method epoll
sip_listen 0.0.0.0:5060
audio_source alsa,null
audio_player alsa,null
audio_alert alsa,null
rtp_ports 20000-20019
module_path /usr/lib/baresip/modules
module g711.so
module aufile.so
module menu.so
module_app account.so
module alsa.soوملف للهاتف باسم عنوان MAC الخاص به دون النقطتين، أي 02ab00002001.cfg، وفيه حساب التحويلة وكلمة مرورها:
# Reception desk, MAC 02:ab:00:00:20:01
<sip:[email protected]>;auth_pass=Prov-2001-x9Kq;answermode=autoوإعداد Nginx في prov/nginx-open.conf يقدم المجلد كما هو، ويكتب في السجل عنوان كل جهاز طلب ملفاً والملف الذي طلبه، وسوف نحتاج هذا السجل كثيراً:
events {}
http {
log_format prov '$remote_addr "$request" $status $body_bytes_sent "$http_user_agent"';
access_log /dev/stdout prov;
server {
listen 80;
root /srv/www;
default_type text/plain;
}
}وسيرفر DHCP يأخذ إعداده من dhcp/dnsmasq.conf، والسطر المهم هو الخيار 66:
port=0
interface=eth0
dhcp-range=10.88.60.100,10.88.60.150,255.255.255.0,1h
dhcp-option=option:router,10.88.60.1
dhcp-option=66,"http://10.88.60.5/prov/"
log-dhcpوport=0 يطفئ خدمة DNS في dnsmasq، لأننا نحتاج منه DHCP فقط. الآن نشغل الشبكة والسيرفرات الثلاثة، ونحن في Git Bash على Windows، لذلك نضع MSYS_NO_PATHCONV=1 حتى لا يغير Git Bash المسارات داخل ال Container، ونأخذ مسار المجلد بصيغة Windows من pwd -W:
export MSYS_NO_PATHCONV=1
P=$(pwd -W)
docker network create --subnet 10.88.60.0/24 ard-vp-net
docker run -d --name ard-vp-pbx --network ard-vp-net --ip 10.88.60.10 \
-v "$P/asterisk/pjsip.conf:/etc/asterisk/pjsip.conf" \
-v "$P/asterisk/extensions.conf:/etc/asterisk/extensions.conf" \
-v "$P/asterisk/rtp.conf:/etc/asterisk/rtp.conf" \
-v "$P/asterisk/logger.conf:/etc/asterisk/logger.conf" \
ard-voip-tools:1 asterisk -fvvv
docker run -d --name ard-vp-prov --network ard-vp-net --ip 10.88.60.5 \
-v "$P/prov/nginx-open.conf:/etc/nginx/nginx.conf:ro" -v "$P/prov/www:/srv/www:ro" \
ard-voip-tools:1 nginx -g 'daemon off;'
docker run -d --name ard-vp-dhcp --network ard-vp-net --ip 10.88.60.2 \
--cap-add NET_ADMIN --cap-add NET_RAW \
-v "$P/dhcp/dnsmasq.conf:/etc/dnsmasq.conf:ro" \
ard-voip-tools:1 dnsmasq -k -C /etc/dnsmasq.conf --log-facility=---internal كما في المقال السابق، والسبب أن Docker يسقط في الشبكة الداخلية الحزم الموجهة إلى خارج نطاقها، ومنها حزمة DHCP التي تذهب إلى العنوان العام 255.255.255.255، فإذا جربت DHCP على شبكة --internal فسوف ترى العميل يكرر broadcasting discover ولا يصل شيء إلى السيرفر.الهاتف يبدأ من الصفر
هذا هو سكربت التشغيل phone-boot.sh الذي يقلد البرنامج الثابت في الهاتف:
#!/bin/sh
# What a desk phone does at boot: DHCP, read option 66, fetch the common and per-MAC files, register
MAC=$(tr -d ':' < /sys/class/net/eth0/address)
echo "MAC: $MAC"
echo 'request subnet-mask, routers, tftp-server-name;' > /tmp/dhclient.conf
printf '#!/bin/sh\n[ -n "$new_tftp_server_name" ] && echo "$new_tftp_server_name" > /tmp/prov_url\nexit 0\n' > /tmp/hook.sh
chmod +x /tmp/hook.sh
dhclient -1 -sf /tmp/hook.sh -cf /tmp/dhclient.conf eth0
URL=$(cat /tmp/prov_url)
echo "Provisioning URL from DHCP option 66: $URL"
mkdir -p /conf
UA="ArabRoot-LabPhone/1.0 $MAC"
curl -fsS -A "$UA" "${URL}common.cfg" -o /conf/config || exit 1
curl -fsS -A "$UA" "${URL}${MAC}.cfg" | grep -v '^#' > /conf/accounts || exit 1
echo "Got $(grep -vc '^#' /conf/config) config lines and $(wc -l < /conf/accounts) account(s)"
exec baresip -f /confفي السكربت أعلاه لاحظ التالي:
- عميل DHCP يطلب صراحة الخيار
tftp-server-name، وهو اسم الخيار 66، والسكربت الصغيرhook.shيحفظ قيمته فقط ولا يغير عنوان ال Container، لأن Docker أعطاه عنواناً مسبقاً. - عنوان MAC يأتي من كرت الشبكة نفسه، كما يفعل الهاتف الحقيقي، والملف يطلب بالحروف الصغيرة دون نقطتين.
- ال User-Agent يحمل عنوان MAC، وأغلب الهواتف الحقيقية تفعل شيئاً مشابهاً، فتعرف من سجل السيرفر أي هاتف طلب أي ملف.
ولاحظ أيضاً أننا استخدمنا isc-dhcp-client وليس عميل busybox الصغير udhcpc، والسبب أن udhcpc يتحقق من أن قيمة الخيار 66 اسم سيرفر صالح كما في المعيار، فيرفض الرابط ويطبع بدله الكلمة bad، وهذا يبين أن قبول رابط كامل في هذا الخيار عرف بين الهواتف وليس جزءاً من المعيار. الآن نشغل الهاتف بعنوان MAC محدد:
docker run -d --name ard-vp-desk --network ard-vp-net --ip 10.88.60.21 \
--mac-address 02:ab:00:00:20:01 --cap-add NET_RAW --cap-add NET_ADMIN \
-v "$P/phone-boot.sh:/boot.sh:ro" ard-voip-tools:1 sh /boot.sh
docker logs ard-vp-deskوالمخرج سوف يكون كما يلي، بعد أن حذفنا منه أسطر تحميل الوحدات:
MAC: 02ab00002001
Provisioning URL from DHCP option 66: http://10.88.60.5/prov/
Got 12 config lines and 1 account(s)
baresip v1.0.0 Copyright (C) 2010 - 2020 Alfred E. Heggestad et al.
Local network address: IPv4=eth0|10.88.60.21
Populated 1 account
baresip is ready.
[email protected]: {0/UDP/v4} 200 OK (ArabRoot-Lab-PBX) [1 binding]الهاتف سجل بالتحويلة 2001 دون أن نكتب فيه شيئاً. ولنرى ما حدث من جهة كل سيرفر، نقرأ سجل DHCP ثم سجل Nginx ثم جدول المقسم:
docker logs ard-vp-dhcp 2>&1 | grep -E "DHCP(DISCOVER|OFFER|REQUEST|ACK)|option: 66|requested options"
docker logs ard-vp-prov
docker exec ard-vp-pbx asterisk -rx 'pjsip show contacts'والمخرج سوف يكون كما يلي، بعد أن حذفنا الأسطر المكررة من سجل DHCP:
dnsmasq-dhcp[1]: 2794899461 DHCPDISCOVER(eth0) 02:ab:00:00:20:01
dnsmasq-dhcp[1]: 2794899461 DHCPOFFER(eth0) 10.88.60.150 02:ab:00:00:20:01
dnsmasq-dhcp[1]: 2794899461 requested options: 1:netmask, 3:router, 66:tftp-server
dnsmasq-dhcp[1]: 2794899461 sent size: 24 option: 66 tftp-server http://10.88.60.5/prov/
dnsmasq-dhcp[1]: 2794899461 DHCPREQUEST(eth0) 10.88.60.150 02:ab:00:00:20:01
dnsmasq-dhcp[1]: 2794899461 DHCPACK(eth0) 10.88.60.150 02:ab:00:00:20:01
10.88.60.21 "GET /prov/common.cfg HTTP/1.1" 200 373 "ArabRoot-LabPhone/1.0 02ab00002001"
10.88.60.21 "GET /prov/02ab00002001.cfg HTTP/1.1" 200 104 "ArabRoot-LabPhone/1.0 02ab00002001"
Contact: 2001/sip:[email protected]:5060 7b7c213434 Avail 0.610في المخرج أعلاه لاحظ التالي:
- الهاتف طلب في رسالته الخيار
66:tftp-server، والسيرفر أرسله في العرض OFFER وفي التأكيد ACK، فالخيار يصل فقط لمن يطلبه، وهذا سبب شائع لهاتف لا يجد سيرفر التهيئة: الخيار مكتوب في السيرفر برقم آخر غير الذي يطلبه الهاتف. - سجل Nginx فيه الطلبان بالترتيب: الملف المشترك ثم ملف MAC، وهذا السجل هو أول مكان تنظر فيه عندما لا يأخذ هاتف إعداده.
- المقسم يرى التحويلة
Avail، أي أنها سجلت وترد على الفحص الدوري.
هاتف لا يعرفه السيرفر
الآن نكسر الإعداد عمداً: نشغل هاتفاً ثانياً بعنوان MAC لم نجهز له ملفاً، وهذا ما يحدث في الواقع عندما يستبدل الفني هاتفاً معطلاً بآخر من المخزن دون أن يخبر من يدير المقسم:
docker run -d --name ard-vp-desk2 --network ard-vp-net --ip 10.88.60.25 \
--mac-address 02:ab:00:00:20:99 --cap-add NET_RAW --cap-add NET_ADMIN \
-v "$P/phone-boot.sh:/boot.sh:ro" ard-voip-tools:1 sh /boot.sh
docker logs ard-vp-desk2
docker logs ard-vp-prov 2>&1 | tail -2والمخرج سوف يكون كما يلي:
MAC: 02ab00002099
Provisioning URL from DHCP option 66: http://10.88.60.5/prov/
curl: (22) The requested URL returned error: 404
2026/10/20 08:16:13 [error] 8#8: *4 open() "/srv/www/prov/02ab00002099.cfg" failed (2: No such file or directory), client: 10.88.60.25, server: , request: "GET /prov/02ab00002099.cfg HTTP/1.1", host:
10.88.60.25 "GET /prov/02ab00002099.cfg HTTP/1.1" 404 162 "ArabRoot-LabPhone/1.0 02ab00002099"الهاتف الحقيقي في هذه الحالة لا يتوقف كما توقف سكربتنا، وإنما يكمل بالملف المشترك وحده، فيظهر للموظف هاتف يعمل وشاشته مضيئة ولكن دون تحويلة. لذلك راقب في سجل سيرفر التهيئة طلبات 404 لملفات MAC، فكل سطر منها هاتف على مكتب أحدهم لا يرن.
ملف التهيئة فيه كلمة المرور: من يستطيع قراءته؟
افتح ملف 02ab00002001.cfg مرة أخرى، وستجد فيه كلمة مرور التحويلة كما هي، وفي الهواتف الحقيقية يحمل الملف المشترك أيضاً كلمة مرور واجهة الإدارة. والسؤال: من يستطيع طلب هذا الملف غير الهاتف؟ والإجابة في إعدادنا الحالي: أي جهاز يصل إلى السيرفر. وعناوين MAC ليست سراً، فأول ستة أرقام منها ثابتة لكل شركة مصنعة، ويكفي أن تعرف عنوان هاتف واحد في المكتب حتى تجرب ما حوله، لذلك نجرب من «حاسوب» في الشبكة نفسها أن يطلب 21 عنواناً متتالياً:
docker run --rm --name ard-vp-laptop --network ard-vp-net --ip 10.88.60.40 ard-voip-tools:1 sh -c '
for i in $(seq 1990 2010); do
code=$(curl -s -o /tmp/f -w "%{http_code}" http://10.88.60.5/prov/02ab0000$i.cfg)
[ "$code" = 200 ] && { echo "found 02ab0000$i.cfg:"; cat /tmp/f; }
done; echo "tried 21 addresses"'والمخرج سوف يكون كما يلي:
found 02ab00002001.cfg:
# Reception desk, MAC 02:ab:00:00:20:01
<sip:[email protected]>;auth_pass=Prov-2001-x9Kq;answermode=auto
tried 21 addressesوبهذا الشكل يحصل أي شخص في الشبكة على تحويلة الاستقبال وكلمة مرورها في أقل من ثانية، ويستطيع أن يسجل بها من حاسوبه ويجري مكالمات باسمها، وإذا كان سيرفر التهيئة مكشوفاً على الإنترنت حتى تصل إليه هواتف الموظفين في بيوتهم فالمشكلة أكبر بكثير، لأن المكالمات الخارجية على حسابك.
والحل أن تجمع أكثر من طبقة، لأن كل طبقة وحدها يمكن تجاوزها:
- شبكة الصوت المنفصلة Voice VLAN: لا يصل إلى سيرفر التهيئة إلا أجهزة شبكة الهواتف، فلا يطلب حاسوب الضيف شيئاً.
- اسم مستخدم وكلمة مرور على السيرفر: الهاتف يرسلهما مع الطلب، ومن لا يعرفهما يستلم 401.
- HTTPS: حتى لا تمر كلمة مرور السيرفر وملفات الإعداد نصاً واضحاً في الشبكة.
- سجل السيرفر: راقب الطلبات المتتالية لعناوين MAC من عنوان IP واحد، فهي علامة واضحة على من يبحث.
ونطبق الآن الطبقة الثانية. نضيف كلمة مرور في prov/prov.htpasswd بالأمر openssl passwd -apr1، ونغير إعداد Nginx إلى prov/nginx-locked.conf:
events {}
http {
log_format prov '$remote_addr "$request" $status $body_bytes_sent "$http_user_agent"';
access_log /dev/stdout prov;
server {
listen 80;
root /srv/www;
default_type text/plain;
location /prov/ {
# phones must send the provisioning password
auth_basic "phones";
auth_basic_user_file /etc/nginx/prov.htpasswd;
# never list the folder
autoindex off;
}
}
}echo "prov:$(openssl passwd -apr1 'Prov-Lab-7Qe2')" > prov/prov.htpasswdوالهاتف يأخذ اسم المستخدم وكلمة المرور من الرابط نفسه في الخيار 66، فنكتب في dhcp/dnsmasq-locked.conf:
dhcp-option=66,"http://prov:[email protected]/prov/"ثم نعيد تشغيل السيرفرين بالإعداد الجديد، ونجرب من الحاسوب ومن الهاتف:
docker rm -f ard-vp-prov ard-vp-dhcp ard-vp-desk ard-vp-desk2
docker run -d --name ard-vp-prov --network ard-vp-net --ip 10.88.60.5 \
-v "$P/prov/nginx-locked.conf:/etc/nginx/nginx.conf:ro" \
-v "$P/prov/prov.htpasswd:/etc/nginx/prov.htpasswd:ro" -v "$P/prov/www:/srv/www:ro" \
ard-voip-tools:1 nginx -g 'daemon off;'
docker run -d --name ard-vp-dhcp --network ard-vp-net --ip 10.88.60.2 \
--cap-add NET_ADMIN --cap-add NET_RAW \
-v "$P/dhcp/dnsmasq-locked.conf:/etc/dnsmasq.conf:ro" \
ard-voip-tools:1 dnsmasq -k -C /etc/dnsmasq.conf --log-facility=-
docker run --rm --network ard-vp-net --ip 10.88.60.40 ard-voip-tools:1 \
curl -s -o /dev/null -w "laptop: %{http_code}\n" http://10.88.60.5/prov/02ab00002001.cfg
docker run -d --name ard-vp-desk --network ard-vp-net --ip 10.88.60.21 \
--mac-address 02:ab:00:00:20:01 --cap-add NET_RAW --cap-add NET_ADMIN \
-v "$P/phone-boot.sh:/boot.sh:ro" ard-voip-tools:1 sh /boot.sh
docker logs ard-vp-desk 2>&1 | grep -E "MAC|URL|Got|200 OK"
docker logs ard-vp-provوالمخرج سوف يكون كما يلي:
laptop: 401
MAC: 02ab00002001
Provisioning URL from DHCP option 66: http://prov:[email protected]/prov/
Got 12 config lines and 1 account(s)
[email protected]: {0/UDP/v4} 200 OK (ArabRoot-Lab-PBX) [1 binding]
10.88.60.40 "GET /prov/02ab00002001.cfg HTTP/1.1" 401 188 "curl/8.5.0"
10.88.60.21 "GET /prov/common.cfg HTTP/1.1" 200 373 "ArabRoot-LabPhone/1.0 02ab00002001"
10.88.60.21 "GET /prov/02ab00002001.cfg HTTP/1.1" 200 104 "ArabRoot-LabPhone/1.0 02ab00002001"الحاسوب استلم 401، والهاتف أخذ إعداده وسجل كما كان. ولكن انظر إلى السطر الثاني: كلمة مرور السيرفر نفسها تصل إلى الهاتف عبر DHCP، وDHCP يرسلها لأي جهاز يطلب عنواناً في الشبكة نفسها ويطلب الخيار 66، أي أن كلمة المرور هذه تحمي من الطلب العشوائي ومن الإنترنت، ولا تحمي من شخص في شبكة الهواتف نفسها يعرف ما يفعل. لذلك لا تقم بالاعتماد عليها وحدها، والسبب أن الحماية الحقيقية هنا هي فصل شبكة الصوت، ثم HTTPS، ثم مراقبة السجل، كما في القائمة أعلاه.
الهواتف البرمجية Softphones: أيها تختار؟
الهاتف البرمجي برنامج يقوم بما يقوم به الهاتف المكتبي على الحاسوب أو الجوال، وفيما يلي أشهرها ممن يعمل مع أي مقسم SIP، مع ترخيص كل منها كما في صفحاته الرسمية عند كتابة المقال في أكتوبر 2026:
| البرنامج | الأنظمة | الترخيص والسعر | يناسب |
|---|---|---|---|
| Linphone | Windows وmacOS وLinux وiOS وAndroid | تطبيق Linphone Desktop بترخيص GPLv3، والمكتبة liblinphone بترخيص AGPLv3 أو ترخيص تجاري لمن يبني عليها تطبيقاً مغلقاً | من يريد برنامجاً واحداً مفتوح المصدر على كل الأنظمة، أو يبني تطبيق اتصال خاصاً بشركته |
| Zoiper | Windows وmacOS وLinux والجوال | نسخة مجانية للاستخدام غير التجاري فقط، ونسخة Zoiper 5 Pro بشراء واحد ب €59.95 بحسب صفحة الشراء، وفيها التهيئة التلقائية | الشركات التي تريد برنامجاً مغلقاً مدعوماً ومعروفاً عند المستخدمين |
| MicroSIP | Windows فقط | مفتوح المصدر بترخيص GPL v2، ومجاني | أجهزة Windows في المكاتب، وهو خفيف جداً |
| baresip | Linux وغيره، من سطر الأوامر | مفتوح المصدر بترخيص BSD | الاختبار والأجهزة المدمجة Embedded والتجارب مثل تجربتنا |
ولاحظ أن ترخيص Zoiper المجاني يمنع الاستخدام التجاري، فإذا وزعته على موظفي شركتك فأنت تحتاج النسخة المدفوعة، وهذا تفصيل يغفل عنه كثيرون عندما يرسلون رابط التحميل للموظفين.
تسجيل Linphone من سطر الأوامر
Linphone له نسخة لسطر الأوامر اسمها linphonec، وهي مفيدة لاختبار مقسم من سيرفر دون واجهة رسومية، ولتجربة ما يحدث خلف الشاشة في التطبيق الرسومي، لأنهما يعتمدان على المكتبة نفسها liblinphone. والحساب يكتب في ملف إعداد بصيغة Linphone، ونضعه في linphone/linphonerc:
[sip]
sip_port=5060
default_proxy=0
[auth_info_0]
username=2002
passwd=Lin-2002-p4Rt
domain=10.88.60.10
[proxy_0]
reg_proxy=<sip:10.88.60.10;transport=udp>
reg_identity=sip:[email protected]
reg_expires=600
reg_sendregister=1وlinphonec برنامج تفاعلي يقرأ أوامره من لوحة المفاتيح، لذلك نشغله في Container يقرأ من ملف FIFO، فنرسل له الأوامر لاحقاً ب docker exec:
docker run -d --name ard-vp-linphone --network ard-vp-net --ip 10.88.60.22 \
-v "$P/linphone:/conf:ro" ard-voip-tools:1 \
sh -c 'cp /conf/linphonerc /root/.linphonerc; mkfifo /tmp/cmd; tail -f /tmp/cmd | linphonec -c /root/.linphonerc'
docker exec ard-vp-linphone sh -c 'echo "status register" > /tmp/cmd'
docker logs ard-vp-linphone 2>&1 | grep -E "database|registered"والمخرج سوف يكون كما يلي:
2026-10-20 08:16:52:920 liblinphone-error-Unable to open linphone database with uri "/root/.local/share/linphone/linphone.db" and backend Sqlite3
registered=-1البرنامج يعمل ويرد على الأوامر، ولكنه لم يرسل أي رسالة REGISTER إلى المقسم، وقد تأكدنا من ذلك بالتقاط tcpdump على المقسم فلم نجد أي حزمة من 10.88.60.22. والسبب في السطر الأول: liblinphone تحتاج مجلداً قابلاً للكتابة تحفظ فيه قاعدة بياناتها، والمجلد ~/.local/share/linphone غير موجود في ال Container، ودون هذه القاعدة لا يكمل البرنامج تشغيل الحسابات. والحل أن ننشئ المجلد قبل التشغيل:
docker rm -f ard-vp-linphone
docker run -d --name ard-vp-linphone --network ard-vp-net --ip 10.88.60.22 \
-v "$P/linphone:/conf:ro" ard-voip-tools:1 \
sh -c 'mkdir -p /root/.local/share/linphone; cp /conf/linphonerc /root/.linphonerc; mkfifo /tmp/cmd; tail -f /tmp/cmd | linphonec -c /root/.linphonerc'
docker exec ard-vp-linphone sh -c 'echo "autoanswer enable" > /tmp/cmd; echo "status register" > /tmp/cmd'
docker logs ard-vp-linphone 2>&1 | grep -E "database|registered|Auto answer"
docker exec ard-vp-pbx asterisk -rx 'pjsip show contacts'والمخرج سوف يكون كما يلي:
Auto answer enabled.
registered, identity=sip:[email protected] duration=600
Contact: 2001/sip:[email protected]:5060 bf0f18ab82 Avail 0.672
Contact: 2002/sip:[email protected]:5060 150deb6fcf Avail 35.938والدرس هنا يتجاوز Linphone: عندما يقول البرنامج إنه يعمل ولا يظهر في المقسم، فلا تقم بتغيير كلمة المرور وعنوان السيرفر عشوائياً، وإنما التقط الحزم على المقسم أولاً، فإذا لم تصل أي رسالة REGISTER فالمشكلة في الجهاز نفسه وليست في المقسم ولا في الحساب.
أزرار المراقبة BLF: كيف يعرف الهاتف أن زميلك في مكالمة؟
على هاتف موظف الاستقبال صف من الأزرار عليها أسماء الزملاء، وكل زر فيه ضوء: أخضر إذا كان الزميل متاحاً، وأحمر إذا كان في مكالمة، ويومض إذا كان هاتفه يرن، فيضغط الزر ليحول المكالمة أو ليرد عنه. وهذه الأزرار اسمها Busy Lamp Field، واختصاراً BLF، وهي من أهم أسباب اختيار الهاتف المكتبي على البرنامج في الاستقبال. والفكرة أن الهاتف لا يسأل عن حالة زميله كل ثانية، وإنما يشترك فيها مرة واحدة برسالة SUBSCRIBE، فيرسل له المقسم رسالة NOTIFY عند كل تغيير، وهذه الآلية هي حزمة الأحداث Event Package باسم dialog في RFC 4235.

وفي Asterisk يربط التلميح Hint رقم التحويلة بحالة الجهاز الذي خلفها، وهو السطر الأول في خطة الاتصال: exten => _200X,hint,PJSIP/${EXTEN}، والخيار subscribe_context=office في القالب يحدد أين يبحث المقسم عن التلميح عندما يصله اشتراك، كما في وثائق Asterisk للاشتراك في الحالة وشرح التلميحات. ولكي نرى الرسائل نفسها نقلد زر BLF واحداً بالأداة SIPp، وهي أداة لاختبار SIP تنفذ سيناريو مكتوباً بصيغة XML، وهذا هو السيناريو blf.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- What a desk phone does for one BLF key: SUBSCRIBE to the dialog state of 2002 -->
<scenario name="blf">
<send retrans="500">
<![CDATA[
SUBSCRIBE sip:2002@[remote_ip] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: <sip:2001@[remote_ip]>;tag=[pid]blf
To: <sip:2002@[remote_ip]>
Call-ID: [call_id]
CSeq: 1 SUBSCRIBE
Contact: <sip:2001@[local_ip]:[local_port]>
Event: dialog
Accept: application/dialog-info+xml
Expires: 120
Max-Forwards: 70
Content-Length: 0
]]>
</send>
<recv response="401" auth="true"/>
<send retrans="500">
<![CDATA[
SUBSCRIBE sip:2002@[remote_ip] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: <sip:2001@[remote_ip]>;tag=[pid]blf
To: <sip:2002@[remote_ip]>
Call-ID: [call_id]
CSeq: 2 SUBSCRIBE
Contact: <sip:2001@[local_ip]:[local_port]>
[authentication username=2001 password=Prov-2001-x9Kq]
Event: dialog
Accept: application/dialog-info+xml
Expires: 120
Max-Forwards: 70
Content-Length: 0
]]>
</send>
<recv response="200"/>
<label id="wait"/>
<recv request="NOTIFY" timeout="40000" ontimeout="stop"/>
<send next="wait">
<![CDATA[
SIP/2.0 200 OK
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Content-Length: 0
]]>
</send>
<label id="stop"/>
</scenario>في السيناريو أعلاه لاحظ التالي:
- الاشتراك يحتاج مصادقة مثل التسجيل، فالمقسم يرد أولاً ب 401، والسيناريو يعيد الطلب ببيانات التحويلة 2001، وإلا استطاع أي شخص أن يراقب حالة كل التحويلات.
Event: dialogهو ما تطلبه أزرار BLF في الهواتف المكتبية، وAsterisk يدعم أيضاً الحدثpresenceالذي تستخدمه بعض البرامج لقائمة الأسماء.- الحلقة بين
waitوstopترد على كل NOTIFY ب 200 OK، حتى يمر 40 ثانية دون إشعار جديد.
نشغل الاشتراك، ثم نتصل بالتحويلة 2002، أي Linphone الذي فعلنا فيه الرد التلقائي، من سطر أوامر المقسم، ونقرأ حالة التلميح أثناء المكالمة وبعدها:
docker run -d --name ard-vp-blf --network ard-vp-net --ip 10.88.60.23 \
-v "$P/blf.xml:/blf.xml:ro" ard-voip-tools:1 \
sh -c 'cd /tmp; sipp 10.88.60.10 -sf /blf.xml -m 1 -nostdin -trace_msg >/dev/null 2>&1; sleep 3600'
docker exec ard-vp-pbx asterisk -rx 'pjsip show subscriptions inbound'
docker exec ard-vp-pbx asterisk -rx 'channel originate PJSIP/2002 application Wait 6'
docker exec ard-vp-pbx asterisk -rx 'core show hints' | grep '2002@office'
docker exec ard-vp-blf sh -c 'grep -hE "^(NOTIFY|SUBSCRIBE|SIP/2.0)|<state|Subscription-State" /tmp/*_messages.log'والمخرج سوف يكون كما يلي، وقد جمعنا فيه سطر التلميح أثناء المكالمة وبعدها:
Endpoint: 2001/"Reception" <2001>
Resource: 2002/dialog
Expiry: 114 [email protected]
1 active subscriptions
2002@office : PJSIP/2002 State:InUse Presence:not_set Watchers 1
2002@office : PJSIP/2002 State:Idle Presence:not_set Watchers 1
SUBSCRIBE sip:[email protected] SIP/2.0
SIP/2.0 401 Unauthorized
SUBSCRIBE sip:[email protected] SIP/2.0
SIP/2.0 200 OK
NOTIFY sip:[email protected]:5060 SIP/2.0
Subscription-State: active;expires=119
<state>terminated</state>
SIP/2.0 200 OK
NOTIFY sip:[email protected]:5060 SIP/2.0
Subscription-State: active;expires=107
<state>early</state>
SIP/2.0 200 OK
NOTIFY sip:[email protected]:5060 SIP/2.0
Subscription-State: active;expires=107
<state>confirmed</state>
SIP/2.0 200 OK
NOTIFY sip:[email protected]:5060 SIP/2.0
Subscription-State: active;expires=101
<state>terminated</state>
SIP/2.0 200 OKفي المخرج أعلاه لاحظ التالي:
- المقسم يعرف الاشتراك ومن صاحبه، و
Watchers 1في التلميح تعني أن هاتفاً واحداً يراقب هذه التحويلة. - أول NOTIFY يصل مباشرة بعد الاشتراك بالحالة الحالية
terminated، أي لا توجد مكالمة، فيضيء الزر أخضر. - ثم
earlyعندما بدأ هاتف Linphone يرن، فيومض الزر، وconfirmedبعد الرد، فيصبح أحمر، ثمterminatedبعد انتهاء المكالمة بست ثوان. expiresتتناقص، لأن الاشتراك مدته 120 ثانية، والهاتف الحقيقي يجدده قبل أن تنتهي، فإذا لم يجدده يتوقف الضوء عن التغير دون أي رسالة خطأ على الشاشة.
وجسم الرسالة NOTIFY نفسها بصيغة XML كما يعرفها المعيار، وهذا مثال من الالتقاط أثناء المكالمة:
<?xml version="1.0" encoding="UTF-8"?>
<dialog-info xmlns="urn:ietf:params:xml:ns:dialog-info" version="2" state="full" entity="sip:[email protected]">
<dialog id="2002">
<state>confirmed</state>
</dialog>
</dialog-info>ولاحظ أن كل زر BLF اشتراك منفصل يجدده الهاتف بشكل دوري، فهاتف استقبال عليه 40 زراً يعني 40 اشتراكاً، ومئة هاتف من هذا النوع تعني آلاف الاشتراكات على المقسم، لذلك عند تخطيط المقسم لمكتب كبير احسب الاشتراكات مع عدد الهواتف.
لماذا لا يرن تطبيق الجوال وهو نائم؟
هذه أكثر شكوى تسمعها بعد أن توزع تطبيق SIP على الموظفين: «التطبيق يعمل عندما أفتحه، ولكن عندما تكون الشاشة مطفأة لا يرن، والمتصل يسمع أن لا أحد يرد». والسبب ليس في المقسم ولا في التطبيق، وإنما في نظام الجوال: فبعد أن ينتقل التطبيق إلى الخلفية يوقفه iOS أو Android لتوفير البطارية، ويقطع عنه الشبكة، فلا يوجد برنامج يستقبل رسالة INVITE. وصفحة Android عن وضع Doze تقول صراحة إنه يعلق وصول التطبيقات إلى الشبكة.
نجرب ذلك: تطبيق متوقف وتسجيل ما زال صالحاً
لا نملك iPhone في Docker، ولكننا نستطيع تقليد ما يفعله النظام بالتطبيق بدقة: الأمر docker pause يجمد كل البرامج في ال Container دون أن يغلقها، فيبقى عنوانها ومنفذها ولكن لا يقرأ أحد الحزم، وهذا بالضبط حال التطبيق المعلق. نشغل تطبيق «الجوال» بالتحويلة 2003، ثم نجمده ونتصل به:
docker run -d --name ard-vp-mobile --network ard-vp-net --ip 10.88.60.24 \
-v "$P/mobile:/conf:ro" ard-voip-tools:1 baresip -f /conf
docker exec ard-vp-pbx asterisk -rx 'pjsip show contacts' | grep 2003
docker pause ard-vp-mobile
docker exec -d ard-vp-pbx timeout 26 tcpdump -i eth0 -n -s0 -w /tmp/sleep.pcap host 10.88.60.24
docker exec ard-vp-pbx asterisk -rx 'channel originate Local/2003@office application Wait 1'
docker exec ard-vp-pbx sh -c 'tcpdump -r /tmp/sleep.pcap -n 2>/dev/null' | grep -E 'INVITE|CANCEL|OPTIONS'
docker exec ard-vp-pbx asterisk -rx 'pjsip show contacts' | grep 2003والمجلد mobile فيه ملف config مثل الملف المشترك، وملف accounts فيه السطر <sip:[email protected]>;auth_pass=Mob-2003-z7Wd;answermode=auto;regint=600. والمخرج سوف يكون كما يلي، بعد أن حذفنا بعض أسطر OPTIONS المكررة:
Contact: 2003/sip:[email protected]:5060 4224f1287b Avail 1.210
08:18:03.112387 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
08:18:03.599420 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
08:18:04.600108 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
08:18:05.989404 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: OPTIONS sip:[email protected]:5060 SIP/2.0
08:18:06.599265 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
08:18:10.602064 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
08:18:18.599963 IP 10.88.60.10.5060 > 10.88.60.24.5060: SIP: INVITE sip:[email protected]:5060 SIP/2.0
Contact: 2003/sip:[email protected]:5060 4224f1287b Unavail nanفي المخرج أعلاه لاحظ التالي:
- المقسم أرسل INVITE ست مرات بالفواصل المتضاعفة نفسها التي رأيناها في المقال السابق، ولم يأت أي رد، ولا حتى
100 Trying، ثم انتهت مهلةDialبعد 20 ثانية. - لا توجد رسالة CANCEL، والسبب أن RFC 3261 يمنع إرسال CANCEL قبل أن يصل أي رد مؤقت، فالمقسم يترك الطلب يموت وحده.
- التحويلة ما زالت في جدول المقسم لأن تسجيلها صالح لعشر دقائق، والذي كشف أن التطبيق لا يرد هو الفحص الدوري
qualify_frequencyفقط، فأصبحتUnavail. ودون هذا الفحص كانت ستبدو متاحة تماماً.
والأغرب ما يحدث عندما «يستيقظ» التطبيق، فنفك التجميد ونقرأ سجله:
docker unpause ard-vp-mobile
docker logs ard-vp-mobile 2>&1 | grep -E "call:|session closed"call: answering call on line 1 from sip:[email protected] with 200
sip:[email protected]: session closed: Connection reset by peerالتطبيق وجد رسالة INVITE قديمة تنتظره في ذاكرة الشبكة، فرد عليها تلقائياً، ولكن المقسم كان قد أنهى هذه المكالمة قبل دقيقة، فرفضها مباشرة. وفي الجوال الحقيقي يرى الموظف أحياناً شيئاً قريباً من هذا: مكالمة فائتة تظهر عندما يفتح الجوال، بعد أن يئس المتصل.
الحل: إشعار Push يوقظ التطبيق قبل المكالمة
الحل البسيط أن تطلب من الموظف أن يمنع النظام من إيقاف التطبيق في إعدادات البطارية، وهذا لا يصلح، فبعض الأنظمة لا تسمح به أصلاً، والتطبيق الذي يبقى مستيقظاً يستنزف البطارية. والحل الذي تعتمده تطبيقات الاتصال كلها أن يوقظ أحد التطبيق عند وصول المكالمة، ولا يملك أحد أن يوقظ تطبيقاً معلقاً إلا خدمة الإشعارات في نظام الجوال نفسه: Apple Push Notification service في iOS، وFirebase Cloud Messaging في Android. وقد شرحنا كيف تعمل هذه الخدمات عموماً في كيف تعمل الإشعارات الفورية Push Notifications. والصورة التالية تبين ترتيب الخطوات:

والمعيار الذي يصف هذا هو RFC 8599 الصادر في 2019، وفيه يضيف التطبيق عند التسجيل معاملات في ترويسة Contact تحمل معرف الإشعارات الخاص بالجهاز، فيعرف الخادم كيف يوقظه. ولكي يعمل هذا يحتاج الخادم أن يحتفظ بالمكالمة عنده بدل أن يرسلها مباشرة إلى عنوان لا يرد، ثم يمررها بعد أن يسجل التطبيق من جديد، وهذا ما لا يفعله المقسم العادي، لذلك يوضع أمامه خادم SIP يتولى الإشعارات، مثل Flexisip من فريق Linphone، الذي يدعم RFC 8599 وخدمتي Apple وGoogle، وترخيصه AGPLv3 أو تجاري. والتطبيقات التجارية الكبيرة تفعل الشيء نفسه عبر خوادم الشركة المصنعة.
وهناك شرطان من أنظمة الجوال يجب أن تعرفهما قبل أن تختار تطبيقاً أو تبنيه:
- iOS: الإشعارات الخاصة بالمكالمات تأتي عبر PushKit، وتقول وثائق Apple إن التطبيقات المبنية بأدوات iOS 13 وما بعده يجب أن تبلغ عن كل إشعار من هذا النوع كمكالمة عبر CallKit، فتظهر شاشة المكالمة الأصلية، والتطبيق الذي لا يفعل ذلك يغلقه النظام، ومع التكرار يتوقف وصول إشعاراته.
- Android: يحتاج الإشعار أن يرسل بأولوية عالية حتى يوقظ التطبيق في وضع Doze، ووثائق Firebase تقول إن الرسائل عالية الأولوية التي لا ينتج عنها إشعار يراه المستخدم قد تخفض أولويتها.
والنتيجة العملية: إذا أردت تحويلات على جوالات الموظفين فلا تقم بتوزيع أي تطبيق SIP مجاني والاكتفاء بالمقسم، والسبب أنه سوف يرن فقط عندما يكون مفتوحاً، وإنما اختر تطبيقاً يدعم الإشعارات مع خادمه، أو خادم إشعارات مثل Flexisip أمام مقسمك، وجرب قبل التوزيع حالة واحدة: الجوال مقفل منذ عشر دقائق والشاشة مطفأة، ثم اتصل به.
إزالة التجربة
نزيل ما أنشأناه بالاسم فقط:
docker rm -f ard-vp-pbx ard-vp-prov ard-vp-dhcp ard-vp-desk ard-vp-desk2 ard-vp-linphone ard-vp-blf ard-vp-mobile
docker network rm ard-vp-net
docker rmi ard-voip-tools:1جدول التشخيص السريع
| العرض | السبب الأرجح | أين تنظر |
|---|---|---|
| الهاتف الجديد لا يطلب أي ملف | الخيار 66 لا يصله، أو يطلب خياراً آخر مثل 43 | سجل DHCP، وسطر requested options |
| الهاتف يعمل وشاشته مضيئة ولكن دون تحويلة | لا يوجد ملف باسم عنوان MAC الخاص به | طلبات 404 في سجل سيرفر التهيئة |
| كل الهواتف أخذت إعداداً قديماً | غيرت الملف ولم تعد تشغيل الهواتف، أو يوجد ملف محلي في الهاتف يتقدم على ملف السيرفر | سجل السيرفر بعد إعادة التشغيل، ووثائق الشركة عن ترتيب الملفات |
| البرنامج يقول إنه يعمل ولا يظهر في المقسم | البرنامج لم يرسل REGISTER أصلاً | tcpdump على المقسم قبل أي تغيير في الحساب |
| ضوء BLF لا يتغير | لا يوجد Hint للتحويلة، أو انتهى الاشتراك ولم يجدد | core show hints وpjsip show subscriptions inbound |
| تطبيق الجوال لا يرن والشاشة مطفأة | النظام علق التطبيق، ولا يوجد خادم إشعارات | حالة التحويلة Unavail مع الفحص الدوري، وINVITE دون رد في الالتقاط |
تدريب عملي
- أضف ملف MAC ثانياً للتحويلة 2003، وشغل هاتفاً جديداً بعنوان MAC الذي اخترته، ثم غير الترميز في الملف المشترك إلى
g711.soفقط مع PCMA، وأعد تشغيل الهاتفين، وتأكد من سجل Nginx أن كليهما أخذ الملف الجديد. - في سيناريو BLF غير
Event: dialogإلىEvent: presenceوAcceptإلىapplication/pidf+xml، وقارن جسم NOTIFY بما رأيته هنا.
الخلاصة
- الهاتف المكتبي والبرنامج وتطبيق الجوال كلها عملاء SIP بالنسبة للمقسم، والفرق في مكان تشغيلها ومن يتحكم فيها: الكهرباء PoE للمكتبي، والسماعة للبرنامج، ونظام التشغيل للجوال.
- التهيئة التلقائية تبدأ من DHCP: الخيار 66 يعطي الهاتف رابط السيرفر، والهاتف يطلب ملفاً مشتركاً وملفاً باسم عنوان MAC الخاص به، وسجل السيرفر هو أول مكان للتشخيص.
- ملف MAC فيه كلمة مرور التحويلة، وعناوين MAC سهلة التخمين، لذلك افصل شبكة الصوت، وضع كلمة مرور وHTTPS على سيرفر التهيئة، وراقب الطلبات المتتالية.
- أزرار BLF اشتراكات SUBSCRIBE بالحدث
dialogيرد عليها المقسم ب NOTIFY عند كل تغيير، وتحتاج تلميحاً Hint لكل تحويلة. - تطبيق الجوال المعلق لا يرد على INVITE رغم أن تسجيله صالح، والحل خادم يحتفظ بالمكالمة ويوقظ التطبيق بإشعار Push كما في RFC 8599، مع CallKit في iOS والأولوية العالية في Android.
وفي المقال القادم بإذن الله سوف نبني على هذه الهواتف مركز اتصال كاملاً: طابوراً للمتصلين، ووكلاء يسجلون دخولهم، وتسجيلاً للمكالمات، وتقارير مستوى الخدمة SLA.
دليلمصطلحات الاتصالات الهاتفية VoIP للمبتدئينشرح مبسط لمصطلحات الهاتف عبر الإنترنت، وما يمر في الشبكة بين ضغط زر الاتصال ووصول الصوت إلى الطرف الآخر.
دليلتتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟نبني مقسماً وهاتفين خلف NAT في تجربة عملية، ونتتبع المكالمة حزمة بحزمة حتى نعرف لماذا يضيع الصوت وكيف نصلحه.
دليلكيف تعمل الإشعارات الفورية Push Notifications؟كيف يصل الإشعار إلى تطبيق مغلق عبر خدمات Apple وGoogle، وما الذي تراه هذه الخدمات من بيانات مستخدميك.
دليلتقسيم الشبكة Network Segmentation بال VLAN وال VPCلماذا تصل كاميرا مخترقة إلى قاعدة بياناتك، وكيف تمنع ذلك بتقسيم الشبكة إلى مناطق لا يمر بينها إلا ما تسمح به.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وبناء التجربة على Asterisk 20.6.0 وbaresip 1.0.0 وLinphone 5.2.0 وSIPp 3.7.2 وNginx 1.24.0 وdnsmasq 2.91 من حزم Ubuntu 24.04 في Docker.