המדריך של ביג' לתכנות רשת
הקודםהבא

8. שאלות נפוצות

ש: איפה אני משיג את הקבצי כותרת (header files) האלו?
ש: מה אני עושה ש bind() מדווחת ש "הכתובת כבר בשימוש" ("Address already in use") ?
ש: איך אני משיג רשימה של שקעים פתוחים במערכת?
ש: איך אני רואה את טבלת הניתוב?
ש: איך אני יכול להפעיל לקוח ושרת אם יש לי רק מחשב אחד? אני לא צריך רשת כדי לכתוב תוכנית לרשת?
ש: איך אני יכול לדעת אם הצד המרוחק סגר את החיבור?
ש: איך אני ממש את הישום "פינג" ("ping") ? מה זה ICMP? איפה אני יכול ללמוד עוד על raw sockets SOCK_RAW?
ש: איך אני מהדר בחלונות?
ש: איך אני מהדר ב Solaris/SunOS? אני מקבל שגיאות קישור שאני מנסה להדר!
ש: למה select() ממשיכה להכשל לי בגלל איתות (signal) ?
ש: איך אני יכול לממש קריאה ל recv() עם שהי?
ש: איך אני מצפין או מכווץ הודעה שאני שולח דרך השקע?
ש: מה זה ה "PF_INET" שאני רואה כל הזמן? זה קרוב ל AF_INET?
ש: איך אני יכול לכתוב שרת שמקבל פקודות מעטפת מקליינט, ואז מבצע אותן?
ש: אני שולח מידע רב, אבל שאני עושה recv(), זה מקבל רק 536 בתים או 1460 בכל פעם. אבל אם אני מריץ את זה על המחשב המקומי אני מקבל את כל המידע בפעם אחת. מה קורה פה?
ש: אני עובד בחלונות, ואין לי את קריאת המערכת fork() או כל סוג של המבנה struct sigaction. מה לעשות?
ש: איך אני שולח מידע באופן מאובטח, עם TCP/IP תוך שימוש בהצפנה?
ש: אני מאחורי חומת-אש -- איך אני נותן לאנשים מבחוץ לדעת את הכתובת ה IP שלי, כך שהם יוכלו להתחבר למחשב שלי?

ש: איפה אני משיג את הקבצי כותרת (header files) האלו?

ת: אם אין לך אותם כבר במערכת, אתה כנראה לא זקוק להם. בדוק את המדריך עבור המערכת המסוימת שלך. אם אתה מהדר בחלונות, כל מה שאתה צריך זה #include <winsock.h>.

ש: מה אני עושה ש bind() מדווחת ש "הכתובת כבר בשימוש" ("Address already in use") ?

ת: אתה צריך להשתמש ב setsockopt() עם הארגומנט SO_REUSEADDR על השקע המאזין. בדוק את החלק על bind() ואת החלק על select() לדוגמא.

ש: איך אני משיג רשימה של שקעים פתוחים במערכת?

ת: אתה משתמש ב netstat. בדוק את עמוד המדריך ( man page) שלה לפרטים מלאים, אבל את צריך לקבל פלט די טוב פשוט ע"י הקשה:


$ netstat

החלק הבעייתי לגלות איך שקע משוייך לאיזה תוכנה. :-)

ש: איך אני רואה את טבלת הניתוב?

ת: הרץ את הפקודה route/sbin אצל רוב משתמש הלינוקס) או את הפקודה netstat -r.

ש: איך אני יכול להפעיל לקוח ושרת אם יש לי רק מחשב אחד? אני לא צריך רשת כדי לכתוב תוכנית לרשת?

ת: למזלך, כמעט כל המחשבים ממשים "התקן" רשת loopback שיושב בגרעין, ומחכה כאילו הוא כרטיס רשת. (זהו ההתקן שמצוין כ "lo" בטבלת הניתוב.)

