> ## 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.

# تمارين Kata في الشبكات بثلاثة مستويات مع الحلول
- URL: https://arabroot.io/articles/تمارين-kata-في-الشبكات/
- Published: 2026-10-06T13:30:00.000Z
- Updated: 2026-10-09T09:44:08.000Z
- Description: تمارين في الشبكات تحلها بيدك دون ذكاء اصطناعي، من حساب الشبكة الفرعية إلى الفرق بين 502 و504، وكل حل مجرب في مختبر Docker.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, أساسيات الحاسب والبرمجة, الشبكات والوصول, أساسيات, الاستضافة الذاتية

في مقال [لماذا تتمرن دون ذكاء اصطناعي؟](https://arabroot.io/articles/%D8%AA%D9%85%D8%A7%D8%B1%D9%8A%D9%86-kata-%D8%AD%D8%A7%D9%81%D8%B8-%D8%B9%D9%84%D9%89-%D9%85%D9%87%D8%A7%D8%B1%D8%AA%D9%83-%D8%AF%D9%88%D9%86-%D8%B0%D9%83%D8%A7%D8%A1-%D8%A7%D8%B5%D8%B7%D9%86%D8%A7%D8%B9%D9%8A/) شرحنا لماذا تضعف المهارة التي لا تستخدمها، وكيف تتمرن بطريقة Kata وحدك أو مع فريقك، وهذا المقال يطبق الطريقة على الشبكات Networking. والسبب أن أغلب البلاغات التي تصلك بعبارة «الموقع لا يفتح» أو «يفتح عندي ولا يفتح عند العميل» هي في الحقيقة سؤال عن الشبكة، فإما أن الاسم لا يحل، أو أن المنفذ لا يجيب، أو أن الشهادة غير صحيحة، أو أن ال Reverse Proxy لا يصل إلى التطبيق، وكل واحدة من هذه لها رسالة مختلفة تعرفها إذا قرأتها بنفسك عشرات المرات، ولا تعرفها إذا كنت تلصقها في أداة وتنتظر الجواب.

وطريقة الاستخدام قصيرة: أغلق أي مساعد ذكاء اصطناعي، واقرأ التمرين، واضبط مؤقتاً على الوقت المقترح، واكتب إجابتك كاملة على ورقة أو في ملف قبل أن تشغل أي أمر. أغلب تمارين المستوى الأول تحل بالورقة والقلم، أما البقية فتحتاج المختبر Lab الذي سوف نجهزه بعد قليل، وإذا علقت فافتح التلميح Hint ثم حاول مرة أخرى، وبعدها فقط افتح الحل Answer وقارنه بإجابتك، وفي النهاية سجل الوقت في [جدول المتابعة](#tracking) وارجع إلى التمرين بعد أسبوع.

وسوف نناقش في هذا المقال ما يلي:

- تجهيز مختبر Lab في Docker فيه سيرفر DNS محلي وNginx وتطبيق خلفه وجهاز كلاينت عليه `dig` و`curl` و`openssl`.
- المستوى الأول: عدد الأجهزة في الشبكة الفرعية Subnet، وهل العنوان داخل الشبكة، والعناوين الخاصة والعامة وCGNAT، وقراءة مخرج `dig` و`curl -v` و`ss`.
- المستوى الثاني: تقسيم شبكة /24 إلى أربع، وجهازان على /23، ولماذا يظهر العنوان القديم بعد تغيير السجل، و301 أم 302، وتوقيتات `curl -w`، وحالة TIME-WAIT.
- المستوى الثالث: توجيه المنافذ Port Forwarding عبر NAT، وسجل CNAME على النطاق الرئيسي، وقراءة الشهادة ب `openssl s_client`، والفرق بين 502 و504، وسلم تشخيص «الموقع لا يفتح».
- جدول متابعة تنسخه لتسجل تقدمك، وروابط المقالات التي تشرح كل موضوع بالتفصيل.

والصورة التالية تبين التمارين السبعة عشر موزعة على المستويات الثلاثة:

![ثلاثة أعمدة ملونة: في اليمين المستوى الأول Beginner بالأخضر من 5 إلى 10 دقائق للتمرين وفيه التمارين 1 إلى 6: عدد الأجهزة في /26، وهل العنوان داخل /22، وخاص أم عام أم CGNAT، وقراءة رد dig، وقراءة curl -v، وقراءة ss -tlnp. وفي الوسط المستوى الثاني Intermediate بالأصفر من 10 إلى 20 دقيقة وفيه التمارين 7 إلى 12: تقسيم /24 إلى أربع، وجهازان على /23، والعنوان القديم ما زال يظهر، و301 أم 302، وتوقيتات curl -w، وTIME-WAIT بعد عشرين طلباً. وفي اليسار المستوى الثالث Advanced بالأحمر من 20 إلى 30 دقيقة وفيه التمارين 13 إلى 17: توجيه المنافذ عبر NAT، وCNAME على النطاق الرئيسي، وقراءة openssl s_client، و502 أم 504، والموقع لا يفتح](https://arabroot.io/content/images/2026/10/katas-network-01-levels-map.webp)

ابدأ من المستوى الأول حتى لو بدا لك سهلاً، فالمستوى الثالث يبني على قراءة المخرجات التي تتمرن عليها في الأول

## تجهيز المختبر Lab

كل التمارين التي تحتاج أوامر تعمل على شبكة Docker خاصة واحدة اسمها `kata-net` بالنطاق `10.77.0.0/24`، وفيها خمسة Containers: سيرفر DNS يحمل سجلات النطاق التجريبي `shop.lab.test` عبر dnsmasq، وسيرفر DNS ثان يعمل كذاكرة مؤقتة Cache مثل الذي يعطيك إياه مزود الإنترنت، وتطبيق صغير يرد على ثلاثة عناوين بسرعات مختلفة، وNginx أمامه، وجهاز كلاينت نشغل منه الأوامر. والنطاق `.test` محجوز للتجارب ولا يمكن أن يتعارض مع نطاق حقيقي على الإنترنت.

ابدأ بإنشاء مجلد للمختبر وفيه ملف إعداد Nginx التالي باسم `nginx/default.conf`:

```nginx
server {
    listen 80;
    server_name shop.lab.test;
    root /usr/share/nginx/html;

    location = /old-price-list { return 301 /price-list; }
    location = /offers        { return 302 /ramadan-offers; }
    location = /price-list    { default_type text/plain; return 200 "price list\n"; }

    location /api/ {
        proxy_pass http://10.77.0.20:8080/cgi-bin/;
        proxy_connect_timeout 2s;
        proxy_read_timeout 3s;
    }
}

server {
    listen 443 ssl;
    server_name shop.lab.test;
    ssl_certificate     /etc/nginx/certs/leaf.crt;
    ssl_certificate_key /etc/nginx/certs/leaf.key;
    root /usr/share/nginx/html;
}

server {
    listen 8443 ssl;
    server_name shop.lab.test;
    ssl_certificate     /etc/nginx/certs/fullchain.crt;
    ssl_certificate_key /etc/nginx/certs/leaf.key;
    root /usr/share/nginx/html;

    location /api/ {
        proxy_pass http://10.77.0.20:8080/cgi-bin/;
        proxy_read_timeout 3s;
    }
}

```

ثم صفحة بسيطة وثلاثة سكربتات CGI للتطبيق، الأول يرد فوراً، والثاني بعد ثانيتين، والثالث بعد عشر ثوان:

```bash
mkdir -p html app/cgi-bin certs
echo '<!doctype html><title>shop</title><h1>shop.lab.test</h1>' > html/index.html
cat > app/cgi-bin/orders <<'EOF'
#!/bin/sh
printf 'Content-Type: application/json

'
echo '{"orders": 3}'
EOF
cat > app/cgi-bin/report <<'EOF'
#!/bin/sh
sleep 2
printf 'Content-Type: application/json

'
echo '{"report": "ready"}'
EOF
cat > app/cgi-bin/export <<'EOF'
#!/bin/sh
sleep 10
printf 'Content-Type: application/json

'
echo '{"export": "done"}'
EOF
chmod +x app/cgi-bin/*

```

ثم ملف `certs/mk.sh` الذي ينشئ جهة إصدار رئيسية Root CA وجهة وسيطة Intermediate CA وشهادة للموقع تحمل اسمين، وهي نفس خطوات [مختبر مقال الشهادات](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/#lab) مختصرة:

```bash
set -e
cd /c
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout root.key -out root.crt -days 3650 -subj "/O=Lab Kata/CN=Lab Kata Root CA" -addext "basicConstraints=critical,CA:TRUE" -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout int.key -out int.csr -subj "/O=Lab Kata/CN=Lab Kata Intermediate CA"
printf "basicConstraints=critical,CA:TRUE,pathlen:0\nkeyUsage=critical,keyCertSign,cRLSign\n" > int.ext
openssl x509 -req -in int.csr -CA root.crt -CAkey root.key -CAcreateserial -out int.crt -days 1825 -extfile int.ext
openssl req -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -keyout leaf.key -out leaf.csr -subj "/CN=shop.lab.test"
printf "subjectAltName=DNS:shop.lab.test,DNS:www.shop.lab.test\nextendedKeyUsage=serverAuth\nbasicConstraints=CA:FALSE\n" > leaf.ext
openssl x509 -req -in leaf.csr -CA int.crt -CAkey int.key -CAcreateserial -out leaf.crt -days 90 -extfile leaf.ext
cat leaf.crt int.crt > fullchain.crt
chmod 644 *

```

الآن نشغل الشبكة وال Containers، ولاحظ أن الكلاينت يثبت أدواته أولاً من شبكة Docker العادية، ثم نجعل سيرفر ال DNS الخاص به هو الذاكرة المؤقتة `10.77.0.54` فقط، وأن Nginx منشور على `127.0.0.1` وحده حتى لا يصل إليه أحد من خارج جهازك:

```bash
docker network create --subnet 10.77.0.0/24 kata-net
docker run --rm -v "$PWD/certs:/c" alpine:3.22 sh -c "apk add -q openssl >/dev/null && sh /c/mk.sh"

docker run -d --name kata-dns --network kata-net --ip 10.77.0.53 alpine:3.22 sh -c "apk add -q dnsmasq && exec dnsmasq -k --no-resolv --no-hosts --local=/lab.test/ --log-queries --log-facility=- --local-ttl=300 --host-record=shop.lab.test,10.77.0.10 --cname=www.shop.lab.test,shop.lab.test --mx-host=shop.lab.test,mail.shop.lab.test,10 --host-record=mail.shop.lab.test,10.77.0.25"
docker run -d --name kata-cache --network kata-net --ip 10.77.0.54 alpine:3.22 sh -c "apk add -q dnsmasq && exec dnsmasq -k --no-resolv --no-hosts --server=10.77.0.53 --cache-size=1000 --log-queries --log-facility=-"
docker run -d --name kata-app --network kata-net --ip 10.77.0.20 -v "$PWD/app:/www" alpine:3.22 sh -c "apk add -q busybox-extras && httpd -p 8080 -h /www && exec sleep infinity"
docker run -d --name kata-web --network kata-net --ip 10.77.0.10 -p 127.0.0.1:21390:80 -p 127.0.0.1:21391:443 \
  -v "$PWD/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro" -v "$PWD/certs:/etc/nginx/certs:ro" -v "$PWD/html:/usr/share/nginx/html:ro" nginx:1.29-alpine
docker run -d --name kata-client --network kata-net --ip 10.77.0.100 -v "$PWD/certs:/certs:ro" alpine:3.22 sleep infinity
docker exec kata-client sh -c 'apk add -q curl bind-tools openssl iproute2 bind python3 && echo "nameserver 10.77.0.54" > /etc/resolv.conf'
docker exec kata-client curl -s http://shop.lab.test/

```

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

```text
<!doctype html><title>shop</title><h1>shop.lab.test</h1>

```

وكل أمر في التمارين يبدأ ب `docker exec kata-client` ما لم نذكر غير ذلك، فإذا أردت أن تكتب الأوامر كما تكتبها على سيرفر حقيقي فادخل الكلاينت مرة واحدة ب `docker exec -it kata-client sh`. ولاحظ أن المعرفات والتواريخ في مخرجاتك سوف تختلف عن مخرجاتنا، لأن الشهادات أنشئت في وقت آخر، أما الأرقام والرسائل فهي نفسها.

## المستوى الأول: مبتدئ Beginner

تمارين هذا المستوى تحل بالورقة أو بقراءة مخرج واحد، والهدف أن تقرأ العنوان والقناع Mask ورد ال DNS ورد HTTP وقائمة المنافذ دون أن تتردد.

### التمرين 1: كم جهازاً يتسع في الشبكة 192.168.10.0/26؟

أعطاك فريق البنية التحتية الشبكة الفرعية Subnet التالية لسيرفرات التطبيق:

```text
192.168.10.0/26

```

اكتب بيدك: قناع الشبكة Netmask بالشكل العشري، وعنوان الشبكة Network Address، وعنوان البث Broadcast Address، وأول وآخر عنوان يمكن إعطاؤه لجهاز، وعدد الأجهزة الممكنة.

**الوقت المقترح:** 5 دقائق.

تلميح Hint

الرقم 26 يعني أن أول 26 بتاً من 32 هي جزء الشبكة، فيبقى 6 بتات للأجهزة، و2 أس 6 يساوي 64 عنواناً، وأول عنوان وآخر عنوان في كل شبكة محجوزان.

الحل Answer 

بقي للأجهزة 32 ناقص 26 أي 6 بتات، فحجم الشبكة 26 \= 64 عنواناً، والبايت الأخير في القناع فيه أول بتين فقط مضاءين، أي 128 + 64 = 192، فالقناع `255.255.255.192`. والشبكة تبدأ من `192.168.10.0` وتنتهي عند `192.168.10.63`، والأول هو عنوان الشبكة والأخير هو عنوان البث، فيبقى 62 عنواناً للأجهزة من `.1` إلى `.62`. وتحققنا من ذلك بالمكتبة `ipaddress` في Python، وتستطيع تشغيلها على جهازك أو داخل الكلاينت ب `docker exec -it kata-client python3`، ونفس الجلسة تخدم التمارين 2 و3 و7 و8:

```python
import ipaddress as ip
n = ip.ip_network('192.168.10.0/26')
print(n.netmask, n.network_address, n.broadcast_address, n.num_addresses, n.num_addresses - 2, n[1], n[-2])

```

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

```text
255.255.255.192 192.168.10.0 192.168.10.63 64 62 192.168.10.1 192.168.10.62

```

لاحظ أن ال Router نفسه يأخذ عادة واحداً من الاثنين والستين، فإذا كان لديك 62 سيرفراً بالضبط فأنت تحتاج شبكة أكبر. وطريقة الحساب في [RFC 4632](https://datatracker.ietf.org/doc/html/rfc4632?ref=arabroot.io) الذي يعرف كتابة CIDR.

للتفصيل: [العناوين الخاصة وأقنعة الشبكات](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/#private-addresses) في مقال NAT.

### التمرين 2: هل هذه العناوين داخل الشبكة 10.20.32.0/22؟

قاعدة في جدار الحماية Firewall تسمح فقط للشبكة `10.20.32.0/22` بالوصول إلى قاعدة البيانات، ووصلتك أربعة عناوين تشتكي أنها لا تصل. حدد بيدك أيها داخل الشبكة وأيها خارجها:

```text
10.20.33.200
10.20.35.255
10.20.36.5
10.20.31.250

```

**الوقت المقترح:** 7 دقائق.

تلميح Hint

القناع /22 يغطي 4 قيم كاملة في البايت الثالث، فابحث عن أول قيمة تقبل القسمة على 4 وأين تنتهي الشبكة.

الحل Answer 

القناع /22 يترك 10 بتات للأجهزة، أي 2 بت من البايت الثالث و8 بتات من الرابع، فكل شبكة /22 تأخذ أربع قيم متتالية في البايت الثالث، والقيمة 32 تقبل القسمة على 4، فالشبكة من `10.20.32.0` إلى `10.20.35.255` والقناع `255.255.252.0`. وبالتالي `10.20.33.200` و`10.20.35.255` داخلها، والعنوانان `10.20.36.5` و`10.20.31.250` خارجها، وهؤلاء من يحتاج قاعدة أخرى. والتحقق:

```python
net = ip.ip_network('10.20.32.0/22')
print(net, net.netmask, net[0], net[-1])
for a in ['10.20.33.200', '10.20.35.255', '10.20.36.5', '10.20.31.250']:
    print(a, ip.ip_address(a) in net)

```

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

```text
10.20.32.0/22 255.255.252.0 10.20.32.0 10.20.35.255
10.20.33.200 True
10.20.35.255 True
10.20.36.5 False
10.20.31.250 False

```

لاحظ أن `10.20.35.255` داخل الشبكة ولكنه عنوان البث، فهو لا يكون عنوان جهاز في الواقع، وإذا ظهر لك في سجل الجدار كمصدر فهناك إعداد خاطئ في مكان ما.

للتفصيل: [العناوين الخاصة](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/#private-addresses) في مقال NAT، ومرجع الكتابة [RFC 4632](https://datatracker.ietf.org/doc/html/rfc4632?ref=arabroot.io).

### التمرين 3: خاص أم عام أم CGNAT؟

صنف كل عنوان من العناوين التالية: خاص Private حسب RFC 1918، أو عام Public يصل من الإنترنت، أو من مساحة CGNAT التي يستخدمها مزود الإنترنت، أو غير ذلك:

```text
10.8.0.1
172.16.5.4
172.32.5.4
192.168.1.10
100.72.3.9
100.128.0.1
169.254.10.20
8.8.8.8

```

**الوقت المقترح:** 7 دقائق.

تلميح Hint

النطاقات الخاصة ثلاثة: 10/8 و172.16/12 و192.168/16، ونطاق CGNAT هو 100.64.0.0/10\. احسب أين ينتهي 172.16/12 وأين ينتهي 100.64/10 قبل أن تحكم على 172.32 و100.128.

الحل Answer 

النطاق `172.16.0.0/12` يمتد من 172.16 إلى 172.31 فقط، لذلك `172.32.5.4` عنوان عام رغم أنه «يشبه» الخاص، وهذا خطأ شائع في قواعد الجدار. والنطاق `100.64.0.0/10` يمتد من 100.64 إلى 100.127، فالعنوان `100.72.3.9` من مساحة CGNAT المشتركة Shared Address Space حسب [RFC 6598](https://datatracker.ietf.org/doc/html/rfc6598?ref=arabroot.io)، و`100.128.0.1` عام. والعنوان `169.254.10.20` عنوان محلي على الرابط Link-Local حسب [RFC 3927](https://datatracker.ietf.org/doc/html/rfc3927?ref=arabroot.io)، ويظهر عادة عندما يفشل الجهاز في الحصول على عنوان من DHCP. وتحققنا بالمكتبة نفسها:

```python
cg = ip.ip_network('100.64.0.0/10')
for a in ['10.8.0.1', '172.16.5.4', '172.32.5.4', '192.168.1.10', '100.72.3.9', '100.128.0.1', '169.254.10.20', '8.8.8.8']:
    x = ip.ip_address(a)
    print(f"{a:15} private={x.is_private!s:5} global={x.is_global!s:5} cgnat={x in cg!s:5} link_local={x.is_link_local}")

```

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

```text
10.8.0.1        private=True  global=False cgnat=False link_local=False
172.16.5.4      private=True  global=False cgnat=False link_local=False
172.32.5.4      private=False global=True  cgnat=False link_local=False
192.168.1.10    private=True  global=False cgnat=False link_local=False
100.72.3.9      private=False global=False cgnat=True  link_local=False
100.128.0.1     private=False global=True  cgnat=False link_local=False
169.254.10.20   private=True  global=False cgnat=False link_local=True
8.8.8.8         private=False global=True  cgnat=False link_local=False

```

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

- عنوان CGNAT ليس خاصاً ولا عاماً في نظر المكتبة، فلا هو من RFC 1918 ولا يصل من الإنترنت، وإذا رأيته على واجهة WAN في ال Router الخاص بك فأنت خلف NAT ثان عند المزود، وتوجيه المنافذ عندك لن يكفي.
- المكتبة تعتبر `169.254.10.20` خاصاً لأنها تتبع [سجل IANA للعناوين الخاصة الاستخدام](https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml?ref=arabroot.io) وليس RFC 1918 وحده، وهو في الواقع عنوان لا يخرج من الشبكة المحلية أصلاً.

للتفصيل: [CGNAT في مقال 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/#cgnat) و[اكتشاف CGNAT](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%AE%D8%A7%D8%AF%D9%85%D9%83-%D9%85%D9%86-%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A7%D9%84%D9%85%D9%86%D8%B2%D9%84/#cgnat) في دليل الاستضافة من إنترنت المنزل.

### التمرين 4: اقرأ رد dig

نفذ زميلك الأمر التالي وأرسل لك المخرج:

```text
$ dig www.shop.lab.test

; <<>> DiG 9.20.29 <<>> www.shop.lab.test
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 60488
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.shop.lab.test.		IN	A

;; ANSWER SECTION:
www.shop.lab.test.	300	IN	CNAME	shop.lab.test.
shop.lab.test.		300	IN	A	10.77.0.10

;; Query time: 0 msec
;; SERVER: 10.77.0.54#53(10.77.0.54) (UDP)
;; WHEN: Wed Oct 07 07:52:35 UTC 2026
;; MSG SIZE  rcvd: 89

```

أجب: هل نجح الاستعلام؟ ما نوعا السجلين في الإجابة ولماذا يوجد سجلان لسؤال واحد؟ ما العنوان الذي سوف يتصل به المتصفح؟ ماذا يعني الرقم 300؟ ومن السيرفر الذي أجاب؟

**الوقت المقترح:** 7 دقائق.

تلميح Hint

ابدأ من السطر `status`، ثم القسم `ANSWER SECTION` سطراً بسطر: الاسم، ثم المدة بالثواني، ثم الصنف، ثم النوع، ثم القيمة. والسطر `SERVER` في الأسفل يقول من سألت.

الحل Answer 

الحالة `NOERROR` تعني أن الاسم موجود وأن هناك إجابة، ولو كان الاسم غير موجود لظهرت `NXDOMAIN`. والسؤال كان عن سجل A للاسم `www.shop.lab.test`، فأجاب السيرفر بأن هذا الاسم هو سجل CNAME أي اسم بديل يشير إلى `shop.lab.test`، ثم أضاف من نفسه سجل A للاسم الذي يشير إليه حتى لا تحتاج سؤالاً ثانياً، لذلك يوجد في الإجابة سجلان. والعنوان الذي سوف يتصل به المتصفح هو `10.77.0.10`.

والرقم 300 هو مدة البقاء TTL بالثواني، أي أن من استلم هذه الإجابة يحق له أن يحفظها خمس دقائق ويجيب منها دون أن يسأل من جديد، وهذا ما سوف تراه في التمرين 9\. والسيرفر الذي أجاب هو `10.77.0.54` على المنفذ 53 عبر UDP، أي الذاكرة المؤقتة وليس السيرفر الذي يحمل السجلات. وفي المختبر أعطانا هذا المخرج الأمر:

```bash
docker exec kata-client dig www.shop.lab.test

```

وإذا أردت السطور المهمة فقط فأضف `+noall +answer`، وإذا أردت سؤال سيرفر بعينه فاكتبه بعد `@`، كما سوف نفعل في التمرين 9.

للتفصيل: [سجل CNAME](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#cname) و[التخزين المؤقت ومدة TTL](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#cache-ttl) و[أدوات الفحص](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#tools) في مقال DNS.

### التمرين 5: اقرأ مخرج curl -v

هذا مخرج `curl -sv -o /dev/null http://shop.lab.test/` كما ظهر في المختبر:

```text
* Host shop.lab.test:80 was resolved.
* IPv6: (none)
* IPv4: 10.77.0.10
*   Trying 10.77.0.10:80...
* Connected to shop.lab.test (10.77.0.10) port 80
* using HTTP/1.x
> GET / HTTP/1.1
> Host: shop.lab.test
> User-Agent: curl/8.14.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Server: nginx/1.29.8
< Date: Wed, 07 Oct 2026 07:55:09 GMT
< Content-Type: text/html
< Content-Length: 57
< Last-Modified: Wed, 07 Oct 2026 07:43:46 GMT
< Connection: keep-alive
< ETag: "6ac5f832-39"
< Accept-Ranges: bytes
<
{ [57 bytes data]
* Connection #0 to host shop.lab.test left intact

```

أجب: أي السطور أرسلها curl وأيها جاءت من السيرفر؟ ما رمز الحالة؟ ما برنامج السيرفر؟ كم حجم الجسم Body؟ وماذا يرجع السيرفر إذا أرسلت الطلب نفسه مع الترويسة `If-None-Match: "6ac5f832-39"`؟

**الوقت المقترح:** 8 دقائق.

تلميح Hint

في curl السطر الذي يبدأ ب `>` خرج منك، والذي يبدأ ب `<` وصلك، والذي يبدأ ب `*` ملاحظة من curl نفسه. والترويسة ETag بصمة للملف تستخدم في الطلبات الشرطية Conditional Requests.

الحل Answer 

السطور التي تبدأ ب `>` هي الطلب: الطريقة `GET` والمسار `/` والترويسة `Host` التي يختار بها Nginx الموقع. والسطور التي تبدأ ب `<` هي الرد: الحالة `200 OK`، والسيرفر `nginx/1.29.8`، والجسم 57 بايتاً من نوع `text/html`، و`Connection: keep-alive` مع السطر الأخير `left intact` يعنيان أن الاتصال بقي مفتوحاً ليستخدم مرة أخرى. والسطور التي تبدأ ب `*` تخبرك أن الاسم حل إلى `10.77.0.10` وأن اتصال TCP نجح على المنفذ 80 قبل أن يرسل أي شيء.

وإذا أرسلت البصمة نفسها في `If-None-Match` فأنت تقول للسيرفر «عندي هذه النسخة، أرسلها فقط إذا تغيرت»، فيرد بالحالة 304 دون جسم:

```bash
docker exec kata-client curl -s -o /dev/null -w "%{http_code}\n" -H 'If-None-Match: "6ac5f832-39"' http://shop.lab.test/

```

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

```text
304

```

لاحظ أن البصمة عندك سوف تختلف لأنها مبنية على وقت تعديل الملف وحجمه، فخذها من مخرج `curl -v` عندك.

للتفصيل: [مم يتكون الطلب والرد](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#request-anatomy) و[رموز 3xx ومنها 304](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#redirects) في مقال HTTP.

### التمرين 6: من يستطيع الوصول إلى هذه المنافذ؟

على سيرفر فيه ثلاث خدمات نفذت `ss -tlnp` فظهر المخرج التالي:

```text
State  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
LISTEN 0      4096      127.0.0.11:43973      0.0.0.0:*
LISTEN 0      5            0.0.0.0:9090       0.0.0.0:*    users:(("python3",pid=339,fd=3))
LISTEN 0      5          127.0.0.1:3000       0.0.0.0:*    users:(("python3",pid=332,fd=3))
LISTEN 0      5                  *:8080             *:*    users:(("python3",pid=345,fd=3))

```

عنوان السيرفر على الشبكة `10.77.0.100`. أي المنافذ يصل إليها جهاز آخر على نفس الشبكة، وأيها لا يصل إليه إلا السيرفر نفسه؟ وماذا يعني السطر الأول الذي ليس له اسم برنامج؟

**الوقت المقترح:** 7 دقائق.

تلميح Hint

انظر إلى العنوان قبل النقطتين في عمود Local Address: `0.0.0.0` و`*` تعنيان كل الواجهات، و`127.0.0.1` تعني الواجهة المحلية Loopback فقط.

الحل Answer 

شغلنا الخدمات الثلاث في الكلاينت بخادم HTTP الموجود في Python، كل واحدة على عنوان مختلف:

```bash
docker exec -d kata-client python3 -m http.server 3000 --bind 127.0.0.1
docker exec -d kata-client python3 -m http.server 9090 --bind 0.0.0.0
docker exec -d kata-client python3 -m http.server 8080 --bind ::
docker exec kata-client ss -tlnp

```

المنفذ 9090 يستمع على `0.0.0.0` أي كل عناوين IPv4، والمنفذ 8080 يستمع على `*` أي كل العناوين في IPv4 وIPv6، فكلاهما يصل إليه أي جهاز يرى السيرفر ما لم يمنعه جدار حماية. أما 3000 فيستمع على `127.0.0.1` وحده، فلا يصل إليه إلا برنامج على السيرفر نفسه، وهذا هو الإعداد الصحيح لتطبيق يقف خلف Reverse Proxy. وجربنا ذلك من Container آخر على الشبكة:

```bash
for p in 3000 9090 8080; do
  docker exec kata-web sh -c "curl -s -o /dev/null -w 'port $p: %{http_code}\n' --connect-timeout 3 http://10.77.0.100:$p/ || echo 'port $p: exit '\$?"
done
docker exec kata-client curl -s -o /dev/null -w 'from inside, port 3000: %{http_code}\n' http://127.0.0.1:3000/

```

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

```text
port 3000: 000
port 3000: exit 7
port 9090: 200
port 8080: 200
from inside, port 3000: 200

```

والرمز 7 في curl يعني أنه لم يستطع الاتصال. والسطر الأول `127.0.0.11:43973` هو سيرفر ال DNS الداخلي الذي يضعه Docker في كل Container على شبكة خاصة، ولا يظهر له اسم برنامج لأنه ليس عملية داخل ال Container، وتجده موثقاً في [توثيق شبكات Docker](https://docs.docker.com/engine/network/?ref=arabroot.io). وعلى سيرفر Ubuntu عادي سوف ترى في مكانه غالباً `127.0.0.53:53` الخاص ب systemd-resolved. وأعمدة المخرج موثقة في [صفحة ss](https://man7.org/linux/man-pages/man8/ss.8.html?ref=arabroot.io).

للتفصيل: [المنافذ المنشورة وغير المنشورة](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/#ports-vs-expose) في دليل شبكات Docker، و[حالات الوصول الجزئي](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#case-slow-some) في دليل التشخيص.

## المستوى الثاني: متوسط Intermediate

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

### التمرين 7: قسم الشبكة 192.168.50.0/24 إلى أربع شبكات متساوية

لديك الشبكة `192.168.50.0/24` وتريد أن تقسمها إلى أربع شبكات متساوية للتطبيق وقواعد البيانات والإدارة والضيوف. اكتب لكل شبكة: الكتابة بصيغة CIDR، وأول عنوان للأجهزة، وآخر عنوان، وعنوان البث، وعدد الأجهزة.

**الوقت المقترح:** 12 دقيقة.

تلميح Hint

أربع شبكات تحتاج بتين إضافيين لجزء الشبكة، فالقناع الجديد 24 + 2\. وحجم كل شبكة 256 مقسوماً على 4.

الحل Answer 

أربع شبكات تساوي 22، فنأخذ بتين من جزء الأجهزة ويصبح القناع /26، وكل شبكة فيها 64 عنواناً منها 62 للأجهزة، وتبدأ الشبكات عند 0 و64 و128 و192:

```python
for s in ip.ip_network('192.168.50.0/24').subnets(new_prefix=26):
    print(s, s[1], s[-2], s.broadcast_address, s.num_addresses - 2)

```

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

```text
192.168.50.0/26 192.168.50.1 192.168.50.62 192.168.50.63 62
192.168.50.64/26 192.168.50.65 192.168.50.126 192.168.50.127 62
192.168.50.128/26 192.168.50.129 192.168.50.190 192.168.50.191 62
192.168.50.192/26 192.168.50.193 192.168.50.254 192.168.50.255 62

```

وقد يتساءل البعض: لماذا لا نعطي كل فريق ما يكفيه بالضبط، فالضيوف مثلاً يحتاجون 120 والإدارة 10؟ والإجابة أن هذا ممكن بأقنعة مختلفة الطول Variable Length Subnet Mask، فتأخذ الضيوف /25 والبقية /26 و/27، ولكن ابدأ دائماً بالأكبر حتى لا تقع الشبكة الكبيرة في منتصف حدود لا تقبل القسمة على حجمها.

للتفصيل: [العناوين الخاصة](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/#private-addresses) في مقال NAT و[توثيق ipaddress](https://docs.python.org/3/library/ipaddress.html?ref=arabroot.io).

### التمرين 8: جهازان على /23 لا يتحدثان

ثلاثة سيرفرات بلا Router بينها، وكلها موصولة بنفس ال Switch، وإعدادها كما يلي:

```text
app1   10.0.1.10/23
app2   10.0.2.20/23
db1    10.0.3.30/23

```

السيرفر `app1` لا يصل إلى `db1`، بينما `app2` يصل إليه. لماذا؟ وما أقل تغيير يحل المشكلة؟

**الوقت المقترح:** 12 دقيقة.

تلميح Hint

احسب عنوان الشبكة لكل سيرفر. الجهاز يرسل مباشرة فقط إلى من يرى أنه في شبكته، وغير ذلك يرسله إلى البوابة الافتراضية Default Gateway، وهنا لا توجد بوابة.

الحل Answer 

القناع /23 يأخذ قيمتين في البايت الثالث تبدآن من رقم زوجي، فالسيرفر `app1` في الشبكة `10.0.0.0/23` أي من 10.0.0.0 إلى 10.0.1.255، بينما `app2` و`db1` في `10.0.2.0/23` أي من 10.0.2.0 إلى 10.0.3.255:

```python
for a in ['10.0.1.10/23', '10.0.2.20/23', '10.0.3.30/23']:
    print(a, '->', ip.ip_interface(a).network)

```

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

```text
10.0.1.10/23 -> 10.0.0.0/23
10.0.2.20/23 -> 10.0.2.0/23
10.0.3.30/23 -> 10.0.2.0/23

```

فعندما يريد `app1` الوصول إلى `10.0.3.30` يرى أنه خارج شبكته فيبحث عن بوابة ولا يجدها، رغم أن الكابل نفسه يصل إليه. وأقل تغيير هو نقل `app1` إلى عنوان داخل `10.0.2.0/23` مثل `10.0.2.10`، أو توسيع القناع على الأجهزة الثلاثة إلى /22 فتصبح كلها في `10.0.0.0/22`. ولا تقم بتغيير القناع على جهاز واحد فقط، والسبب أن الاتصال يحتاج طرفين، فالرد من `db1` سوف يفشل بنفس الطريقة.

للتفصيل: [ماذا يفعل ال Router فعلاً](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B5%D9%84-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%AA%D9%83/#router-jobs) في مقال وصول الإنترنت.

### التمرين 9: غيرنا السجل والعنوان القديم ما زال يظهر

نقلت الموقع من السيرفر `10.77.0.10` إلى `10.77.0.11` وغيرت سجل A عند مزود ال DNS، ثم سألت من جهازك فظهر لك ما يلي:

```text
$ dig +noall +answer shop.lab.test
shop.lab.test.		262	IN	A	10.77.0.10
$ dig +noall +answer @10.77.0.53 shop.lab.test
shop.lab.test.		300	IN	A	10.77.0.11

```

وقبل التغيير بدقائق كان الأمر الأول قد أظهر `286` بدل `262`. فسر المخرج: لماذا يختلف الجوابان؟ ماذا يعني تناقص الرقم؟ ومتى سوف يرى جهازك العنوان الجديد؟ وماذا كان يجب أن تفعل قبل النقل؟

**الوقت المقترح:** 12 دقيقة.

تلميح Hint

الأمر الأول لم يحدد سيرفراً، فمن أجاب؟ والأمر الثاني سأل السيرفر الذي يحمل السجل مباشرة. والرقم الثاني في السطر هو المدة المتبقية وليس المدة الأصلية.

الحل Answer 

الأمر الأول سأل الذاكرة المؤقتة `10.77.0.54` التي يستخدمها جهازك، وهي حفظت الجواب القديم عندما سألتها أول مرة لمدة 300 ثانية، والرقم الذي يتناقص من 286 إلى 262 هو الوقت المتبقي قبل أن ترمي الجواب وتسأل من جديد. أما الأمر الثاني فسأل السيرفر الذي يحمل السجل نفسه، فأعطاك العنوان الجديد بمدته الكاملة 300\. وبالتالي جهازك سوف يرى العنوان الجديد بعد 262 ثانية على الأكثر، وهذا ما يسميه الناس «انتشار DNS» وهو في الحقيقة انتهاء صلاحية النسخ المحفوظة واحدة بعد الأخرى.

وفي المختبر أنتجنا هذا المخرج بتغيير السجل في سيرفر `kata-dns` بعد أن سألنا عنه مرة:

```bash
docker exec kata-client dig +noall +answer shop.lab.test
docker rm -f kata-dns
docker run -d --name kata-dns --network kata-net --ip 10.77.0.53 alpine:3.22 sh -c "apk add -q dnsmasq && exec dnsmasq -k --no-resolv --no-hosts --local=/lab.test/ --local-ttl=300 --host-record=shop.lab.test,10.77.0.11 --cname=www.shop.lab.test,shop.lab.test"
docker exec kata-client dig +noall +answer shop.lab.test
docker exec kata-client dig +noall +answer @10.77.0.53 shop.lab.test

```

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

```text
shop.lab.test.		286	IN	A	10.77.0.10
shop.lab.test.		262	IN	A	10.77.0.10
shop.lab.test.		300	IN	A	10.77.0.11

```

وما كان يجب فعله هو أن تخفض TTL إلى 60 أو 300 ثانية قبل النقل بمدة لا تقل عن قيمته القديمة، فإذا كانت يوماً كاملاً فخفضها قبل يوم، ثم تنقل، ثم تبقي السيرفر القديم يعمل حتى تنتهي المدة، ثم ترجع القيمة إلى ما كانت. ولا تقم بإيقاف السيرفر القديم لحظة تغيير السجل، والسبب أن بعض المستخدمين سوف يذهبون إليه حتى تنتهي مدة النسخة المحفوظة عندهم. وأرجع السجل إلى `10.77.0.10` بنفس الأمر قبل أن تكمل بقية التمارين.

للتفصيل: [«انتشار» DNS: الحقيقة أنه تخزين مؤقت](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#ttl-propagation) في مقال DNS، و[حالة الاسم الذي لا يحل أو التغيير الذي لم يصل](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#case-dns) في دليل التشخيص.

### التمرين 10: 301 أم 302؟

في إعداد Nginx في المختبر رابطان يعيدان التوجيه:

```nginx
location = /old-price-list { return 301 /price-list; }
location = /offers        { return 302 /ramadan-offers; }

```

اكتب بيدك ما يتوقع أن يظهر في سطر الحالة والترويسة `Location` لكل واحد. ثم أجب: أيهما يصلح لصفحة نقلتها نهائياً وأيهما لعرض موسمي، وماذا يحدث في متصفحات المستخدمين إذا استخدمت 301 للعرض الموسمي ثم أردت إلغاءه بعد شهر؟

**الوقت المقترح:** 10 دقائق.

تلميح Hint

فكر في من يحفظ الجواب. أحد الرمزين قابل للحفظ في ذاكرة المتصفح Cache افتراضياً حتى دون ترويسة Cache-Control، والآخر لا.

الحل Answer 

الطلب بالطريقة HEAD عبر `curl -sI` يظهر لك الحالة والترويسات فقط:

```bash
docker exec kata-client curl -sI http://shop.lab.test/old-price-list
docker exec kata-client curl -sI http://shop.lab.test/offers
docker exec kata-client curl -sL -o /dev/null -w '%{http_code} %{url_effective} redirects=%{num_redirects}\n' http://shop.lab.test/old-price-list

```

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

```text
HTTP/1.1 301 Moved Permanently
Server: nginx/1.29.8
Date: Wed, 07 Oct 2026 07:54:35 GMT
Content-Type: text/html
Content-Length: 169
Location: http://shop.lab.test/price-list
Connection: keep-alive

HTTP/1.1 302 Moved Temporarily
Server: nginx/1.29.8
Date: Wed, 07 Oct 2026 07:54:38 GMT
Content-Type: text/html
Content-Length: 145
Location: http://shop.lab.test/ramadan-offers
Connection: keep-alive

200 http://shop.lab.test/price-list redirects=1

```

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

- Nginx حول المسار النسبي في `return` إلى رابط كامل في `Location`.
- Nginx يكتب للرمز 302 العبارة القديمة `Moved Temporarily` بينما اسمه في [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110?ref=arabroot.io#section-15.4.3) هو `Found`، والمهم هو الرقم وليس العبارة.
- الخيار `-L` جعل curl يتبع التوجيه، فانتهى عند `/price-list` بالحالة 200 بعد توجيه واحد.

والرمز 301 يعني أن المكان تغير نهائياً، ويحق للمتصفح أن يحفظه في ذاكرته ويذهب مباشرة إلى العنوان الجديد في المرات القادمة دون أن يسألك، لأنه قابل للحفظ افتراضياً حسب [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110?ref=arabroot.io#section-15.4.2)، لذلك هو المناسب لصفحة نقلتها نهائياً، ومحركات البحث تنقل ترتيب الصفحة القديمة إلى الجديدة. أما العرض الموسمي فيأخذ 302، والسبب أنك إذا وضعت له 301 ثم ألغيته فإن المتصفحات التي زارته سوف تستمر في الذهاب إلى صفحة العرض من ذاكرتها، ولن تستطيع إصلاح ذلك من السيرفر.

للتفصيل: [إعادة التوجيه 301 و302 و307 و308](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#redirects) في مقال HTTP.

### التمرين 11: أين ذهبت الثانيتان؟ توقيتات curl -w

اشتكى مستخدم أن صفحة التقرير بطيئة، فقست الطلب بالأمر التالي:

```bash
F='namelookup:    %{time_namelookup}\nconnect:       %{time_connect}\nappconnect:    %{time_appconnect}\nstarttransfer: %{time_starttransfer}\ntotal:         %{time_total}\nhttp_code:     %{http_code}\n'
docker exec kata-client curl -s -o /dev/null --cacert /certs/root.crt -w "$F" https://shop.lab.test:8443/api/report

```

وكان المخرج:

```text
namelookup:    0.001194
connect:       0.001365
appconnect:    0.077486
starttransfer: 2.125221
total:         2.125309
http_code:     200

```

احسب كم أخذ كل جزء: حل الاسم DNS، واتصال TCP، ومصافحة TLS، وانتظار السيرفر، ونقل الجسم. ثم حدد أين تبحث عن السبب.

**الوقت المقترح:** 12 دقيقة.

تلميح Hint

كل رقم في `-w` هو وقت تراكمي من بداية الطلب وليس مدة المرحلة وحدها، فاطرح كل رقم من الذي بعده.

الحل Answer 

الأرقام تراكمية بالثواني من لحظة البداية كما في [دليل curl](https://curl.se/docs/manpage.html?ref=arabroot.io)، فمدة كل مرحلة هي الفرق بين رقمين:

```text
DNS          namelookup                    0.0012 s  (1.2 ms)
TCP          connect - namelookup          0.0002 s  (0.2 ms)
TLS          appconnect - connect          0.0761 s  (76 ms)
server wait  starttransfer - appconnect    2.0477 s  (2048 ms)
body         total - starttransfer         0.0001 s

```

فالشبكة كلها أخذت أقل من 80 ملي ثانية، وأكثر من ثانيتين ضاعت بين إرسال الطلب ووصول أول بايت من الرد، أي داخل السيرفر. والطلب يمر عبر Nginx إلى التطبيق، فتبحث في التطبيق وليس في الشبكة، وفي المختبر السبب معروف لأن السكربت `report` ينام ثانيتين قبل أن يرد. وقارن ذلك بالعنوان السريع:

```bash
docker exec kata-client curl -s -o /dev/null -w 'dns=%{time_namelookup} connect=%{time_connect} ttfb=%{time_starttransfer} total=%{time_total} code=%{http_code}\n' http://shop.lab.test/api/orders

```

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

```text
dns=0.045843 connect=0.046078 ttfb=0.203511 total=0.207435 code=200

```

ولاحظ أن ال DNS هنا أخذ 45 ملي ثانية لأن الذاكرة المؤقتة لم تكن تحمل الاسم في تلك اللحظة فسألت السيرفر الآخر، وفي الطلب التالي نزل إلى أقل من 2 ملي ثانية، لذلك لا تحكم من طلب واحد وكرر القياس.

للتفصيل: [ال curl -w: خمسة أرقام تقسم الطلب](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A7%D9%84%D8%B7%D9%84%D8%A8-%D8%A7%D9%84%D8%A8%D8%B7%D9%8A%D8%A1-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%89-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA/#curl-w) في دليل الطلب البطيء، و[أين تذهب المللي ثانية](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B5%D9%84-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%AA%D9%83/#latency) في مقال وصول الإنترنت.

### التمرين 12: عشرون طلباً وعشرون اتصالاً في TIME-WAIT

شغلت من الكلاينت عشرين أمر curl متتالياً إلى Nginx، ثم نظرت إلى حالات الاتصالات في الطرفين:

```text
$ for i in $(seq 1 20); do curl -s -o /dev/null http://shop.lab.test/; done
$ ss -tan state time-wait | head -4
Recv-Q Send-Q Local Address:Port  Peer Address:Port
0      0        10.77.0.100:38620   10.77.0.10:80
0      0        10.77.0.100:38564   10.77.0.10:80
0      0        10.77.0.100:38648   10.77.0.10:80
client TIME-WAIT: 20
server TIME-WAIT: 0

```

لماذا ظهرت حالة TIME-WAIT عند الكلاينت وليس عند Nginx؟ وما فائدة هذه الحالة؟ ومتى تصبح مشكلة؟

**الوقت المقترح:** 15 دقيقة.

تلميح Hint

حالة TIME-WAIT تبقى عند الطرف الذي أغلق الاتصال أولاً. فمن أغلق هنا: curl عندما انتهى، أم Nginx الذي يقول `Connection: keep-alive`؟

الحل Answer 

كل أمر curl هو برنامج مستقل يفتح اتصالاً واحداً وينهيه عند الخروج، فهو الطرف الذي يغلق أولاً Active Close، بينما Nginx كان مستعداً لإبقاء الاتصال مفتوحاً، لذلك بقيت الاتصالات العشرون في TIME-WAIT عند الكلاينت ولا يوجد شيء منها عند السيرفر. والأوامر التي أنتجت المخرج:

```bash
docker exec kata-client sh -c 'for i in $(seq 1 20); do curl -s -o /dev/null http://shop.lab.test/; done; sleep 1; ss -tan state time-wait | head -4; echo "client TIME-WAIT: $(ss -Htan state time-wait | wc -l)"'
docker exec kata-web sh -c 'echo "server TIME-WAIT: $(netstat -tan | grep -c TIME_WAIT)"'

```

وفائدة الحالة أن الطرف الذي أغلق ينتظر مدة تساوي ضعف أطول عمر للحزمة 2MSL حسب [RFC 9293](https://datatracker.ietf.org/doc/html/rfc9293?ref=arabroot.io)، حتى لا تصل حزمة متأخرة من هذا الاتصال فتختلط باتصال جديد يستخدم نفس المنفذ، وحتى يعيد إرسال آخر ACK إذا ضاع. وهي لا تستهلك شيئاً يذكر، ولكنها تصبح مشكلة عندما يفتح تطبيقك آلاف الاتصالات القصيرة في الثانية إلى نفس السيرفر، كتطبيق ينشئ كلاينت HTTP جديداً لكل طلب، فتنفد المنافذ المحلية Ephemeral Ports وتظهر أخطاء مثل `Cannot assign requested address`. والحل ليس تعديل إعدادات النواة Kernel، وإنما إعادة استخدام الاتصال عبر مجمع اتصالات Connection Pool، وأعدنا التجربة بعد أن انتهت الحالات السابقة، ولكن بأمر curl واحد يطلب الرابط عشرين مرة، والمتغير `num_connects` يطبع عدد الاتصالات الجديدة التي فتحها لكل طلب:

```bash
docker exec kata-client sh -c 'curl -s $(for i in $(seq 1 20); do printf -- "-o /dev/null "; done) "http://shop.lab.test/?[1-20]" -w "%{num_connects} "; sleep 1; echo; echo "client TIME-WAIT: $(ss -Htan state time-wait | wc -l)"'

```

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

```text
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
client TIME-WAIT: 1

```

فالطلب الأول فتح اتصالاً والتسعة عشر الباقية استخدمته، وبقي اتصال واحد في TIME-WAIT بدل عشرين.

للتفصيل: [إعادة استخدام الاتصال](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#connection-reuse) في مقال HTTP، و[إعادة استخدام اتصالات HTTP](https://arabroot.io/articles/%D8%AA%D8%AC%D9%85%D9%8A%D8%B9-%D8%A7%D9%84%D8%A7%D8%AA%D8%B5%D8%A7%D9%84%D8%A7%D8%AA-connection-pooling-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#http-reuse) في مقال تجميع الاتصالات، و[صفحة tcp في Linux](https://man7.org/linux/man-pages/man7/tcp.7.html?ref=arabroot.io).

## المستوى الثالث: متقدم Advanced

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

### التمرين 13: توجيه المنفذ عبر NAT ومن يرى عنوان من

الصورة التالية تبين المختبر: سيرفر منزلي خلف Router، وجهاز على الإنترنت، وقاعدتان في ال Router:

![مخطط فيه في اليمين شبكة الإنترنت 203.0.113.0/24 وعليها جهاز خارجي عنوانه 203.0.113.50، وفي الوسط Router له واجهتان eth0 بالعنوان 192.168.77.2 وeth1 بالعنوان 203.0.113.2، وفي اليسار الشبكة المنزلية 192.168.77.0/24 وعليها سيرفر Nginx بالعنوان 192.168.77.20. سهمان برتقاليان للدخول: من 203.0.113.50 إلى 203.0.113.2:8443 ثم من 203.0.113.50 إلى 192.168.77.20:80، وسهمان أزرقان للخروج: من 192.168.77.20 إلى 203.0.113.50:80 ثم من 203.0.113.2 إلى 203.0.113.50:80، وفي الأسفل قاعدة DNAT وقاعدة MASQUERADE](https://arabroot.io/content/images/2026/10/katas-network-02-nat-lab.webp)

ال DNAT يغير الوجهة عند الدخول، وال MASQUERADE يغير المصدر عند الخروج

```text
iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 8443 -j DNAT --to-destination 192.168.77.20:80
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

```

أجب بيدك: (1) عندما يطلب الجهاز الخارجي `http://203.0.113.2:8443/`، ما عنوان العميل الذي يكتبه Nginx في سجله على السيرفر المنزلي؟ (2) عندما يطلب السيرفر المنزلي صفحة من الجهاز الخارجي، ما العنوان الذي يراه الجهاز الخارجي؟ (3) غير أحدهم البوابة الافتراضية للسيرفر المنزلي إلى جهاز آخر على الشبكة، فتوقف التوجيه رغم أن القاعدة لم تتغير. لماذا؟ (4) واجهة WAN في ال Router عندك عنوانها `100.72.3.9`، هل يكفي توجيه المنفذ؟

**الوقت المقترح:** 25 دقيقة.

تلميح Hint

DNAT يغير عنوان الوجهة فقط ولا يلمس المصدر. والرد يجب أن يمر بنفس ال Router حتى يعيد الترجمة، وإلا وصل إلى الجهاز الخارجي من عنوان لم يكلمه. وارجع إلى التمرين 3 للعنوان الأخير.

الحل Answer 

بنينا المختبر بشبكتين جديدتين ألغينا فيهما ترجمة Docker نفسه، حتى تكون الترجمة الوحيدة هي قاعدتنا، بنفس طريقة [مختبر مقال 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/#lab-setup):

```bash
docker network create --subnet 192.168.77.0/24 -o com.docker.network.bridge.enable_ip_masquerade=false kata-lan
docker network create --subnet 203.0.113.0/24 -o com.docker.network.bridge.enable_ip_masquerade=false kata-wan
docker run -d --name kata-router --cap-add NET_ADMIN --sysctl net.ipv4.ip_forward=1 alpine:3.22 sleep infinity
docker run -d --name kata-homesrv --cap-add NET_ADMIN nginx:1.29-alpine
docker run -d --name kata-ext --cap-add NET_ADMIN nginx:1.29-alpine
docker exec kata-router apk add -q iptables conntrack-tools iproute2
for c in router homesrv ext; do docker network disconnect bridge kata-$c; done
docker network connect --ip 192.168.77.2 kata-lan kata-router
docker network connect --ip 203.0.113.2 kata-wan kata-router
docker network connect --ip 192.168.77.20 kata-lan kata-homesrv
docker network connect --ip 203.0.113.50 kata-wan kata-ext
docker exec kata-homesrv ip route replace default via 192.168.77.2
docker exec kata-router ip route del default
docker exec kata-ext ip route del default
docker exec kata-router sh -c 'iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 8443 -j DNAT --to-destination 192.168.77.20:80; iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE'

```

ثم جربنا الاتجاهين وقرأنا جدول تتبع الاتصالات Connection Tracking في ال Router وسجل Nginx في كل طرف:

```bash
docker exec kata-ext curl -s -m 4 -o /dev/null -w '%{http_code}\n' http://203.0.113.2:8443/
docker exec kata-router conntrack -L -p tcp | grep 8443
docker logs kata-homesrv 2>/dev/null | grep 'GET /' | tail -1
docker exec kata-homesrv curl -s -m 4 -o /dev/null -w '%{http_code}\n' http://203.0.113.50/
docker exec kata-router conntrack -L -p tcp | grep 'src=192.168.77.20 dst=203.0.113.50'
docker logs kata-ext 2>/dev/null | grep 'GET /' | tail -1

```

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

```text
200
tcp      6 118 TIME_WAIT src=203.0.113.50 dst=203.0.113.2 sport=42598 dport=8443 src=192.168.77.20 dst=203.0.113.50 sport=80 dport=42598 [ASSURED] mark=0 use=1
203.0.113.50 - - [07/Oct/2026:08:13:41 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.17.0" "-"
200
tcp      6 118 TIME_WAIT src=192.168.77.20 dst=203.0.113.50 sport=43846 dport=80 src=203.0.113.50 dst=203.0.113.2 sport=80 dport=43846 [ASSURED] mark=0 use=1
203.0.113.2 - - [07/Oct/2026:08:13:46 +0000] "GET / HTTP/1.1" 200 896 "-" "curl/8.17.0" "-"

```

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

- (1) سجل السيرفر المنزلي يكتب `203.0.113.50`، أي العنوان الحقيقي للعميل، لأن DNAT غير الوجهة فقط. وفي سطر conntrack الأول ترى الاتجاهين: الطلب كما وصل إلى `203.0.113.2:8443`، والرد المتوقع من `192.168.77.20:80`.
- (2) الجهاز الخارجي يرى `203.0.113.2`، أي عنوان ال Router العام، لأن MASQUERADE يغير المصدر عند الخروج من `eth1`، والعنوان الخاص لا يظهر خارج البيت أبداً.

(3) وعندما غيرنا البوابة الافتراضية للسيرفر المنزلي إلى `192.168.77.1` فشل الطلب نفسه:

```bash
docker exec kata-homesrv ip route replace default via 192.168.77.1
docker exec kata-ext curl -sS -m 4 -o /dev/null -w '%{http_code}\n' http://203.0.113.2:8443/
docker exec kata-router conntrack -L -p tcp | grep SYN
docker exec kata-homesrv ip route replace default via 192.168.77.2

```

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

```text
curl: (28) Connection timed out after 4001 milliseconds
000
tcp      6 112 SYN_SENT src=203.0.113.50 dst=203.0.113.2 sport=36704 dport=8443 [UNREPLIED] src=192.168.77.20 dst=203.0.113.50 sport=80 dport=36704 mark=0 use=1

```

والحالة `[UNREPLIED]` تقول إن الطلب ترجم ووصل، ولكن الرد لم يمر بال Router، لأن السيرفر أرسله إلى بوابة أخرى بعنوانه الخاص، فوصل إلى الجهاز الخارجي من عنوان لم يكلمه فرماه. وهذا سبب شائع لعبارة «وجهت المنفذ ولا يصل أحد».

(4) العنوان `100.72.3.9` من مساحة CGNAT كما في التمرين 3، فال Router عندك خلف NAT آخر عند المزود، والطلبات من الإنترنت لا تصل إلى ال Router أصلاً، لذلك لا يكفي توجيه المنفذ، والحل أن تطلب عنواناً عاماً من المزود أو تستخدم نفقاً Tunnel أو IPv6.

للتفصيل: [توجيه المنافذ DNAT](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/#port-forwarding) و[وجهت المنفذ ولا يصل أحد](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/#port-forward-not-working) في مقال NAT، و[كل الطرق الممكنة](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%AE%D8%A7%D8%AF%D9%85%D9%83-%D9%85%D9%86-%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A7%D9%84%D9%85%D9%86%D8%B2%D9%84/#all-ways) في دليل الاستضافة من المنزل، و[توثيق DNAT وMASQUERADE](https://man7.org/linux/man-pages/man8/iptables-extensions.8.html?ref=arabroot.io).

### التمرين 14: سجل CNAME على النطاق الرئيسي

تريد أن يعمل متجرك على `shop.example` نفسه وليس فقط على `www`، ومنصة الاستضافة تعطيك اسماً وليس عنواناً: `shop-store.hosting.example`. فكتبت ملف المنطقة Zone File التالي:

```text
$TTL 300
@      IN SOA   ns1.dns-provider.example. admin.shop.example. ( 2026100701 3600 600 1209600 300 )
@      IN NS    ns1.dns-provider.example.
@      IN NS    ns2.dns-provider.example.
@      IN MX    10 mail.shop.example.
@      IN CNAME shop-store.hosting.example.
www    IN CNAME shop-store.hosting.example.
mail   IN A     203.0.113.25

```

ما الخطأ؟ وماذا سوف يقول سيرفر DNS عند تحميل الملف؟ وما الحلول الصحيحة؟

**الوقت المقترح:** 15 دقيقة.

تلميح Hint

الرمز `@` يعني النطاق الرئيسي نفسه. عد السجلات التي تحمل الاسم `@`، ثم تذكر القاعدة الأساسية لسجل CNAME: الاسم الذي له CNAME لا يجوز أن يكون له شيء آخر.

الحل Answer 

الاسم `@` عليه SOA وسجلا NS وسجل MX، وهذه إجبارية للنطاق الرئيسي Root Domain، وحسب [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034?ref=arabroot.io#section-3.6.2) و[RFC 2181](https://datatracker.ietf.org/doc/html/rfc2181?ref=arabroot.io#section-10.1) إذا كان للاسم سجل CNAME فلا يجوز أن يكون له أي سجل آخر، والسبب أن CNAME يقول «كل ما تسأل عنه لهذا الاسم موجود عند الاسم الآخر»، فلو سمح به هنا لضاع سجل MX والبريد معه. وتحققنا بالأداة `named-checkzone` من BIND بعد أن حفظنا الملف أعلاه باسم `shop.example.zone` ونسخناه إلى الكلاينت:

```bash
docker exec -i kata-client sh -c 'cat > /tmp/shop.example.zone' < shop.example.zone
docker exec kata-client named-checkzone shop.example /tmp/shop.example.zone; echo "exit=$?"

```

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

```text
dns_master_load: /tmp/shop.example.zone:6: shop.example: CNAME and other data
dns_master_load: /tmp/shop.example.zone:6: shop.example: CNAME and other data
dns_master_load: /tmp/shop.example.zone:6: shop.example: CNAME and other data
zone shop.example/IN: loading from master file /tmp/shop.example.zone failed: CNAME and other data
zone shop.example/IN: not loaded due to errors.
exit=1

```

والحلول الصحيحة ثلاثة: أن تضع سجل A بالعنوان الذي تعطيك إياه المنصة للنطاق الرئيسي، أو أن تستخدم ميزة ALIAS أو CNAME Flattening إذا كان مزود ال DNS يدعمها، وهي ليست سجلاً في المعيار وإنما يحل المزود الاسم بنفسه ويجيب بسجل A، أو أن تجعل النطاق الرئيسي يعيد التوجيه إلى `www` وتترك CNAME على `www` وحده. وعندما استبدلنا السطر السادس بسجل A قبل السيرفر الملف:

```text
@      IN A     203.0.113.80

```

```text
zone shop.example/IN: loaded serial 2026100701
OK
exit=0

```

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

للتفصيل: [سجل CNAME](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#cname) و[أخطاء شائعة](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#common-mistakes) في مقال DNS.

### التمرين 15: اقرأ الشهادة ب openssl s\_client

المتصفح على جهازك يفتح الموقع دون مشكلة، ولكن تطبيق الموبايل وأداة المراقبة يفشلان مع خطأ في الشهادة. نفذت الأمر التالي، و`root.crt` هي الشهادة الرئيسية لجهة الإصدار التي تثق بها الأداة:

```text
$ echo | openssl s_client -connect shop.lab.test:443 -servername shop.lab.test -CAfile /certs/root.crt
Connecting to 10.77.0.10
CONNECTED(00000003)
depth=0 CN=shop.lab.test
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN=shop.lab.test
verify error:num=21:unable to verify the first certificate
verify return:1
depth=0 CN=shop.lab.test
verify return:1
---
Certificate chain
 0 s:CN=shop.lab.test
   i:O=Lab Kata, CN=Lab Kata Intermediate CA
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Oct  7 07:44:16 2026 GMT; NotAfter: Jan  5 07:44:16 2027 GMT
---
...
Verification error: unable to verify the first certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
...
Verify return code: 21 (unable to verify the first certificate)

```

أجب: متى تنتهي الشهادة؟ كم شهادة أرسل السيرفر؟ لماذا فشل التحقق؟ لماذا يعمل المتصفح رغم ذلك؟ وكيف تعرف الأسماء التي تغطيها الشهادة؟

**الوقت المقترح:** 20 دقيقة.

تلميح Hint

في قسم `Certificate chain` السطر `s` هو صاحب الشهادة Subject و`i` هو من أصدرها Issuer. والأداة تملك الشهادة الرئيسية فقط، فمن أين تأتي بالشهادة الوسيطة؟

الحل Answer 

الشهادة تنتهي في `Jan 5 07:44:16 2027 GMT` كما في السطر `v`، أي بعد 90 يوماً من إصدارها. والسيرفر أرسل شهادة واحدة فقط برقم 0، وهي شهادة الموقع التي أصدرتها الجهة الوسيطة `Lab Kata Intermediate CA`، والأداة لا تملك إلا الشهادة الرئيسية، فلا تستطيع أن تبني السلسلة من شهادة الموقع إلى الرئيسية لأن الحلقة التي في الوسط ناقصة، وهذا معنى الخطأ 20 `unable to get local issuer certificate` ثم 21\. والمتصفح يعمل غالباً لأنه حفظ الشهادة الوسيطة من موقع آخر زاره، أو لأنه يجلبها بنفسه، بينما الأدوات وتطبيقات الموبايل لا تفعل ذلك، والحل الوحيد الصحيح أن يرسل السيرفر السلسلة كاملة، أي الملف `fullchain` وليس الشهادة وحدها.

والمنفذ 8443 في المختبر يستخدم `fullchain.crt`، فقارن:

```bash
docker exec kata-client sh -c 'echo | openssl s_client -connect shop.lab.test:8443 -servername shop.lab.test -CAfile /certs/root.crt 2>/dev/null | sed -n "/Certificate chain/,/^---/p;/Verify return code/p"'

```

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

```text
Certificate chain
 0 s:CN=shop.lab.test
   i:O=Lab Kata, CN=Lab Kata Intermediate CA
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Oct  7 07:44:16 2026 GMT; NotAfter: Jan  5 07:44:16 2027 GMT
 1 s:O=Lab Kata, CN=Lab Kata Intermediate CA
   i:O=Lab Kata, CN=Lab Kata Root CA
   a:PKEY: EC, (prime256v1); sigalg: ecdsa-with-SHA256
   v:NotBefore: Oct  7 07:44:15 2026 GMT; NotAfter: Oct  6 07:44:15 2031 GMT
---
Verify return code: 0 (ok)

```

والأسماء التي تغطيها الشهادة في الامتداد Subject Alternative Name وليس في CN، ومعها التواريخ وسؤال «هل تنتهي خلال 30 يوماً؟» الذي تضعه في المراقبة:

```bash
docker exec kata-client sh -c 'echo | openssl s_client -connect shop.lab.test:443 -servername shop.lab.test 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName'
docker exec kata-client sh -c 'echo | openssl s_client -connect shop.lab.test:443 -servername shop.lab.test 2>/dev/null | openssl x509 -noout -checkend 2592000; echo exit=$?'
docker exec kata-client sh -c 'echo | openssl s_client -connect shop.lab.test:443 -servername shop.lab.test 2>/dev/null | openssl x509 -noout -checkend 8640000; echo exit=$?'

```

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

```text
subject=CN=shop.lab.test
issuer=O=Lab Kata, CN=Lab Kata Intermediate CA
notBefore=Oct  7 07:44:16 2026 GMT
notAfter=Jan  5 07:44:16 2027 GMT
X509v3 Subject Alternative Name:
    DNS:shop.lab.test, DNS:www.shop.lab.test
Certificate will not expire
exit=0
Certificate will expire
exit=1

```

والرقم 2592000 هو 30 يوماً بالثواني فالشهادة لن تنتهي خلالها، و8640000 هو 100 يوم فهي سوف تنتهي خلالها. وأرقام أخطاء التحقق موثقة في [خيارات التحقق في OpenSSL](https://docs.openssl.org/3.5/man1/openssl-verification-options/?ref=arabroot.io).

للتفصيل: [لماذا يجب أن يرسل السيرفر الشهادة الوسيطة](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/#send-intermediate) و[متى تنتهي الشهادة](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/#lab-expiry) في مقال الشهادات.

### التمرين 16: 502 أم 504؟ ثلاث حالات من سجل Nginx

Nginx في المختبر يمرر `/api/` إلى التطبيق على `10.77.0.20:8080` ب `proxy_connect_timeout 2s` و`proxy_read_timeout 3s`. وظهرت ثلاثة أسطر في `error.log` في ثلاثة أوقات مختلفة:

```text
(A) connect() failed (111: Connection refused) while connecting to upstream, ... upstream: "http://10.77.0.20:8080/cgi-bin/orders"
(B) upstream timed out (110: Operation timed out) while reading response header from upstream, ... upstream: "http://10.77.0.20:8080/cgi-bin/export"
(C) upstream timed out (110: Operation timed out) while connecting to upstream, ... upstream: "http://10.77.0.20:8080/cgi-bin/orders"

```

لكل سطر: ما رمز الحالة الذي رآه المستخدم؟ كم انتظر تقريباً؟ وما الذي حدث للتطبيق؟

**الوقت المقترح:** 20 دقيقة.

تلميح Hint

الرمز 502 يعني أن ال Proxy تلقى جواباً سيئاً أو رفضاً، و504 يعني أنه انتظر ولم يصله شيء في الوقت المحدد. وانظر في كل سطر: هل فشل أثناء الاتصال connecting أم أثناء القراءة reading؟ وأي مهلة تنطبق؟

الحل Answer 

أنتجنا الحالات الثلاث في المختبر وقسنا ما يراه المستخدم:

```bash
docker exec kata-app pkill httpd
docker exec kata-client curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://shop.lab.test/api/orders
docker exec kata-app httpd -p 8080 -h /www
docker exec kata-client curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://shop.lab.test/api/export
docker stop kata-app
docker exec kata-client curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total}\n' http://shop.lab.test/api/orders
docker start kata-app

```

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

```text
code=502 total=0.003244
code=504 total=3.004797
code=504 total=2.010858

```

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

- (A) التطبيق متوقف ولكن الجهاز موجود، فنظام التشغيل رد فوراً برفض الاتصال Connection Refused، وNginx أرجع `502 Bad Gateway` خلال 3 ملي ثانية. وهذا ما تراه عندما ينهار التطبيق أو يعاد تشغيله أو يستمع على منفذ آخر.
- (B) التطبيق قبل الاتصال ولكنه لم يرسل ترويسات الرد خلال `proxy_read_timeout`، فأرجع Nginx `504 Gateway Time-out` بعد 3 ثوان بالضبط، والتطبيق في الحقيقة ما زال يعمل على الطلب، وهذا هو الاستعلام البطيء أو الخدمة الخارجية التي لا ترد.
- (C) هنا الحالة المفاجئة: أوقفنا ال Container كله فاختفى العنوان من الشبكة، فلم يرد أحد لا بالقبول ولا بالرفض، وانتهت مهلة الاتصال `proxy_connect_timeout` بعد ثانيتين فأرجع Nginx الرمز 504 وليس 502\. فالقاعدة الصحيحة ليست «502 يعني التطبيق متوقف»، وإنما: 502 جواب سيئ أو رفض سريع، و504 لا جواب خلال المهلة، واقرأ السطر في `error.log` لتعرف أي مرحلة.

وتعريف الرمزين في [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110?ref=arabroot.io#section-15.6.3)، والمهلتان في [توثيق ngx\_http\_proxy\_module](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fproxy%5Fmodule.html?ref=arabroot.io#proxy%5Fread%5Ftimeout).

للتفصيل: [من أين يأتي 502 و503 و504](https://arabroot.io/articles/http-%D9%88-https-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#server-errors) في مقال HTTP، و[حالة الخطأ 502 أو 504](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#case-502-504) في دليل التشخيص، و[من الذي أغلق الطلب](https://arabroot.io/articles/%D8%AA%D8%AA%D8%A8%D8%B9-%D8%A7%D9%84%D8%B7%D9%84%D8%A8-%D8%A7%D9%84%D8%A8%D8%B7%D9%8A%D8%A1-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D8%A7%D9%84%D9%89-%D9%82%D8%A7%D8%B9%D8%AF%D8%A9-%D8%A7%D9%84%D8%A8%D9%8A%D8%A7%D9%86%D8%A7%D8%AA/#status-codes) في دليل الطلب البطيء.

### التمرين 17: «الموقع لا يفتح»، رتب الرسائل على السلم

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

```text
1) curl: (6) Could not resolve host: shpo.lab.test
2) curl: (7) Failed to connect to shop.lab.test port 8081 after 5 ms: Could not connect to server
3) curl: (28) Connection timed out after 3115 milliseconds
4) curl: (60) SSL certificate problem: unable to get local issuer certificate
5) curl: (60) SSL: no alternative certificate subject name matches target ipv4 address '10.77.0.10'
6) HTTP/1.1 504 Gateway Time-out

```

**الوقت المقترح:** 25 دقيقة.

تلميح Hint

الطلب يصعد سلماً: الاسم، ثم الاتصال، ثم التشفير، ثم HTTP، ثم التطبيق. ورقم الخطأ في curl يخبرك بالدرجة قبل أن تقرأ النص، وكلها في [قائمة أخطاء libcurl](https://curl.se/libcurl/c/libcurl-errors.html?ref=arabroot.io).

الحل Answer 

الصورة التالية تبين السلم الذي تمشي عليه، وكل رسالة في الصورة خرجت فعلاً من المختبر:

![سلم من خمس درجات من الأسفل إلى الأعلى: الأولى DNS وأداتها dig ورسالتها curl (6) Could not resolve host، والثانية TCP وأداتها nc وss ورسالتاها curl (7) للرفض وcurl (28) لانتهاء المهلة، والثالثة TLS وأداتها openssl s_client ورسالتاها curl (60) للسلسلة الناقصة ولاسم لا تغطيه الشهادة، والرابعة HTTP وأداتها curl -v وسجل nginx ورسالتاها 502 و504، والخامسة التطبيق وأداتها توقيتات curl -w ورسالتها 200 مع starttransfer 2.12 ثانية](https://arabroot.io/content/images/2026/10/katas-network-03-diagnosis-ladder.webp)

كل درجة تفترض أن التي تحتها تعمل، فلا تفحص الشهادة قبل أن تتأكد أن الاسم يحل والمنفذ يجيب

والأوامر التي أنتجت الرسائل في المختبر:

```bash
docker exec kata-client curl -sS http://shpo.lab.test/
docker exec kata-client curl -sS http://shop.lab.test:8081/
docker exec kata-client curl -sS --connect-timeout 3 http://10.77.0.99/
docker exec kata-client curl -sS --cacert /certs/root.crt https://shop.lab.test/
docker exec kata-client curl -sS --cacert /certs/root.crt https://10.77.0.10:8443/
docker exec kata-client curl -sv http://shop.lab.test:8081/ 2>&1 | grep -iE "trying|refused|failed"

```

والسطر الأخير يظهر السبب الحقيقي خلف الرسالة 2، لأن curl 8.14 يكتب «Could not connect to server» في الرسالة المختصرة:

```text
*   Trying 10.77.0.10:8081...
* connect to 10.77.0.10 port 8081 from 10.77.0.100 port 55696 failed: Connection refused
* Failed to connect to shop.lab.test port 8081 after 1 ms: Could not connect to server

```

وبالترتيب:

1. الرمز 6 من درجة DNS: الاسم مكتوب خطأ `shpo`، وتتحقق ب `dig shpo.lab.test` فترى `status: NXDOMAIN`. وإذا كان الاسم صحيحاً فابحث عن سجل لم يضف أو تغيير لم يصل كما في التمرين 9.
2. الرمز 7 من درجة TCP مع رفض فوري خلال ملي ثوان: الجهاز موجود ولا يستمع أحد على المنفذ 8081، وتتحقق ب `ss -tlnp` على السيرفر كما في التمرين 6، والسبب الأرجح منفذ خاطئ أو خدمة متوقفة.
3. الرمز 28 من درجة TCP أيضاً ولكن دون أي رد: العنوان لا يجيب أصلاً، إما لأنه لا يوجد جهاز عليه، أو لأن جدار حماية يرمي الحزم بصمت، أو لأن الرد يذهب في طريق آخر كما في التمرين 13\. وتتحقق ب `nc -zv -w 3` من داخل الشبكة ومن خارجها.
4. الرمز 60 مع `unable to get local issuer certificate` من درجة TLS: السلسلة ناقصة كما في التمرين 15، أو الشهادة من جهة لا يثق بها الكلاينت، وتتحقق ب `openssl s_client`.
5. الرمز 60 مع `no alternative certificate subject name matches` من درجة TLS: المستخدم فتح الموقع بالعنوان `10.77.0.10` أو باسم لا تغطيه الشهادة، والأسماء المقبولة في SAN فقط.
6. 504 من درجة HTTP: كل ما تحت هذه الدرجة يعمل، وال Reverse Proxy لم يصله رد من التطبيق في المهلة، فاقرأ `error.log` كما في التمرين 16.

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

للتفصيل: [طريقة التحقيق خطوة بخطوة](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#method) و[حالة الشهادة المنتهية أو غير الموثوقة](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/#case-certificate) في دليل التشخيص، و[قس الطريق بنفسك](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B5%D9%84-%D8%A7%D9%84%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A5%D9%84%D9%89-%D8%A8%D9%8A%D8%AA%D9%83/#measure) في مقال وصول الإنترنت.

## جدول المتابعة

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

```text
| #  | المستوى  | التمرين                                  | التاريخ | الوقت | تلميح؟ | المراجعة القادمة |
|----|----------|------------------------------------------|---------|-------|--------|------------------|
| 1  | مبتدئ    | عدد الأجهزة في /26                       |         |       |        |                  |
| 2  | مبتدئ    | هل العنوان داخل /22                      |         |       |        |                  |
| 3  | مبتدئ    | خاص أم عام أم CGNAT                      |         |       |        |                  |
| 4  | مبتدئ    | قراءة رد dig                             |         |       |        |                  |
| 5  | مبتدئ    | قراءة curl -v و304                       |         |       |        |                  |
| 6  | مبتدئ    | قراءة ss -tlnp                           |         |       |        |                  |
| 7  | متوسط    | تقسيم /24 إلى أربع                       |         |       |        |                  |
| 8  | متوسط    | جهازان على /23                           |         |       |        |                  |
| 9  | متوسط    | العنوان القديم وTTL                      |         |       |        |                  |
| 10 | متوسط    | 301 أم 302                               |         |       |        |                  |
| 11 | متوسط    | توقيتات curl -w                          |         |       |        |                  |
| 12 | متوسط    | TIME-WAIT                                |         |       |        |                  |
| 13 | متقدم    | توجيه المنفذ عبر NAT                     |         |       |        |                  |
| 14 | متقدم    | CNAME على النطاق الرئيسي                 |         |       |        |                  |
| 15 | متقدم    | قراءة openssl s_client                   |         |       |        |                  |
| 16 | متقدم    | 502 أم 504                               |         |       |        |                  |
| 17 | متقدم    | سلم «الموقع لا يفتح»                     |         |       |        |                  |

```

وعندما تنتهي احذف المختبر بالأسماء نفسها التي أنشأته بها:

```bash
docker rm -f kata-client kata-web kata-app kata-cache kata-dns kata-router kata-homesrv kata-ext
docker network rm kata-net kata-lan kata-wan

```

## الخلاصة

وصلنا لنهاية تمارين الشبكات، وأهم ما تخرج به منها:

- القناع يقول كم بتاً للشبكة والباقي للأجهزة، ومن هذه الجملة وحدها تحسب الحجم والحدود، وتعرف لماذا لا يتحدث جهازان على نفس الكابل، ولماذا `172.32` ليس خاصاً و`100.72` ليس عاماً.
- مخرج `dig` و`curl -v` و`ss` ليس ضجيجاً، فكل سطر فيه يجيب عن سؤال محدد: من أجاب، وكم تبقى من مدة الحفظ، وما الذي أرسلته، وعلى أي عنوان يستمع البرنامج.
- «انتشار DNS» هو انتهاء مدة TTL، فخفضها قبل النقل وليس بعده، و301 يحفظ في المتصفح فلا تستخدمه لشيء مؤقت.
- DNAT يغير الوجهة ويحفظ عنوان العميل، والرد يجب أن يمر بنفس ال Router، وعنوان CGNAT على واجهة WAN يعني أن توجيه المنافذ لن يصل.
- كل طبقة تفشل برسالة مختلفة، فاصعد السلم من الاسم إلى الاتصال إلى الشهادة إلى HTTP إلى التطبيق، و502 و504 يفرق بينهما سطر واحد في `error.log`.

وهذه المقالات التي تشرح كل موضوع في التمارين بالتفصيل:

[![](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/dns-records-cover-cover.webp)دليلشرح DNS وسجلاته للمبتدئين بمثال حقيقيكيف يتحول اسم نطاقك إلى عنوان سيرفرك، وما وظيفة كل سجل تضيفه في لوحة النطاق، والأخطاء التي تضيع فيها الساعات عادة.](https://arabroot.io/articles/%D8%B4%D8%B1%D8%AD-dns-%D9%88%D8%B3%D8%AC%D9%84%D8%A7%D8%AA%D9%87-%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/http-basics-cover-cover.webp)دليلHTTP وHTTPS ببساطة: ماذا يحدث بين الرابط والصفحةماذا يحدث بين كتابة الرابط وظهور الصفحة، وكيف تقرأ الطلب والرد ورموز مثل 502 و504، ولماذا يكلف الاتصال الجديد أكثر.](https://arabroot.io/articles/http-%D9%88-https-%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/certificates-cover-cover.webp)دليلالشهادات الرقمية TLS للمبتدئين وصيغ ملفاتهاكيف يعرف المتصفح أنه يتحدث مع الموقع الحقيقي، وماذا يوجد داخل الشهادة، ولماذا تأتي ملفاتها بأكثر من صيغة.](https://arabroot.io/articles/%D8%A7%D9%84%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-%D8%A7%D9%84%D8%B1%D9%82%D9%85%D9%8A%D8%A9-tls-%D9%88-x509-%D9%88%D8%B5%D9%8A%D8%BA-pem-%D9%88-der/)[![](https://arabroot.io/content/images/2026/10/troubleshooting-guide-cover-cover.webp)دليلاستكشاف الأخطاء وتحليل الأداء للمبتدئين: من العرض إلى السببعندما يبطئ الموقع أو يعطي 502، هذه طريقة منظمة توصلك من العرض إلى السبب قبل أن تخمن أو تشتري سيرفراً أكبر.](https://arabroot.io/articles/%D8%A7%D8%B3%D8%AA%D9%83%D8%B4%D8%A7%D9%81-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%88%D8%AA%D8%AD%D9%84%D9%8A%D9%84-%D8%A7%D9%84%D8%A7%D8%AF%D8%A7%D8%A1-%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/home-internet-cover-cover.webp)دليلتشغيل خادمك من إنترنت المنزل: كل طرق الوصول من الخارجكيف تصل إلى سيرفر في بيتك من الإنترنت حتى لو كنت خلف CGNAT، وما الطريقة المناسبة لك من بين ثماني طرق.](https://arabroot.io/articles/%D8%AA%D8%B4%D8%BA%D9%8A%D9%84-%D8%AE%D8%A7%D8%AF%D9%85%D9%83-%D9%85%D9%86-%D8%A5%D9%86%D8%AA%D8%B1%D9%86%D8%AA-%D8%A7%D9%84%D9%85%D9%86%D8%B2%D9%84/)

وفي بقية سلسلة [تمارين Kata](https://arabroot.io/start/#stage-katas) سوف نطبق نفس الطريقة بإذن الله على SQL والبرمجة والأمن وDevOps وتشخيص الأعطال.

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

- أكتوبر 2026: كتابة التمارين السبعة عشر، وتنفيذ كل الحلول في مختبر Docker على شبكة خاصة: dnsmasq 2.91 للسجلات والذاكرة المؤقتة، وnginx 1.29.8، وكلاينت Alpine 3.22.6 عليه curl 8.14.1 وDiG 9.20.29 وOpenSSL 3.5.9 وnamed-checkzone، ومختبر NAT ب iptables 1.8.11 (nf\_tables) وconntrack 1.4.8، وحسابات الشبكات بمكتبة ipaddress في Python 3.13.3، على Docker Engine 29.8.0 (Docker Desktop).