Beej-ov vodič za mrežno programiranje

Prethodno

 

Dalje


8. Često postavljana pitanja

Q: Gdje da nađem sve te .h datoteke koje mi trebaju?

Q: Šta da radim kad mi bind() odgovori "Adresa već u upotrebi"?

Q: Kako da dobijem listu otvoreih soketa na sistemu?

Q: Kako da pregledam tabelu usmjeravanja?

Q: Kako da pišem klijent-server programe kad imam samo jedan računar? Zar mi ne treba mreža za mrežne programe?

Q: Kako da saznam da li je s druge strane zatvorena veza?

Q: Kako sam da napravim "ping"? Šta je ICMP? Gdje da nađem još informacija o "sirovim soketima" i  SOCK_RAW?

Q: Kako da pravim ove programe u Windows-u?

Q: Kako da pravim ove programe za Solaris/SunOS? Stalno dobijam greške pri povezivanju izvrpne datoteke!

Q: Zašto mi  select() stalno pada na signalu?

Q: Kako da ostvarim tajmaut pri pozivu funkcije recv()?

Q: Kako da šifriram ili kompresujem podatke prije nego ih pošaljem kroz soket?

Q: Šta je taj "PF_INET" koji stalno viđam? Jel' ima veze sa "AF_INET"?

Q: Kako da napravim server koji prima komande ljuske od klijenta, i izvršava ih?

Q: Šaljem gomilu podataka, ali kad ih primam pomoću recv(), primim stalno samo po 536 bajtova ili 1460 bajtova. Ali ako pokrenem program lokalno, primim sve podatke odjednom. Šta se dešava?

Q: Imam Windows, pa nemam fork(), i nemam nikakav struct sigaction. Šta da radim?

Q: Kako da pošaljem podatke sigurnom vezom, pomođu TCP/IP koristeći enkripciju?

Q: Koristim firewall – kako da pustim ljude izvan njega da znaju moju IP adresu pa da se spoje na moju mašinu?

Q: Gdje da nađem sve te .h datoteke koje mi trebaju?

A: Ako ih već nemaš instalirane, vjerovatno ti i ne trebaju. Provjeri man stranice u vezi svoje platforme. Ako radiš u Windows-u, treba ti samo #include <winsock.h>.

Q: Šta da radim kad mi bind() odgovori "Adresa već u upotrebi"?

A: Treba ti setsockopt() sa uključenom opcijom SO_REUSEADDR da ga primijeniš na soketu koji čeka pozive. Pogledaj  poglavlje bind() i poglavlje select() ako ti trebaju primjeri.

Q: Kako da dobijem listu otvorenih soketa na sistemu?

A: Koristi netstat. Pogledaj man stranice za detalje, ali možeš dosta stvari shvatiti i samo kucanjem:

$ netstat

Jedino je problem odrediti koji soket je povezan sa kojim programom. :-)

Q: Kako da pregledam tabelu usmjeravanja?

A: Koristi komandu route (/sbin/route na većini Linux sistema) ili netstat -r.

Q: Kako da pišem klijent-server programe kad imam samo jedan računar? Zar mi ne treba mreža za mrežne programe?

A: Srećom po tebe, svi računari imaju virtuelni povratni mrežni "uređaj" koji čuči u jezgru i pravi se da je mrežna kartica. (Ovo je interfejs koji je predstavljen kao "lo" u tabeli usmjeravanja.)

Zamislimo da smo prijavljeni na računar pod nazivom "goat". Pokreni klijent u jednom prozoru i server u drugom. Ili možeš i da pokreneš server u pozadini ("server &") i onda u istom prozoru i klijent. Rezultat je da možeš da se spojiš na goat ili localhost (pošto je "localhost" vjerovatno definisan u datoteci /etc/hosts) i imaćeš razgovor klijenta i servera bez mreže!

Ukratko, nema potrebe vršiti ikakve izmjene u  izvornom kôdu da bi proradio na sistemu bez mreže! Abrakadabra!

Q: Kako da saznam da li je s druge strane zatvorena veza?

A: Možeš da znaš po tome što recv() vraća nulu (0).

Q: Kako sam da napravim "ping"? Šta je ICMP? Gdje da nađem još informacija o "sirovim soketima" i  SOCK_RAW?

A: Sva tvoja pitanja u vezi "sirovih soketa" imaju odgovor u knjigama Ričarda Stivensa koje se tiču UNIX mrežnog programiranja. Pogledaj odjeljak knjige u okviru ovog vodiča.

Q: Kako da pravim ove programe u Windows-u?

A: Prvo, izbriši Windows i instaliraj Linux ili BSD. };-). Ne, šalim se, samo pogledaj primjedbu za Windows programere u uvodu ove knjige.

