المقسم الذي بنيناه في تتبع مكالمة SIP عبر الشبكة يربط موظفي المكتب ببعضهم، ولكن العميل لا يتصل بالتحويلة 1001، وإنما برقم الشركة المكتوب على موقعها، والموظف يحتاج أن يتصل بجوال العميل. وهذا يعني أن مقسمك يحتاج طريقاً إلى شبكة الهاتف العامة، وأرقاماً حقيقية يعرفها الناس، وهنا يأتي دور الخط الخارجي SIP Trunk والأرقام المباشرة DID.
المواضيع في السلسلة:
- مصطلحات الاتصالات الهاتفية 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
ولأنك لن تحصل على خط حقيقي من مزود لتجربة، سوف نبني المزود نفسه: مقسم Asterisk ثان يلعب دور شركة الاتصالات، وفيه مشتركون بأرقام جوال، ونربط به مقسم المكتب كما يربط أي مقسم بمزوده، ثم نكسر الإعداد في كل خطوة لنرى الأعطال التي تقابلها عند أول ربط حقيقي. وسوف نتناول في هذا المقال ما يلي:
- ما هو SIP Trunk وDID، وصيغة الأرقام الدولية E.164، وأرقام المملكة كما تنشرها هيئة الاتصالات والفضاء والتقنية.
- الفرق بين الخط بالتسجيل Registration والخط بعنوان IP، ومتى تختار كلاً منهما.
- ربط مقسم المكتب بالمزود بالتسجيل، وتوجيه المكالمات القادمة بحسب الرقم المطلوب، وأربعة أعطال حقيقية في الطريق.
- المكالمات الخارجة: تحويل الرقم المحلي إلى E.164، وهوية المتصل Caller ID، ولماذا يرفض المزود المكالمة ب 403.
- الخط بعنوان IP، وثغرة تجعل أي جهاز في الشبكة يتصل باسم المقسم دون كلمة مرور، وكيف تغلقها.
ما هو SIP Trunk؟
قبل VoIP كان المقسم يتصل بشركة الاتصالات بخطوط نحاسية، كل خط تماثلي Analog يحمل مكالمة واحدة، أو بخط رقمي يحمل 30 مكالمة في الدول التي تعمل بنظام E1. أما الخط الخارجي SIP Trunk فهو الاتفاق نفسه ولكن عبر الإنترنت أو عبر وصلة خاصة من المزود: مقسمك يرسل رسائل SIP إلى سيرفر المزود، والمزود يوصل المكالمة إلى أي هاتف في العالم، وعدد المكالمات في الوقت نفسه يحدده العقد وسرعة الخط وليس عدد الأسلاك. والصورة التالية تبين الأطراف:

وفيما يلي المصطلحات التي سوف تقابلها في أي عقد أو لوحة تحكم لمزود:
- مزود خدمة الاتصال عبر الإنترنت Internet Telephony Service Provider واختصاراً ITSP: الشركة التي تبيعك الخط والأرقام، وقد تكون شركة الاتصالات نفسها أو شركة مرخصة أخرى.
- الرقم المباشر Direct Inward Dialing واختصاراً DID: رقم هاتف حقيقي يملكه المزود ويوجه مكالماته إلى خطك، وغالباً تشتري كتلة أرقام متتالية، فيكون للاستقبال رقم وللمبيعات رقم وللمدير رقم، وكلها تأتي عبر الخط نفسه.
- القنوات Channels أو المكالمات المتزامنة Concurrent Calls: الحد الأقصى للمكالمات التي يمررها الخط في الوقت نفسه، وهو ما تدفع عليه في أغلب العقود.
- هوية المتصل Caller ID أو CLI: الرقم الذي يظهر عند من تتصل به، والمزود يقرر هل يقبل ما ترسله أم يستبدله.
صيغة الأرقام E.164
الموظف في المكتب يطلب الجوال بالشكل الذي يعرفه، أي 0550000123، ولكن هذا الشكل لا معنى له خارج المملكة، فالصفر في البداية يعني «رقم محلي» في بلد ويعني شيئاً آخر في بلد آخر. لذلك تتفاهم شبكات الهاتف بصيغة واحدة تعرفها توصية ITU-T E.164: علامة + ثم رمز الدولة ثم الرقم الوطني دون الصفر، وبحد أقصى 15 رقماً. فالرقم أعلاه يصبح +966550000123، وأغلب المزودين يطلبون منك أن ترسل الأرقام بهذه الصيغة، ويرسلون لك الأرقام بها.
وفي المملكة تنشر هيئة الاتصالات والفضاء والتقنية خطة الترقيم الوطنية، ومنها ما يهمك عند كتابة خطة الاتصال:
| النوع | الشكل المحلي | بصيغة E.164 |
|---|---|---|
| جوال، مثال الهيئة نفسها | 0551234567، عشرة أرقام | +966551234567 |
| ثابت في منطقة الرياض | 011 ثم سبعة أرقام | +96611 ثم سبعة أرقام |
| مناطق الثابت الأخرى | 012 و013 و014 و016 و017 | +96612 وهكذا |
| رقم مجاني، مثال الهيئة | 8001232222 | يحدد المزود طريقة إرساله |
ولاحظ أن كل الأرقام في التجربة وهمية، فقد اخترنا +96611000010X لكتلة الشركة و+9665500001XX للجوالات، لأن رقم المشترك لا يبدأ بأصفار، فلا يمكن أن تتصل بأحد بالخطأ إذا نسختها.
الترخيص في المملكة
الاتصال بشبكة الهاتف العامة عبر الإنترنت خدمة منظمة، وفي صفحة تصريح تقديم خدمات الصوت الافتراضية VVSP تقول الهيئة إن هذا التصريح يمكن من تقديم خدمات تسمح للمستخدمين في المملكة بإجراء المكالمات واستقبالها مع شبكات الهاتف العامة PSTN عبر بروتوكول الإنترنت باستخدام ترقيم E.164، وإنه لمقدمي الخدمات المرخصين بحسب تنظيمات الهيئة، ورسومه المنشورة في الصفحة عند كتابة المقال 5000 ريال. وما يعنيك كمؤسسة تشغل مقسمها بنفسها أن تشتري الخط والأرقام من مزود مرخص، وأن تطلب منه شروطه في هوية المتصل وأرقام الطوارئ وتسجيل المكالمات قبل التوقيع، والسبب أن هذه الشروط تختلف بين المزودين وتحدد ما تكتبه في خطة الاتصال. ولا تقم بتمرير مكالماتك المحلية عبر مزود في الخارج لأن سعره أقل، فهذا ليس قراراً تقنياً فقط.
كيف يعرفك المزود؟ التسجيل أم عنوان IP
عندما تصل رسالة INVITE إلى المزود، يحتاج أن يعرف أنها من مقسمك أنت وليست من شخص يريد أن يتصل على حسابك، وعندما يرسل المزود مكالمة إليك يحتاج أن يعرف أين يجدك. وهناك طريقتان شائعتان لذلك، والصورة التالية تبينهما:

| التسجيل Registration | عنوان IP Authentication | |
|---|---|---|
| كيف يعرفك المزود | اسم مستخدم وكلمة مرور في كل REGISTER وINVITE | عنوان IP الذي تأتي منه الرسالة |
| كيف يجدك المزود | العنوان الذي حفظه من آخر تسجيل | عنوان ثابت كتبه في إعداده |
| يحتاج عنواناً ثابتاً | لا، ويعمل خلف NAT | نعم |
| يناسب | المكاتب الصغيرة والإنترنت المنزلي | المقسمات في مراكز البيانات والسحابة |
| نقطة الضعف | إذا سقط التسجيل لا تصلك المكالمات | أي خطأ في مطابقة العنوان يفتح الخط لغيرك |
وسوف نبدأ بالتسجيل لأنه الأكثر شيوعاً مع المزودين، ثم نحول الخط نفسه إلى عنوان IP في آخر التجربة.
بناء التجربة: مزود ومقسم وأربعة هواتف
التجربة كلها في شبكة Docker داخلية واحدة دون أي منفذ على جهازك، وفيها:
| ال Container | العنوان | الدور |
|---|---|---|
lab-vt-carrier | 10.89.10.5 | المزود: Asterisk يملك كتلة الأرقام +966110000100 إلى +966110000109 ويبيعها للعميل acme |
lab-vt-office | 10.89.10.10 | مقسم شركة acme، وفيه التحويلتان 1001 للاستقبال و1002 للمبيعات |
lab-vt-ext1001 وext1002 | 10.89.10.21 و.22 | هاتفا المكتب، مسجلان في مقسم المكتب |
lab-vt-mobile1 وmobile2 | 10.89.10.31 و.32 | جوالان على شبكة المزود برقمي +966550000123 و+966550000456 |
ونستخدم ال Image نفسها من المقال السابق، أي Asterisk 20.6.0 وbaresip وtcpdump من حزم Ubuntu 24.04، باسم lab-voip-tools:1، مع السكربت sipflow.sh الذي يطبع سطراً لكل رسالة SIP في ملف الالتقاط. قم بإنشاء مجلدين carrier وoffice، وفي كل منهما الملفات pjsip.conf وextensions.conf وrtp.conf وlogger.conf ونسخة من sipflow.sh.
المزود
هذا هو carrier/pjsip.conf، وفيه حساب العميل acme الذي يسجل به مقسم المكتب، والجوالان:
[global]
type=global
user_agent=Lab-Carrier
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
; Template for every endpoint on the carrier side
[ep](!)
type=endpoint
disallow=all
allow=alaw,ulaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
; The customer trunk "acme": the office PBX registers with this account
[acme]
type=auth
auth_type=userpass
username=acme
password=Trunk-Lab-7f3k9q
[acme]
type=aor
max_contacts=1
remove_existing=yes
[acme](ep)
context=from-customer
auth=acme
aors=acme
; Two mobile subscribers on the public network
[mobile1]
type=auth
auth_type=userpass
username=mobile1
password=Mobile1-Lab-pass
[mobile1]
type=aor
max_contacts=1
remove_existing=yes
[mobile1](ep)
context=from-pstn
auth=mobile1
aors=mobile1
callerid=<+966550000123>
[mobile2]
type=auth
auth_type=userpass
username=mobile2
password=Mobile2-Lab-pass
[mobile2]
type=aor
max_contacts=1
remove_existing=yes
[mobile2](ep)
context=from-pstn
auth=mobile2
aors=mobile2
callerid=<+966550000456>وخطة الاتصال عند المزود في carrier/extensions.conf فيها سياستان كما يفعل المزود الحقيقي: كل مكالمة إلى رقم من كتلة acme ترسل عبر خطها، وكل مكالمة تخرج من acme يجب أن تكون بصيغة E.164 وأن تحمل هوية متصل من أرقامها:
[from-pstn]
; DID block +966110000100 to +966110000109 belongs to the customer acme
exten => _+96611000010X,1,NoOp(DID ${EXTEN} for acme from ${CALLERID(num)})
same => n,Dial(PJSIP/${EXTEN}@acme,30)
same => n,Hangup()
exten => +966550000123,1,Dial(PJSIP/mobile1,30)
exten => +966550000456,1,Dial(PJSIP/mobile2,30)
[from-customer]
; Numbers must arrive in E.164 form: + then country code
exten => _X.,1,NoOp(Rejected, not E.164: ${EXTEN})
same => n,Hangup(28)
exten => _+X.,1,NoOp(Outbound from acme to ${EXTEN} with caller ID ${CALLERID(num)})
same => n,GotoIf($["${CALLERID(num):0:12}" = "+96611000010"]?allowed)
same => n,NoOp(Caller ID ${CALLERID(num)} is not one of acme's numbers)
same => n,Hangup(21)
same => n(allowed),Goto(from-pstn,${EXTEN},1)في الإعداد أعلاه لاحظ التالي:
Dial(PJSIP/${EXTEN}@acme)يرسل المكالمة إلى العنوان الذي سجل منه مقسم acme، ويضع الرقم المطلوب في أول سطر من INVITE، أيINVITE sip:+966110000105@…، وهذا ما يفعله أغلب المزودين.- النمط
_X.يطابق أي رقم لا يبدأ ب+، فيرفضه بالسبب 28 الذي يتحول في SIP إلى484 Address Incomplete. - السبب 21 يعني «رفض المكالمة» ويتحول إلى
403 Forbidden، وهو ما يرسله المزود عندما تكون هوية المتصل ليست من أرقامك. وبعض المزودين يستبدلون الهوية برقمك الرئيسي بدل الرفض، فاسأل مزودك أي السلوكين يتبع.
مقسم المكتب: الخط بالتسجيل
في مقسم المكتب نعرف الخط بأربعة أجزاء بالاسم نفسه carrier، وهذا هو الجزء الخاص بالخط في office/pjsip.conf، والتحويلتان بعده بالقالب نفسه الذي استخدمناه في المقالات السابقة:
[global]
type=global
user_agent=Acme-Office-PBX
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
; ---------- The trunk to the carrier ----------
[carrier]
type=registration
outbound_auth=carrier
server_uri=sip:10.89.10.5
client_uri=sip:[email protected]
retry_interval=30
expiration=300
[carrier]
type=auth
auth_type=userpass
username=acme
password=Trunk-Lab-7f3k9q
[carrier]
type=aor
contact=sip:10.89.10.5:5060
[carrier]
type=endpoint
context=from-trunk
disallow=all
allow=alaw,ulaw
outbound_auth=carrier
aors=carrier
from_user=acme
direct_media=no
; ---------- Office extensions ----------
[phone](!)
type=endpoint
context=office
disallow=all
allow=alaw,ulaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
[1001]
type=auth
auth_type=userpass
username=1001
password=Lab-1001-pass
[1001]
type=aor
max_contacts=1
remove_existing=yes
[1001](phone)
auth=1001
aors=1001
callerid="Reception" <1001>
[1002]
type=auth
auth_type=userpass
username=1002
password=Lab-1002-pass
[1002]
type=aor
max_contacts=1
remove_existing=yes
[1002](phone)
auth=1002
aors=1002
callerid="Sales" <1002>في الإعداد أعلاه لاحظ التالي:
type=registrationيجعل المقسم يسجل نفسه عند المزود كما يسجل الهاتف عند المقسم، ويجدد التسجيل قبل انتهاءexpiration، ويعيد المحاولة كل 30 ثانية إذا فشل. والخيارات كلها موثقة في صفحة res_pjsip_outbound_registration.outbound_authفي ال endpoint يعني أن المقسم يستخدم كلمة المرور نفسها عندما يطلب منه المزود المصادقة على INVITE، وليس فقط على REGISTER.aorفيه عنوان ثابت للمزود، فالمقسم يعرف أين يرسل المكالمات الخارجة دون أن يسجل المزود عنده.from_user=acmeيضع اسم الحساب في ترويسةFrom، وهو ما يطلبه كثير من المزودين ليعرفوا الحساب من أول رسالة، وسوف نرى أثره الجانبي بعد قليل.
وخطة الاتصال في office/extensions.conf فيها ثلاثة سياقات: from-trunk للمكالمات القادمة من المزود، وoffice لما يطلبه الموظفون، وto-carrier لكل ما يخرج:
[from-trunk]
; Calls from the carrier: the DID decides who rings
exten => +966110000100,1,Goto(office,1001,1)
exten => +966110000102,1,Goto(office,1002,1)
; Any other number of the block goes to reception
exten => _+96611000010X,1,NoOp(Unassigned DID ${EXTEN})
same => n,Goto(office,1001,1)
[office]
exten => _10XX,1,Dial(PJSIP/${EXTEN},20)
same => n,Hangup()
; Outgoing calls: local mobile 05XXXXXXXX and landline 01XXXXXXXX become E.164
exten => _05XXXXXXXX,1,Goto(to-carrier,+966${EXTEN:1},1)
exten => _01XXXXXXXX,1,Goto(to-carrier,+966${EXTEN:1},1)
exten => _00X.,1,Goto(to-carrier,+${EXTEN:2},1)
[to-carrier]
exten => _+X.,1,Set(CALLERID(num)=+966110000100)
same => n,Dial(PJSIP/${EXTEN}@carrier,30)
same => n,Hangup()ولاحظ أن الرقم الأدق يغلب في Asterisk، فالرقم +966110000102 يذهب إلى 1002 مع أنه يطابق النمط _+96611000010X أيضاً، وكل رقم آخر من الكتلة يذهب إلى الاستقبال حتى لا تضيع مكالمة على رقم لم توزعه بعد. وطريقة المطابقة بالأنماط موثقة في صفحة Pattern Matching. والملفان الباقيان هما نفسهما في المجلدين:
for h in carrier office; do
printf '[general]\nrtpstart=10000\nrtpend=10099\n' > $h/rtp.conf
printf '[general]\n[logfiles]\nconsole => notice,warning,error,verbose\nmessages => notice,warning,error,verbose(3)\n' > $h/logger.conf
doneوالسطر messages يكتب تنفيذ خطة الاتصال في الملف /var/log/asterisk/messages، ومنه سوف نعرف أين ذهبت كل مكالمة.
التشغيل والتسجيل
نشغل المقسمين، وكل منهما ينسخ ملفاته من المجلد إلى /etc/asterisk عند البدء، حتى نعدل الملفات ونعيد تحميلها بالسكربت reload.sh:
docker network create --internal --subnet 10.89.10.0/24 lab-vt-net
for n in carrier:5 office:10; do name=${n%%:*}; ip=${n#*:}
docker run -d --name lab-vt-$name --network lab-vt-net --ip 10.89.10.$ip --cap-add NET_ADMIN --cap-add NET_RAW \
-v "$PWD/$name:/lab" lab-voip-tools:1 sh -c 'cp /lab/*.conf /etc/asterisk/ && exec asterisk -f'
done
cat > reload.sh <<'EOF'
#!/bin/sh
# copies the edited files into the PBX and reloads them: ./reload.sh carrier|office
docker exec lab-vt-$1 sh -c 'cp /lab/*.conf /etc/asterisk/ && asterisk -rx "pjsip reload" >/dev/null && asterisk -rx "dialplan reload" >/dev/null'
EOF
chmod +x reload.sh
sleep 10
docker exec lab-vt-office asterisk -rx 'pjsip show registrations'
docker exec lab-vt-carrier asterisk -rx 'pjsip show contacts'والمخرج سوف يكون كما يلي:
carrier/sip:10.89.10.5 carrier Registered (exp. 273s)
Contact: acme/sip:[email protected]:5060 b486a41273 NonQual nanمقسم المكتب مسجل، والمزود حفظ له العنوان sip:[email protected]:5060. ولاحظ الحرف s قبل العنوان، فهو القيمة الافتراضية للخيار contact_user في Asterisk، وسوف نحتاج هذه الملاحظة بعد قليل.
ثم نكتب إعداد الهواتف الأربعة، وهو إعداد baresip من المقال السابق مع نغمة ثابتة مصدراً للصوت، وكل هاتف مسجل في المقسم الذي يتبعه:
mkdir -p sounds
docker run --rm -v "$PWD/sounds:/sounds" lab-voip-tools:1 sox -n -r 8000 -c 1 -b 16 /sounds/tone.wav synth 30 sine 440 vol 0.5
for p in ext1001:1001:Lab-1001-pass:10.89.10.10 ext1002:1002:Lab-1002-pass:10.89.10.10 mobile1:mobile1:Mobile1-Lab-pass:10.89.10.5 mobile2:mobile2:Mobile2-Lab-pass:10.89.10.5; do
IFS=: read n u pw srv <<EOF
$p
EOF
mkdir -p phones/$n
cat > phones/$n/config <<CFG
poll_method epoll
sip_listen 0.0.0.0:5060
audio_source aufile,/sounds/tone.wav
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
CFG
echo "<sip:$u@$srv>;auth_pass=$pw;answermode=auto" > phones/$n/accounts
done
echo "module stdio.so" | tee -a phones/ext1002/config phones/mobile2/configهاتف الاستقبال 1001 وجوال 1 يعملان طوال التجربة ويردان تلقائياً، والهاتفان الآخران فيهما الوحدة stdio، ونشغلهما عند كل مكالمة بالسكربت dial.sh، الذي يلتقط SIP على المقسمين، ويرسل أمر الاتصال، ويطبع رسائل المكالمة كما رآها المزود:
for p in ext1001:10.89.10.21 mobile1:10.89.10.31; do
docker run -d --name lab-vt-${p%%:*} --network lab-vt-net --ip ${p#*:} \
-v "$PWD/phones/${p%%:*}:/conf" -v "$PWD/sounds:/sounds:ro" lab-voip-tools:1 baresip -f /conf
done
cat > dial.sh <<'EOF'
#!/bin/sh
# usage: ./dial.sh <phone> <ip> <number> <name> one call, captured on both PBXs
for h in carrier office; do docker exec -d lab-vt-$h tcpdump -i eth0 -n -s0 -w /tmp/$4.pcap udp port 5060; done
sleep 1
(sleep 5; echo "/dial $3"; sleep 8; echo "/hangup"; sleep 2; echo "q") | \
docker run -i --rm --name lab-vt-dialer --network lab-vt-net --ip $2 \
-v "$PWD/phones/$1:/conf" -v "$PWD/sounds:/sounds:ro" lab-voip-tools:1 baresip -f /conf 2>&1 | tr '\r' '\n' > $4-$1.log
sleep 1; for h in carrier office; do docker exec lab-vt-$h pkill tcpdump; done; sleep 1
docker exec lab-vt-carrier sh /lab/sipflow.sh /tmp/$4.pcap | grep -v -E "REGISTER|OPTIONS"
grep -a -E "audio=" $4-$1.log | tail -1
EOF
chmod +x dial.shالمكالمات القادمة: من الرقم إلى التحويلة
العطل الأول: المقسم يطلب من المزود كلمة مرور
الجوال 2 يتصل برقم الشركة الرئيسي:
./dial.sh mobile2 10.89.10.32 +966110000100 in1
docker logs lab-vt-office 2>&1 | grep NOTICE | tail -1والمخرج سوف يكون كما يلي، بعد أن حذفنا منه ردود التسجيل:
11:50:24 10.89.10.32.5060 > 10.89.10.5.5060 INVITE sip:[email protected] SIP/2.0
11:50:24 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 401 Unauthorized
11:50:24 10.89.10.32.5060 > 10.89.10.5.5060 ACK sip:[email protected] SIP/2.0
11:50:24 10.89.10.32.5060 > 10.89.10.5.5060 INVITE sip:[email protected] SIP/2.0
11:50:24 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 100 Trying
11:50:24 10.89.10.5.5060 > 10.89.10.10.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:50:24 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 401 Unauthorized
11:50:24 10.89.10.5.5060 > 10.89.10.10.5060 ACK sip:[email protected]:5060 SIP/2.0
11:50:24 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 403 Forbidden
[Oct 20 11:50:24] NOTICE[37]: res_pjsip/pjsip_distributor.c:673 log_failed_request: Request 'INVITE' from '<sip:[email protected]>' failed for '10.89.10.5:5060' (callid: 6e2de7b6-2de3-4a91-87d5-cfd6b0c59ef0) - No matching endpoint foundالمزود أوصل المكالمة إلى مقسم المكتب بالرقم الصحيح في أول سطر، ولكن المقسم رد ب 401 Unauthorized، أي «من أنت؟ أعطني كلمة المرور»، والمزود لا يعطي كلمة مرور لعملائه، فأنهى المكالمة، وسمع المتصل أن الرقم مرفوض. والسبب في السطر الأخير: المقسم لم يجد ال endpoint الذي جاءت منه الرسالة، لأنه يبحث افتراضياً باسم المستخدم في ترويسة From، والاسم هنا هو رقم المتصل +966550000456 وليس carrier. والتسجيل الناجح لا يعني أن المقسم يعرف المزود عندما يتصل هو به، فالتسجيل يعمل في اتجاه واحد.
والحل أن نخبر المقسم أن كل ما يأتي من عنوان المزود يخص ال endpoint المسمى carrier، وهذا هو الجزء identify، فنضيفه إلى office/pjsip.conf:
[carrier]
type=identify
endpoint=carrier
match=10.89.10.5ثم نعيد التحميل ونتصل مرة أخرى:
./reload.sh office
./dial.sh mobile2 10.89.10.32 +966110000100 in211:53:05 10.89.10.5.5060 > 10.89.10.10.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:53:05 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 100 Trying
11:53:05 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 180 Ringing
11:53:05 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 180 Ringing
11:53:05 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 200 OK
11:53:05 10.89.10.5.5060 > 10.89.10.10.5060 ACK sip:10.89.10.10:5060 SIP/2.0
11:53:05 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 200 OK
11:53:13 10.89.10.32.5060 > 10.89.10.5.5060 BYE sip:10.89.10.5:5060 SIP/2.0
[0:00:08] audio=64000/63573 (bit/s)رن هاتف الاستقبال ورد، والصوت يمر في الاتجاهين بمعدل G.711.
توجيه الأرقام DID
نتصل الآن برقم من الكتلة لم نوزعه على أحد، ونقرأ ما فعلته خطة الاتصال في المقسمين:
./dial.sh mobile2 10.89.10.32 +966110000105 in3 >/dev/null
for h in carrier office; do docker exec lab-vt-$h sh -c 'grep -E "Executing|answered" /var/log/asterisk/messages'; doneوالمخرج سوف يكون كما يلي، بعد أن حذفنا منه الوقت من كل سطر:
VERBOSE[201][C-00000004] pbx.c: Executing [+966110000105@from-pstn:1] NoOp("PJSIP/mobile2-00000006", "DID +966110000105 for acme from +966550000456") in new stack
VERBOSE[201][C-00000004] pbx.c: Executing [+966110000105@from-pstn:2] Dial("PJSIP/mobile2-00000006", "PJSIP/+966110000105@acme,30") in new stack
VERBOSE[201][C-00000004] app_dial.c: PJSIP/acme-00000007 answered PJSIP/mobile2-00000006
VERBOSE[199][C-00000003] pbx.c: Executing [+966110000105@from-trunk:1] NoOp("PJSIP/carrier-00000004", "Unassigned DID +966110000105") in new stack
VERBOSE[199][C-00000003] pbx.c: Executing [+966110000105@from-trunk:2] Goto("PJSIP/carrier-00000004", "office,1001,1") in new stack
VERBOSE[199][C-00000003] pbx.c: Executing [1001@office:1] Dial("PJSIP/carrier-00000004", "PJSIP/1001,20") in new stack
VERBOSE[199][C-00000003] app_dial.c: PJSIP/1001-00000005 answered PJSIP/carrier-00000004المزود طابق الرقم بكتلة acme وأرسله عبر خطها، ومقسم المكتب استقبله في السياق from-trunk لأن ال endpoint carrier مضبوط عليه، ولم يجد له تحويلة محددة فأرسله إلى الاستقبال. والصورة التالية تجمع المسار في الاتجاهين، ومنه المكالمات الخارجة التي سوف نصل إليها:

العطل الثاني: المكالمة تصل إلى الامتداد s
ليس كل مزود يضع الرقم المطلوب في أول سطر من INVITE، فبعض المزودين يرسلون المكالمة إلى العنوان الذي سجلت به كما هو حرفياً، ولنرى ماذا يحدث نغير سطر الإرسال عند المزود إلى Dial(PJSIP/acme,30)، أي دون الرقم:
sed -i 's#Dial(PJSIP/${EXTEN}@acme,30)#Dial(PJSIP/acme,30)#' carrier/extensions.conf
./reload.sh carrier
./dial.sh mobile2 10.89.10.32 +966110000101 in4
docker exec lab-vt-office sh -c 'grep NOTICE /var/log/asterisk/messages | tail -1'
docker exec lab-vt-office sh -c 'tcpdump -r /tmp/in4.pcap -n -A 2>/dev/null | grep -A14 "INVITE sip:s@" | grep -E "^(From|To)"'والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
11:54:48 10.89.10.5.5060 > 10.89.10.10.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:54:48 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 404 Not Found
11:54:48 10.89.10.5.5060 > 10.89.10.32.5060 SIP/2.0 404 Not Found
NOTICE[126] res_pjsip_session.c: carrier: Call (UDP:10.89.10.5:5060) to extension 's' rejected because extension not found in context 'from-trunk'.
From: <sip:[email protected]>
To: <sip:[email protected]>المكالمة وصلت إلى الامتداد s، وهو الحرف الذي كتبه المقسم في Contact عند التسجيل، ولا يوجد s في from-trunk، فرد المقسم ب 404 وسمع المتصل أن الرقم غير موجود. ولاحظ أن الرقم المطلوب ضاع تماماً، فلا هو في أول سطر ولا في To، أي أن هذا المزود لا يستطيع أن يميز لك بين أرقام الكتلة عبر حساب واحد. وفي هذه الحالة إما أن تطلب من المزود أن يرسل الرقم، وهو الحل الصحيح، أو أن تجعل المقسم يسجل باسم رقمك الرئيسي بالخيار contact_user في جزء registration، فتصل كل المكالمات إلى الاستقبال على الأقل:
contact_user=+966110000100./reload.sh office
docker exec lab-vt-office asterisk -rx 'pjsip send register carrier'
./dial.sh mobile2 10.89.10.32 +966110000101 in5والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
11:55:28 10.89.10.5.5060 > 10.89.10.10.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:55:28 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 180 Ringing
[0:00:08] audio=64000/64000 (bit/s)ثم نعيد سطر المزود إلى Dial(PJSIP/${EXTEN}@acme,30) ونكمل، ويبقى contact_user في الإعداد لأنه لا يضر مع مزود يرسل الرقم، ويحمي من مزود لا يرسله.
المكالمات الخارجة وهوية المتصل
العطل الثالث: المزود يرفض ب 403
موظف المبيعات يطلب جوال العميل بالشكل المحلي:
sed -i 's#Dial(PJSIP/acme,30)#Dial(PJSIP/${EXTEN}@acme,30)#' carrier/extensions.conf
./reload.sh carrier
./dial.sh ext1002 10.89.10.22 0550000123 out1
docker exec lab-vt-carrier sh -c 'grep Executing /var/log/asterisk/messages | tail -4'
docker exec lab-vt-carrier sh -c 'tcpdump -r /tmp/out1.pcap -n -A 2>/dev/null | grep -A14 "INVITE sip:+9665" | grep -E "^(From|To)" | head -2'والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
11:56:02 10.89.10.10.5060 > 10.89.10.5.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:56:02 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 401 Unauthorized
11:56:02 10.89.10.10.5060 > 10.89.10.5.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:56:02 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 100 Trying
11:56:02 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 403 Forbidden
VERBOSE[332][C-00000007] pbx.c: Executing [+966550000123@from-customer:1] NoOp("PJSIP/acme-0000000c", "Outbound from acme to +966550000123 with caller ID acme") in new stack
VERBOSE[332][C-00000007] pbx.c: Executing [+966550000123@from-customer:2] GotoIf("PJSIP/acme-0000000c", "0?allowed") in new stack
VERBOSE[332][C-00000007] pbx.c: Executing [+966550000123@from-customer:3] NoOp("PJSIP/acme-0000000c", "Caller ID acme is not one of acme's numbers") in new stack
VERBOSE[332][C-00000007] pbx.c: Executing [+966550000123@from-customer:4] Hangup("PJSIP/acme-0000000c", "21") in new stack
From: <sip:[email protected]>;tag=b0d8eb47-2f82-4508-abd7-5f355797bbb7
To: <sip:[email protected]>الجزء الأول نجح: خطة الاتصال حولت 0550000123 إلى +966550000123، والمقسم أجاب طلب المصادقة 401 بكلمة مرور الخط. ولكن المزود رفض المكالمة ب 403، لأن هوية المتصل التي وصلته هي acme وليست رقماً من الكتلة. والسبب أن from_user=acme يستبدل الجزء الأول من From كله، وهو المكان نفسه الذي يحمل رقم المتصل، فضاع الرقم الذي وضعناه في Set(CALLERID(num)=…).
قد يتساءل البعض: لماذا لا نحذف from_user؟ والإجابة أن كثيراً من المزودين يحتاجون اسم الحساب في From ليعرفوك، فالحل الصحيح أن تحمل الهوية في ترويسة أخرى مخصصة لها، وهي P-Asserted-Identity المعرفة في RFC 3325، فنضيف إلى ال endpoint carrier في مقسم المكتب:
send_pai=yesوالمزود من جهته يجب أن يثق بهذه الترويسة من عملائه فقط، وهذا عنده الخيار trust_id_inbound=yes على ال endpoint acme، وهو قرار المزود وليس قرارك، فاسأله في أي ترويسة يقرأ الهوية:
./reload.sh office; ./reload.sh carrier
./dial.sh ext1002 10.89.10.22 0550000123 out2
docker exec lab-vt-carrier sh -c 'tcpdump -r /tmp/out2.pcap -n -A 2>/dev/null | grep -A16 "INVITE sip:+9665" | grep -E "^(From|P-Asserted)" | head -2'
docker logs lab-vt-mobile1 2>&1 | grep -a "Call established" | tail -1والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
11:56:37 10.89.10.5.5060 > 10.89.10.31.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:56:37 10.89.10.31.5060 > 10.89.10.5.5060 SIP/2.0 180 Ringing
11:56:37 10.89.10.31.5060 > 10.89.10.5.5060 SIP/2.0 200 Answering
[0:00:08] audio=64405/63978 (bit/s)
From: <sip:[email protected]>;tag=ba7a0023-412e-4620-af6d-39c80773ddec
P-Asserted-Identity: "Sales" <sip:[email protected]>
[email protected]: Call established: sip:[email protected]From ما زال يحمل اسم الحساب، والرقم انتقل إلى P-Asserted-Identity، والجوال رأى الرقم الرئيسي للشركة. ولاحظ أن الترويسة تحمل الاسم "Sales" أيضاً من إعداد التحويلة، فإذا كنت لا تريد أن تخرج أسماء الأقسام الداخلية فاضبط CALLERID(name) في to-carrier كما ضبطت الرقم.
رقم مباشر لكل تحويلة
المكالمة خرجت برقم الشركة الرئيسي، ولكن موظف المبيعات له رقمه المباشر +966110000102، والعميل الذي يعيد الاتصال يجب أن يصل إليه هو. والطريقة الأنظف أن تضع الرقم مع التحويلة نفسها بالخيار set_var، فيصبح متغيراً في كل مكالمة تبدأ منها:
[1001](phone)
auth=1001
aors=1001
callerid="Reception" <1001>
set_var=OUTBOUND_CID=+966110000100
[1002](phone)
auth=1002
aors=1002
callerid="Sales" <1002>
set_var=OUTBOUND_CID=+966110000102ونجعل to-carrier يستخدم هذا المتغير إذا وجد، والرقم الرئيسي إذا لم يوجد:
[to-carrier]
exten => _+X.,1,Set(CALLERID(num)=${IF($["${OUTBOUND_CID}" != ""]?${OUTBOUND_CID}:+966110000100)})
same => n,Dial(PJSIP/${EXTEN}@carrier,30)
same => n,Hangup()ونجرب هذه المرة الطلب الدولي بالبادئة 00:
./reload.sh office
./dial.sh ext1002 10.89.10.22 00966550000123 out4 | tail -1
docker exec lab-vt-office sh -c 'grep Executing /var/log/asterisk/messages | tail -3'
docker logs lab-vt-mobile1 2>&1 | grep -a "Call established" | tail -1[0:00:08] audio=64000/64000 (bit/s)
VERBOSE[421][C-00000008] pbx.c: Executing [00966550000123@office:1] Goto("PJSIP/1002-0000000e", "to-carrier,+966550000123,1") in new stack
VERBOSE[421][C-00000008] pbx.c: Executing [+966550000123@to-carrier:1] Set("PJSIP/1002-0000000e", "CALLERID(num)=+966110000102") in new stack
VERBOSE[421][C-00000008] pbx.c: Executing [+966550000123@to-carrier:2] Dial("PJSIP/1002-0000000e", "PJSIP/+966550000123@carrier,30") in new stack
[email protected]: Call established: sip:[email protected]البادئة 00 تحولت إلى +، والجوال رأى الرقم المباشر لموظف المبيعات.
العطل الرابع: رقم بغير صيغة E.164
لنرى ما يحدث لمن يكتب خطة اتصال ترسل الرقم كما طلبه الموظف، نغير مؤقتاً سطر الجوال في office إلى Dial(PJSIP/${EXTEN}@carrier,30) مباشرة:
11:57:16 10.89.10.10.5060 > 10.89.10.5.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:57:16 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 401 Unauthorized
11:57:16 10.89.10.10.5060 > 10.89.10.5.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:57:16 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 100 Trying
11:57:16 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 484 Address Incomplete
VERBOSE[441][C-00000009] pbx.c: Executing [0550000123@from-customer:1] NoOp("PJSIP/acme-0000000f", "Rejected, not E.164: 0550000123") in new stackالمزود رد ب 484 Address Incomplete، وبعض المزودين يقبلون الشكل المحلي ويحولونه بأنفسهم، وبعضهم يرفضه كما هنا، وبعضهم يقبله ثم يخطئ فهمه. لذلك حول كل رقم إلى E.164 في مقسمك، والسبب أن خطة الاتصال عندها تبقى صحيحة إذا غيرت المزود، ثم أعدنا السطر كما كان.
الخط بعنوان IP
الآن نحول الخط نفسه إلى المصادقة بعنوان IP، كما يحدث عندما ينقل المقسم إلى مركز بيانات بعنوان ثابت. عند المزود نحذف كلمة المرور من حساب acme، ونكتب عنوان المقسم الثابت، ونطابق كل ما يأتي من هذا العنوان بالحساب:
; The customer trunk "acme": recognised by the IP address of its PBX
[acme]
type=aor
contact=sip:10.89.10.10:5060
[acme](ep)
context=from-customer
aors=acme
trust_id_inbound=yes
[acme]
type=identify
endpoint=acme
match=10.89.10.10وفي مقسم المكتب نحذف جزأي registration وauth، ونحذف outbound_auth وfrom_user من ال endpoint، فيصبح الخط:
[carrier]
type=aor
contact=sip:10.89.10.5:5060
[carrier]
type=endpoint
context=from-trunk
disallow=all
allow=alaw,ulaw
aors=carrier
send_pai=yes
direct_media=no
[carrier]
type=identify
endpoint=carrier
match=10.89.10.5ولأن حذف التسجيل لا يكفيه إعادة التحميل، نعيد تشغيل المقسمين ثم نتصل في الاتجاهين:
docker restart lab-vt-carrier lab-vt-office
docker exec lab-vt-office asterisk -rx 'pjsip show registrations'
./dial.sh ext1002 10.89.10.22 0550000123 ip2
./dial.sh mobile2 10.89.10.32 +966110000100 ip3والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
No objects found.
11:59:33 10.89.10.10.5060 > 10.89.10.5.5060 INVITE sip:[email protected]:5060 SIP/2.0
11:59:33 10.89.10.5.5060 > 10.89.10.10.5060 SIP/2.0 100 Trying
11:59:33 10.89.10.5.5060 > 10.89.10.31.5060 INVITE sip:[email protected]:5060 SIP/2.0
[0:00:08] audio=63573/64000 (bit/s)
12:00:13 10.89.10.5.5060 > 10.89.10.10.5060 INVITE sip:[email protected]:5060 SIP/2.0
12:00:13 10.89.10.10.5060 > 10.89.10.5.5060 SIP/2.0 180 Ringing
[0:00:06] audio=64000/64000 (bit/s)لا يوجد تسجيل، والمكالمات تعمل في الاتجاهين، ولاحظ أن المكالمة الخارجة لم تعد تمر ب 401 Unauthorized، فالمزود يقبلها من أول INVITE لأنها جاءت من العنوان الصحيح.
ثغرة: من يتصل باسم الخط؟
هل هذا الإعداد آمن؟ لا، والتفصيل فيما يلي. نشغل جهازاً غريباً على عنوان آخر في الشبكة، 10.89.10.66، بإعداد الهاتف 1002 نفسه، ونعطيه حساباً اسمه acme دون كلمة مرور ودون تسجيل، ونجعله يتصل بجوال عبر المزود:
mkdir -p phones/stranger && cp phones/ext1002/config phones/stranger/
echo "<sip:[email protected]>;regint=0;answermode=auto" > phones/stranger/accounts
./dial.sh stranger 10.89.10.66 +966550000123 ip4
docker exec lab-vt-carrier sh -c 'grep Executing /var/log/asterisk/messages | tail -4'
docker exec lab-vt-carrier asterisk -rx 'pjsip show endpoint acme' | grep identify_by12:00:35 10.89.10.66.5060 > 10.89.10.5.5060 INVITE sip:[email protected] SIP/2.0
12:00:35 10.89.10.5.5060 > 10.89.10.66.5060 SIP/2.0 100 Trying
12:00:35 10.89.10.5.5060 > 10.89.10.66.5060 SIP/2.0 403 Forbidden
VERBOSE[182][C-00000004] pbx.c: Executing [+966550000123@from-customer:1] NoOp("PJSIP/acme-00000006", "Outbound from acme to +966550000123 with caller ID acme") in new stack
VERBOSE[182][C-00000004] pbx.c: Executing [+966550000123@from-customer:2] GotoIf("PJSIP/acme-00000006", "0?allowed") in new stack
VERBOSE[182][C-00000004] pbx.c: Executing [+966550000123@from-customer:3] NoOp("PJSIP/acme-00000006", "Caller ID acme is not one of acme's numbers") in new stack
VERBOSE[182][C-00000004] pbx.c: Executing [+966550000123@from-customer:4] Hangup("PJSIP/acme-00000006", "21") in new stack
identify_by : username,ipالرد 403 قد يبدو مطمئناً، ولكن اقرأ السطر الأول من خطة الاتصال: المكالمة دخلت باسم القناة PJSIP/acme وفي سياق العميل from-customer، أي أن المزود عاملها كأنها من مقسم acme، مع أنها جاءت من عنوان آخر ودون كلمة مرور. والذي أوقفها هو قاعدة هوية المتصل في خطة الاتصال فقط، وقاعدة في خطة الاتصال ليست حدوداً أمنية. والسبب في السطر الأخير: identify_by قيمته الافتراضية username,ip، فيبحث Asterisk أولاً باسم المستخدم في From، فإذا وجد endpoint بالاسم نفسه دون كلمة مرور قبله مباشرة، ولا يصل إلى مطابقة العنوان أصلاً.
والمشكلة نفسها موجودة في مقسم المكتب، فال endpoint اسمه carrier، وأي جهاز يرسل INVITE مباشرة إلى المقسم وفي From الاسم carrier يدخل في سياق from-trunk، ولنجرب ذلك نغير حساب الجهاز الغريب ونتصل بالرقم الرئيسي على عنوان المقسم مباشرة:
echo "<sip:[email protected]>;regint=0;answermode=auto" > phones/stranger/accounts
./dial.sh stranger 10.89.10.66 +966110000100 ip7 >/dev/null
docker exec lab-vt-office sh /lab/sipflow.sh /tmp/ip7.pcap | grep 10.89.10.66 | head -4
docker exec lab-vt-office sh -c 'grep Executing /var/log/asterisk/messages | tail -2'12:02:04 10.89.10.66.5060 > 10.89.10.10.5060 INVITE sip:[email protected] SIP/2.0
12:02:04 10.89.10.10.5060 > 10.89.10.66.5060 SIP/2.0 100 Trying
12:02:04 10.89.10.10.5060 > 10.89.10.66.5060 SIP/2.0 180 Ringing
12:02:04 10.89.10.10.5060 > 10.89.10.66.5060 SIP/2.0 200 OK
VERBOSE[209][C-00000005] pbx.c: Executing [+966110000100@from-trunk:1] Goto("PJSIP/carrier-00000007", "office,1001,1") in new stack
VERBOSE[209][C-00000005] pbx.c: Executing [1001@office:1] Dial("PJSIP/carrier-00000007", "PJSIP/1001,20") in new stackهاتف الاستقبال رن ورد على جهاز غريب دخل باسم المزود. وهنا الضرر إزعاج فقط لأن from-trunk لا يخرج إلى الخارج، ولكن خطأ واحداً في السياق، مثل أن تضع فيه Goto إلى office كله، يجعل هذا الجهاز يتصل بأي رقم في العالم على حسابك. والحل أن تجعل الخطوط التي تعتمد على العنوان تطابق بالعنوان فقط، فنضيف إلى ال endpoint في الجهتين:
identify_by=ip./reload.sh carrier; ./reload.sh office
./dial.sh stranger 10.89.10.66 +966550000123 ip5
docker exec lab-vt-carrier sh -c 'grep NOTICE /var/log/asterisk/messages | tail -1'والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى الأسطر المهمة:
12:01:10 10.89.10.66.5060 > 10.89.10.5.5060 INVITE sip:[email protected] SIP/2.0
12:01:10 10.89.10.5.5060 > 10.89.10.66.5060 SIP/2.0 401 Unauthorized
NOTICE[37] res_pjsip/pjsip_distributor.c: Request 'INVITE' from '<sip:[email protected]>' failed for '10.89.10.66:5060' (callid: c717f183078bd768) - Failed to authenticateالجهاز الغريب لم يعد يطابق أي endpoint، فطلب منه المزود كلمة مرور لا يملكها، ولم تصل المكالمة إلى خطة الاتصال أصلاً، وفي مقسم المكتب ظهر السطر نفسه No matching endpoint found للتجربة المقابلة، بينما بقيت مكالمات المزود الحقيقي تعمل. وتذكر أن الخط بالتسجيل لم يكن فيه هذا الخطر عند المزود، لأن الحساب acme كان له كلمة مرور، فالمطابقة بالاسم تنتهي بطلب المصادقة.
identify_by=ip، وأن يكون سياقه لا يخرج إلى الخارج إلا بما تريده، وأن يغلق جدار الحماية منفذ SIP لكل العناوين ما عدا عناوين المزود. والاحتيال على المكالمات Toll Fraud يبدأ غالباً من خطأ صغير كهذا، وسوف نخصص له مقال تأمين المقسم في هذه السلسلة.قبل أن تربط مقسمك بمزود حقيقي
- اطلب من المزود ورقة الإعداد كاملة: عنوان السيرفر، ونوع الخط تسجيل أم IP، وأي ترويسة يقرأ منها هوية المتصل، وأين يضع الرقم المطلوب في المكالمات القادمة، والترميزات المسموحة، وعدد القنوات.
- افتح منفذ SIP ونطاق RTP لعناوين المزود فقط، والمزود الجاد يعطيك قائمة عناوينه، وإذا كان خطك بالتسجيل خلف NAT فطبق ما شرحناه في المقال السابق.
- استخدم TLS وSRTP إذا كان المزود يدعمهما، لأن الخط يمر عبر الإنترنت ويحمل مكالمات عملائك.
- حدد في خطة الاتصال ما يسمح بالخروج: الأرقام المحلية والجوال، والدولي فقط للتحويلات التي تحتاجه، والسبب أن فاتورة المكالمات الدولية هي ما يبحث عنه المحتال.
- لا تترك رقماً من الكتلة دون وجهة، فكل رقم لم توزعه يذهب إلى الاستقبال أو إلى رسالة صوتية، حتى لا يسمع العميل «الرقم غير موجود» على رقم مطبوع في إعلان قديم.
- راقب حالة التسجيل في الخط بالتسجيل، ف
Unregisteredفيpjsip show registrationsتعني أن المكالمات القادمة لا تصلك، ولن يخبرك أحد بذلك إلا العملاء.
جدول التشخيص السريع
| العرض | السبب الأرجح | أين تنظر |
|---|---|---|
| المتصل يسمع أن الرقم مرفوض، والمقسم يرد 401 على المزود | لا يوجد identify لعنوان المزود | No matching endpoint found في سجل المقسم |
| المكالمات القادمة ترجع 404 | المزود يرسل إلى s أو برقم لا يوجد في from-trunk | أول سطر من INVITE وترويسة To في الالتقاط |
| المكالمات القادمة لا تصل أبداً والخارجة تعمل | التسجيل سقط أو ربط NAT انتهى | pjsip show registrations |
| المكالمة الخارجة ترفض ب 403 | هوية المتصل ليست من أرقامك، أو في ترويسة لا يقرؤها المزود | From وP-Asserted-Identity في الالتقاط |
| المكالمة الخارجة ترفض ب 484 أو 404 | الرقم ليس بصيغة E.164 | أول سطر من INVITE الخارج |
| جهاز غريب يدخل في سياق الخط | endpoint دون كلمة مرور يطابق باسم المستخدم | identify_by في pjsip show endpoint |
إزالة التجربة
docker rm -f lab-vt-carrier lab-vt-office lab-vt-ext1001 lab-vt-mobile1
docker network rm lab-vt-netتدريب عملي
- أضف إلى المزود مكالمات الأرقام الثابتة
+9661…، واجعل التحويلة 1001 فقط تستطيع الاتصال الدولي، ثم جرب الدولي من 1002 واقرأ ما يحدث. - في الخط بالتسجيل أوقف مقسم المزود دقيقتين ثم أعد تشغيله، وراقب
pjsip show registrationsفي مقسم المكتب، وقس كم يتأخر رجوع المكالمات القادمة بحسبretry_interval.
الخلاصة
- الخط الخارجي SIP Trunk يربط مقسمك بمزود يوصلك بشبكة الهاتف العامة، والأرقام DID كلها تأتي عبر الخط نفسه، والمقسم يختار التحويلة من الرقم المطلوب.
- حول كل رقم إلى E.164 داخل مقسمك، فهي اللغة التي يفهمها كل مزود، ولا تعتمد على أن يفهم المزود الشكل المحلي.
- التسجيل الناجح لا يعني أن المقسم يعرف المزود، فأضف
identifyلعناوين المزود، وانتبه لمزود يرسل المكالمات إلىs. - هوية المتصل تحتاج أن تصل في الترويسة التي يقرؤها المزود، وغالباً
P-Asserted-Identity، وأن تكون من أرقامك، وإلا رفض المكالمة ب 403. - الخط بعنوان IP لا يحتاج تسجيلاً ولكنه يجعل العنوان هو المفتاح الوحيد، فاجعل مطابقته
identify_by=ipواغلق SIP لكل العناوين ما عدا عناوين المزود.
وفي المقال القادم بإذن الله ندخل في Asterisk نفسه بالتفصيل: خطة الاتصال، والبريد الصوتي، والرد الآلي IVR، والتحكم فيه من برامجك.
دليلمصطلحات الاتصالات الهاتفية VoIP للمبتدئينشرح مبسط لمصطلحات الهاتف عبر الإنترنت، وما يمر في الشبكة بين ضغط زر الاتصال ووصول الصوت إلى الطرف الآخر.
دليلتتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟نبني مقسماً وهاتفين خلف NAT في تجربة عملية، ونتتبع المكالمة حزمة بحزمة حتى نعرف لماذا يضيع الصوت وكيف نصلحه.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وبناء التجربة على Asterisk 20.6.0 مزوداً ومقسماً وbaresip 1.0.0 من حزم Ubuntu 24.04 في Docker، ومراجعة صفحتي خطة الترقيم الوطنية وتصريح خدمات الصوت الافتراضية في موقع هيئة الاتصالات والفضاء والتقنية.