חשוב לרגע שאתה מחובר למחשב בשם "goat". הרץ את הלקוח בחלון אחד, ואת השרת באחר. או שתתחיל את השרת ברקע ("server &") והרץ את הלקוח באותו חלון. התוצאה של התקן ה loopback, היא שאתה יכול לעשות או client goat או client localhost (מכיוון ש "localhost" כנראה מוגדר בקובץ your /etc/hosts שלך) ואז הלקוח שלך ידבר עם השרת ללא רשת!

בקיצור, אין צורך בשינויים לכל חלק בקוד כדי לגרום לו לעבוד על מחשב יחיד ללא רשת!

ש: איך אני יכול לדעת אם הצד המרוחק סגר את החיבור?

ת: אתה יכול דגעת כי recv() תחזיר 0.

ש: איך אני ממש את הישום "פינג" ("ping") ? מה זה ICMP? איפה אני יכול ללמוד עוד על raw sockets ו SOCK_RAW?

ת: כל השאלות שלך לגבי "raw sockets" יענו ב ספריו ש ו.ריצ'ארד סטיבנס על תכנות של יונקס ברשת. ראה את החלק על ספרים עבור מדריך זה.

ש: איך אני מהדר בחלונות?

ת: קודם כל, מחק אחת חלונות והתקן לינוקס או BSD. };-). לא, למעשה, פשוט ראה את החלק על הידור עבור חלונות במבוא.

ש: איך אני מהדר ב Solaris/SunOS? אני מקבל שגיאות קישור שאני מנסה להדר!

ת: שגיאות הקישור קורות משום שמחשבי Sun לא מהדרות אוטומטית בספריות השקעים. ראה את החלק על הידור ב Solaris/SunOS במבוא עבור דוגמא איך לעשות זאת.

ש: למה select() ממשיכה להכשל לי בדלל איתות (signal) ?

ת: איתות נוטים לגרום לקריאות מערכת שחוסמות להחזיר -1 כשב errno יהיה EINTR. כאשר את מכין מנהל אות עם sigaction(), אתה יכול לשים את הדגל SA_RESTART, שאמור להתחיל מחדש את קריאת המערכת אחרי שהאיתות התקבל.

כמובן, זה לא תמיד עובד.

הפתרון החביב עלי, תומן בתוכו את ההצהרה goto אתה יודע שזה מעצבן את הפרופסורים שלך בלי סוף, אז לך על זה!


select_restart:
    if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
        if (errno == EINTR) {
            // some signal just interrupted us, so restart
            goto select_restart;
        }
        // handle the real error here:
        perror("select");
    } 

בטח, אתה לא צריך להשתמש ב goto במקרה זה; אתה יכול לעבוד עם מבני בקרה אחרים לאותו תפקוד, אבל אני חושב שהצהרת goto היא למעשה יותר ברורה.

ש: איך אני יכול לממש קריאה ל recv()עם שהי?

ת: השתמש ב select()! היא נותנת לך לספק ארגומנט עבור שהי עבור מתארי השקע שאתה רוצה לקרוא מהם. או שאתה יכול לעטוף את כל הפונקציונליות בפונקציה אחת, כמו כאן:


#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

int recvtimeout(int s, char *buf, int len, int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // set up the file descriptor set
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // set up the struct timeval for the timeout
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // wait until timeout or data received
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // timeout!
    if (n == -1) return -1; // error

    // data must be here, so do a normal recv()
    return recv(s, buf, len, 0);
}

// Sample call to recvtimeout():
    .
    .
    n = recvtimeout(s, buf, sizeof(buf), 10); // 10 second timeout

    if (n == -1) {
        // error occurred
        perror("recvtimeout");
    }
    else if (n == -2) {
        // timeout occurred
    } else {
        // got some data in buf
    }
    .
    .

שים לב ש recvtimeout() תחזיר -2 במקרה של הגעה לסוף השהי. למה לא להחזיר 0 ? טוב, אם אתה זוכר, ערך החזר 0 מקריאה ל recv() אומר שהצד המרוחק סגר את החיבור. כבר דובר על ערך ההחזר הזה, ו -1 אומר "שגיאה", אז בחרתי את -2 להיות מה שיגיד לי על סוף השהי.