Q: Kako da pravim ove programe za Solaris/SunOS? Stalno dobijam greške pri povezivanju izvršne datoteke!

A: Greške pri povezivanju se dešavaju jer se Sun sistemima mora eksplicitno ukazati na to da treba da prevedu sa bibliotekama soket-funkcija. Pogledaj primjedbu za Solaris/SunOS programere.

Q: Zašto mi  select() stalno pada pri signalu?

A: Signali uvijek žele da zaustave blokirajuće sistemske pozive, koji bi zatim vratili -1 i podesili errno  na EINTR. Kad podesiš rukovalac signala pomoću sigaction(), možeš postaviti opciju SA_RESTART, koji će uzrokovati da se sistemski poziv ponovo pokrene nakon što se prekine.

Naravno, ovo ne uspijeva uvijek.

Moje omiljeno rješenje je goto naredba. Znaš da ovo jako nervira tvoje profesore, zato baš i hoćemo tako!

select_restart:
    if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
        if (errno == EINTR) {
            // neki signal nas je prekinuo, zato hajde ispočetka
            goto select_restart;
        }
        // ovde obradi pravu grešku:
        perror("select");
    } 

Naravno, goto nije neophodno u ovom slučaju; možeš da koristiš bilo koju drugu kontrolnu strukturu. Ali ja mislim da je sa goto stvar jasnija.

Q: Kako da ostvarim tajmaut pri pozivu funkcije recv()?

A: Koristi select()! Pomoću njega možeš odrediti tajmaut za soket-deskriptor od kog očekuješ informacije. Mogao bi sve da odradiš u jednoj jedinoj funkciji, kao što je ova:

#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;
 
    // podesi skup fajl-deskriptora
    FD_ZERO(&fds);
    FD_SET(s, &fds);
 
    // Podesi struct timeval za tajmout
    tv.tv_sec = timeout;
    tv.tv_usec = 0;
 
    // Čekaj dok ne primiš informacije ili se tajmaut ne potroši
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // timeout!
    if (n == -1) return -1; // error
 
    // ovde moraju biti podaci, tako da ćemo uraditi obični recv()
    return recv(s, buf, len, 0);
}
 
// Primjer poziva funkcije recvtimeout():
    .
    .
    n = recvtimeout(s, buf, sizeof(buf), 10); // tajmaut od 10 sekundi
 
    if (n == -1) {
        // greška se desila
        perror("recvtimeout");
    }
    else if (n == -2) {
        // potrošio se tajmaut
    } else {
        // neki podaci su u buf
    }
    .
    . 

Primijeti da recvtimeout() vraća -2 u slučaju da se potrošio tajmout. Yašto ne vrati 0? Pa, ako možeš da se sjetiš, recv() vraća nulu ako je došlo do zatvaranja veze sa druge strane. Na taj način, već je potrošena nula, a -1 znači "greška", pa sam izabrao -2 kao indikator tajmauta.

Q: Kako da šifriram ili kompresujem podatke prije nego ih pošaljem kroz soket?

A: Jedan jednostavan način je da koristiš SSL (secure sockets layer(sloj sigurnosnih soketa)), ali to je van domašaja ove knjige.

Ali ako pretpostavimo da hoćeš da ubaciš sopstveni kompresor ili sistem šifrovanja,  onda samo zamisli podatke kako prelaze preko niza stepenika između dva kraja. Svaki stepenik mijenja podatke na neki način.

1.      server čita podatke iz datoteke (ili odnekle drugo)

2.      server šifrira podatke (ovo ti radiš)

3.      server šalje šifrirane podatke

A sad s druge strane:

4.      klijent prima šifrirane podatke

5.      klijent dešifruje podatke (ovo takođe ti radiš)

6.      klijent piše podatke u datoteku (ili šta god)

Takođe možeš da uradiš kompresiju odnosno dekompresiju na mjestu gdje je u gornjem primjeru urađeno šifriranje odnosno dešifrovanje. A može i oboje! Samo zapamti da kompresuješ prije nego što šifriraš. :)

Dok god klijent vraća unatrag opercije koje je uradio server, podaci će biti bezbijedni, bez obzira koliko tih operacija ima..

Na taj način, možeš jednostavno da koristiš moj kôd da bi slao i primao podatke, a u njemu nađi pogodno mjesto da ubaciš obradu datih podataka.

Q: Šta je taj "PF_INET" koji stalno viđam? Jel' ima veze sa "AF_INET"?

A: Da, da, ima. Pogledaj poglavlje o funkciji socket() ako te interesuju detaljni podaci.

Q: Kako da napravim server koji prima komande ljuske od klijenta, i izvršava ih?

A: Radi jednostavnosti, recimo da se klijent spaja (connect()), šalje podatke (send()), i zatvara (close()) vezu (dakle, nema naknadnih sistemskih poziva bez ponovnog klijentovog povezivanja.)

