> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# تتبع مكالمة SIP عبر الشبكة: لماذا يصل الصوت في اتجاه واحد؟
- URL: https://arabroot.io/articles/تتبع-مكالمة-sip-عبر-الشبكة-والصوت-في-اتجاه-واحد/
- Published: 2026-10-09T06:15:00.000Z
- Updated: 2026-10-09T13:01:51.000Z
- Description: نبني مقسماً وهاتفين خلف NAT في تجربة عملية، ونتتبع المكالمة حزمة بحزمة حتى نعرف لماذا يضيع الصوت وكيف نصلحه.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, الاستضافة الذاتية والتطبيقات, الشبكات والوصول, تطبيقات لفريقك, الاستضافة الذاتية

في [مصطلحات الاتصالات الهاتفية VoIP للمبتدئين](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/) رأينا أن المكالمة لها طريقان: رسائل SIP التي تجهزها، وصوت RTP الذي يمر على منافذ أخرى، وأن العنوان الذي يستقبل عليه كل هاتف الصوت مكتوب داخل الرسالة نفسها في SDP. وهذا التفصيل الصغير هو سبب أشهر شكوى في كل مكتب انتقل إلى VoIP: الهاتف يرن، والموظف يرد، والعميل يسمعه، أما الموظف فلا يسمع شيئاً، أو الهاتف يظهر مسجلاً في لوحة المقسم ولا يرن عندما يتصل به أحد.