ש: איך אני מצפין או מכווץ הודעה שאני שולח דרך השקע?

ת: דרך קלה אחת להצפין את המידע היא השתמש ב SSL (secure sockets layer), אבל זה מעבר לתחום של מדריך זה.

אבל בהניה שאתה רוצה לממש מערכת הצפנה או כיווץ משלך, זה רק עניין של הרצת המידע דרך סדרת צעדים בשני הקצוות. כל צעד משנה מעט את המידע בדרך מסוימת.

  1. השרת קורא מידע מקובץ (או מכל דבר אחר)

  2. השרת מצפין את המידע (אתה מוסיף את החלק הזה)

  3. השרת שולח (עם send()) את המידע המוצפן

עכשיו בצד השני:

  1. הלקוח מקבל (עם recv()) את המידע המוצפן

  2. הלקוח מפענח את המידע (אתה מוסיף את החלק הזה)

  3. הלקוח כותה את המידע לקובץ (או כל דבר אחר)

אתה יכול לבצע כיווץ באותה נקודה שבה ביצעת הצפנה/פיענוח, לעיל. או שתוכל לעשות את שניהם! פשוט זכור לכווץ לפני שאתה מצפין. :)

כל עוד הלקוח מצליח לשחזר את מה שהשרת עשה, המידע היה בסדר בסוף, לא חשוב כמה צעדים הוספת באמצע.

אז כל מה שתצטרך לעשות כדי להשתמש בקוד שלי, הוא למצוא את המקום בין איפה שהמידע נקרא, לבין איפה שהוא נשלח (עם send()) לרשת, לתקוע שם קוד שיבצע את ההצפנה.

ש: מה זה ה "PF_INET" I keep שאני רואה כל הזמן? זה קרוב ל AF_INET?

ת: כן, זה כן. ראה את החלק socket() לפרטים.

ש: איך אני יכול לכתוב שרת שמקבל פקודות מעטפת מקליינט, ואז מבצע אותן?

ת: לשם הפשטות,נאמר כי הקליינט מתחבר (עם connect()), שולח (עם send()), וסוגר(עם close()) את החיבור (זאת אומרת שאין עוד קריאות מערכת באמצע, מבלי שהקליינט יתחבר שוב.)

התהליך אצל הלקוח הוא:

  1. להתחבר (עם connect() ) לשרת.

  2. send("/sbin/ls > /tmp/client.out")

  3. לסגור (עם close() ) את החיבור

בנתיים, השרת מנהל את המידע ומריץ אותו:

  1. קבל (עם accept() ) את החיבור מהלקוח

  2. קבל (recv(str)) את מחרוזת הפקודה

  3. סגור (עם close() ) את החיבור

  4. בצע system(str) כדי להריץ את הפקודה

הזהר! לתת לשרת להריץ פקודות שהלקוח אומר זה כמו לתת גישת מעטפת מרוחקת, ואנשים יוכלו לעשות דברים לחשבון שלך שהם מתחברים לשרת. להמחשה, בדוגמא לעיל, מה היה קורא אם הקליינט היה שולח "rm -rf ~"? זה מוחק את הכל בחשבון שלך, זה מה!

אז אתה מתחכם, ואתה מונע מהלקוח להשתמש אלא בישומים שאתה יודע שהם בטוחים, כמו הישום foobar (לדוגמא):


    if (!strcmp(str, "foobar")) {
        sprintf(sysstr, "%s > /tmp/server.out", str);
        system(sysstr);
    } 