Procedura po kojoj ide klijent je sledeća:

1.      connect() na server

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

3.      close()

U međuvremenu, server rukuje podacima izvršavajući ih:

1.      accept() vezu od klijenta

2.      recv(str)

3.      close()

4.      system(str) da bi pokrenuo datu komandu

Pazi! Ako server izvršava sve što klijent kaže, to je kao da si dao klijentu ljusku na svom sistemu, i sve svoje dozvole!. Eto, u gornjem primjeru, šta ako klijent pošalje "rm -rf ~"? Briše se sve što je pod tvojim nalogom, eto šta!

Zato budi pametan, i ne dozvoli da klijent može raditi išta sem nekoliko potpuno bezbijednih operacija, kao što je odrađeno u  foobar programu:

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

Ali još si ranjiv, nažžalost: šta ako klijent pošalje "foobar; rm -rf ~"? Jedina sigurna stvar je da napišeš malu funkciju koja će postaviti karakter "\" ispred svih ne-alfanumeričkih karaktera (uključujući razmake, ako je potrebno) argumenta komande.

Kao što vidiš, sigurnost je prilično velika stvar kda server počne da ima ulogu izvršavanja klijentovih komandi.

Q: Šaljem gomilu podataka, ali kad ih primam pomoću recv(), primim stalno samo po 536 bajtova ili 1460 bajtova. Ali ako pokrenem program lokalno, primim sve podatke odjednom. Šta se dešava?

A: Dostižeš MTU – najveći broj podataka kojim fizički uređaj može da rukuje. Lokalno, tvoj virtuelni povratni mrežni uređaj može da održi 8k ili i više pa nema problema. Ali na mreži, koja može da rukuje sa samo 1500 bajtova skupa sa zaglavljem, dostižeš to ograničenje. Preko modema, sa MTU-om od 576 bajtova (opet, uključujući zaglavlje), dostižeš još niže ograničenje.

Treba uvijek da budeš siguran da si poslao sve podatke, pod jedan. (Vidi sendall() radi detalja.) Kad si se to uvjerio, onda treba u nekoj petlji da pozivaš recv() dok ne pročitaš sve podatke.

Pročitaj poglavlje  O enkapsulaciji podataka radi detalja o primanju čitavih paketa podataka koristeći višestruke pozive komande recv().

Q: Imam Windows, pa nemam fork(), i nemam nikakav struct sigaction. Šta da radim?

A: Ako ih ima igdje, onda su u POSIX bibliotekama koje si možda dobio uz svoj prevodilac. Pošto nemam Windows, stvarno ne mogu da ti odgovorim, ali mislim da se sjećam da Microsoft ima sloj kompatibilnosti sa POSIX-om i tu bi fork() trebao biti. (a možda čak i sigaction.)

Pretraži pomoćne datoteke (help) koje dolaze uz VC++  za riječima "fork" ili "POSIX" pa vidi ako ima išta.

Ako nema ništa od toga, odustani od fork() i sigaction, i počni da koristiš Win32 ekvivalent: CreateProcess(). Ne znam kako se koristi CreateProcess() – uzima mali milion argumenata, ali vjerovatno ga pokriva dokumentacija koja dolazi uz VC++.

Q: Kako da pošaljem podatke sigurnom vezom, pomođu TCP/IP koristeći enkripciju?

A: Pregledaj OpenSSL projekat.

Q: Koristim firewall – kako da pustim ljude izvan njega da znaju moju IP adresu pa da se spoje na moju mašinu?

A: Nažalost, uloga firewall-a je da spreči ljude van njega da se spajaju na računar unutar njega, tako da ako dozvoliš ljudima da ipak uđu – praktično si napravio rupu u bezbijednosti.

Ali, nije sve izgubljeno. Prva stvar, još uvijek možeš da se spajaš (connect()) kroz firewall ako se sve našminka kako treba. Jednostavno, tvoj program unutar firewall-a treba da inicira vezu, i sve će biti u redu.

Ako nisi zadovoljan, pitaj administratora sistema da napravi rupu u firewall-i da bi se ljudi mogli spajati s tobom. Firewall može da ti prosleđuje podatke kroz svoje NAT aplikacije, ili kroz proxy ili nešto slično.

Budi pažljiv da rupa u firewall-u nije ništa naivno. Moraš uvijek da paziš da loši ljudi ne dospiju unutar tvoje mreže; ako si početnik, napraviti program bezbijednim je teže nego što možeš i zamisliti.

Nemoj da se tvoj administrator ljuti na mene. ;-)


Prethodno

Glavna strana

Dalje

Više informacija o ovoj temi

 

Odricanje od odgovornosti i poziv za pomoć