رفتن به محتوا
LoopX

پورت‌ها و سرویس‌های شبکه

توی این درس یاد می‌گیری آدرس IP می‌گوید «کدام ماشین» و پورت می‌گوید «کدام برنامه‌ی روی آن ماشین». دو پروتکل اصلی TCP (اتصال‌محور و قابل‌اطمینان) و UDP (سریع و بدون اتصال) را می‌شناسی، پورت‌های معروف (۲۲ برای SSH، ۸۰ برای HTTP، ۴۴۳ برای HTTPS…) را می‌دانی، با ss -tulpn می‌بینی هر برنامه روی چه پورتی گوش می‌دهد (listen)، و با curl -I و nc -zv host 22 اتصال به یک سرویس را آزمایش می‌کنی. تمرین: پیدا می‌کنی کدام پروسه پورت ۲۲ را گرفته است.

مسئله: برنامه بالا آمده، ولی کار نمی‌کند

Section titled “مسئله: برنامه بالا آمده، ولی کار نمی‌کند”

یک وب‌سرور را راه انداخته‌ای ولی از بیرون باز نمی‌شود. یا برنامه‌ات می‌گوید Address already in use. یا ssh وصل نمی‌شود. همه‌ی این‌ها یک ریشه دارند: پورت‌ها. باید بتوانی بپرسی: چه کسی روی این پورت گوش می‌دهد؟ روی کدام آدرس؟ و آیا من از بیرون می‌توانم به آن برسم؟

تشبیه: ساختمان و شماره‌ی واحد

Section titled “تشبیه: ساختمان و شماره‌ی واحد”

IP آدرس یک ساختمان اداری است؛ پورت شماره‌ی واحد (اتاق) در آن. وب‌سرور در واحد ۸۰ می‌نشیند، ssh در واحد ۲۲ و دیتابیس در واحد ۵۴۳۲. برنامه‌ای که «گوش می‌دهد» مثل کارمندی است که پشت در یک واحد منتظر مراجعه‌کننده نشسته. هر واحد فقط یک کارمند دارد (دو برنامه نمی‌توانند یک پورت را بگیرند). و TCP مثل تماس تلفنی است (اول وصل می‌شوی، تضمین می‌کند حرفت کامل برسد) در حالی که UDP مثل کارت‌پستال است (می‌فرستی و می‌روی، بدون تضمین).

TCP برای چیزهایی که باید کامل و مرتب برسند (وب، ssh، دیتابیس) و UDP برای چیزهای سریع و کوچک یا زنده (DNS، ویدیوی زنده).

یک اتصال در شبکه را چهار چیز مشخص می‌کنند: IP و پورت کلاینت، IP و پورت سرور. مثلاً مرورگر تو از 203.0.113.5:51234 (پورت موقت و تصادفی) به 192.0.2.10:443 (پورت ثابت وب‌سرور) وصل می‌شود. سرور روی یک پورت ثابت و معروف منتظر می‌ماند و کلاینت‌ها از پورت‌های موقت (ephemeral) وصل می‌شوند. هر پورت یک عدد ۱۶ بیتی است: 0 تا 65535.

این درس را روی ماشین systemd‌دار آزمایشی (Ubuntu) اجرا کرده‌ام که در آن sshd و nginx واقعی نصب‌اند و من root هستم.

عدد پورت قرارداد است و فهرست رسمی‌اش در /etc/services هست:

Terminal window
grep -E '^(ssh|telnet|smtp|domain|http|https|ntp|imaps|mysql|postgresql|redis)\s' /etc/services
echo "--- پرسیدن مستقیم (getent services):"
getent services 22/tcp
getent services 443/tcp
getent services 53/udp
خروجی
ssh 22/tcp # SSH Remote Login Protocol
telnet 23/tcp
smtp 25/tcp mail
domain 53/tcp # Domain Name Server
domain 53/udp
http 80/tcp www # WorldWideWeb HTTP
ntp 123/udp # Network Time Protocol
https 443/tcp # http protocol over TLS/SSL
https 443/udp # HTTP/3
imaps 993/tcp # IMAP over SSL
mysql 3306/tcp
postgresql 5432/tcp postgres # PostgreSQL Database
redis 6379/tcp
--- پرسیدن مستقیم (getent services):
ssh 22/tcp
https 443/tcp
domain 53/udp
پورت پروتکل سرویس
22 TCP SSH (ورود امن به سرور)
53 TCP و UDP DNS
80 TCP HTTP (وب بدون رمزنگاری)
443 TCP HTTPS (وب امن)
25 / 465 / 587 TCP ایمیل (SMTP)
3306 TCP MySQL / MariaDB
5432 TCP PostgreSQL
6379 TCP Redis
123 UDP NTP (همگام‌سازی ساعت)