وفي هذا المقال لن نشرح هذه المشكلات من بعيد، وإنما سوف نبنيها بأيدينا: مقسم Asterisk حقيقي، وهاتفان برمجيان، وراوتر يعمل بال NAT بينهما، ثم نتتبع كل مكالمة حزمة بحزمة، ونكسر الإعداد عمداً في كل مرة ونرى العرض الذي يراه المستخدم، ثم نصلحه بالخيار المناسب. وهذا المقال من [مرحلة الاتصالات الهاتفية VoIP](https://arabroot.io/start/#stage-voip)، وسوف نتناول فيه ما يلي:

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

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

نحتاج في التجربة شبكتين: شبكة تلعب دور الإنترنت فيها المقسم وهاتف Alice، وشبكة مكتب خاصة فيها هاتف Bob، وبينهما راوتر يعمل بال NAT كما يعمل أي راوتر منزلي أو مكتبي، أي يغير عنوان الحزم الخارجة إلى عنوانه هو، ويرفض أي اتصال جديد يأتي من الخارج. وقد شرحنا هذا السلوك في [شرح NAT للمبتدئين](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%88-nat-%D8%B4%D8%B1%D8%AD-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#how-nat-works). والصورة التالية تبين الخريطة:

![شبكة تمثل الإنترنت باسم 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](https://arabroot.io/content/images/2026/10/voip-call-path-01-lab-map.webp)

المقسم يرى Bob دائماً بعنوان الراوتر، ولا يستطيع الوصول إلى عنوانه الخاص

ولاحظ أن كل شيء يعمل داخل شبكات Docker، فلا يوجد منفذ واحد مفتوح على جهازك، والشبكة التي تمثل الإنترنت أنشأناها بالخيار `--internal` حتى لا يكون لها طريق إلى خارج جهازك أصلاً. وإذا كنت جديداً على شبكات Docker فقد شرحناها في [شبكات Docker وأفضل الممارسات](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A7%D8%AA-docker-%D9%88%D8%A3%D9%81%D8%B6%D9%84-%D8%A7%D9%84%D9%85%D9%85%D8%A7%D8%B1%D8%B3%D8%A7%D8%AA/).

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

لا توجد Image رسمية من مشروع Asterisk على Docker Hub، لذلك سوف نبني Image واحدة صغيرة من Ubuntu 24.04 فيها كل الأدوات، ونثبت إصدار كل حزمة حتى تحصل على النتيجة نفسها: Asterisk مقسماً، و[baresip](https://github.com/baresip/baresip?ref=arabroot.io) هاتفاً برمجياً يعمل من سطر الأوامر، وtcpdump لالتقاط الحزم، و[sngrep](https://github.com/irontec/sngrep?ref=arabroot.io) لعرض المكالمات بشكل تفاعلي. قم بإنشاء مجلد للتجربة، وضع فيه الملف `Dockerfile` التالي:

```bash
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:

```bash
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
```

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

```text
Asterisk 20.6.0~dfsg+~cs6.13.40431414-2build5
```

⚠️

استخدمنا حزمة Ubuntu لأنها أسرع طريقة لتجربة قصيرة، ولكن إصدارها 20.6.0 قديم، فآخر إصدار من فرع 20 عند كتابة المقال هو 20.21.0، والفرع 22 هو أحدث فرع منشور بدعم طويل LTS بحسب [جدول إصدارات Asterisk](https://docs.asterisk.org/About-the-Project/Asterisk-Versions/?ref=arabroot.io)، والفرع 20 ينتقل إلى إصلاحات الأمان فقط في 19 أكتوبر 2026\. لا تقم باستخدام هذه ال Image لمقسم حقيقي، والسبب أن المقسم المتصل بخط خارجي هدف دائم للمهاجمين، وسوف نشرح التثبيت الصحيح في دليل Asterisk ضمن هذه المرحلة.

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

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

```text
[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` التي شرحناها في [مقال المصطلحات](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#dial-plan)، ونطاق صغير لمنافذ RTP في `asterisk/rtp.conf` حتى يسهل فتحه وتتبعه، وإعداد السجلات في `asterisk/logger.conf`:

```bash
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
```

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

```bash
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` لأن أي هاتف لم يسجل بعد:

```text
 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 كما في [ملف الترخيص](https://github.com/baresip/baresip/blob/main/LICENSE?ref=arabroot.io)، ويعمل دون واجهة رسومية، فيناسب التجربة. ولأنه لا توجد سماعة ولا ميكروفون في ال Container، سوف نجعل مصدر الصوت ملفاً فيه نغمة ثابتة، ونجعل الهاتف يحفظ ما يسمعه في ملف، فإذا كان الملف فارغاً فهذا يعني أن الهاتف لم يسمع شيئاً. وهذه هي الأوامر:

```bash
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:$ext@10.77.50.10>;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، ثم نسأل المقسم أين يجده:

```bash
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'
```

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

```text
  Contact:  1002/sip:1002-0x55edcd0ca420@10.77.50.22:5060  74f845b4f3 NonQual         nan

Objects found: 1
```

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

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

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

```bash
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
```

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

```text
19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:1002@10.77.50.10 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:1002@10.77.50.10 SIP/2.0
19:13:44  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:1002@10.77.50.10 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:1002-0x55edcd0ca420@10.77.50.22: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:1002-0x55edcd0ca420@10.77.50.22: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:1002-0x55edcd0ca420@10.77.50.22: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)
```

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

- الرسائل هي التسلسل نفسه الذي رسمناه في [مقال المصطلحات](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#call-end-to-end): 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`، ويسمح من الخارج بالردود على اتصالات بدأت من الداخل فقط، كما يفعل أي راوتر مكتبي. ثم نجعل المقسم يرسل كل ما لا يعرفه عبر هذا الراوتر، حتى إذا حاول الوصول إلى عنوان خاص في شبكة المكتب يصل إلى الراوتر ويرفض هناك، تماماً كما يضيع العنوان الخاص في الإنترنت الحقيقي:

```bash
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 نفسه نقابل أول عطل، والمخرج سوف يكون كما يلي:

```text
reg: sip:1002@10.77.50.10: 403 Forbidden (ArabRoot-Lab-PBX)
```

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

```text
[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` في التحويلتين:

```text
remove_existing=yes
```

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

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

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

```text
  Contact:  1002/sip:1002-0x558cf0253420@192.168.77.22:506 7f13451082 NonQual         nan

Objects found: 1
```

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

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

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

```bash
./call.sh c2 25
```

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

```text
19:14:29  10.77.50.21.5060   > 10.77.50.10.5060   INVITE sip:1002@10.77.50.10 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:1002-0x558cf0253420@192.168.77.22:5060 SIP/2.0
19:14:29  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:1002-0x558cf0253420@192.168.77.22:5060 SIP/2.0
19:14:30  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:1002-0x558cf0253420@192.168.77.22:5060 SIP/2.0
19:14:32  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:1002-0x558cf0253420@192.168.77.22:5060 SIP/2.0
19:14:36  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:1002-0x558cf0253420@192.168.77.22:5060 SIP/2.0
19:14:44  10.77.50.10.5060   > 192.168.77.22.5060 INVITE sip:1002-0x558cf0253420@192.168.77.22: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](https://www.rfc-editor.org/rfc/rfc3581?ref=arabroot.io).
- `rewrite_contact=yes`: يستبدل المقسم العنوان في ترويسة `Contact` عند التسجيل بعنوان مصدر الحزمة، فترسل المكالمات القادمة إلى الراوتر، ويمررها الراوتر إلى الهاتف لأنها تطابق الاتصال الذي فتحه الهاتف بالتسجيل.

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

```bash
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 '
```

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

```text
Via: SIP/2.0/UDP 192.168.77.22:5060;branch=z9hG4bK63629205ffe0240b;rport
Contact: <sip:1002-0x560cb5487420@192.168.77.22:5060>;expires=3768
Via: SIP/2.0/UDP 192.168.77.22:5060;rport=5060;received=10.77.50.2;branch=z9hG4bK63629205ffe0240b
Contact: <sip:1002-0x560cb5487420@192.168.77.22:5060>;expires=3767
 contact              : sip:1002-0x560cb5487420@10.77.50.2: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'`:

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

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

```text
19:15:17  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x560cb5487420@10.77.50.2: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:1002-0x560cb5487420@10.77.50.2: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`، كما يظهر من الالتقاط نفسه:

```text
Contact: <sip:1002-0x560cb5487420@192.168.77.22: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 فيمرره الراوتر إليه](https://arabroot.io/content/images/2026/10/voip-call-path-02-private-address.webp)

ال NAT يغير عنوان الحزمة ولا يقرأ ما بداخلها

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

```text
 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:

```bash
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
```

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

```text
    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 في كل مكان، ونرى ماذا يحدث:

```bash
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
```

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

```text
[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 من أول دخول](https://arabroot.io/articles/%D8%AA%D8%A3%D9%85%D9%8A%D9%86-%D8%AE%D8%A7%D8%AF%D9%85-vps-%D9%85%D9%86-%D8%A3%D9%88%D9%84-%D8%AF%D8%AE%D9%88%D9%84/).

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

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

```bash
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
```

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

```text
19:17:53  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x55bdc8a99420@10.77.50.2:5060 SIP/2.0
19:17:54  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x55bdc8a99420@10.77.50.2:5060 SIP/2.0
19:17:55  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x55bdc8a99420@10.77.50.2:5060 SIP/2.0
19:17:57  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x55bdc8a99420@10.77.50.2:5060 SIP/2.0
19:18:01  10.77.50.10.5060   > 10.77.50.2.5060    INVITE sip:1002-0x55bdc8a99420@10.77.50.2:5060 SIP/2.0
19:18:01  10.77.50.21.5060   > 10.77.50.10.5060   CANCEL sip:1002@10.77.50.10 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 أي لا يوجد فحص، فنضيف إلى التحويلتين:

```text
qualify_frequency=15
```

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

```bash
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
```

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

```text
  Contact:  1002/sip:1002-0x565471f59420@10.77.50.2:14204; baf8c0212d Avail         0.793

Objects found: 1
19:19:15  10.77.50.10.5060   > 10.77.50.2.14204   INVITE sip:1002-0x565471f59420@10.77.50.2: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:1002-0x565471f59420@10.77.50.2: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](https://arabroot.io/content/images/2026/10/voip-call-path-03-nat-timeout.webp)

الفحص الدوري يكشف الهاتف الميت ويبقي ربط NAT حياً في الوقت نفسه

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

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

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

```text
[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](https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res%5Fpjsip/Configuring-res%5Fpjsip-to-work-through-NAT/?ref=arabroot.io) في قسم الهواتف البعيدة خلف NAT، وقد رأينا الآن أثر كل خيار منها بنفسه.

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

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

- إذا كان المقسم نفسه خلف NAT، مثل سيرفر في السحابة له عنوان خاص وعنوان عام، فهو أيضاً يكتب عنوانه الخاص في SDP، والحل في جزء `transport` بالخيارات `external_media_address` و`external_signaling_address` و`local_net` كما في [الوثائق نفسها](https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res%5Fpjsip/Configuring-res%5Fpjsip-to-work-through-NAT/?ref=arabroot.io)، وإلا تكرر عطل الصوت في اتجاه واحد من الجهة الأخرى.
- افتح في جدار الحماية منفذ SIP ونطاق RTP كما في `rtp.conf` فقط، ولا تقم بفتح المنفذ 5060 لكل الإنترنت إذا كانت الهواتف كلها في مكاتبك، والسبب أن أي شخص على الإنترنت يستطيع عندها أن يجرب كلمات المرور على تحويلاتك، والأفضل أن تصل الفروع والموظفون عن بعد عبر شبكة خاصة كما في [WireGuard](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AE%D8%A7%D8%B5%D8%A9-%D9%85%D8%B9-wireguard-%D9%88-wg-easy/)، فيبقى SIP داخلها.
- استخدم SIP عبر TLS على 5061 مع SRTP عندما يمر الصوت عبر الإنترنت، لأن RTP العادي يمكن تسجيله من أي نقطة في الطريق.
- استخدم إصداراً مدعوماً من Asterisk بحسب [جدول الإصدارات](https://docs.asterisk.org/About-the-Project/Asterisk-Versions/?ref=arabroot.io)، وليس حزمة قديمة من التوزيعة.
- إذا كانت مكالماتك من المتصفح عبر WebRTC، فطريق اكتشاف العناوين مختلف ويعتمد على ICE وSTUN وTURN، وقد شرحناه في [مكالمات WebRTC خلف NAT](https://arabroot.io/articles/%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A7%D8%AA-webrtc-%D8%AE%D9%84%D9%81-nat-%D8%B4%D8%B1%D8%AD-ice-%D9%88-stun-%D9%88-turn/).

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

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

| العرض                                              | السبب الأرجح                                    | أين تنظر                                                        |
| -------------------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
| التسجيل يرفض ب 403 بعد تغيير الشبكة                | تسجيل قديم وmax\_contacts ممتلئ                 | سجل المقسم will exceed max contacts، والحل remove\_existing=yes |
| الهاتف مسجل ولا يرن أبداً، والمتصل يرى 603 أو 408  | المقسم يرسل INVITE إلى العنوان الخاص في Contact | pjsip show contacts، والحل rewrite\_contact=yes                 |
| الهاتف يرن والطرف الذي خلف NAT لا يسمع             | المقسم يرسل RTP إلى العنوان الخاص في SDP        | عد حزم RTP في الالتقاط، والحل rtp\_symmetric=yes                |
| المكالمة تتصل ولا يسمع أحد الآخر                   | نطاق RTP مغلق في جدار الحماية                   | عدادات جدار الحماية، وrtp.conf                                  |
| الهاتف يعمل ثم يتوقف عن الرنين بعد دقائق من السكوت | الراوتر حذف ربط NAT                             | NonQual في pjsip show contacts، والحل qualify\_frequency        |
| صوت في اتجاه واحد والمقسم نفسه خلف NAT             | المقسم يكتب عنوانه الخاص في SDP                 | c= في SDP الخارج من المقسم، والحل external\_media\_address      |

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

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

```bash
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 في الإنتاج.

[![](https://arabroot.io/content/images/2026/10/voip-terms-cover-cover.webp)دليلمصطلحات الاتصالات الهاتفية VoIP للمبتدئينشرح مبسط لمصطلحات الهاتف عبر الإنترنت، وما يمر في الشبكة بين ضغط زر الاتصال ووصول الصوت إلى الطرف الآخر.](https://arabroot.io/articles/%D9%85%D8%B5%D8%B7%D9%84%D8%AD%D8%A7%D8%AA-voip-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/nat-explained-cover-cover.webp)دليلما هو NAT؟ شرح ترجمة العناوين وتوجيه المنافذلماذا تخرج كل أجهزة بيتك بعنوان عام واحد، وكيف يترجم ال Router العناوين، ولماذا يفشل توجيه المنافذ خلف CGNAT.](https://arabroot.io/articles/%D9%85%D8%A7-%D9%87%D9%88-nat-%D8%B4%D8%B1%D8%AD-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/webrtc-turn-cover-cover.webp)دليلمكالمات WebRTC خلف NAT: شرح STUN وTURN وICEلماذا تتصل المكالمة ولا يصل الصوت، وكيف تحل STUN وTURN المشكلة دون أن تفتح باباً إلى شبكتك الداخلية.](https://arabroot.io/articles/%D9%85%D9%83%D8%A7%D9%84%D9%85%D8%A7%D8%AA-webrtc-%D8%AE%D9%84%D9%81-nat-%D8%B4%D8%B1%D8%AD-ice-%D9%88-stun-%D9%88-turn/)

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

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