الأدلة التقنية

تتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟

نبني مقسماً وهاتفين خلف NAT في تجربة عملية، ونتتبع المكالمة حزمة بحزمة حتى نعرف لماذا يضيع الصوت وكيف نصلحه.

تتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟

في مصطلحات الاتصالات الهاتفية VoIP للمبتدئين رأينا أن المكالمة لها طريقان: رسائل SIP التي تجهزها، وصوت RTP الذي يمر على منافذ أخرى، وأن العنوان الذي يستقبل عليه كل هاتف الصوت مكتوب داخل الرسالة نفسها في SDP. وهذا التفصيل الصغير هو سبب أشهر شكوى في كل مكتب انتقل إلى VoIP: الهاتف يرن، والموظف يرد، والعميل يسمعه، أما الموظف فلا يسمع شيئاً، أو الهاتف يظهر مسجلاً في لوحة المقسم ولا يرن عندما يتصل به أحد.

وفي هذا المقال لن نشرح هذه المشكلات من بعيد، وإنما سوف نبنيها بأيدينا: مقسم Asterisk حقيقي، وهاتفان برمجيان، وراوتر يعمل بال NAT بينهما، ثم نتتبع كل مكالمة حزمة بحزمة، ونكسر الإعداد عمداً في كل مرة ونرى العرض الذي يراه المستخدم، ثم نصلحه بالخيار المناسب. وهذا المقال من مرحلة الاتصالات الهاتفية VoIP، وسوف نتناول فيه ما يلي:

  • بناء التجربة في Docker: Asterisk وbaresip وtcpdump وراوتر NAT في Containers.
  • المكالمة الأولى دون NAT، وكيف تقرأ رسائلها وحزم الصوت فيها.
  • نقل الهاتف خلف NAT، ثم أربعة أعطال حقيقية: رفض التسجيل 403، والهاتف الذي لا يرن، والصوت في اتجاه واحد، والصوت المقطوع في الاتجاهين بسبب جدار الحماية.
  • الهاتف المسجل الذي يتوقف عن الرنين بعد ثوان من السكوت، وكيف يحله الفحص الدوري.
  • الإعداد النهائي، وما يجب أن تغيره قبل أن تنقل هذا إلى سيرفر حقيقي، وجدول سريع للتشخيص.

خريطة التجربة

نحتاج في التجربة شبكتين: شبكة تلعب دور الإنترنت فيها المقسم وهاتف Alice، وشبكة مكتب خاصة فيها هاتف Bob، وبينهما راوتر يعمل بال NAT كما يعمل أي راوتر منزلي أو مكتبي، أي يغير عنوان الحزم الخارجة إلى عنوانه هو، ويرفض أي اتصال جديد يأتي من الخارج. وقد شرحنا هذا السلوك في شرح NAT للمبتدئين. والصورة التالية تبين الخريطة:

شبكة تمثل الإنترنت باسم lab-voip-wan فيها هاتف Alice بالتحويلة 1001 على العنوان 10.77.50.21 ومقسم Asterisk على 10.77.50.10 بمنفذ SIP 5060 ومنافذ RTP من 10000 إلى 10099، وراوتر NAT بعنوان 10.77.50.2 من الخارج و192.168.77.254 من الداخل، وشبكة مكتب باسم lab-voip-lan فيها حاسوب Bob بالتحويلة 1002 على العنوان 192.168.77.22، والحزم من Bob تصل إلى المقسم بعنوان الراوتر 10.77.50.2
المقسم يرى Bob دائماً بعنوان الراوتر، ولا يستطيع الوصول إلى عنوانه الخاص

ولاحظ أن كل شيء يعمل داخل شبكات Docker، فلا يوجد منفذ واحد مفتوح على جهازك، والشبكة التي تمثل الإنترنت أنشأناها بالخيار --internal حتى لا يكون لها طريق إلى خارج جهازك أصلاً. وإذا كنت جديداً على شبكات Docker فقد شرحناها في شبكات Docker وأفضل الممارسات.

تجهيز ال Image للتجربة

