Programatorsky manual

k programu

Fosforuv DNS server v1.0



Obsah


Uvod

Tento program vznikl jako semestralni prace v ramci studia programovaciho jazyka C++ na Fakulte elektrotechnicke, CVUT. Ukolem teto prace primarne nebylo vytvorit DNS server, ale procvicit si a prohloubit znalosti ziskane studiem. Z tohoto duvodu neni vysledkem "idealni" program, ale spise naznak, jak lze postupovat pri reseni takoveho problemu, jako je tvorba serveru sitove sluzby.

Nahoru

Co to je DNS

DNS je zkratka anglickeho Domain Name Service - Sluzba domenovych jmen. Obecne je to sluzba, ktera usnadnuje uzivatelum - lidem - orientaci a pohyb po spletite siti internetu.

V pocitacove siti je dulezite jednoznacne (a rychle) urceni kazdeho pocitace. Proto existuji tzv. IP adresy, coz jsou (prozatim) 4 bytova cisla. V kazde pocitacove siti musi mit kazdy pocitac pridelenou jedinecnou IP adresu, prostrednictvim niz s nim komunikuji ostatni pocitace. Pro uzivatele pocitacovych siti je bezpochyby pracne pamatovat si vice takovychto cisel, proto byla zavedena tzv. domenova jmeno. Domenove jmeno je posloupnost znaku, ktera v dane siti jednoznacne specifikuje nejakou IP adresu. Nutnost zavedeni domenovych jmen je patrna z uzivani internetu.

IP adresa se vetsinou zobrazuje jako ctyri cisla od 0 do 255 oddelene teckami (tedy napr. 212.80.76.3).

Domenove jmeno (DJ) se sklada z posloupnosti retezcu oddelenych teckami. Vyjimecnym pripadem DJ je jeden znak - mezera. Toto je korenove DJ, pod ktere spadaji vsechny ostatni. Pod korenovym DJ se ostatni stromovite vetvi tak, ze uzel stromnu tvori jeden retezec. Plati, ze cim je retezec blize konci DJ, tim je blize korenovemu DJ. Kazdemu uzlu takto vznikleho stromu muze byt prirazena jedna IP adresa, ktera je charakteristicka pro DJ, jenz vznikne z daneho uzlu pridanim vsech uzlu, ktere jsou nad nim na ceste ke korenovemu DJ (tyto uzly se pridavaji doprava).

Pokud bychom tedy meli DN:

bude prislusny strom vypadat nasledovne:


kde _ znaci korenove domenove jmeno.

Oznaceni DNS tedy zahrnuje sluzby zprostredkovavajici ukladani DJ a jejich IP a vyhledavani v takto ulozenych zaznamech. DNS sluzby pouzivane ve vetsich pocitacovych sitich shromazduji mnohem vice informaci a mnohem sloziteji. Pro ucely teto prace vsak postaci tento strucny uvod.

Nahoru

DNS komunikace

Sitova komunikace mezi DNS serverem a klientem probiha prostrednictvim protokolu UDP/IP, tedy nespojoveho protokolu. Volba tohoto protokolu vyplyva z pozadavku rychleho ziskani pozadovanych informaci a maleho zatizeni pocitacove site. Absence potvrzeni prijatych datagramu je osetrena nasobnymi pokusy o ziskani odpovedi.

Konktetni komunikace vypada takto: server posloucha na portu 53. Pokud mu prijde dotaz, zpracuje ho a na adresu a port, ze ktere dotaz prisel, posle odpoved. Format dotazove i odpovedni zpravy je shodny: na zacatku datagram obsahuje hlavicku s infomaci, zda se jedna o dotaz ci odpoved, s poradovym cislem dotazu, s poctem dodatecnych informaci a s dalsimi udaji. Nasleduji dodatecne infomaci. Jako dodatecne informace se v teto zprave vyskytuje prave dotaz, ci odpoved.

V praxi to vypada tak, ze klient vytvori zpravu dotazu (tj. vytvori hlavicku a prida dodatecnou informaci - domenove jmeno). Server tento pozadavek zpracuje, prida ke zprave dalsi dodatecnou informaci - odpoved (jen ve tvaru "IP adresa dotazovaneho DJ je ...") a zpravu vrati klientovi.

Nahoru

Navrh programu

Navrh programu vychazel z pozadavku udelat DNS server, ktery bude schopny poskytovat nejake zakladni sluzby a pritom nebude prilis slozity. Jako ukol jsem si tedy stanovil udelat DNS server, ktery bude umet uchovavat DNS zaznamy (dvojice DJ - IP) a na dotaz na IP adresu konkretniho DJ bude schopen odpovedet touto IP, pokud ji bude znat.

Navrh struktury tedy byl:

Nahoru

Blokove schema

Zakladni usporadani behu programu je patrne z blokoveho schematu:


Nahoru

Realizace programu