אבל אתה עדיין אינך בטוח, לרוע המזל: אם הקליינט יכניס "foobar; rm -rf ~"? הדבר הטוח הוא לעשות שגרה קטנה ששמה תו ("\") בריחה לפני כל תו לא אלפא-נומרי (כולל רווחים, אם מתאים) בארגומנטים לפני הפקודה.

כמו שאתה רואה, אבטחה היא נושא חשוב מאוד כשהשרת מתחיל לבצע פקודות שהלקוח שולח.

ש: אני שולח מידע רב, אבל שאני עושה recv(), זה מקבל רק 536 בתים או 1460 בכל פעם. אבל אם אני מריץ את זה על המחשב המקומי אני מקבל את כל המידע בפעם אחת. מה קורה פה?

ת: הגעתה ליחדת העברה המקסמלית של הרשת ( MTU ) -- הגודל המירבי שהמדיום הפיזי יכול לנהל. במחשב המקומי, אתה משתמש בהתקן ה loopback שיכול לנהל 8K או יותר ללא בעיה. אבל בכרטיס רשת (ethernet) , שיכול לנהל רק 1500 בתים עם כותרת, אתה פגעת הגבול הזה. במודם, עם MTU יש 576 (שוב, עם כותרת) אתה אפילו פגעת בגבול היותר נמוך

עליך לוודא שכל המידע נשלח.(ראה את מימוש הפונקציה sendall() לעוד פרטים.) ברגע שאתה בטוח בזה, עליך לקרוא ל recv() בלולאה עד שכל המידע שלך נקרא.

קרא את החלק על כימוס מידע בשביל פרטים על קבלת חבילות מידע שלמות עם כמה קריאות ל recv().

ש: אני עובד בחלונות, ואין לי את קריאת המערכת fork() או כל סוג של המבנה struct sigaction. מה לעשות?

ת: אם הן איפהשהו, הן יהיו בספריות POSIX שאולי הגעו עם המהדר שלך. מגיוון שאין לי מחשב עם וחלונות, אני לא יכול להגיד לכם את התשובה, אבל אני זוכר שמיקרוסופט היתה שכבת התאמה ל POSIX ושםfork() אמורה להיות. (ואולי אפילו sigaction.)

חפש את העזרה שבאה עם VC++ עבור "fork" או "POSIX" וראה אם זה נותן לך רמזים.

אם זה לא עובד בכלל, נטוש את כל הדברים עם fork()/sigaction והחלף אותה עם המקבילה ה Win32 שלה: CreateProcess(). אני לא יודע איך להשתמש ב CreateProcess()--היא לוקחת מיליון ארגומנטים, אבל היא צריכה להיות מתועדת בתיעוד שבא עם VC++.

ש: איך אני שולח מידע באופן מאובטח, עם TCP/IP תוך שימוש בהצפנה?

ת: בדוק את מיזם OpenSSL.

ש: אני מאחורי חומת-אש -- איך אני נותן לאנשים מבחוץ לדעת את הכתובת ה IP שלי, כך שהם יוכלו להתחבר למחשב שלי?

ת: לרוע המזל, המטרה של חומת אש היא למנוע מאנשים מבחוץ להתחבר למחשבים מבפנים, אז להרשום להם זה בעצם נחשב פירצה באבטחה.

זה לא אומר שהכל אבוד. דבר ראשון, אתה בדרך כלל עדיין יכול להתחבר (עם connect() ) דרך חומת האש, אם הוא עושה סוג כלשהוא של מיסוך (masquerading) או תרגום כתובות רשת (NAT) או משהו כזה, פשוט תכנן את התוכניות שלך כך שאתה הוא תמיד זה שיוזם את החיבור, ואתה תיהיה בסדר.

אם זה לא מספק, אתה יכול לבקש ממנהל המערכת לסדר חור בחומת האש כך שאנשים יוכלו להתחבר אליך. חומת האש יכולה להעביר אותה דרך תוכנת תרגום כתובת רשת (NAT) או דרך מתווך (proxy) או משהו דומה.

דע שחור בחומת האש הוא לא דבר שנלקח בקלות. עליך לוודא שאתה לא נותן לאנשים גישה לרשת הפנימית שלך; אם אתה מתחיל, זה הרבה יותר קשה לעשות תוכנה בטוחה מאשר אתה מדמיין.

אל תגרום למנהל המערכת של לכעוס עליי. ;-)