لا توجد Image رسمية من مشروع Asterisk على Docker Hub، لذلك سوف نبني Image واحدة صغيرة من Ubuntu 24.04 فيها كل الأدوات، ونثبت إصدار كل حزمة حتى تحصل على النتيجة نفسها: Asterisk مقسماً، وbaresip هاتفاً برمجياً يعمل من سطر الأوامر، وtcpdump لالتقاط الحزم، وsngrep لعرض المكالمات بشكل تفاعلي. قم بإنشاء مجلد للتجربة، وضع فيه الملف Dockerfile التالي:

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 \
      baresip-core=1.0.0-4build14 \
      sngrep=1.8.0-1build2 \
      tcpdump=4.99.4-3ubuntu4.24.04.1 \
      sox iproute2 iptables iputils-ping \
 && rm -rf /var/lib/apt/lists/*
COPY sipflow.sh /usr/local/bin/sipflow
COPY rtpcount.sh /usr/local/bin/rtpcount
RUN chmod +x /usr/local/bin/sipflow /usr/local/bin/rtpcount

والسكربتان sipflow.sh وrtpcount.sh أداتان صغيرتان كتبناهما لقراءة ملف الالتقاط pcap: الأولى تطبع سطراً لكل رسالة SIP فيه الوقت والمصدر والوجهة وأول سطر من الرسالة، والثانية تعد حزم UDP في كل اتجاه بعد استبعاد SIP، أي حزم الصوت RTP وتقاريرها RTCP:

cat > sipflow.sh <<'EOF'
#!/bin/sh
# Prints one line per SIP message in a pcap: time, source > destination, first line
tcpdump -r "$1" -n -A 'port 5060' 2>/dev/null | awk '
  / IP [0-9.]+ > [0-9.]+:/ { split($1,t,"."); src=$3; dst=$5; sub(":","",dst); want=1; next }
  want && match($0, /(REGISTER|INVITE|ACK|BYE|CANCEL|OPTIONS) sip:[^ ]+ SIP\/2\.0|SIP\/2\.0 [0-9][0-9][0-9] [A-Za-z ]+/) {
      printf "%s  %-18s > %-18s %s\n", t[1], src, dst, substr($0, RSTART, RLENGTH); want=0 }'
EOF
cat > rtpcount.sh <<'EOF'
#!/bin/sh
# Counts packets per UDP flow (RTP and RTCP) in a pcap, SIP excluded
tcpdump -r "$1" -n 'udp and not port 5060' 2>/dev/null | awk '{sub(":$","",$5); print $3, $4, $5}' | sort | uniq -c | sort -rn
EOF
docker build -t lab-voip-tools:1 .
docker run --rm lab-voip-tools:1 asterisk -V

والمخرج سوف يكون كما يلي:

Asterisk 20.6.0~dfsg+~cs6.13.40431414-2build5
⚠️
استخدمنا حزمة Ubuntu لأنها أسرع طريقة لتجربة قصيرة، ولكن إصدارها 20.6.0 قديم، فآخر إصدار من فرع 20 عند كتابة المقال هو 20.21.0، والفرع 22 هو أحدث فرع منشور بدعم طويل LTS بحسب جدول إصدارات Asterisk، والفرع 20 ينتقل إلى إصلاحات الأمان فقط في 19 أكتوبر 2026. لا تقم باستخدام هذه ال Image لمقسم حقيقي، والسبب أن المقسم المتصل بخط خارجي هدف دائم للمهاجمين، وسوف نشرح التثبيت الصحيح في دليل Asterisk ضمن هذه المرحلة.

إعداد Asterisk: تحويلتان وخطة اتصال

Asterisk يقرأ إعداداته من ملفات نصية في /etc/asterisk، وسوف نستبدل منها أربعة ملفات فقط. والملف الأهم هو asterisk/pjsip.conf الذي يعرف التحويلات عبر وحدة PJSIP، وهي الوحدة الحالية للتعامل مع SIP في Asterisk، وقد بدأنا عمداً بإعداد يفترض أن كل الهواتف في الشبكة نفسها مع المقسم، وأطفأنا فيه خيارات NAT الثلاثة صراحة حتى نرى أثر كل منها:

[global]
type=global
user_agent=ArabRoot-Lab-PBX

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

; قالب مشترك للتحويلات الداخلية Extensions
[phone](!)
type=endpoint
context=office
disallow=all
allow=ulaw,alaw
direct_media=no
rtp_symmetric=no
force_rport=no
rewrite_contact=no

[1001](phone)
auth=1001
aors=1001
callerid="Alice" <1001>

[1001]
type=auth
auth_type=userpass
username=1001
password=Lab-1001-pass

[1001]
type=aor
max_contacts=1

[1002](phone)
auth=1002
aors=1002
callerid="Bob" <1002>

[1002]
type=auth
auth_type=userpass
username=1002
password=Lab-1002-pass

[1002]
type=aor
max_contacts=1

في الإعداد أعلاه لاحظ التالي:

  • كل تحويلة في PJSIP تتكون من ثلاثة أجزاء بالاسم نفسه: endpoint يصف سلوكها، وauth يحمل اسم المستخدم وكلمة المرور، وaor وهو اختصار Address of Record، ويحفظ العنوان الذي سجل منه الهاتف آخر مرة.
  • [phone](!) قالب Template لا يصبح تحويلة بنفسه، وكل تحويلة ترث منه بكتابة (phone) بعد اسمها، فتغير خيار NAT في مكان واحد.
  • direct_media=no يجبر الصوت على المرور عبر المقسم، والقيمة الافتراضية yes كما في المساعدة المدمجة في Asterisk، وسوف نحتاج هذا في كل الخطوات حتى نرى الصوت في الالتقاط.
  • القيم الافتراضية للخيارات الثلاثة الأخرى في هذا الإصدار هي rtp_symmetric=no وforce_rport=yes وrewrite_contact=no، وقد جعلنا force_rport أيضاً no حتى نبدأ من إعداد لا يعرف شيئاً عن NAT.
  • كلمات المرور هنا للتجربة فقط، وفي المقسم الحقيقي يجب أن تكون طويلة وعشوائية، لأن التحويلة المكشوفة تعني مكالمات على حسابك.

وبقية الملفات قصيرة: خطة الاتصال في asterisk/extensions.conf التي شرحناها في مقال المصطلحات، ونطاق صغير لمنافذ RTP في asterisk/rtp.conf حتى يسهل فتحه وتتبعه، وإعداد السجلات في asterisk/logger.conf:

cat > asterisk/extensions.conf <<'EOF'
[office]
exten => _10XX,1,NoOp(Call from ${CALLERID(num)} to ${EXTEN})
 same => n,Dial(PJSIP/${EXTEN},20)
 same => n,Hangup()
EOF
cat > asterisk/rtp.conf <<'EOF'
[general]
rtpstart=10000
rtpend=10099
EOF
cat > asterisk/logger.conf <<'EOF'
[general]
[logfiles]
console => notice,warning,error,verbose
EOF

الآن نقوم بإنشاء الشبكتين وتشغيل المقسم:

docker network create --internal --subnet 10.77.50.0/24 lab-voip-wan
docker network create --subnet 192.168.77.0/24 lab-voip-lan

docker run -d --name lab-voip-pbx --network lab-voip-wan --ip 10.77.50.10 \
  --cap-add NET_ADMIN --cap-add NET_RAW \
  -v "$PWD/asterisk/pjsip.conf:/etc/asterisk/pjsip.conf" \
  -v "$PWD/asterisk/extensions.conf:/etc/asterisk/extensions.conf" \
  -v "$PWD/asterisk/rtp.conf:/etc/asterisk/rtp.conf" \
  -v "$PWD/asterisk/logger.conf:/etc/asterisk/logger.conf" \
  lab-voip-tools:1 asterisk -f

docker exec lab-voip-pbx asterisk -rx 'pjsip show endpoints'

والصلاحيتان NET_ADMIN وNET_RAW نحتاجهما لاحقاً لتشغيل tcpdump وتعديل جدار الحماية داخل ال Container، والمخرج سوف يكون كما يلي، ولاحظ أن التحويلتين Unavailable لأن أي هاتف لم يسجل بعد:

 Endpoint:  1001/1001                                            Unavailable   0 of inf
     InAuth:  1001/1001
        Aor:  1001                                               1

 Endpoint:  1002/1002                                            Unavailable   0 of inf
     InAuth:  1002/1002
        Aor:  1002                                               1


Objects found: 2

الهاتفان: baresip في Container

baresip هاتف SIP برمجي صغير مرخص بترخيص BSD كما في ملف الترخيص، ويعمل دون واجهة رسومية، فيناسب التجربة. ولأنه لا توجد سماعة ولا ميكروفون في ال Container، سوف نجعل مصدر الصوت ملفاً فيه نغمة ثابتة، ونجعل الهاتف يحفظ ما يسمعه في ملف، فإذا كان الملف فارغاً فهذا يعني أن الهاتف لم يسمع شيئاً. وهذه هي الأوامر:

mkdir -p alice bob sounds rec-alice rec-bob
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 alice:1001 bob:1002; do n=${p%%:*}; ext=${p#*:}
cat > $n/config <<EOF
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
module           sndfile.so
snd_path         /rec
EOF
echo "<sip:[email protected]>;auth_pass=Lab-$ext-pass;answermode=auto" > $n/accounts
done
echo "module           stdio.so" >> alice/config

في الإعداد أعلاه لاحظ التالي:

  • audio_source aufile يرسل النغمة من الملف بدل الميكروفون، وalsa,null مشغل صوت وهمي لا يخرج شيئاً.
  • الوحدة sndfile تحفظ ما يرسله الهاتف وما يستقبله في ملفين في /rec، ومنها نعرف هل وصل الصوت فعلاً.
  • answermode=auto يجعل الهاتف يرد تلقائياً، لأنه لا يوجد أحد يضغط زر الرد.
  • هاتف Alice وحده فيه الوحدة stdio حتى نرسل له الأوامر من سطر الأوامر، مثل /dial 1002، وحتى يطبع سطر الإحصاءات الذي سنحتاجه.

نبدأ ب Bob على شبكة الإنترنت نفسها مع المقسم، أي دون NAT، ثم نسأل المقسم أين يجده:

docker run -d --name lab-voip-bob --network lab-voip-wan --ip 10.77.50.22 \
  --cap-add NET_ADMIN --cap-add NET_RAW \
  -v "$PWD/bob:/conf" -v "$PWD/sounds:/sounds:ro" -v "$PWD/rec-bob:/rec" \
  lab-voip-tools:1 sh -c 'ip route replace default via 192.168.77.254 2>/dev/null; exec baresip -f /conf'

docker exec lab-voip-pbx asterisk -rx 'pjsip show contacts'

والمخرج سوف يكون كما يلي:

  Contact:  1002/sip:[email protected]:5060  74f845b4f3 NonQual         nan

Objects found: 1

والأمر ip route replace في تشغيل Bob لا يفعل شيئاً الآن، وسوف نحتاجه عندما ننقله إلى شبكة المكتب. والسطر أعلاه يعني أن المقسم حفظ للتحويلة 1002 العنوان الذي أعلنه الهاتف في الترويسة Contact عند التسجيل، وNonQual تعني أن المقسم لا يفحص هذا الهاتف بشكل دوري، وسوف نعود لهذه الكلمة في آخر التجربة.

المكالمة الأولى دون NAT

لكل مكالمة سوف نقوم بثلاثة أشياء: نشغل tcpdump على المقسم، ثم نشغل هاتف Alice ونرسل له أمر الاتصال، ثم نوقف الالتقاط ونقرأه. ولأننا سوف نكرر ذلك في كل خطوة، نضعه في السكربت call.sh:

cat > call.sh <<'EOF'
#!/bin/sh
# usage: ./call.sh <name> <seconds in call>
docker exec -d lab-voip-pbx tcpdump -i eth0 -n -s0 -w /tmp/$1.pcap udp
sleep 1
(sleep 3; echo "/dial 1002"; sleep $2; echo "/hangup"; sleep 2; echo "q") | \
  docker run -i --rm --name lab-voip-alice --network lab-voip-wan --ip 10.77.50.21 \
    -v "$PWD/alice:/conf" -v "$PWD/sounds:/sounds:ro" -v "$PWD/rec-alice:/rec" \
    lab-voip-tools:1 baresip -f /conf 2>&1 | tr '\r' '\n' > $1-alice.log
sleep 1; docker exec lab-voip-pbx pkill tcpdump; sleep 1
docker exec lab-voip-pbx sipflow /tmp/$1.pcap
docker exec lab-voip-pbx rtpcount /tmp/$1.pcap
grep -a 'audio=' $1-alice.log | tail -1
EOF
chmod +x call.sh
./call.sh c1 10

والمخرج سوف يكون كما يلي، بعد أن حذفنا منه أسطر التسجيل في البداية والنهاية:

19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:[email protected] SIP/2.0
19:13:44  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 401 Unauthorized
19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   ACK sip:[email protected] SIP/2.0
19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:[email protected] SIP/2.0
19:13:44  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 100 Trying
19:13:44  10.77.50.10.5060   > 10.77.50.22.5060   INVITE sip:[email protected]:5060 SIP/2.0
19:13:44  10.77.50.22.5060   > 10.77.50.10.5060   SIP/2.0 180 Ringing
19:13:44  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 180 Ringing
19:13:44  10.77.50.22.5060   > 10.77.50.10.5060   SIP/2.0 200 Answering
19:13:44  10.77.50.10.5060   > 10.77.50.22.5060   ACK sip:[email protected]:5060 SIP/2.0
19:13:44  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 200 OK
19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   ACK sip:10.77.50.10:5060 SIP/2.0
19:13:54  10.77.50.21.5060   > 10.77.50.10.5060   BYE sip:10.77.50.10:5060 SIP/2.0
19:13:54  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 200 OK
19:13:54  10.77.50.10.5060   > 10.77.50.22.5060   BYE sip:[email protected]:5060 SIP/2.0
19:13:54  10.77.50.22.5060   > 10.77.50.10.5060   SIP/2.0 200 OK
    497 10.77.50.22.20008 > 10.77.50.10.10012
    496 10.77.50.10.10068 > 10.77.50.21.20004
    492 10.77.50.21.20004 > 10.77.50.10.10068
    491 10.77.50.10.10012 > 10.77.50.22.20008
      4 10.77.50.22.20009 > 10.77.50.10.10013
      3 10.77.50.21.20005 > 10.77.50.10.10069
      1 10.77.50.10.10069 > 10.77.50.21.20005
      1 10.77.50.10.10013 > 10.77.50.22.20009
[0:00:10] audio=62657/63936 (bit/s)

في المخرج أعلاه لاحظ التالي:

  • الرسائل هي التسلسل نفسه الذي رسمناه في مقال المصطلحات: INVITE يحتاج المصادقة، ثم المقسم يبدأ INVITE جديداً إلى العنوان المحفوظ لبوب 10.77.50.22.
  • هناك أربعة تدفقات RTP كبيرة، لأن المقسم في المنتصف: Bob إلى المقسم، والمقسم إلى Alice، وAlice إلى المقسم، والمقسم إلى Bob، وكل منها نحو 490 حزمة في عشر ثوان، أي 50 حزمة في الثانية.
  • التدفقات الصغيرة على المنافذ الفردية 20009 و10013 هي تقارير RTCP.
  • السطر الأخير من هاتف Alice يقول إنها ترسل وتستقبل نحو 64 kbit/s، وهو معدل G.711 بالضبط، أي أن الصوت وصل في الاتجاهين.

وإذا أردت أن ترى المكالمة كسلم رسائل تفاعلي بدل الأسطر، فشغل docker exec -it lab-voip-pbx sngrep -I /tmp/c1.pcap، واختر المكالمة بالسهم واضغط Enter، وهي الطريقة الأسرع لقراءة SIP على سيرفر حقيقي.

ننقل Bob خلف NAT

الآن نبني الراوتر: Container متصل بالشبكتين، يغير عنوان الحزم الخارجة من شبكة المكتب إلى عنوانه بقاعدة MASQUERADE، ويسمح من الخارج بالردود على اتصالات بدأت من الداخل فقط، كما يفعل أي راوتر مكتبي. ثم نجعل المقسم يرسل كل ما لا يعرفه عبر هذا الراوتر، حتى إذا حاول الوصول إلى عنوان خاص في شبكة المكتب يصل إلى الراوتر ويرفض هناك، تماماً كما يضيع العنوان الخاص في الإنترنت الحقيقي:

docker run -d --name lab-voip-nat --network lab-voip-wan --ip 10.77.50.2 \
  --cap-add NET_ADMIN --sysctl net.ipv4.ip_forward=1 lab-voip-tools:1 sleep infinity
docker network connect --ip 192.168.77.254 lab-voip-lan lab-voip-nat
docker exec lab-voip-nat sh -c '
  iptables -t nat -A POSTROUTING -s 192.168.77.0/24 -o eth0 -j MASQUERADE
  iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  iptables -A FORWARD -i eth0 -o eth1 -j DROP'
docker exec lab-voip-pbx ip route add default via 10.77.50.2

docker rm -f lab-voip-bob
docker run -d --name lab-voip-bob --network lab-voip-lan --ip 192.168.77.22 \
  --cap-add NET_ADMIN --cap-add NET_RAW \
  -v "$PWD/bob:/conf" -v "$PWD/sounds:/sounds:ro" -v "$PWD/rec-bob:/rec" \
  lab-voip-tools:1 sh -c 'ip route replace default via 192.168.77.254 2>/dev/null; exec baresip -f /conf'
docker logs lab-voip-bob 2>&1 | tail -1

العطل الأول: رفض التسجيل 403 Forbidden

قبل أن نصل إلى ال NAT نفسه نقابل أول عطل، والمخرج سوف يكون كما يلي:

reg: sip:[email protected]: 403 Forbidden (ArabRoot-Lab-PBX)

وعند قراءة سجل المقسم ب docker logs lab-voip-pbx سوف تجد السبب:

[Oct  8 19:14:10] WARNING[36]: res_pjsip_registrar.c:769 register_aor_core: Registration attempt from endpoint '1002' (10.77.50.2:5060) to AOR '1002' will exceed max contacts of 1

والسبب أن الهاتف القديم على العنوان 10.77.50.22 ما زال مسجلاً، ومدة تسجيله نحو ساعة، وقد حددنا max_contacts=1، فرفض المقسم التسجيل الجديد بدل أن يستبدل القديم، لأن القيمة الافتراضية للخيار remove_existing هي no. وهذا ما يحدث في الواقع عندما ينقل موظف حاسوبه من شبكة المكتب إلى شبكة البيت، أو عندما يتغير عنوان الراوتر العام. ولاحظ أيضاً أن المقسم يرى الطلب قادماً من 10.77.50.2، أي من الراوتر، وليس من عنوان Bob الحقيقي. والحل أن نضيف إلى جزء aor في التحويلتين:

remove_existing=yes

ثم نعيد تحميل الإعداد ونعيد تشغيل الهاتف:

docker exec lab-voip-pbx asterisk -rx 'pjsip reload'
docker restart lab-voip-bob
docker exec lab-voip-pbx asterisk -rx 'pjsip show contacts'

والمخرج سوف يكون كما يلي:

  Contact:  1002/sip:[email protected]:506 7f13451082 NonQual         nan

Objects found: 1

نجح التسجيل، ولكن انظر إلى العنوان الذي حفظه المقسم: 192.168.77.22، وهو العنوان الخاص لبوب داخل المكتب، والجدول يقطع آخر رقم من المنفذ 5060 لضيق العمود فقط. والمقسم حفظه لأن الهاتف كتبه بنفسه في الترويسة Contact، وال NAT غير عنوان الحزمة ولم يغير ما بداخلها.

العطل الثاني: الهاتف مسجل ولا يرن

نتصل الآن من Alice، وننتظر 25 ثانية:

./call.sh c2 25

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

19:14:29  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:[email protected] SIP/2.0
19:14:29  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 100 Trying
19:14:29  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:29  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:30  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:32  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:36  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:44  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:[email protected]:5060 SIP/2.0
19:14:49  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 603 Decline

المقسم يرسل INVITE إلى العنوان الخاص 192.168.77.22، والراوتر يرفضه لأنه اتصال جديد من الخارج، فلا يأتي أي رد، فيعيد المقسم الإرسال بفواصل تتضاعف: نصف ثانية ثم ثانية ثم ثانيتان ثم أربع ثم ثمان، وهذه هي مؤقتات إعادة الإرسال عبر UDP في RFC 3261، ثم ينتهي وقت الانتظار الذي وضعناه في Dial بعد 20 ثانية فيرد المقسم على Alice ب 603 Decline. والمستخدم يرى في هذه الحالة أن «الطرف الآخر رفض المكالمة»، وهذا مضلل تماماً، فهاتف Bob لم يعلم بالمكالمة أصلاً.

rewrite_contact وforce_rport: الرد إلى مصدر الحزمة

الحل البسيط الذي يخطر على البال هو أن نكتب في إعداد الهاتف العنوان العام للراوتر بدل عنوانه الخاص، ولكن هذا الحل غير مناسب إطلاقاً، فالمنفذ الذي يختاره ال NAT يتغير، كما سنرى بعد قليل، وأغلب الهواتف تتنقل بين الشبكات. والطريقة الصحيحة أن يتجاهل المقسم ما يقوله الهاتف عن نفسه، ويعتمد على العنوان والمنفذ اللذين وصلت منهما الحزمة فعلاً، وهذا ما يفعله خياران في endpoint:

  • force_rport=yes: يرسل المقسم ردوده على أي طلب إلى العنوان والمنفذ اللذين جاء منهما الطلب، وليس إلى ما في ترويسة Via، وهو السلوك الذي يعرفه RFC 3581.
  • rewrite_contact=yes: يستبدل المقسم العنوان في ترويسة Contact عند التسجيل بعنوان مصدر الحزمة، فترسل المكالمات القادمة إلى الراوتر، ويمررها الراوتر إلى الهاتف لأنها تطابق الاتصال الذي فتحه الهاتف بالتسجيل.

نغير الخيارين في القالب، ثم نعيد التحميل ونلتقط تسجيل Bob لنرى ما يحدث:

sed -i 's/^force_rport=no/force_rport=yes/; s/^rewrite_contact=no/rewrite_contact=yes/' asterisk/pjsip.conf
docker exec lab-voip-pbx asterisk -rx 'pjsip reload'
docker exec -d lab-voip-pbx tcpdump -i eth0 -n -s0 -w /tmp/reg.pcap udp port 5060
docker restart lab-voip-bob; sleep 5; docker exec lab-voip-pbx pkill tcpdump
docker exec lab-voip-pbx sh -c "tcpdump -r /tmp/reg.pcap -n -A 2>/dev/null" | grep -E '^Via|^Contact'
docker exec lab-voip-pbx asterisk -rx 'pjsip show aor 1002' | grep -E '^ *contact '

والمخرج سوف يكون كما يلي، وقد أخذنا منه أسطر التسجيل الجديد:

Via: SIP/2.0/UDP 192.168.77.22:5060;branch=z9hG4bK63629205ffe0240b;rport
Contact: <sip:[email protected]:5060>;expires=3768
Via: SIP/2.0/UDP 192.168.77.22:5060;rport=5060;received=10.77.50.2;branch=z9hG4bK63629205ffe0240b
Contact: <sip:[email protected]:5060>;expires=3767
 contact              : sip:[email protected]:5060;x-ast-orig-host=192.168.77.22:5060

في المخرج أعلاه لاحظ التالي:

  • الهاتف ما زال يكتب عنوانه الخاص في Via وContact، فهو لا يعلم أنه خلف NAT.
  • المقسم يرد بإضافة received=10.77.50.2 وrport=5060، أي «رأيت طلبك قادماً من هذا العنوان وهذا المنفذ».
  • العنوان المحفوظ للتحويلة أصبح 10.77.50.2، وحفظ المقسم العنوان الأصلي في x-ast-orig-host للمعلومة فقط.

العطل الثالث: الصوت في اتجاه واحد One-way Audio

نتصل مرة أخرى، وأثناء المكالمة نسأل المقسم عن إحصاءات الصوت من نافذة أخرى بالأمر docker exec lab-voip-pbx asterisk -rx 'pjsip show channelstats':

./call.sh c3 10
docker exec lab-voip-bob ls -l /rec

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

19:15:17  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:15:17  10.77.50.2.5060    > 10.77.50.10.5060   SIP/2.0 180 Ringing
19:15:17  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 180 Ringing
19:15:18  10.77.50.2.5060    > 10.77.50.10.5060   SIP/2.0 200 Answering
19:15:18  10.77.50.10.5060   > 10.77.50.2.5060    ACK sip:[email protected]:5060 SIP/2.0
19:15:18  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 200 OK
    492 10.77.50.2.20012 > 10.77.50.10.10010
    487 10.77.50.21.20018 > 10.77.50.10.10052
    487 10.77.50.10.10010 > 192.168.77.22.20012
    486 10.77.50.10.10052 > 10.77.50.21.20018
[0:00:09] audio=64426/64426 (bit/s)
-rw-r--r-- 1 root root     44 Oct  8 19:15 dump-2026-10-08-19-15-17-dec.wav
-rw-r--r-- 1 root root 157484 Oct  8 19:15 dump-2026-10-08-19-15-17-enc.wav

الهاتف رن، وBob رد، والمكالمة قائمة، وAlice تستقبل الصوت بمعدل 64 kbit/s، أي أنها تسمع Bob. أما ملف dec.wav عند Bob، وهو ما سمعه، فحجمه 44 بايت فقط، وهو حجم ترويسة ملف WAV دون أي صوت، أي أن Bob لم يسمع شيئاً. والسبب في السطر الثالث من عداد الحزم: المقسم أرسل 487 حزمة صوت إلى 192.168.77.22، وهو العنوان الخاص الذي كتبه Bob في SDP داخل رده 200 OK، كما يظهر من الالتقاط نفسه:

Contact: <sip:[email protected]:5060>
o=- 3114984680 42264411 IN IP4 192.168.77.22
c=IN IP4 192.168.77.22
m=audio 20012 RTP/AVP 0 8 101

والخياران السابقان يصلحان عنوان SIP فقط، أما عنوان الصوت في SDP فيبقى كما هو، والصورة التالية تبين ما يحدث للحزمة:

حزمة تخرج من حاسوب Bob بعنوان مصدر 192.168.77.22 وفي داخلها SDP يقول إن عنوان الصوت 192.168.77.22، ثم بعد الراوتر يتغير عنوان المصدر إلى 10.77.50.2 ويبقى عنوان SDP كما هو، وتحتها حالتان: مع rtp_symmetric=no يرسل المقسم الصوت إلى العنوان الخاص فيضيع ولا يسمع Bob شيئاً، ومع rtp_symmetric=yes يرسله إلى العنوان والمنفذ الذي جاءت منه حزم Bob فيمرره الراوتر إليه
ال NAT يغير عنوان الحزمة ولا يقرأ ما بداخلها

والأخطر من ذلك ما رأيناه في إحصاءات المقسم أثناء المكالمة:

 BridgeId ChannelId ........ UpTime.. Codec.   Count    Lost Pct  Jitter   Count    Lost Pct  Jitter RTT....
 fcb7d283 1001-00000004      00:00:09 ulaw      401       0    0   0.006    401       0    0   0.003   0.000
 fcb7d283 1002-00000005      00:00:09 ulaw      401       0    0   0.000    401       0    0   0.003   0.000

المقسم يقول إنه أرسل 401 حزمة إلى Bob دون أي فقد، وهذا صحيح من جهته، فالحزم خرجت منه فعلاً، ولكنه لا يعلم أنها ضاعت بعد ذلك. لذلك لا تقم بالحكم على مشكلة الصوت من إحصاءات المقسم وحدها، والسبب أن الذي يعرف هل وصل الصوت هو الطرف الذي يستقبله، فقارن دائماً ما خرج من المقسم بما وصل إلى الهاتف.

rtp_symmetric: الصوت في الاتجاهين

الحل هو الخيار rtp_symmetric=yes، وهو يجعل المقسم يرسل الصوت إلى العنوان والمنفذ اللذين يصل منهما صوت الهاتف فعلاً، بدل ما هو مكتوب في SDP، وهذا يعمل لأن أغلب الهواتف ترسل وتستقبل RTP على المنفذ نفسه، وهو ما يسمى RTP المتماثل Symmetric RTP:

sed -i 's/^rtp_symmetric=no/rtp_symmetric=yes/' asterisk/pjsip.conf
docker exec lab-voip-pbx asterisk -rx 'pjsip reload'
docker restart lab-voip-bob
./call.sh c4 10
docker exec lab-voip-bob ls -l /rec | tail -2

والمخرج سوف يكون كما يلي، بعد أن اختصرناه إلى عداد الحزم والسطرين الأخيرين:

    455 10.77.50.2.20016 > 10.77.50.10.10028
    451 10.77.50.10.10046 > 10.77.50.21.20004
    429 10.77.50.21.20004 > 10.77.50.10.10046
    427 10.77.50.10.10028 > 10.77.50.2.20016
      3 10.77.50.21.20005 > 10.77.50.10.10047
      3 10.77.50.2.20017 > 10.77.50.10.10029
      1 10.77.50.10.10047 > 10.77.50.21.20005
      1 10.77.50.10.10029 > 10.77.50.2.20017
[0:00:09] audio=64000/64426 (bit/s)
-rw-r--r-- 1 root root 135084 Oct  8 19:16 dump-2026-10-08-19-15-55-dec.wav
-rw-r--r-- 1 root root 145644 Oct  8 19:16 dump-2026-10-08-19-15-55-enc.wav

كل حزم المقسم ذهبت هذه المرة إلى 10.77.50.2:20016، وهو المصدر الذي جاء منه صوت Bob، ولم تذهب أي حزمة إلى العنوان الخاص، وملف ما سمعه Bob أصبح 135 KB، أي أن الصوت وصل في الاتجاهين. وقد رأينا في تشغيل سابق للتجربة أن المقسم أرسل ثلاث حزم إلى العنوان الخاص في أول المكالمة قبل أن يصله أول صوت من Bob ويتعلم العنوان الصحيح، وهذا طبيعي ولا يسمعه أحد.

العطل الرابع: جدار الحماية Firewall يغلق منافذ RTP

الآن نفترض أن من أعد السيرفر فتح منفذ SIP فقط في جدار الحماية، وهو خطأ شائع لأن الوثائق تذكر المنفذ 5060 في كل مكان، ونرى ماذا يحدث:

docker exec lab-voip-pbx sh -c 'iptables -A INPUT -p udp --dport 5060 -j ACCEPT; iptables -A INPUT -p udp -j DROP'
docker restart lab-voip-bob
./call.sh c5 10
docker exec lab-voip-pbx iptables -L INPUT -v -n
docker exec lab-voip-pbx iptables -F INPUT

والمخرج سوف يكون كما يلي:

[0:00:10] audio=63530/0 (bit/s)
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination
   16 10456 ACCEPT     17   --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:5060
  993  198K DROP       17   --  *      *       0.0.0.0/0            0.0.0.0/0

المكالمة اتصلت لأن SIP مسموح، ولكن Alice ترسل ولا تستقبل شيئاً، وملف ما سمعه Bob 44 بايت مرة أخرى، وعداد القاعدة الثانية يقول إن جدار الحماية أسقط 993 حزمة صوت من الطرفين. والحل أن تفتح نطاق المنافذ المكتوب في rtp.conf بالضبط، ولهذا اخترنا نطاقاً صغيراً من 10000 إلى 10099، فكل مكالمة تمر عبر المقسم تستخدم منفذين على الأقل في كل جهة، ونطاق أوسع يعني مكالمات أكثر في الوقت نفسه. وقد شرحنا ضبط جدار الحماية على السيرفر في تأمين خادم VPS من أول دخول.

الهاتف مسجل ولا يرن بعد ثوان من السكوت: مهلة NAT Timeout

بقي عطل أخير يظهر في الإنتاج بعد أيام وليس في أول تجربة: الهاتف يظهر مسجلاً في لوحة المقسم، ولكن المكالمات القادمة لا تصل إليه، ثم يعود فجأة إلى العمل عندما يجري الموظف مكالمة. والسبب أن الراوتر لا يحفظ ربط NAT Mapping إلى الأبد، وإنما يحذفه بعد مدة من السكوت، ومدة التسجيل في SIP ساعة كاملة، فبين تسجيل وآخر يحذف الراوتر الربط، وعندها يصبح INVITE القادم من المقسم اتصالاً جديداً من الخارج فيرفض. ولكي نرى هذا بسرعة نعيد تشغيل الراوتر بمهلة 30 ثانية لحزم UDP، ونترك Bob ساكناً 50 ثانية ثم نتصل به:

docker rm -f lab-voip-nat
docker run -d --name lab-voip-nat --network lab-voip-wan --ip 10.77.50.2 \
  --cap-add NET_ADMIN --sysctl net.ipv4.ip_forward=1 \
  --sysctl net.netfilter.nf_conntrack_udp_timeout=30 \
  --sysctl net.netfilter.nf_conntrack_udp_timeout_stream=30 \
  lab-voip-tools:1 sleep infinity
docker network connect --ip 192.168.77.254 lab-voip-lan lab-voip-nat
docker exec lab-voip-nat sh -c '
  iptables -t nat -A POSTROUTING -s 192.168.77.0/24 -o eth0 -j MASQUERADE
  iptables -A FORWARD -i eth0 -o eth1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
  iptables -A FORWARD -i eth0 -o eth1 -j DROP'
docker restart lab-voip-bob; sleep 50
./call.sh c6 8

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

19:17:53  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:17:54  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:17:55  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:17:57  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:18:01  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:[email protected]:5060 SIP/2.0
19:18:01  10.77.50.21.5060   > 10.77.50.10.5060   CANCEL sip:[email protected] SIP/2.0
19:18:01  10.77.50.10.5060   > 10.77.50.21.5060   SIP/2.0 487 Request Terminated

العنوان صحيح هذه المرة، فهو عنوان الراوتر 10.77.50.2، ومع ذلك لا يرد أحد، لأن الراوتر نسي أن المنفذ 5060 عنده يخص Bob، وفي النهاية ألغت Alice المكالمة ب CANCEL. والحل أن يرسل المقسم إلى الهاتف رسالة صغيرة بشكل دوري، فيبقى الربط حياً في الراوتر، وهذا ما يفعله الخيار qualify_frequency في جزء aor، وقيمته الافتراضية 0 أي لا يوجد فحص، فنضيف إلى التحويلتين:

qualify_frequency=15

ونعيد التحميل ونترك Bob ساكناً 50 ثانية مرة أخرى:

docker exec lab-voip-pbx asterisk -rx 'pjsip reload'
docker restart lab-voip-bob; sleep 50
docker exec lab-voip-pbx asterisk -rx 'pjsip show contacts'
./call.sh c7 8

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

  Contact:  1002/sip:[email protected]:14204; baf8c0212d Avail         0.793

Objects found: 1
19:19:15  10.77.50.10.5060   > 10.77.50.2.14204   INVITE sip:[email protected]:14204 SIP/2.0
19:19:15  10.77.50.2.14204   > 10.77.50.10.5060   SIP/2.0 180 Ringing
19:19:15  10.77.50.2.14204   > 10.77.50.10.5060   SIP/2.0 200 Answering
19:19:15  10.77.50.10.5060   > 10.77.50.2.14204   OPTIONS sip:[email protected]:14204 SIP/2.0
19:19:15  10.77.50.2.14204   > 10.77.50.10.5060   SIP/2.0 200 OK
[0:00:07] audio=64362/64788 (bit/s)

في المخرج أعلاه لاحظ التالي:

  • حالة الهاتف أصبحت Avail بدل NonQual، ومعها زمن الرد RTT بالملي ثانية، فأصبح المقسم يعرف هل الهاتف حي فعلاً، وليس فقط هل سجل.
  • رسائل OPTIONS وردودها تمر كل 15 ثانية، فلا يسكت الربط في الراوتر أكثر من مهلته، ووصل INVITE بعد 50 ثانية من السكوت.
  • الراوتر اختار هذه المرة المنفذ 14204 بدل 5060، وهذا دليل عملي على أن كتابة العنوان العام والمنفذ يدوياً في إعداد الهاتف لا تصلح، وأن rewrite_contact هو الطريقة الصحيحة.

والصورة التالية تلخص الفرق:

خطان زمنيان من صفر إلى ستين ثانية: في الأول دون فحص دوري يفتح REGISTER ربط NAT يبقى ثلاثين ثانية ثم يحذف، فيرفض الراوتر INVITE القادم عند الثانية الخمسين، وفي الثاني مع qualify_frequency=15 يرسل المقسم OPTIONS عند 15 و30 و45 ثانية فيبقى الربط حياً ويصل INVITE
الفحص الدوري يكشف الهاتف الميت ويبقي ربط NAT حياً في الوقت نفسه

والمهلة 30 ثانية في التجربة اخترناها لنرى الأثر بسرعة، أما في الراوترات الحقيقية فتختلف من جهاز لآخر، لذلك اجعل الفحص الدوري أقصر من أقصر مهلة تتوقعها، و15 إلى 30 ثانية قيمة شائعة.

الإعداد النهائي

وبهذا الشكل نكون قد وصلنا إلى قالب يعمل مع هواتف داخل الشبكة وخلف NAT، وهذا هو الجزء الذي تغير من pjsip.conf:

[phone](!)
type=endpoint
context=office
disallow=all
allow=ulaw,alaw
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes

[1002]
type=aor
max_contacts=1
remove_existing=yes
qualify_frequency=15

وهذه الخيارات هي نفسها التي تذكرها وثائق Asterisk للعمل عبر NAT في قسم الهواتف البعيدة خلف NAT، وقد رأينا الآن أثر كل خيار منها بنفسه.

قبل أن تنقل هذا إلى سيرفر حقيقي

التجربة تغطي حالة الهاتف خلف NAT والمقسم على عنوان عام، وفي الواقع هناك أمور إضافية:

  • إذا كان المقسم نفسه خلف NAT، مثل سيرفر في السحابة له عنوان خاص وعنوان عام، فهو أيضاً يكتب عنوانه الخاص في SDP، والحل في جزء transport بالخيارات external_media_address وexternal_signaling_address وlocal_net كما في الوثائق نفسها، وإلا تكرر عطل الصوت في اتجاه واحد من الجهة الأخرى.
  • افتح في جدار الحماية منفذ SIP ونطاق RTP كما في rtp.conf فقط، ولا تقم بفتح المنفذ 5060 لكل الإنترنت إذا كانت الهواتف كلها في مكاتبك، والسبب أن أي شخص على الإنترنت يستطيع عندها أن يجرب كلمات المرور على تحويلاتك، والأفضل أن تصل الفروع والموظفون عن بعد عبر شبكة خاصة كما في WireGuard، فيبقى SIP داخلها.
  • استخدم SIP عبر TLS على 5061 مع SRTP عندما يمر الصوت عبر الإنترنت، لأن RTP العادي يمكن تسجيله من أي نقطة في الطريق.
  • استخدم إصداراً مدعوماً من Asterisk بحسب جدول الإصدارات، وليس حزمة قديمة من التوزيعة.
  • إذا كانت مكالماتك من المتصفح عبر WebRTC، فطريق اكتشاف العناوين مختلف ويعتمد على ICE وSTUN وTURN، وقد شرحناه في مكالمات WebRTC خلف NAT.

وسوف نتناول في مقالات هذه المرحلة القادمة تأمين المقسم ضد الاحتيال على المكالمات بالتفصيل، وتثبيت Asterisk وFreePBX للإنتاج.

جدول التشخيص السريع

العرضالسبب الأرجحأين تنظر
التسجيل يرفض ب 403 بعد تغيير الشبكةتسجيل قديم وmax_contacts ممتلئسجل المقسم will exceed max contacts، والحل remove_existing=yes
الهاتف مسجل ولا يرن أبداً، والمتصل يرى 603 أو 408المقسم يرسل INVITE إلى العنوان الخاص في Contactpjsip show contacts، والحل rewrite_contact=yes
الهاتف يرن والطرف الذي خلف NAT لا يسمعالمقسم يرسل RTP إلى العنوان الخاص في SDPعد حزم RTP في الالتقاط، والحل rtp_symmetric=yes
المكالمة تتصل ولا يسمع أحد الآخرنطاق RTP مغلق في جدار الحمايةعدادات جدار الحماية، وrtp.conf
الهاتف يعمل ثم يتوقف عن الرنين بعد دقائق من السكوتالراوتر حذف ربط NATNonQual في pjsip show contacts، والحل qualify_frequency
صوت في اتجاه واحد والمقسم نفسه خلف NATالمقسم يكتب عنوانه الخاص في SDPc= في SDP الخارج من المقسم، والحل external_media_address

إزالة التجربة

نزيل ما أنشأناه بالاسم فقط، دون أوامر التنظيف العامة التي قد تحذف Containers أو Volumes تخص مشاريع أخرى على الجهاز نفسه:

docker rm -f lab-voip-alice lab-voip-bob lab-voip-nat lab-voip-pbx
docker network rm lab-voip-wan lab-voip-lan
docker rmi lab-voip-tools:1

تدريب عملي

  • افتح الالتقاط /tmp/c3.pcap في sngrep، وابحث عن السطر c= في رد 200 OK القادم من Bob، ثم قارنه بالعنوان الذي أرسل إليه المقسم الصوت في عداد الحزم.
  • أعد rewrite_contact إلى no مع بقاء qualify_frequency=15، وأعد تشغيل Bob، ثم اقرأ حالته في pjsip show contacts، وفسر لماذا تغيرت.

الخلاصة

  • الهاتف خلف NAT يكتب عنوانه الخاص في Contact وفي SDP، وال NAT يغير عنوان الحزمة فقط، فيبقى العنوان الخاص داخل الرسالة.
  • rewrite_contact وforce_rport يجعلان المقسم يرسل SIP إلى مصدر الحزمة، فيرن الهاتف، وrtp_symmetric يفعل الشيء نفسه للصوت، فيسمع الطرفان بعضهما.
  • الصوت في اتجاه واحد لا يظهر في إحصاءات المقسم، فقارن ما خرج منه بما وصل إلى الهاتف، والتقاط tcpdump مع عداد بسيط للحزم يكفي لمعرفة السبب.
  • افتح نطاق RTP المحدد في rtp.conf مع منفذ SIP، وإلا اتصلت المكالمة دون صوت.
  • الفحص الدوري qualify_frequency يبقي ربط NAT حياً ويكشف الهاتف الذي اختفى، وremove_existing يمنع رفض الهاتف الذي غير شبكته.
  • إذا كان المقسم نفسه خلف NAT فأضف external_media_address وexternal_signaling_address، واستخدم إصداراً مدعوماً من Asterisk وTLS وSRTP في الإنتاج.
دليلمصطلحات الاتصالات الهاتفية VoIP للمبتدئينشرح مبسط لمصطلحات الهاتف عبر الإنترنت، وما يمر في الشبكة بين ضغط زر الاتصال ووصول الصوت إلى الطرف الآخر.دليلما هو NAT؟ شرح ترجمة العناوين وتوجيه المنافذلماذا تخرج كل أجهزة بيتك بعنوان عام واحد، وكيف يترجم ال Router العناوين، ولماذا يفشل توجيه المنافذ خلف CGNAT.دليلمكالمات WebRTC خلف NAT: شرح STUN وTURN وICEلماذا تتصل المكالمة ولا يصل الصوت، وكيف تحل STUN وTURN المشكلة دون أن تفتح باباً إلى شبكتك الداخلية.

سجل التحديثات

  • أكتوبر 2026: كتابة المقال، وبناء التجربة على Asterisk 20.6.0 وbaresip 1.0.0 وtcpdump 4.99.4 من حزم Ubuntu 24.04 في Docker، مع راوتر NAT بقواعد iptables.
Could not load content
نشرة عرب رووت | ArabRoot

معرفة تستحق مكاناً في بريدك.

مقالات مختارة وأدوات مفيدة وأفكار لمشروعك القادم، في رسالة واحدة كل أسبوع.

يمكنك إلغاء الاشتراك متى شئت. الخصوصية

تم استلام طلبك. افتح بريدك واضغط رابط التأكيد لإتمام الاشتراك.