پورت‌های ۰ تا ۱۰۲۳ «پورت‌های ممتاز» هستند: در لینوکس فقط root (یا برنامه‌ای با اجازه‌ی ویژه) می‌تواند روی آن‌ها گوش بدهد؛ پورت‌های ۱۰۲۴ تا ۴۹۱۵۱ ثبت‌شده‌اند و بالاتر پورت‌های موقتِ کلاینت‌ها.

مثال ۲: ss -tulpn، چه کسی گوش می‌دهد؟

Section titled “مثال ۲: ss -tulpn، چه کسی گوش می‌دهد؟”

ss (socket statistics؛ جایگزین قدیمی netstat) سوکت‌ها را نشان می‌دهد. اولین حالت پرکاربردش را با ۵ فلگ می‌زنند: -t TCP، -u UDP، -l فقط سوکت‌های در حال گوش‌دادن، -p نام پروسه‌ی صاحب سوکت (برای دیدن پروسه‌های دیگران root لازم است)، -n عدد پورت به‌جای اسم (سریع‌تر و بدون DNS). اول وضع فعلی این سرور، بعد چند سرویس راه می‌اندازم تا چیز بیشتری برای دیدن باشد:

Terminal window
echo "--- قبل از راه‌اندازی سرویس‌ها:"
ss -tulpn
خروجی
--- قبل از راه‌اندازی سرویس‌ها:
Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("systemd",pid=1,fd=46))
tcp LISTEN 0 4096 [::]:22 [::]:* users:(("systemd",pid=1,fd=49))

فقط پورت ۲۲ باز است؛ ولی عجیب: صاحبش systemd است، نه sshd! در Ubuntu جدید، ssh با socket activation کار می‌کند: خود systemd (پروسه‌ی ۱) روی پورت ۲۲ گوش می‌دهد و فقط وقتی اولین اتصال برسد، sshd را بالا می‌آورد (درس بعد). حالا یک وب‌سرور واقعی (nginx)، دو وب‌سرور ساده‌ی پایتون (یکی فقط روی 127.0.0.1 و یکی روی همه‌ی آدرس‌ها) و یک سوکت UDP:

Terminal window
systemctl start nginx
python3 -m http.server 8098 --bind 127.0.0.1 > /dev/null 2>&1 &
python3 -m http.server 8099 --bind 0.0.0.0 > /dev/null 2>&1 &
python3 -c 'import socket, time; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(("0.0.0.0", 9999)); time.sleep(600)' &
sleep 1
ss -tulpn
خروجی
Netid State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
udp UNCONN 0 0 0.0.0.0:9999 0.0.0.0:* users:(("python3",pid=935,fd=3))
tcp LISTEN 0 5 127.0.0.1:8098 0.0.0.0:* users:(("python3",pid=933,fd=3))
tcp LISTEN 0 5 0.0.0.0:8099 0.0.0.0:* users:(("python3",pid=934,fd=3))
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("systemd",pid=1,fd=46))
tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=931,fd=5),("nginx",pid=930,fd=5),("nginx",pid=929,fd=5))
tcp LISTEN 0 4096 [::]:22 [::]:* users:(("systemd",pid=1,fd=49))
tcp LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=931,fd=6),("nginx",pid=930,fd=6),("nginx",pid=929,fd=6))

ستون‌ها:

ستون معنی
Netid پروتکل: tcp یا udp
State LISTEN (TCP در حال گوش‌دادن) یا UNCONN (سوکت UDP که منتظر بسته است)
Recv-Q / Send-Q صف‌های دریافت و ارسال (برای LISTEN: اندازه‌ی صف انتظار اتصال‌ها)
Local Address:Port روی کدام آدرس و کدام پورت گوش می‌دهد
Peer Address:Port طرف مقابل (برای LISTEN همیشه *)
Process users:(("نام",pid=...,fd=...)): چه پروسه‌ای

Local Address بسیار مهم است (مثال ۳): 0.0.0.0:80 یعنی روی همه‌ی آدرس‌های IPv4 این ماشین، [::]:80 یعنی همه‌ی آدرس‌های IPv6، و 127.0.0.1:8098 یعنی فقط از خود همین ماشین. برای nginx چند ردیف users هست چون پروسه‌ی اصلی و کارگرها (worker) همه همان سوکت را دارند. برای فیلتر یک پورت از زبان فیلتر ss استفاده کن:

Terminal window
echo "--- فقط پورت 80:"
ss -tlnp 'sport = :80'
echo "--- خلاصه‌ی کل سوکت‌ها (ss -s، چهار خط اول):"
ss -s | head -4
خروجی
--- فقط پورت 80:
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=931,fd=5),("nginx",pid=930,fd=5),("nginx",pid=929,fd=5))
LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=931,fd=6),("nginx",pid=930,fd=6),("nginx",pid=929,fd=6))
--- خلاصه‌ی کل سوکت‌ها (ss -s، چهار خط اول):
Total: 55
TCP: 68 (estab 0, closed 62, orphaned 0, timewait 0)
Transport Total IP IPv6

مثال ۳: گوش‌دادن روی 127.0.0.1 در برابر 0.0.0.0

Section titled “مثال ۳: گوش‌دادن روی 127.0.0.1 در برابر 0.0.0.0”

آدرسی که یک سرویس روی آن گوش می‌دهد، تعیین می‌کند چه کسی می‌تواند به آن برسد: 127.0.0.1 فقط خود ماشین؛ 0.0.0.0 همه‌ی آدرس‌های ماشین، یعنی از بیرون هم. دو وب‌سرور نمونه‌ی بالا دقیقاً همین را نشان می‌دهند. از آدرس شبکه‌ی همین ماشین (نه 127.0.0.1) به هر دو وصل می‌شوم:

Terminal window
ip=$(hostname -I | awk '{print $1}')
echo "آدرس شبکه‌ی این ماشین: $ip"
echo "--- از طریق 127.0.0.1 به هر دو:"
curl -s -o /dev/null -w '8098 (فقط localhost): HTTP %{http_code}\n' http://127.0.0.1:8098
curl -s -o /dev/null -w '8099 (همه‌ی آدرس‌ها): HTTP %{http_code}\n' http://127.0.0.1:8099
echo "--- از طریق آدرس شبکه:"
curl -s -m 3 -o /dev/null http://$ip:8098 && echo "8098: پاسخ آمد" || echo "8098: اتصال رد شد"
curl -s -m 3 -o /dev/null http://$ip:8099 && echo "8099: پاسخ آمد" || echo "8099: اتصال رد شد"
خروجی
آدرس شبکه‌ی این ماشین: 172.17.0.4
--- از طریق 127.0.0.1 به هر دو:
8098 (فقط localhost): HTTP 200
8099 (همه‌ی آدرس‌ها): HTTP 200
--- از طریق آدرس شبکه:
8098: اتصال رد شد
8099: پاسخ آمد

سرویسی که فقط به 127.0.0.1 bind شده، از بیرون غیرقابل‌دسترس است، حتی اگر فایروال باز باشد. این دقیقاً همان چیزی است که برای دیتابیس‌ها می‌خواهی (فقط برنامه‌ی روی همان ماشین به آن برسد) و برای وب‌سرور عمومی نمی‌خواهی. خطای رایج «سایت از بیرون باز نمی‌شود» اغلب همین 127.0.0.1 در تنظیمات است.

مثال ۴: curl -I، آزمایش یک سرویس HTTP

Section titled “مثال ۴: curl -I، آزمایش یک سرویس HTTP”

curl -I فقط سرآیندهای (headers) پاسخ HTTP را می‌گیرد (درخواست HEAD) و برای بررسی سلامت سریع یک وب‌سرور عالی است. (-s نوار پیشرفت را خاموش می‌کند.)

Terminal window
curl -sI http://localhost
echo "--- پورت بسته (نه nginx، نه هیچ برنامه‌ای روی 81):"
curl -sI http://localhost:81
echo "کد خروج curl: $?"
خروجی
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 03 Oct 2026 21:57:18 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Sat, 03 Oct 2026 12:33:24 GMT
Connection: keep-alive
ETag: "6ac0f614-267"
Accept-Ranges: bytes
--- پورت بسته (نه nginx، نه هیچ برنامه‌ای روی 81):
کد خروج curl: 7

خط اول HTTP/1.1 200 OK یعنی درخواست موفق بود (کدهای ۲xx موفق، ۳xx ریدایرکت، ۴xx خطای کلاینت مثل 404، ۵xx خطای سرور). Server: نرم‌افزار و Content-Type نوع محتوا است. برای پورتی که کسی گوش نمی‌دهد، curl چیزی چاپ نکرد و با کد خروج ۷ (Couldn’t connect) تمام شد. فقط کد وضعیت را هم می‌شود گرفت:

Terminal window
echo "کد HTTP: $(curl -s -o /dev/null -w '%{http_code}' http://localhost)"
echo "--- با -v مراحل اتصال TCP را هم ببین (چند خط اول):"
curl -sv -o /dev/null http://localhost 2>&1 | grep -E '^(\* Trying|\* Connected|> GET|< HTTP)'
خروجی
کد HTTP: 200
--- با -v مراحل اتصال TCP را هم ببین (چند خط اول):
* Trying [::1]:80...
* Connected to localhost (::1) port 80
> GET / HTTP/1.1
< HTTP/1.1 200 OK

مثال ۵: nc -zv، آیا این پورت باز است؟

Section titled “مثال ۵: nc -zv، آیا این پورت باز است؟”

nc (netcat) برای آزمایش اتصال TCP ساده‌ترین ابزار است. -z یعنی فقط وصل شو و بدون فرستادن داده ببند (اسکن)، -v یعنی گزارش بده، و -w 2 حداکثر دو ثانیه صبر کن. من 127.0.0.1 را صریح می‌نویسم تا IPv6 قاطی نشود (-4 هم همین است):

Terminal window
echo "--- پورت 22 (ssh، باز):"
nc -zv 127.0.0.1 22
echo "--- پورت 80 (nginx، باز):"
nc -zv 127.0.0.1 80
echo "--- پورت 81 (کسی گوش نمی‌دهد):"
nc -zv 127.0.0.1 81
echo "--- یک بازه (-z روی چند پورت، با -w 1):"
nc -zv -w 1 127.0.0.1 78-82 2>&1
خروجی
--- پورت 22 (ssh، باز):
Connection to 127.0.0.1 22 port [tcp/ssh] succeeded!
--- پورت 80 (nginx، باز):
Connection to 127.0.0.1 80 port [tcp/http] succeeded!
--- پورت 81 (کسی گوش نمی‌دهد):
nc: connect to 127.0.0.1 port 81 (tcp) failed: Connection refused
--- یک بازه (-z روی چند پورت، با -w 1):
nc: connect to 127.0.0.1 port 78 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 79 (tcp) failed: Connection refused
Connection to 127.0.0.1 80 port [tcp/http] succeeded!
nc: connect to 127.0.0.1 port 81 (tcp) failed: Connection refused
nc: connect to 127.0.0.1 port 82 (tcp) failed: Connection refused

دو خروجی ممکن: succeeded! یعنی اتصال TCP برقرار شد (یک برنامه آنجا گوش می‌دهد) و Connection refused یعنی ماشین جواب داد ولی هیچ برنامه‌ای روی آن پورت گوش نمی‌دهد. و nc -zv روی ماشین دیگر هم همین‌طور کار می‌کند (nc -zv example.com 443)؛ تنها ابزار تو برای جواب «از این ماشین به آن پورت راه دارم؟».

⚡ بررسی سریع

nc -zv host 80 نوشت «Connection refused». یعنی…

مثال ۶: «refused» یا «timed out»؟

Section titled “مثال ۶: «refused» یا «timed out»؟”

وقتی اتصال نمی‌گیرد، متن خطا علت را نشان می‌دهد: Connection refused یعنی ماشین رسید و گفت «اینجا کسی نیست» (پورت بسته)؛ ولی timed out یعنی هیچ جوابی نیامد: بسته‌ها در راه یا در خود ماشین دور ریخته می‌شوند (فایروال، مسیر نادرست). با یک قانون فایروال (iptables، درس فایروال) روی پورت ۸۰۹۹ می‌بینیم:

Terminal window
echo "--- پورت 8099 بدون فایروال:"
nc -zv -w 2 127.0.0.1 8099
iptables -A INPUT -p tcp --dport 8099 -j DROP
echo "--- بعد از قانون DROP (بسته‌ها بی‌صدا دور ریخته می‌شوند):"
nc -zv -w 2 127.0.0.1 8099
iptables -D INPUT -p tcp --dport 8099 -j DROP
echo "--- بعد از برداشتن قانون:"
nc -zv -w 2 127.0.0.1 8099
echo "--- و پورتی که اصلاً برنامه‌ای ندارد (8097):"
nc -zv -w 2 127.0.0.1 8097
خروجی
--- پورت 8099 بدون فایروال:
Connection to 127.0.0.1 8099 port [tcp/*] succeeded!
--- بعد از قانون DROP (بسته‌ها بی‌صدا دور ریخته می‌شوند):
nc: connect to 127.0.0.1 port 8099 (tcp) timed out: Operation now in progress
--- بعد از برداشتن قانون:
Connection to 127.0.0.1 8099 port [tcp/*] succeeded!
--- و پورتی که اصلاً برنامه‌ای ندارد (8097):
nc: connect to 127.0.0.1 port 8097 (tcp) failed: Connection refused
نتیجه معنی معمولاً کجا را بگرد
succeeded سرویس گوش می‌دهد و مسیر باز است
Connection refused ماشین رسید، پورت بسته (برنامه‌ای گوش نمی‌دهد) سرویس بالا نیست؟ پورت دیگری است؟
timed out هیچ جوابی نیامد فایروال (روی سرور یا بین راه)، مسیر، ماشین خاموش
No route to host / Network is unreachable مسیر پیدا نشد شبکه، gateway

مثال ۷: چه کسی پورت را گرفته؟ (تمرین اصلی)

Section titled “مثال ۷: چه کسی پورت را گرفته؟ (تمرین اصلی)”

وقتی Address already in use می‌بینی، باید صاحب پورت را پیدا کنی. سه ابزار: ss -tlnp، lsof -i و fuser. همین الآن پورت ۲۲ را بپرس:

Terminal window
echo "--- ss:"
ss -tlnp 'sport = :22'
echo "--- lsof -i :22:"
lsof -i :22
echo "--- fuser (فقط PID ها):"
fuser 22/tcp
خروجی
--- ss:
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=965,fd=3),("systemd",pid=1,fd=46))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=965,fd=4),("systemd",pid=1,fd=49))
--- lsof -i :22:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd 1 root 46u IPv4 3265877 0t0 TCP *:ssh (LISTEN)
systemd 1 root 49u IPv6 3264996 0t0 TCP *:ssh (LISTEN)
sshd 965 root 3u IPv4 3265877 0t0 TCP *:ssh (LISTEN)
sshd 965 root 4u IPv6 3264996 0t0 TCP *:ssh (LISTEN)
--- fuser (فقط PID ها):
22/tcp: 1 965

می‌بینی چه کسی پورت ۲۲ را گرفته: systemd (PID 1) به‌خاطر socket activation. بیا یک اتصال بزنیم تا sshd هم بیاید و دوباره بپرسیم:

Terminal window
nc -z 127.0.0.1 22
sleep 1
ss -tlnp 'sport = :22'
echo "--- sshd حالا هم کار می‌کند:"
systemctl is-active ssh.service
ps -eo pid,comm | grep -w sshd | head -2
خروجی
Connection to 127.0.0.1 22 port [tcp/ssh] succeeded!
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=965,fd=3),("systemd",pid=1,fd=46))
LISTEN 0 4096 [::]:22 [::]:* users:(("sshd",pid=965,fd=4),("systemd",pid=1,fd=49))
--- sshd حالا هم کار می‌کند:
active
965 sshd

بعد از اولین اتصال، sshd راه افتاد و همان سوکت را از systemd گرفت؛ حالا هر دو در ستون Process هستند. حالا خطای معروف را بسازیم: دوباره روی پورتی که پر است گوش بده:

Terminal window
python3 -m http.server 8099 2>&1 | tail -2
echo "--- پیدا کردن صاحب پورت:"
ss -tlnp 'sport = :8099'
خروجی
self.socket.bind(self.server_address)
OSError: [Errno 98] Address already in use
--- پیدا کردن صاحب پورت:
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 0.0.0.0:8099 0.0.0.0:* users:(("python3",pid=934,fd=3))

OSError: [Errno 98] Address already in use: همین خطا را nginx هم می‌دهد (bind() ... failed (98: Address already in use)). با ss -tlnp صاحب پورت (اینجا وب‌سرور پایتون دیگر ما با PID مشخص) را پیدا می‌کنی؛ بعد یا او را متوقف می‌کنی (kill PID)، یا سرویس خودت را روی پورت دیگری می‌گذاری.

مثال ۸: اتصال‌های برقرارشده

Section titled “مثال ۸: اتصال‌های برقرارشده”

ss -tln فقط گوش‌دهنده‌ها را نشان می‌داد. اتصال‌های در جریان (ESTAB) را بدون -l می‌بینی. با nc یک اتصال می‌سازم و چند ثانیه باز نگه می‌دارم:

Terminal window
nc -l 9100 > /dev/null &
server=$!
sleep 1
( sleep 4 | nc -N 127.0.0.1 9100 ) &
client=$!
sleep 1
echo "--- اتصال‌های ESTABLISHED روی پورت 9100 (هر اتصال دو سر دارد):"
ss -tn state established '( sport = :9100 or dport = :9100 )'
wait $server $client
خروجی
--- اتصال‌های ESTABLISHED روی پورت 9100 (هر اتصال دو سر دارد):
Recv-Q Send-Q Local Address:Port Peer Address:Port Process
0 0 127.0.0.1:43044 127.0.0.1:9100
0 0 127.0.0.1:9100 127.0.0.1:43044

هر اتصال دو ردیف دارد (یکی از دید سرور، یکی از دید کلاینت): 127.0.0.1:9100 ↔ 127.0.0.1:پورت-موقت. پورت کلاینت (عددی مثل ۴۰۰۰۰) را سیستم تصادفی از بازه‌ی پورت‌های موقت می‌دهد. وضعیت‌های مهم TCP:

وضعیت معنی
LISTEN سرور منتظر اتصال است
ESTAB اتصال برقرار و فعال
SYN-SENT کلاینت درخواست اتصال فرستاده و منتظر جواب است (اگر ماندگار شود: سرور/فایروال جواب نمی‌دهد)
TIME-WAIT اتصال بسته شده و سیستم چند ثانیه آن را نگه می‌دارد (عادی)
CLOSE-WAIT طرف مقابل بسته ولی برنامه‌ی ما هنوز سوکت را نبسته (اگر زیاد شد: باگ برنامه)

برنامه‌ها از طریق سوکت (socket) با شبکه حرف می‌زنند: یک «پریز» داخل هسته که آدرس و پورتی دارد. سرور یک سوکت می‌سازد، با bind() آن را به یک آدرس و پورت می‌بندد، با listen() شروع به گوش‌دادن می‌کند و با accept() اتصال‌ها را می‌پذیرد. کلاینت با connect() وصل می‌شود. پیام‌های خطای این درس همان‌ها هستند: Address already in use از bind()، و Connection refused از connect(). ss اطلاعاتش را از هسته می‌خواند (فایل‌های /proc/net/tcp و مشابه):

Terminal window
echo "--- بازه‌ی پورت‌های موقت کلاینت:"
cat /proc/sys/net/ipv4/ip_local_port_range
echo "--- اولین پورتی که کاربر عادی می‌تواند bind کند:"
sysctl net.ipv4.ip_unprivileged_port_start
echo "--- سه خط اول /proc/net/tcp (سوکت‌های TCP خام؛ پورت‌ها هگزادسیمال‌اند):"
head -3 /proc/net/tcp | cut -c1-90
خروجی
--- بازه‌ی پورت‌های موقت کلاینت:
32768 60999
--- اولین پورتی که کاربر عادی می‌تواند bind کند:
net.ipv4.ip_unprivileged_port_start = 1024
--- سه خط اول /proc/net/tcp (سوکت‌های TCP خام؛ پورت‌ها هگزادسیمال‌اند):
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout
0: 0100007F:1FA2 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0
1: 00000000:1FA3 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0

در فایل خام، 0100007F:1FA2 یعنی 127.0.0.1:8098 (آدرس به‌صورت هگزا و برعکس، 1FA2 هگزای ۸۱۹۴ است؛ پورت واقعی همان است که ss برایت ترجمه می‌کند). و چون «پورت‌های زیر ۱۰۲۴ ممتازند»، کاربر عادی روی آن‌ها گوش نمی‌دهد:

Terminal window
su - ali -c 'python3 -m http.server 81 2>&1 | tail -1'
echo "--- کاربر عادی با ss -p، نام پروسه‌ی سوکت‌های root را نمی‌بیند:"
su - ali -c 'ss -tlnp | head -4'
خروجی
PermissionError: [Errno 13] Permission denied
--- کاربر عادی با ss -p، نام پروسه‌ی سوکت‌های root را نمی‌بیند:
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 127.0.0.1:8098 0.0.0.0:*
LISTEN 0 5 0.0.0.0:8099 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*

PermissionError: Permission denied: برای همین سرویس‌هایی مثل nginx ابتدا (به‌عنوان root) به پورت ۸۰ گوش می‌دهند و بعد دسترسی کارگرها را پایین می‌آورند. و ستون Process برای ali خالی است: ss -p برای دیدن پروسه‌های دیگران sudo لازم دارد.

دستور کار
ss -tulpn همه‌ی گوش‌دهنده‌ها (TCP و UDP) با پروسه، عددی
ss -tlnp 'sport = :22' فقط یک پورت
ss -tn / ss -tn state established اتصال‌های برقرار
ss -s خلاصه‌ی آمار
lsof -i :80 چه کسی پورت ۸۰ را دارد
fuser 80/tcp PID های پورت
curl -I آدرس / curl -sI آدرس فقط سرآیندهای HTTP
curl -s -o /dev/null -w '%{http_code}' آدرس فقط کد وضعیت
nc -zv host 22 آزمایش پورت TCP
nc -zv -w 2 host 20-25 آزمایش بازه‌ی پورت‌ها
getent services 22/tcp اسم سرویس یک پورت
فلگ ss معنی
-t / -u TCP / UDP
-l فقط در حال گوش‌دادن
-p نام پروسه (دیدن پروسه‌های دیگران: sudo)
-n عددی (بدون DNS و بدون اسم سرویس)
-a همه (شامل گوش‌دهنده‌ها و برقرارشده‌ها)

۱) سرویس فقط روی 127.0.0.1 گوش می‌دهد

Section titled “۱) سرویس فقط روی 127.0.0.1 گوش می‌دهد”

مثال ۳. علامتش: از خود سرور کار می‌کند، از بیرون نه، و ss -tln آدرس 127.0.0.1:پورت را نشان می‌دهد. راه‌حل: در تنظیمات سرویس آدرس 0.0.0.0 (یا IP مشخص) را بگذار؛ برای دیتابیس‌ها برعکس: عمداً روی 127.0.0.1 بماند.

مثال ۷: دو برنامه یک پورت را نمی‌گیرند. راه‌حل: ss -tlnp 'sport = :پورت' برای پیدا کردن صاحب، بعد متوقف کردنش یا عوض کردن پورت.

۳) فایروال را با «پورت بسته» اشتباه گرفتن

Section titled “۳) فایروال را با «پورت بسته» اشتباه گرفتن”

مثال ۶: refused (برنامه نیست) در برابر timed out (بسته‌ها دور ریخته می‌شوند). راه‌حل: اول از روی خود سرور با nc -zv 127.0.0.1 پورت بزن؛ اگر آنجا succeeded شد ولی از بیرون timed out، فایروال یا مسیر است؛ اگر آنجا هم refused بود، سرویس را بررسی کن.

Terminal window
su - ali -c 'ss -tlnp | cat' | head -3
خروجی
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 5 127.0.0.1:8098 0.0.0.0:*
LISTEN 0 5 0.0.0.0:8099 0.0.0.0:*

برای سوکت‌های دیگران ستون Process خالی می‌ماند. راه‌حل: sudo ss -tulpn.

۵) گوش‌دادن روی پورت زیر ۱۰۲۴ با کاربر عادی

Section titled “۵) گوش‌دادن روی پورت زیر ۱۰۲۴ با کاربر عادی”

مثال «پشت پرده»: Permission denied. راه‌حل: پورت بالاتر (مثل ۸۰۸۰) با یک reverse proxy روی ۸۰ و ۴۴۳، یا اجرا به‌عنوان root برای bind اولیه (روش nginx).

۶) UDP را با nc -z آزمایش‌کردن

Section titled “۶) UDP را با nc -z آزمایش‌کردن”

UDP اتصال ندارد، پس «succeeded» برای UDP فقط یعنی «خطایی برنگشت»، نه «برنامه‌ای آنجاست». برای UDP بهتر است ابزار خود آن سرویس (مثل dig @سرور برای DNS) را آزمایش کنی.

✎ تمرینآسان

بگو کدام پروسه روی پورت ۸۰ گوش می‌دهد (با ss). (nginx در مثال ۲ راه افتاد.)

دیدن جواب
Terminal window
ss -tlnp 'sport = :80' | awk 'NR>1 {print $4, $6}' | cut -c1-70
خروجی
0.0.0.0:80 users:(("nginx",pid=931,fd=5),("nginx",pid=930,fd=5),("ngin
[::]:80 users:(("nginx",pid=931,fd=6),("nginx",pid=930,fd=6),("nginx",
✎ تمرینمتوسط

تمرین اصلی: پیدا کن کدام پروسه پورت ۲۲ را گرفته است. با دو ابزار (ss -tlnp و lsof -i) صاحب پورت ۲۲ را پیدا کن و بگو چرا اسم آن sshd نیست (راهنمایی: مثال ۲ و ۷).

دیدن جواب
Terminal window
ss -tlnp 'sport = :22' | awk 'NR>1 {print $4, $6}'
lsof -i :22 | awk 'NR>1 {print $1, $2, $9}'
خروجی
0.0.0.0:22 users:(("sshd",pid=965,fd=3),("systemd",pid=1,fd=46))
[::]:22 users:(("sshd",pid=965,fd=4),("systemd",pid=1,fd=49))
systemd 1 *:ssh
systemd 1 *:ssh
sshd 965 *:ssh
sshd 965 *:ssh

پورت ۲۲ را systemd (PID 1) نگه می‌دارد و (چون در مثال ۷ یک اتصال زدیم) sshd هم همان سوکت را دارد. سرویس ssh با socket activation کار می‌کند: systemd (از طریق ssh.socket) پورت را از ابتدا نگه می‌دارد و فقط با اولین اتصال sshd را بالا می‌آورد و همان سوکت را به او هم می‌دهد؛ برای همین قبل از اولین اتصال فقط systemd را می‌بینی.

✎ تمرینسخت

یک «پورت‌چکر» کوچک بنویس: برای فهرستی از پورت‌ها (22 80 443 8099 9999) روی 127.0.0.1 با nc -z -w 1 بررسی کن باز است یا بسته، و اسم سرویس آن را با getent services چاپ کن (برای پورت‌هایی که اسم ندارند «نامشخص»).

دیدن جواب
Terminal window
for p in 22 80 443 8099 9999; do
name=$(getent services $p/tcp | awk '{print $1}')
if nc -z -w 1 127.0.0.1 $p 2>/dev/null; then st="باز"; else st="بسته"; fi
echo "پورت $p (${name:-نامشخص}): $st"
done
خروجی
پورت 22 (ssh): باز
پورت 80 (http): باز
پورت 443 (https): بسته
پورت 8099 (نامشخص): باز
پورت 9999 (نامشخص): بسته

(پورت ۹۹۹۹ در این ماشین فقط UDP است؛ برای TCP بسته نشان داده می‌شود.)

؟ آزمونک
  1. تفاوت اصلی TCP و UDP؟

  2. ss -tulpn چه نشان می‌دهد؟

  3. سرویسی روی 127.0.0.1:5432 گوش می‌دهد. از ماشین دیگر می‌شود به آن وصل شد؟

  4. nc -zv host 80 نتیجه می‌دهد «Connection refused». یعنی…

  5. برنامه‌ات خطای «Address already in use» می‌دهد. اول چه می‌زنی؟

  6. curl -I http://localhost چه می‌کند؟

  • پورت عدد ۱۶ بیتی که مشخص می‌کند کدام برنامه؛ معروف‌ها: 22 ssh، 80 http، 443 https، 53 dns، 5432 postgres. هر اتصال = IP و پورت دو طرف. TCP قابل‌اطمینان، UDP سریع.
  • ss -tulpn: چه کسی روی چه پورتی و چه آدرسی گوش می‌دهد؛ ss -tlnp 'sport = :22' برای یک پورت؛ lsof -i :پورت و fuser. دیدن پروسه‌ها به sudo نیاز دارد.
  • آدرس گوش‌دادن: 127.0.0.1 فقط خود ماشین، 0.0.0.0 (و [::]) همه‌ی آدرس‌ها.
  • آزمایش اتصال: curl -I برای HTTP، nc -zv host پورت برای هر پورت TCP.
  • refused = ماشین زنده، پورت بسته؛ timed out = جوابی نیامد (فایروال/مسیر).
  • Address already in use: صاحب پورت را با ss پیدا کن. پورت‌های زیر ۱۰۲۴ برای کاربر عادی Permission denied می‌دهند. ssh در Ubuntu جدید با socket activation است.
برگه‌ی تقلب این درس
دستورکاری که می‌کند
ss -tulpnهمه‌ی گوش‌دهنده‌ها با پروسه (sudo برای همه)
ss -tlnp 'sport = :22'چه کسی پورت ۲۲ را دارد
ss -tn state establishedاتصال‌های برقرار
lsof -i :80 / fuser 80/tcpصاحب پورت
curl -sI http://hostسرآیندهای HTTP
curl -s -o /dev/null -w '%{http_code}' URLفقط کد وضعیت
nc -zv -w 2 host 22آیا پورت TCP باز است؟
nc -zv -w 1 host 20-25یک بازه
getent services 443/tcpاسم سرویس یک پورت
cat /etc/servicesفهرست پورت‌های معروف