רשתות ואינטרנט
איך HTTP עובד: בקשות, תשובות, כותרות וקודי סטטוס
קריאה של חילופי HTTP אמיתיים שורה אחר שורה: מתודות, כתובות URL, כותרות, מחלקות של קודי סטטוס ועוגיות, עם בדיקות curl מול שרת מקומי.
פורט הוא דלת ממוספרת במכונה שלך. איך TCP ו-UDP משתמשים בפורטים, ואיך מפעילים, מוצאים ובודקים שרת מקומי על 127.0.0.1.
פורט הוא מספר שאומר למערכת ההפעלה איזו תוכנה צריכה לקבל פיסת תעבורת רשת. למכונה שלך יש כתובת IP, והיא יכולה להריץ עשרות תוכנות רשת בו־זמנית: דפדפן שמביא דפים, תוכנת דואר, מסד נתונים, שרת ווב מקומי. כתובת ה-IP מביאה את התעבורה למחשב הנכון. הפורט מביא אותה לתוכנה הנכונה.
המאמר מה DNS עושה מסביר איך שם הופך לכתובת. כאן נראה מאיפה באים פורטים, איך שני פרוטוקולי התעבורה העיקריים משתמשים בהם, ואיך רואים על אילו פורטים המכונה שלך מאזינה. המעבדה רצה על 127.0.0.1, כתובת ה-loopback שתמיד פירושה "המכונה הזו", כך ששום דבר לא יוצא מהמחשב שלך.
פורט הוא מספר של 16 ביט, כלומר טווח מ-0 עד 65535. גם בכותרת TCP וגם בכותרת UDP יש פורט מקור ופורט יעד. כשחבילה מגיעה למחשב, מערכת ההפעלה קוראת את פורט היעד ומעבירה את הנתונים לתוכנה שביקשה לקבל במספר הזה.
דימוי טוב הוא בניין דירות. כתובת הרחוב מביאה את המשלוח לבניין. מספר הדירה אומר לשליח באיזו דלת לדפוק. בתמונה הזו כתובת ה-IP היא הבניין והפורט הוא הדירה. מישהו קובע איזו דירה היא איזו, ודייר יכול לעזוב ולתת למישהו אחר לקחת את המספר.
פורט מאזין הוא פורט שתוכנה אמרה עליו למערכת ההפעלה, למעשה, "את החיבורים למספר הזה לשלוח אליי". תוכנה במצב הזה מאזינה. כשהתוכנה נסגרת, מערכת ההפעלה משחררת את הפורט, ואף אחד כבר לא עונה בו.
כל שירות שרץ אצלך פותח לפחות פורט אחד. זה כולל תוכנות שלא הופעלו בכוונה: מעדכנים, לקוחות סנכרון, שרתי פיתוח, תוספים ותוכנות שמתקינות את עצמן כשירות רקע. רשימת הפורטים המאזינים היא התמונה הפשוטה והמדויקת ביותר של מה שהמכונה שלך מציעה לרשת.
בסקירה של מארח בבעלותך, זה הדבר הראשון שכדאי לרשום. לפני שבודקים אם שירות מעודכן או מוגדר היטב, צריך לדעת שהוא קיים. עבודת אבטחה מתחילה במצאי, והמצאי מתחיל בדלתות.
TCP/IP הוא אוסף הפרוטוקולים שהאינטרנט רץ עליו. הוא בנוי משכבות, ועדיין לא צריך את כולן. כאן רלוונטיות ארבע שכבות:
כל שכבה עוטפת את הנתונים של השכבה שמעליה בכותרת משלה. כותרת ה-IP נושאת את כתובות המקור והיעד. כותרת ה-TCP או ה-UDP נושאת את פורטי המקור והיעד. נתוני האפליקציה יושבים בפנים. כשהחבילה מגיעה, כל שכבה מסירה את הכותרת שלה ומעבירה את השאר למעלה.
הפורט שייך לשכבת התעבורה. IP לא יודע איזו תוכנה צריכה לקבל את הנתונים. זה התפקיד של שכבת התעבורה, ובזכות זה כתובת IP אחת יכולה להגיש דף ווב בפורט 443 ומסד נתונים בפורט 5432 בו־זמנית.
TCP, שמוגדר ב-RFC 9293, נותן לתוכנה זרם בתים אמין ומסודר. לפני שנתוני אפליקציה עוברים, שני הצדדים מסכימים על מספרי רצף התחלתיים ומוודאים שכל צד יכול לשלוח ולקבל. ההסכמה הזו היא לחיצת היד המשולשת.
כשאף תוכנה לא מאזינה בפורט, מכונת היעד לא שולחת SYN-ACK. היא שולחת מקטע RST (reset), והלקוח מוותר מיד עם הודעה כמו "connection refused". התשובה המהירה הזו שימושית. היא מלמדת שהמארח קיבל את החבילה ושאף תוכנה לא מקבלת חיבורים בפורט הזה.
המתנה שקטה שנגמרת ב-timeout אומרת משהו אחר. הרבה פעמים חומת אש זרקה את החבילות בלי לענות. לכן "refused" ו-"timed out" הם שני רמזים שונים, וכדאי ללמוד לקרוא אותם.
חיבורים על 127.0.0.1 משתמשים באותה לוגיקת TCP, למרות שהחבילות אף פעם לא מגיעות לכרטיס רשת. מערכת ההפעלה מריצה את לחיצת היד בתוכנה, ולכן כתובת ה-loopback היא מקום בטוח לתרגול.
UDP, שמוגדר ב-RFC 768, פשוט בהרבה. הכותרת שלו היא 8 בתים: פורט מקור, פורט יעד, אורך ו-checksum. אין לחיצת יד, אין אישורים, אין שליחה חוזרת ואין סדר. השולח משדר datagram, והרשת מעבירה אותו או שלא. המקבל לא מאשר כלום, אלא אם האפליקציה בונה את הלוגיקה הזו בעצמה.
לפשטות הזו יש שימושים. שאילתות DNS עוברות בדרך כלל ב-UDP בפורט 53, כי שאלה קצרה ותשובה קצרה נכנסות בחילוף אחד, ולחיצת יד הייתה מוסיפה סבבים. קול ווידאו בזמן אמת משתמשים הרבה פעמים ב-UDP, וכך גם QUIC, התעבורה שמתחת ל-HTTP/3. כל אחד מהפרוטוקולים האלה מביא כללים משלו לאובדן חבילות ולניסיונות חוזרים.
פורטים עובדים באותו אופן ב-UDP. פורט היעד בוחר את התוכנה. ההבדל הוא שלפורט UDP אין מצב חיבור. כל תוכנה שקשורה לפורט מקבלת את מה שמגיע, ממי שלא ישלח. מאזין TCP רואה רק חיבורים שהשלימו את לחיצת היד. שניהם עדיין עונים על אותה שאלה: איזו תוכנה מקבלת את החבילה.
Internet Assigned Numbers Authority (IANA) מתחזק את Service Name and Transport Protocol Port Number Registry. המרשם מתעד איזה שירות מקושר בדרך כלל לכל מספר פורט ב-TCP, ב-UDP ובעוד כמה פרוטוקולי תעבורה. רשומות מוכרות הן 22 עבור ssh, 53 עבור domain (DNS), 80 עבור http ו-443 עבור https.
RFC 6335 מחלק את מרחב המספרים לשלושה טווחים:
המרשם מתעד הקצאות ומוסכמות. שום דבר ב-TCP או ב-UDP לא אוכף אותן. כל תוכנה עם הרשאה יכולה להיקשר כמעט לכל פורט פנוי. המרשם אומר מה רץ בדרך כלל על מספר, והוא יכול לטעות לגבי המכונה שלך. לפני שמסיקים מסקנה, צריך להסתכל על מה שבאמת מאזין.
צריך טרמינל ו-Python 3. המעבדה משתמשת בשרת ה-HTTP שמובנה ב-Python, כך שאין צורך להתקין שום דבר נוסף.
את פקודות Windows שלמטה הרצתי על Windows 11 Pro עם Git Bash, Python 3.12.10 ו-curl 8.12.1. את פקודות Linux ו-macOS לא הרצתי במכונה הזו, ולכן הפלט שלהן לא מוצג. כל בלוק פלט שמופיע הגיע מהפקודה שמעליו.
הערה על Python: בסשן Git Bash שלי הפקודה python3 לא נמצאה, ואילו הפקודה python הריצה Python 3.12. אם ב-Windows הפקודה python3 נכשלת, אפשר להשתמש בפקודה python. שאר הפקודה נשאר אותו דבר.
השרת מפרסם את כל מה שנמצא בתיקייה שממנה הוא הופעל, ולכן כדאי לשים שם קודם משהו קטן.
mkdir -p ~/port-lab && cd ~/port-lab
printf '<!doctype html><title>lab</title><h1>Port 8000 lab</h1>\n' > index.htmlב-PowerShell, ליצור את אותה תיקייה ולכתוב את שתי השורות האלה בקובץ index.html בכל עורך.
צריך להשאיר את הטרמינל הזה פתוח. השרת רץ עד שעוצרים אותו.
cd ~/port-lab
python3 -m http.server 8000 --bind 127.0.0.1הדגל --bind 127.0.0.1 אומר ל-Python להאזין רק בממשק ה-loopback. בלעדיו, Python מאזין בכל הממשקים של המכונה. ההבדל הזה חשוב בהמשך, כשקוראים את רשימת המאזינים.
לבחור את הפקודה למערכת ההפעלה שלך. כל אחת מציגה את ה-sockets במצב LISTEN שמשתמשים בפורט 8000.
Linux:
ss -tlnp | grep 8000macOS:
lsof -iTCP -sTCP:LISTEN -n -P | grep 8000Windows PowerShell:
Get-NetTCPConnection -State Listen -LocalPort 8000זה הפלט שקיבלתי מפקודת ה-Windows במכונה שלי, דרך Format-Table:
LocalAddress LocalPort State OwningProcess
------------ --------- ----- -------------
127.0.0.1 8000 Listen 45608מזהה התהליך יהיה שונה במכונה שלך. כדי לוודא שהבעלים הוא תהליך ה-Python שהופעל, צריך להריץ Get-Process -Id <OwningProcess> עם המספר שלך. בהרצה שלי זה היה python.
על מה להסתכל:
LocalAddress הוא 127.0.0.1. ה-socket מקבל חיבורים רק מהמכונה הזו. בלי הדגל --bind, הציפייה היא לראות כאן 0.0.0.0. לא בדקתי את הווריאנט הזה במכונה הזו, אז זו התוצאה הצפויה ולא תוצאה שנצפתה.State הוא Listen. ה-socket ממתין לחיבורים.OwningProcess מצביע על התוכנה הצפויה. אם הוא מצביע על משהו לא מוכר, צריך לעצור ולברר מה זה לפני שממשיכים.לפתוח טרמינל שני ולהריץ:
curl -v http://127.0.0.1:8000/זה הפלט מההרצה שלי, מקוצר לשורות הרלוונטיות:
* Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000
> GET / HTTP/1.1
> Host: 127.0.0.1:8000
< HTTP/1.0 200 OK
< Server: SimpleHTTP/0.6 Python/3.12.10
< Content-type: text/html
<!doctype html><title>lab</title><h1>Port 8000 lab</h1>קוראים אותו לפי הסדר. Trying אומר ש-curl פותח חיבור TCP. הכתובת כבר IP, ולכן אין שאילתת DNS. Connected אומר שלחיצת היד המשולשת הסתיימה. GET / HTTP/1.1 הוא שכבת האפליקציה: עכשיו curl מדבר HTTP על החיבור הזה. 200 OK אומר שהשרת מצא את הקובץ וענה. שורת ה-HTML בתחתית היא הקובץ שלך.
השרת כותב שורה על הבקשה בטרמינל שלו. אצלי הופיע:
127.0.0.1 - - [08/Nov/2026 06:07:56] "GET / HTTP/1.1" 200 -כתובת המקור היא אותה כתובת loopback, ולכן הבקשה הגיעה מהמכונה הזו.
לחזור לטרמינל של השרת וללחוץ Ctrl+C. זו הדרך הנקייה לעצור אותו. במכונה שלי עצרתי את התהליך עם Stop-Process -Force, שמסיים אותו בלי לתת לו הזדמנות להיסגר בצורה נקייה. בכל מקרה, ה-socket המאזין נעלם כשהתהליך מסתיים.
לוודא שה-socket נעלם:
Get-NetTCPConnection -State Listen -LocalPort 8000הפלט שלי היה ריק. אחר כך להריץ שוב את אותה פקודת curl:
curl -v http://127.0.0.1:8000/זה הפלט מההרצה שלי:
* Failed to connect to 127.0.0.1 port 8000 after 2048 ms: Could not connect to server
curl: (7) Failed to connect to 127.0.0.1 port 8000 after 2048 ms: Could not connect to serverקוד היציאה של curl היה 7, הקוד שפירושו שלא הצליח להתחבר.
מה קרה: ה-SYN הגיע אל 127.0.0.1 בפורט 8000. אף תוכנה לא האזינה שם, ולכן מערכת ההפעלה ענתה ב-reset. curl דיווח על הסירוב מיד, בלי לחכות ל-timeout. אם במקום זה יש המתנה ארוכה שאחריה timeout, החשד הוא חומת אש או נתיב שזורק חבילות.
אותה כתובת ואותו פורט התנהגו בשתי דרכים. עם מאזין, לחיצת היד הושלמה והשרת ענה. בלעדיו, הסירוב חזר תוך מילישניות. רשימת המאזינים היא מה שמבדיל בין שני המקרים. כשחיבור מפתיע, בודקים קודם את הרשימה.
קשירה לכל הממשקים בטעות. הרבה שרתים שמרימים על הדרך מאזינים כברירת מחדל על כל הכתובות. במחשב נייד שמחובר לרשת של בית קפה, זה יכול לחשוף שרת פיתוח למכשירים אחרים באותה רשת. כשצריך רק גישה מקומית, כדאי להגדיר כתובת קשירה מפורשת.
התייחסות למספר פורט כהוכחת זהות. פורט 80 בדרך כלל אומר HTTP, אבל שום דבר לא מכריח תוכנה לדבר בו HTTP. צריך להתחבר ולקרוא מה השירות שולח, או לבדוק איזה תהליך מחזיק ב-socket.
שכחה של UDP. הפקודה ss -tlnp מציגה רק מאזיני TCP. בשביל UDP יש ss -ulnp, או Get-NetUDPEndpoint ב-Windows. למכונה יכול להיות שירות UDP עמוס שאף פעם לא מופיע בבדיקת TCP.
קריאת timeout בתור "אין שירות". timeout אומר הרבה פעמים שיש מסנן בדרך אל הפורט. סירוב אומר שהמארח ענה ואף תוכנה לא מאזינה. כל אחד מהם מוביל לצעד אחר.
הריגת התהליך הלא נכון. לפני שעוצרים תהליך, צריך לוודא את השם שלו ואת שורת הפקודה שלו. שרת שנשאר מניסיון קודם יכול עדיין להחזיק בפורט ולענות עם קבצים מתיקייה שלא התכוונו לפרסם. צריך לבדוק מי הבעלים לפני שמניחים שהפורט שלך.
פורטים מאזינים מתארים מה המכונה שלך מציעה לרשת שלה, בכוונה או שלא. לפני שבודקים משהו אחר במארח בבעלותך, כדאי לרשום מה מאזין, להתאים כל רשומה לתוכנה מוכרת ולסגור את מה שלא צריך. שרת פיתוח שנשכח פועל הוא דוגמה טובה לרשומה שנראית תמימה ועדיין עונה.
אפשר להשתמש באותן פקודות על מכונות שאינן בשליטתך, אבל סריקה או בדיקה של מערכת שאינה בבעלותך דורשת הרשאה בכתב מהבעלים שלה. התרגול צריך להישאר על המכונות שלך ובמעבדה שלך.
לחזור על התרגיל עם UDP. בהרצה שלי, מאזין Python קצר על 127.0.0.1 בפורט 5399 קיבל datagram משולח. המאזין לא עשה שום דבר מיוחד:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("127.0.0.1", 5399))
data, addr = s.recvfrom(1024)
print("got", data, "from", addr)להריץ אותו בטרמינל אחד, ואז לשלוח datagram מטרמינל אחר עם socket.sendto. הפלט שקיבלתי היה got b'hello' from ('127.0.0.1', 63411). לא הייתה שום לחיצת יד, החבילה פשוט הגיעה. פורט 5353 כבר היה בשימוש במכונה שלי, וקשירה אליו נכשלה בשגיאת הרשאות, ולכן בחרתי 5399 לבדיקה.
אחר כך לקרוא את החלק על הקמת חיבור ב-RFC 9293 ולהשוות אותו לזרימת החבילות שלמעלה. ואז להמשיך אל איך HTTP עובד, שמסביר איך HTTP רץ מעל TCP ומה יש בבקשה ובתשובה.
RFC 9293, Transmission Control Protocol (TCP), IETF, אוגוסט 2022. https://www.rfc-editor.org/rfc/rfc9293
RFC 768, User Datagram Protocol, IETF, אוגוסט 1980. https://www.rfc-editor.org/rfc/rfc768
Service Name and Transport Protocol Port Number Registry, IANA. https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
RFC 6335, Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry, IETF, אוגוסט 2011. https://www.rfc-editor.org/rfc/rfc6335
רשתות ואינטרנט
קריאה של חילופי HTTP אמיתיים שורה אחר שורה: מתודות, כתובות URL, כותרות, מחלקות של קודי סטטוס ועוגיות, עם בדיקות curl מול שרת מקומי.
רשתות ואינטרנט
TLS 1.3 נותן ל-HTTPS סודיות, שלמות ואימות שרת. לחיצת היד, שרשראות תעודות, ומה המנעול בדפדפן לא מוכיח.
רשתות ואינטרנט
איך DNS הופך שמות לכתובות: resolvers, שרתים סמכותיים, מטמון TTL, הרשומות A, AAAA, CNAME, MX ו-TXT, ובדיקה בטוחה.