Realizace programu se vicemene nelisi od navrhu. Pribyla jen specialni entita pro uchovani DJ (usnadnuje vymenu infomaci uvnitr programu) a zapouzdreni programu do izolovaneho vlakna ovladaneho jednoduchou konzolovou aplikaci.

V prubehu programovani byly vytvoreny tyto programove struktury:

Nahoru

Trida DNSDomain

Tato trida rerezenuje jedno DJ. Jak je uvedeno v kapitole Co to je DNS, jedna se o posloupnost retezcu. Daty v teto tride jsou tedy ukazatel na typ string a jeden typ int, ktery uchovava pocet retezcu. Objekt teto tridy ma nemenny obsah, je ho tedy treba nainicializovat jiz konstruktorem na nejake konkretni DJ. Pokud se zadne neuvede, vytvori se korenove DJ.

Trida obsahuje tyto metody:

DNSDomain();
implicitni konstruktor - vytvori korenove DJ
DNSDomain(char * DNSretezec);
konstruktor - vytvori DJ z retezce, ktery se pouziva v DNS zpravach
DNSretezec ::= { {<1 byte - delka retezce> <retezec>} | {<1 byte - delka retezce> <retezec> DNSretezec} } <byte 0>
DNSDomain(string *);
konstruktor - vytvori DJ z retezce, ktery pouzivaji uzivatele (napr. www.seznam.cz)
int getCount();
vrati pocet retezcu v DJ
string getLabel(int);
vrati konkretni retezec
string toString();
prevede DJ do retezce, ktery pouzivaji uzivatele (napr. www.seznam.cz)
friend ostream & operator<<(ostream &, DNSDomain &);
do vystupniho proudu posle DJ ve tvaru retezce, ktery pouzivaji uzivatele (napr. www.seznam.cz)
Nahoru

Trida Sock

Trida zajistujici sitovou komunikaci. Pro potreby teto tridy je definovana struktura SockData:

struct SockData {
   char * data;
   int size;
   int maxSize;
   sockaddr_in info;
};

kde:

data
je ukazatel na zasobnik pro vstupni/vystupni data
size
je velikost dat ulozenych v zasobniku data
maxSize
je maximalni kapacita zasobniku data
info
je sitova informace o pocitaci (IP adresa, port, typ adresy,...)

Vlastni trida ma jednu promenou typu SOCK, ktera predstavuje ukazatel na sitovy socket. Dale ma nasledujici metody:

Sock();
konstruktor, ktery provede inicializaci sitoveho spojeni
void listen();
zahaji naslouchani na implicitnim portu (53) a na vsech sitovych adresach dostupnych v pocitaci
void listen(unsigned int port);
zahaji naslouchani na zadanem portu a na vsech sitovych adresach dostupnych v pocitaci
void listens(unsigned int port, const char * ip);
zahaji naslouchani na zadanem portu a na zadane sitove adrese (typu 10.10.10.10)
void listen(unsigned int port, long ip);
zahaji naslouchani na zadanem portu a na zadane sitove adrese (IP adresa zanana jako 4bytove cislo)
Sock & operator>>(SockData &);
blokujici metoda, ktera zahaji cekani na prichozi data; tyto ulozi do promeny typu SockData a nastavi parametry teto struktury (info bude sitova informace o pocitace, ze ktereho data prisla)
Sock & operator<<(SockData &);
posle data z promene typu SockData na pocitac specifikovany v polozce info teto promene
private: void chyba();
metoda slouzici k prekladu ciselneho kodu chyby socketu na retezec specifikujici chybu; volani teto metody vzdy vyhodi retezcovou vyjimku
Nahoru

Trida DBase

Trida uchovava DNS zaznamy, tj. sitove adresy k jednotlivym DJ. Take umoznuje vyhledavani v teto databazi, tj. nalezeni sitove adresy k zadanemu DJ.

Pro potreby teto tridy je definovana struktura subdomain, ktera predstavuje uzel stromu (viz Co to je DNS):

struct subdomain {
   string name;
   long ip;
   subdomain * next;
   subdomain * sub;
};

kde:

name
je retezcova reprezentace uzlu
ip
je sitova adresa prirazena uzlu (pokud je 0, znamena to, ze neni prirazena zadna adresa)
next
ukazatel na uzel stejne urovne
sub
ukazatel na uzel nizsi urovne

Pro snazsi datovou reprezentaci byl pouzit binarni strom. Data uvedena v kapitole Co to je DNS budou tedy reprezentovana nasledovne:


Jako data proto vlastni tride staci jeden ukazatel na korenove DJ. Jeji metody jsou:

DBase();
konstruktor - zalozi stromovou strukturu (vytvori korenove DJ)
void add(string *, unsigned long);
prida do stromu DJ reprezentovane DNS-retezcem a IP adresu
void add(DNSDomain *, unsigned long);
prida do stromu DJ reprezentovane tridou DNSDomain a IP adresu
bool loadFile(char *);
nahraje konfiguracni soubor (struktura tohoto souboru: viz Uzivatelsky manual)
long find(DNSDomain *);
vrati IP adresu k danemu DJ, pokud takovy zaznam nema, vrati 0
string showSubdomains(subdomain *, int);
prevede celou databazi do zobrazitelneho retezce
friend ostream & operator<<(ostream &, DBase &);
posle celou databazi ve tvaru zobrazitelneho retezce do vystupniho proudu
friend ostream & operator<<(ostream &, DBase *);
posle celou databazi ve tvaru zobrazitelneho retezce do vystupniho proudu
static string inet_addr_rev(unsigned long);
prevede 4bytove cislo reprezentujici IP adresu na retezec pouzivany uzivateli (napr. 10.10.10.10)

Nedostatek v teto tride je absence destruktoru. Jelikoz je ale pro cely beh programu pouzivana jedna databaze deklarovana ve funkci main(), upustil jsem od psani destruktoru. Kdyby bylo potreba ho dopsat, je nutne zajistit spravnou destrukci vsech uzlu stromu.

Nahoru

Trida Message

Tato trida zpracovava DNS zpravy - prijme dotaz, vyhleda v databazi, zda k dotazovanemu DJ existuje zaznam, a pokud existuje, vytvori z jeho IP adresy odpoved. Tato trida by mela zajistovat kompatibilnost programu s definici DNS. Vzhledem k rozsahlosti kompletniho DNS a charakteru teto prace, jsem se omezil jen vytvoreni jednoho typu odpovedi, pokud se najde odpovidajici zaznam. Pokud zaznam nebude nalezen, vrati program klientovi jeho nezmeneny dotaz. Klient proto musi mit nastaveny dalsi DNS server, ktereho se muze ptat dal. DNS sluzba resi situaci nenalezeni DNS zaznamu tim, ze se DNS server zepta jineho DNS serveru, nebo klientovi posle informaci, ze IP adresu nezna a posle muze mu poslat IP adresu jineho DNS serveru. Po prostudovani DNS definice v RFC dokumentech lze uvazovat o pridani tohoto typu zprav.

Data tridy predstavuje ukazatel na strukturu SockData (kterou trida zpracovava) a dalsi promene predstavujici infomace ulozene v hlavicce DNS zpravy. Metody teto tridy jsou:

Message(DBase *);
konstruktor - jako parametr pozaduje ukazatel na databazi DJ, kterou bude vyuzivat
void load(SockData *);
nahraje data ke zpracovani
void makeRespons();
zpracuje dara (vytvori odpoved) - zpracovana data ulozi do struktury, na kterou byl ukazatel predan funkci load()
private: void parse();
metoda slouzici pro "rozkodovani" DNS zpravy - vola se na konci metody load()
Nahoru

Hlavni programova cast

Hlavni cast programu (zdrojovy soubor Dns.cpp) obsahuje dve funkce. Prvni z nich je jadro servru - funkce, ze ktere je mozne vytvorit samostatne vlakno programu. Jelikoz lze takto vytvorenemu vlaknu predat jen jeden parametr, je zde zavedena struktura PARAM:

struct PARAM {
   DBase * db;
   bool quit;
};

prostrednictvim ktere se vlaknu predava ukazatel na databazi DJ a zaroven promenou, ktera specifikuje, zda ma vlakno bezet. (Parametr quit jsem nakonec nevyuzil, protoze funkce recvfrom ve volani "Sock >> SockData" je blokovaci.) Obsahem teto funkce je inicializace prostredi a provadeni vlastniho jadra serveroveho programu ve smyslu blokoveho schematu uvedeneho v kapitole Blokove schema.

V metoda main() probiha vytvoreni databaze DJ, vytvoreni vlakna pro jadro serveru a obsluha programu pomoci "konzole". Z konzole lze pouze vypsat obsah pouzivane DNS databaze nebo ukoncit program. Ukonceni programu neni korektni - probiha terminovanim beziciho vlakna. Lepsi by bylo nastavit promenou quit v parametru vlakna na true a ve vlaknu zajistit kontrolu tohoto parametru. Opet ale narazime na blokovaci funkci recvfrom, na ktere je vlakno po vetsinu casu blokovane.

Nahoru

Zaver

Program sice nesplnuje specifikaci DNS serveru uvedenou v dokumentech RFC, ale pro nenarocne pouziti je plne funkcni. Lze jej s vyhodou snadne konfigurace a ovladani vyuzit v male pocitacove siti nekladouci naroky na uplnou korektnost jejiho provedeni (v domacnosti, v malem podniku, na LAN-party,...).

Pripadne dotazy, pripominky a navrhy prosim zasilejte na adresu autora:

Jan Pospisil
fosfor(dot)software(at)seznam(dot)cz
http://www.vpp-net.com

Vice informaci na tema DNS servery lze nalezt na internetu a to hlavne v odpovidajicich RFC dokumentech. Jedna se o dokumenty 1034, 1035 a dalsi.