├── .gitignore ├── README.md ├── docs ├── maarittelydokumentti.md ├── testausdokumentti.md ├── toteutusdokumentti.md ├── viikkopalautus1.md ├── viikkopalautus2.md ├── viikkopalautus3.md ├── viikkopalautus4.md ├── viikkopalautus5.md └── viikkopalautus6.md └── src ├── client.cpp ├── convert_utility.cpp ├── convert_utility.h ├── dns_packet.cpp ├── dns_packet.h ├── hsocket.cpp ├── hsocket.h ├── ip_utility.cpp ├── ip_utility.h ├── makefile ├── message.cpp ├── message.h ├── pque.h ├── server.cpp ├── tests └── main.cpp ├── tunnel.h ├── tunnel_dns.cpp ├── tunnel_dns.h └── vec.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dns_tunnel 2 | A program to tunnel data between a server and a client using DNS protocol and UDP sockets. 3 | 4 | ## Examples 5 | The server takes listening address and port as parameters: ./server <listen_ip> <port> and the client takes the server address and port as parameters: ./client <server_ip> <server_port> 6 | 7 | The tool will be used like netcat, except that there will be separate binaries for the server and the client. An example usage on localhost for linux could be for example: 8 | 9 | $./server 127.0.0.1 53 > file & 10 | 11 | $cat file | ./client 127.0.0.1 53 12 | 13 | or the other way around: 14 | 15 | $cat file | ./server 127.0.0.1 53 & 16 | 17 | $./client 127.0.0.1 53 > file 18 | -------------------------------------------------------------------------------- /docs/maarittelydokumentti.md: -------------------------------------------------------------------------------- 1 | #Aiheen määrittely 2 | 3 | Työssäni tulen käyttämään ainakin linkitettyä listaa, jonoa ja todennäköisesti myös kekoa. Algoritmeja työhön tulee muun muassa osittainen base64-tyyppinen koodaus datalle ja asynkronisen I/O:n toteutus viestijonolla. 4 | 5 | Työni tulee olemaan C++:lla toteutettu kaksisuuntainen DNS data tunneli, joka koostuu palvelimesta ja asiakkaasta. Tunnelin asiakas-ohjelma ottaa yhtettä palvelimeen, joka kuuntelee UDP-portissa DNS-kyselyitä. Sekä palvelin, että asiakas voivat ottaa mitä tahansa syötettä vastaan standard inputtiin ja se siirretään vastaavasti toisella puolella olevan palvelimen tai asiakkaan standard outputtiin. 6 | 7 | Tunneli siis toimii molempiin suuntiin ja asiakas ja palvelin viittaavat tässä tapauksessa vain kuuntelevaan ja yhteyttä ottavaan osapuoleen. Silloin, kun tunnelissa ei kulje aktiivisesti dataa lähettää asiakas silti palvelimelle jatkuvasti heartbeat viestejä kertoen olemassaolostaan. Palvelin ei nimittäin voi lähettää dataa asiakkalle muuta kuin DNS-vastauksien kautta. 8 | 9 | Asiakasohjelmassa kaikki data koodataan ascii merkeiksi DNS-kyselyyn sopivaan muotoon ja muutetaan DNS-vastaukseksi, joka koodataan tavuiksi muotoon, jonka voi lähettää UDP:lla palvelimelle. Palvelimessa dataa ei tarvitse koodata ascii-merkeiksi, sillä DNS-vastauksissa palautetaan IP:itä tavumuodossa. DNS-vastauksissa voi myös olla useita vastauksia samaan kysymykseen. Käytän todennäköisesti ensin vain yhtä kysymystyyppiä "A", joka on siis kysymys, johon vastataan IPv4-osoitteilla. Palvelin siis jakaa lähetettävän datapaketin neljän tavun lohkoihin ja lisää sen DNS-vastaukseen. 10 | 11 | Datapaketit vaativat oman protokollan, jotta kommunikaatio voi olla kaksisuuntaista. Datapaketit tulevat siis koostumaan paketin pituudesta, operaatiokoodista ja itse datasta. Operaatiokoodit tulevat olemaan esimerkiksi, HEARTBEAT, OK, DATA, tms. 12 | 13 | Datapaketit koodataan siis DNS-kyselyihin ja vastauksiin ja DNS toimii vain transport-protokollana. 14 | 15 | Aika- ja tilavaativuudet ovat vielä hieman hämärän peitossa, mutta DNS-kyselyitä parsitaan yksi kerrallaan, ja ne ovat vakiopituisia (max 4096 tavua). Muusta datan käsittelystä tulee todennäköisesti enemmän vaatimuksia. 16 | 17 | Lähteenä käytän DNS-kyselyiden osalta http://www.tcpipguide.com/free/t_DNSMessageHeaderandQuestionSectionFormat.htm, muissa kysymyksissä käytän stackoverflow-foorumia. 18 | -------------------------------------------------------------------------------- /docs/testausdokumentti.md: -------------------------------------------------------------------------------- 1 | #Testausdokumentti 2 | 3 | ##integraatiotestaus 4 | 5 | Integraatiotestausta en ole automatisoinut. Testasin manuaalisesti palvelin- ja asiakasohjelmia lähettämällä testiviestejä ja tiedostoja kumpaankin suuntaan. Samalla ajoin tcpdumppia, jolla tarkistin, että DNS-paketit näyttävät speksin mukaiselta ja niissä on data koodattu oikein. 6 | 7 | ##hsocket 8 | 9 | Hsocket-luokkaa on testattu avaamalla kaksi sockettia toinen vastaanottamaan ja toinen lähettämään. Näin saadaan testattu hsocket, luokan bind-, connect-, ja lähetys- ja vastaanottometodit. 10 | 11 | Testejä on kaksi, joista toinen testaa blokkaavia socketteja ja toinen ei-blokkaavia socketteja. 12 | 13 | ##dns_packet 14 | 15 | DNS-pakettiluokalle on kolme testiä. Ensimmäiset kaksi testiä testaavat pakettien muuttamista oliosta tavuvirraksi ja takaisin. Ensimmäisessä testataan vain kysymyksiä sisältävällä DNS-paketilla ja toisessa testataan kysymyksiä sekä vastauksia sisältävää DNS-pakettioliota. 16 | 17 | Viimeisessä testissä testataan oikean DNS-kyselyn tekemistä Googlen palvelimelle osoitteessa 8.8.8.8. Testissä kysytään helsinki.fi.-nimen osoitetta. DNS-kysely muutetaan tavuiksi dns_packet::str()-metodilla ja lähetetään socketin kautta Googlelle. Vastaukseksi saatu tavuvirta muutetaan DNS-paketiksi ja tarkistetaan onko vastauksissa helsinki.fi:ä vastaavaa A-tyyppistä IP-osoitetta (IPv4). Mikäli vastauksen IP-osoite on oikein, testi onnistuu. 18 | 19 | Huomion arvoista on, että DNS-luokan testaamisessa käytän aliluokkaa dns_test_t, jolle on määritelty DNS-headerin palauttava metodi, koska tavallisesti DNS-headeriin ei tarvitse koskea käyttäessa DNS-luokkaa. 20 | 21 | ##message 22 | 23 | Message-luokalle on yksi testi. Testi testaa message-luokan olioiden muuttamista tavuvirraksi ja takaisin, mikä on message-luokan ainoa haluttu toiminnallisuus. 24 | 25 | Testi testaa ensin HEARTBEAT-tyyppistä viestiä, sitten OK-tyyppistä viestiä ja lopuksi M_ERROR-tyyppistä viestiä, jonka rakentamista tavuista ei tueta, joten tuloksen pitää olla viimeisessä tilanteessa eri alkuperäisestä (ei dataa). 26 | 27 | M_ERROR-tyyppiä ei käytetä muuhun kuin virheellisen viestin tarkistukseen. 28 | 29 | ##convert_util 30 | 31 | Convert_util on moduuli, joka sisältää apufunktioita, joilla voidaan muuttaa dataa heksamuotoon ja takaisin. 32 | 33 | Moduulille on yksi testi, joka testaa, että, kun kutsutaan datalla to_hex()-funktiota ja sen tuloksella from_hex()-funktiota, saadaan takaisin alkuperäinen data. 34 | 35 | ##tunnel_dns 36 | 37 | Tunnel_dns-luokkaa testataan kolmella testillä. DNS-tunneliobjekteja on tällä hetkellä INCOMING- ja OUTGOING-tyyppisiä, jonka lisäksi ne ovat joko kysely-tyyppiä tai vastaus-tyyppiä. Vain A-tyyppisille tallenteille on toteutettu tuki. 38 | 39 | Ensimmäinen testi testaa, että OUTGOING-tyyppiseen DNS-kyselytunneliin laitettu data koodautuu oikein domain-nimeksi. 40 | 41 | Toinen testi testaa, että OUTGOING-tyyppisestä DNS-kyselytunnelista saatu data koodautuu takaisin dataksi, kun se laitaan INCOMING-tyyppisen DNS-kyselytunnelin läpi. 42 | 43 | Kolmas testi testaa, että OUTGOING-tyyppisestä DNS-vastaustunnelista saatu data kodautuu oikein IP-osoitteiksi. IP-osoitteita on monta, sillä yksi DNS-vastauspaketti voi sisältää monta IP-osoitetta vastauksena kysytylle domain-nimelle. 44 | 45 | ##pque 46 | 47 | Pque-luokkaa testataan lisäämällä sinne std::string-objekteja vectorista epäjärjestyksessä. Sen jälkeen sieltä poistetaan kaikki alkiot ja lisätään uuteen vektoriin. Vanha vektori järjestetään std::sort-funktiolla ja tulosvektoria verrataan vanhaan. Jos vektorit ovat identtisiä, testi menee läpi. 48 | Testissä tulee testatuksi, size(), insert(), remove() ja pop() -operaatiot pque-luokasta. 49 | 50 | ##vec 51 | 52 | Vec-luokkaa testataan lisäämällä sinne std::string objekteja ja sen jälkeen vertaamalla sen sisältöä ja kokoa vastaavaan std::vectoriin. Lisäksi vec-instanssi alustetaan yhden kokoiseksi, jolloin myös resize-ominaisuus tulee testattua. 53 | 54 | ##Testien ajaminen 55 | 56 | Testit voidaan ajaa suorittamalla ```make``` tai ```make win``` komento src kansiossa riippuen siitä halutaanko kääntää ristiin windowsille vai kääntää natiivisti linuxille. 57 | 58 | Jos halutaan ajaa vain testit se onnistuu ajamalla ```make tests```. 59 | 60 | Huomioi, että ohjelmaa ei ole tarkoitettu käännettävän windowsissa vaan vain linuxissa make-ohjelmalla ja gcc:llä. 61 | -------------------------------------------------------------------------------- /docs/toteutusdokumentti.md: -------------------------------------------------------------------------------- 1 | #Toteutusdokumentti 2 | 3 | ##Ohjelman rakenne 4 | 5 | Ohjelma koostuu kahdesta erillisestä ohjelmasta. Palvelimesta ja asiakkaasta. Palvelin kuuntelee DNS-viestejä ja asiakas lähettää niitä. 6 | 7 | Ohjelmat kommunikoivat lähettämällä kahdentyyppisiä viestejä HEARTBEAT ja OK. HEARTBEAT viestit eivät sisällä dataa. Ne kertovat vain, että yhteys on vielä elossa ja mahdollistavat esimerkiksi palvelimen kommunikoinnin asiakkaalle, vaikka asiakkaalla ei olisi mitään dataa lähetettävänä. OK-tyyppiset viestit sisältävät datan, joka halutaan kuljettaa tunnelin läpi. 8 | 9 | ## Toteutetut algoritmit 10 | 11 | Projektissa on toteutettu prioriteettijono, jolla järjestetään asiakkaassa vastaanotetut DNS-vastauksen vastausosion osat. DNS-protokolla ei nimittäin missään nimessä takaa, että vastaukset pysyisivät samassa järjestyksessä, missä ne lähetettiin, mikäli vastaus saapuu useamman kuin yhden DNS-palvelimen kautta. 12 | 13 | Vectori on projektissa toteutettu dynaamisesti allokoituvana taulukkona. Tätä luokkaa käytetään DNS-kyselyluokan kyselyiden ja vastauksien säilyttämiseen. 14 | 15 | ## Aikavaativuus 16 | 17 | Asiakkaalta palvelimelle viestin lähetys on O(n) ja palvelimelta asiakkalle on O(nlog(n)). Aikavaativuudet on käyty tarkemmin läpi alla. 18 | 19 | Yhden paketin lähettäminen asiakkaalta palvelimelle käy läpi seuraavat vaiheet: 20 | 21 | Asiakas: 22 | 23 | ``` 24 | handle_stdin() O(n) 25 | m = make_message() O(n) 26 | tun_out << m.str() O(n) 27 | data.append() 28 | handle_outgoing() O(n) 29 | tun_out >> qname O(n) 30 | d.add_question() O(n) 31 | s << d.str() O(n) 32 | ``` 33 | Palvelin: 34 | ``` 35 | handle_incoming() O(n) 36 | tun_in << data 37 | dns_to_data() O(n) 38 | tun_in >> data O(n) 39 | data.append() 40 | message m(data) O(n) 41 | ``` 42 | 43 | Yhden paketin lähettäminen palvelimelta asiakkalle käy läpi seuraavat vaiheet: 44 | 45 | Palvelin: 46 | 47 | ``` 48 | handle_stdin() O(n) 49 | m = make_message() O(n) 50 | tun_out << m.str() O(n) 51 | data.append() 52 | handle_outgoing() O(n) 53 | tun_out >> ip O(n) 54 | last.add_response() 55 | s << last.str() O(n) 56 | ``` 57 | Asiakas: 58 | ``` 59 | handle_incoming() O(nlog(n)) 60 | Q.insert() O(nlog(n)) 61 | tun_in << Q.pop() O(n) 62 | tun_in >> data O(n) 63 | data.append() 64 | message m(data) O(n) 65 | ``` 66 | 67 | ## Protokollat ja paketit 68 | 69 | Kaikki protokolla-objektit on kuvattu objektien tavuesityksinä, jota tarvitaan niiden kuljettamiseen verkon yli. 70 | 71 | Message-objekti, jota käytetään kommunikointiin on rakenteeltaan seuraavanlainen: 72 | 73 | |8 bit| 16 bit | n bit| 74 | |-----|-------------|------| 75 | |type | data length | data | 76 | 77 | 78 | DNS-objekti on taas rakenteeltaan seuraavanlainen: 79 | 80 | |12 bytes |<= 25 bytes| n bytes | n bytes | n bytes | 81 | |---------|-----------|-----------|-------------|-------------| 82 | | header | questions | responses | authorities | additionals | 83 | 84 | Questions-osio, sisältää suurimmassa osassa tapauksia yhden kysymyksen. Responses-osio sisältää n-määrän vastauksia tavallisessa kyselyvastauksessa. Tavallisessa vastauksessa ei yleensä ole authority-osita, eikä additionals-osioita. 85 | 86 | Ylläolevassa kaaviossa on oletettu, että questions-osio sisältää vain yhden kysymyksen. 87 | 88 | Questions-osion kysymykset ovat kysymysmuotoa. Responses-, authorities- ja additionals-osiot ovat taas kaikki samaa vastausmuotoa. 89 | 90 | Kysymys- ja vastausmuodoista voi lukea lisätietoja täältä http://www.zytrax.com/books/dns/ch15/. 91 | 92 | ## Tiedonsiirto 93 | 94 | Aina, kun halutaan siirtää tietoa asiakkaalta palvelimelle tai palvelimelta asiakkaalle, tai molemmille samanaikaisesti, täytyy asiakkaan lähettää DNS-kysely ja palvelimen vastata siihen DNS-vastauksella. Tämän takia, vaikka dataa ei tarvisi kuljettaa juuri tietyllä hetkellä, lähetetään silti palvelimen ja asiakkaan välillä HEARTBEAT-viestejä. 95 | 96 | Yhden viestiobjektin lähetys toimii seuraavasti: 97 | 98 | Viestiobjekti muutetaan ensin tavuiksi, jotka lisätään uloskulkevaan puskuriin. Sen jälkeen puskurista otetaan sopiva pala, joka mahtuu yhteen DNS-kyselyyn. Asiakkaalla se tarkoittaa sitä, että datan täytyy sopia domain-nimeen ja palvelimella johonkin ennaltamääritettyyn määrään IP-osoitteita, kun käytetään A-tyypin vastauksia ja kyselyitä. 99 | 100 | Kun DNS-kyselyn data dekoodataan takaisin tavuvirraksi toisessa päässä, se lisätään sisääntulevaan puskuriin. Kun puskurissa on tarpeeksi dataa vastaanotetun message-paketin muodostamiseksi, se muutetaan tavuista taas messageksi. Messagen tyypistä riippuen ei tehdä mitään (HEARTBEAT) tai tulostetaan saatu data (OK). 101 | -------------------------------------------------------------------------------- /docs/viikkopalautus1.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 1 - TiRa labra 2 | 3 | Tällä viikolla olen kirjoittanut määrittelydokumentin ja tehnyt IP-utility tiedoston, jota tulen tarvitsemaan muuttaakseni IP-osoitteita merkkijonomuodosta tavumuotoon. 4 | 5 | Aikaa on kulunut pari tuntia. En sinänsä ole oppinut mitään konkreettista uutta, mutta aiheen miettiminen on selventänyt ajatuksia ja olen miettinyt valmiiksi jo joitain ratkaisuja tuleviin ongelmiin. 6 | 7 | Mielestäni mikään ei ole vielä jäänyt epäselväksi. Seuraavaksi alan totetuttamaan pientä wrapperia sockettien käyttöön. 8 | -------------------------------------------------------------------------------- /docs/viikkopalautus2.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 2 - TiRa labra 2 | 3 | Tällä viikolla olen aloittanut testausdokumentin ja toteuttanut UDP socket -luokan, sekä tehnyt sille pari testiä. Lisäksi olen laatinut makefilet natiiville linux-kääntämiselle sekä ristiinkääntämiselle windowsille. 4 | 5 | Aikaa on kulunut noin viisi tuntia. 6 | 7 | Mielestäni mikään ei ole vielä jäänyt epäselväksi. Seuraavaksi alan toteuttamaan DNS-pakettiluokkaa. 8 | -------------------------------------------------------------------------------- /docs/viikkopalautus3.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 3 - TiRa labra 2 | 3 | Tällä viikolla olen jatkanut testausdokumentin ja toteuttanut dns_packet-luokan, sekä tehnyt sille muutaman testin. Mukana on myös oikean DNS-kyselyn tekevä testi. 4 | 5 | Aikaa on kulunut noin 10 tuntia. 6 | 7 | Ei ole ollut mitään epäselvää. Koska dns_packet-luokka ja hsocket-luokka eivät ole DNS-tunnelin pääasia, aion toteuttaa kattavimmat testit monimutkaisemmille ja kriittisemmille osille. 8 | 9 | Seuraavaksi alan toteuttamaan jonkun näköistä moduulia, jolla voi koodata dataa DNS-muotoon sopivaksi. 10 | -------------------------------------------------------------------------------- /docs/viikkopalautus4.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 4 - TiRa labra 2 | 3 | Tällä viikolla olen toteuttanut message-luokan, jota tulen käyttämään viestien välitykseen palvelimen ja asiakkaan välillä, convert_util-moduulin, jota käytän datan muuntamiseen heksoiksi ja takaisin, tunnel_dns-luokan, joka hoitaa raa'an datan koodaamisen DNS-domainnimiksi tai IP-osoitteiksi, ja takaisin. 4 | 5 | 6 | Lisäksi, olen tehnyt kaikille edellämainituille komponenteille akuutteja testejä ja päivittänyt testausdokumentin. 7 | 8 | Olen myös tehnyt pienet esimerkkiohjelmat asiakkaasta ja palvelimesta. Mitään dokkareita toiminnasta en ole vielä ehtinyt tehdä. 9 | 10 | Aikaa on kulunut noin 12 tuntia. 11 | 12 | Seuraavaksi jatkan itse palvelin- ja asiakas-ohjelmien toteuttamista ja toteutan todennäköisesti jonkun hallintaluokan, joka yksinkertaistaa asiakas- ja palvelinohjelmien toimintalooppia. 13 | -------------------------------------------------------------------------------- /docs/viikkopalautus5.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 5 - TiRa labra 2 | 3 | Tällä viikolla olen toteuttanut pque-luokan, joka on siis priority que. Olen myös saanut asiakkaan ja palvelimen viestinnän toimimaan minkä tahansa pituisilla data-paketeilla molempiin suuntiin. 4 | 5 | Asiakas käyttää priority que:ta palvelimelta DNS-vastauksessa tulevien IP-osoitteiden järjestämiseen. 6 | 7 | Olen tehnyt myös testauksen pque-luokalle, päivittänyt testausdokumenttia ja aloittanut toteuttamisdokumenttia. 8 | 9 | Aikaa on kulunut noin 10 tuntia. 10 | 11 | Seuraavaksi toteutan ainakin vektori-luokan luokan ja refaktoroin asiakas- ja palvelinohjelmia. Koodi ei nimittäin ole aivan yhtä laadukasta asiakas- ja palvelinohjelmissa ajanpuutteen takia. 12 | -------------------------------------------------------------------------------- /docs/viikkopalautus6.md: -------------------------------------------------------------------------------- 1 | #Viikkopalautus 6 - TiRa labra 2 | 3 | Tällä viikolla olen toteuttanut vec-luokan, joka on siis dynaamisesti allokoituva vektori, ja tehnyt sille myös testin. Olen myös parantanut palvelin- ja asiakasohjelmien koodia ja päivittänyt dokumentteja. 4 | 5 | Aikaa on kulunut noin 7 tuntia. 6 | 7 | Aikavaativuuksia en ole miettinyt, koska ohjelman toiminnallisuuden takia, se ei ole kovin olennaista. 8 | -------------------------------------------------------------------------------- /src/client.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WIN32 2 | #define usleep Sleep 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | #include "hsocket.h" 9 | #include "dns_packet.h" 10 | #include "tunnel_dns.h" 11 | #include "message.h" 12 | #include "pque.h" 13 | 14 | void handle_stdin(std::string& output); 15 | void handle_incoming(tunnel_dns& tun_in, hsocket& s); 16 | void handle_outgoing(tunnel_dns& tun_out,hsocket& s); 17 | message make_message(std::string& data,message::message_type t); 18 | message parse_message(std::string& data); 19 | 20 | int main(int argc, char** argv) { 21 | if (argc < 3) { 22 | std::cout << "Usage: " << argv[0] << " \n"; 23 | return -1; 24 | } 25 | hsocket s(hsocket::UDP); 26 | std::string domain = "example.org"; 27 | tunnel_dns tun_in(tunnel::INCOMING, dns::response_t, dns::A, domain); 28 | tunnel_dns tun_out(tunnel::OUTGOING, dns::query_t, dns::A, domain); 29 | message heartbeat(message::HEARTBEAT); 30 | tun_in.set_response_limit(10); 31 | std::string data_out,data_in; 32 | s.connect(std::stoi(argv[2]),argv[1]); 33 | s << hsocket::NONBLOCKING; 34 | int counter = 0; 35 | while (1) { 36 | usleep(10); 37 | counter++; 38 | // get data to be sent from stdin 39 | handle_stdin(data_out); 40 | if (data_out.length() > 0) { 41 | // consumes the data_out 42 | message m = make_message(data_out,message::OK); 43 | tun_out << m.str(); 44 | } 45 | // send outgoing data from tunnel 46 | if (counter > 1000) { 47 | if (tun_out.bytes_available() == 0) 48 | tun_out << heartbeat.str(); 49 | handle_outgoing(tun_out, s); 50 | counter = 0; 51 | } 52 | // recieve incoming data to tunnel 53 | handle_incoming(tun_in, s); 54 | tun_in >> data_in; 55 | message recvd(data_in); 56 | if (recvd.type == message::OK) { 57 | std::cout << recvd.data << '\n'; 58 | } 59 | 60 | } 61 | return 0; 62 | } 63 | 64 | 65 | /* Makes a message from data_out and consumes all data 66 | * which is taken from data_out 67 | */ 68 | message make_message(std::string& data,message::message_type t) { 69 | unsigned int maxlen = message::MAXLEN; 70 | message m(t,data.substr(0,maxlen)); 71 | data.erase(0,maxlen); 72 | return m; 73 | } 74 | void handle_outgoing(tunnel_dns& tun_out,hsocket& s) { 75 | dns_packet d(1337,dns::query_t); 76 | std::string qname; 77 | tun_out >> qname; 78 | if (qname.empty()) return; 79 | d.add_question(qname,dns::A); 80 | s << d.str(); 81 | } 82 | void handle_incoming(tunnel_dns& tun_in, hsocket& s) { 83 | std::string data; 84 | s >> data; 85 | if (data.empty()) return; 86 | dns_packet d(data); 87 | // We need the pque because DNS protocol does not guarantee the order of responses 88 | pque Q; 89 | for (auto r : d.responses) 90 | Q.insert(r.data); 91 | while (Q.size() > 0) { 92 | tun_in << Q.pop(); 93 | Q.remove(); 94 | } 95 | } 96 | 97 | void handle_stdin(std::string& buffer) { 98 | struct timeval tv; 99 | fd_set fds; 100 | FD_ZERO (&fds); 101 | FD_SET (STDIN_FILENO, &fds); 102 | tv.tv_sec = 0; 103 | tv.tv_usec = 100; 104 | int result = select (STDIN_FILENO + 1, &fds, NULL, NULL, &tv); 105 | if (result != 0) { 106 | std::string tmp; 107 | //std::cin >> tmp; 108 | std::getline(std::cin, tmp); 109 | buffer.append(tmp); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/convert_utility.cpp: -------------------------------------------------------------------------------- 1 | #include "convert_utility.h" 2 | #include 3 | #include 4 | 5 | std::string to_hex(const std::string& data, int len) { 6 | std::string ret; 7 | if (len > data.length()) 8 | len = data.length(); 9 | static const std::vector val_to_hex = { 10 | '0','1','2','3','4','5','6','7', 11 | '8','9','a','b','c','d','e','f' }; 12 | for (int i = 0; i < len; i++) { 13 | int a = (data[i] & 0xf0)>>4; 14 | int b = data[i] & 0xf; 15 | ret.append(1,val_to_hex[a]); 16 | ret.append(1,val_to_hex[b]); 17 | } 18 | return ret; 19 | } 20 | std::string from_hex(const std::string& data) { 21 | std::string ret; 22 | if (data.length()%2 != 0) return ret; 23 | for (int i = 0; i < data.length()-1; i+=2) { 24 | int a = hex_to_val(data[i]), 25 | b = hex_to_val(data[i+1]); 26 | 27 | if (a < 0 || b < 0) { 28 | ret.clear(); 29 | break; 30 | } 31 | char c = b | (a << 4); 32 | ret.append(1,c); 33 | } 34 | return ret; 35 | } 36 | int hex_to_val(char c) { 37 | int ret = -1; 38 | if (c <= 'F' && c >= 'A') 39 | c = c ^ 0x20; // to lowercase 40 | if (c <= 'f' && c >= 'a') { 41 | ret = c - 'a' + 10; 42 | } else if (c <= '9' && c >= '0') { 43 | ret = c - '0'; 44 | } 45 | return ret; 46 | } 47 | -------------------------------------------------------------------------------- /src/convert_utility.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONVERT_UTILITY_H 2 | #define _CONVERT_UTILITY_H 3 | #include 4 | std::string to_hex(const std::string& data, int len); 5 | std::string from_hex(const std::string& data); 6 | int hex_to_val(char c); 7 | #endif 8 | -------------------------------------------------------------------------------- /src/dns_packet.cpp: -------------------------------------------------------------------------------- 1 | #include "dns_packet.h" 2 | #include "vec.h" 3 | #ifdef WIN32 4 | #include 5 | #include 6 | #include 7 | extern "C" WINSOCK_API_LINKAGE const char* WSAAPI inet_ntop(int af, const void* src, 8 | char *dst, socklen_t size); 9 | #else 10 | #include 11 | #endif 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* struct compare functions for tests */ 18 | bool operator!=(const dns::DNS_HEADER& lhs, const dns::DNS_HEADER& rhs) { 19 | return !(lhs == rhs); 20 | } 21 | bool operator==(const dns::DNS_HEADER& lhs, const dns::DNS_HEADER& rhs) 22 | { 23 | bool ret = true; 24 | ret &= lhs.id == rhs.id; 25 | if (ret == false) 26 | std::cout << "id " << ntohs(lhs.id) << " " << ntohs(rhs.id) << '\n'; 27 | ret &= lhs.rd == rhs.rd; 28 | if (!ret) 29 | std::cout << "rd " << (int)(lhs.rd) << " " << (int)(rhs.rd) << '\n'; 30 | ret &= lhs.tc == rhs.tc; 31 | ret &= lhs.aa == rhs.aa; 32 | ret &= lhs.opcode == rhs.opcode; 33 | ret &= lhs.qr == rhs.qr; 34 | ret &= lhs.cd == rhs.cd; 35 | ret &= lhs.ad == rhs.ad; 36 | ret &= lhs.z == rhs.z; 37 | ret &= lhs.ra == rhs.ra; 38 | ret &= lhs.q_count == rhs.q_count; 39 | ret &= lhs.ans_count == rhs.ans_count; 40 | ret &= lhs.auth_count == rhs.auth_count; 41 | ret &= lhs.add_count == rhs.add_count; 42 | return ret; 43 | } 44 | bool operator!=(const dns::QUERY& lhs, const dns::QUERY& rhs) { 45 | return !(lhs == rhs); 46 | } 47 | bool operator==(const dns::QUERY& lhs, const dns::QUERY& rhs) { 48 | if (lhs.name != rhs.name) return false; 49 | if (lhs.ques.qtype != rhs.ques.qtype || lhs.ques.qclass != rhs.ques.qclass) 50 | return false; 51 | return true; 52 | } 53 | bool operator!=(const dns::RES_RECORD& lhs, const dns::RES_RECORD& rhs) { 54 | return !(lhs == rhs); 55 | } 56 | bool operator==(const dns::RES_RECORD& lhs, const dns::RES_RECORD& rhs) { 57 | if (lhs.name != rhs.name) return false; 58 | if (lhs.data != rhs.data) return false; 59 | if (lhs.resource.type != rhs.resource.type 60 | || lhs.resource._class != rhs.resource._class 61 | || lhs.resource.ttl != rhs.resource.ttl 62 | || lhs.resource.data_len != rhs.resource.data_len) 63 | return false; 64 | return true; 65 | } 66 | 67 | 68 | /* dns_packet member functions */ 69 | 70 | dns_packet::dns_packet(unsigned short message_id, dns::dns_type type) { 71 | this->header.rcode = 0; 72 | if (type == dns::response_t) { 73 | this->header.qr = 1; 74 | } else if (type == dns::query_t) { 75 | this->header.qr = 0; 76 | } else { 77 | this->header.rcode = dns::NOT_IMPLEMENTED; 78 | } 79 | // recursion desired 80 | this->header.rd = 1; 81 | this->header.tc = 0; 82 | this->header.aa = 0; 83 | this->header.cd = 0; 84 | this->header.ad = 0; 85 | this->header.z = 0; 86 | this->header.ra = 0; 87 | // standard query 88 | this->header.opcode = 0; 89 | this->header.id = htons(message_id); 90 | // this changes when new questions are added 91 | this->header.q_count = 0; 92 | this->header.ans_count = 0; 93 | this->header.auth_count = 0; 94 | this->header.add_count = 0; 95 | } 96 | dns_packet::dns_packet(const std::string& bytes) : dns_packet(0) { 97 | int pos = 0; 98 | int len = bytes.length(); 99 | // if parsing fails 100 | this->header.rcode = dns::PARSING_ERROR; 101 | // parse header 102 | if (len < sizeof(dns::DNS_HEADER)) return; 103 | std::string h_bytes = bytes.substr(pos,sizeof(dns::DNS_HEADER)); 104 | pos += sizeof(header); 105 | dns::DNS_HEADER header = *(dns::DNS_HEADER*)(h_bytes.data()); 106 | vec questions; 107 | int q_count = ntohs(header.q_count); 108 | // parse questions 109 | for (int i = 0; i < q_count; i++) { 110 | dns::QUERY q; 111 | size_t null = bytes.find('\0',pos); 112 | if (null == std::string::npos || null-pos > 255) return; 113 | q.name = qname_to_ascii(bytes.substr(pos,null-pos)); 114 | pos = null+1; 115 | if (len < pos + sizeof(dns::QUESTION)) return; 116 | q.ques = *(dns::QUESTION*)(bytes.substr(pos,sizeof(dns::QUESTION)).data()); 117 | pos += sizeof(dns::QUESTION); 118 | questions.push_back(std::move(q)); 119 | } 120 | // parse answers 121 | int ans_count = ntohs(header.ans_count); 122 | for (int i = 0; i < ans_count; i++) { 123 | pos = this->parse_response(bytes,pos,len,RESPONSE); 124 | if (pos < 0 || pos > len) { 125 | this->responses.clear(); 126 | return; 127 | } 128 | } 129 | // parse authorities 130 | int auth_count = ntohs(header.auth_count); 131 | for (int i = 0; i < auth_count; i++) { 132 | pos = this->parse_response(bytes,pos,len,AUTHORITY); 133 | if (pos < 0 || pos > len) { 134 | this->authorities.clear(); 135 | return; 136 | } 137 | } 138 | // parse additional entries 139 | int add_count = ntohs(header.add_count); 140 | for (int i = 0; i < add_count; i++) { 141 | pos = this->parse_response(bytes,pos,len,ADDITIONAL); 142 | if (pos < 0 || pos > len) { 143 | this->additionals.clear(); 144 | return; 145 | } 146 | } 147 | this->header = header; 148 | this->questions = std::move(questions); 149 | } 150 | // mainly for debug purposes 151 | void dns_packet::print_header() const { 152 | dns::DNS_HEADER h = this->header; 153 | std::string error = "undefined"; 154 | switch (this->header.rcode) { 155 | case dns::FORMAT_ERROR: 156 | error = "format error"; 157 | break; 158 | case dns::SERVER_FAILURE: 159 | error = "server failure"; 160 | break; 161 | case dns::NAME_ERROR: 162 | error = "name error"; 163 | break; 164 | case dns::NOT_IMPLEMENTED: 165 | error = "not implemented"; 166 | break; 167 | case dns::REFUSED: 168 | error = "refused"; 169 | break; 170 | case dns::PARSING_ERROR: 171 | error = "parsing error"; 172 | break; 173 | case dns::NON_ERROR: 174 | error = "no error"; 175 | break; 176 | }; 177 | std::cout << "id = " << ntohs(h.id) 178 | << "\nrd = " << int(h.rd) 179 | << "\ntc = " << int(h.tc) 180 | << "\naa = " << int(h.aa) 181 | << "\nopcode = " << int(h.opcode) 182 | << "\nqr = " << int(h.qr) 183 | << "\ncd = " << int(h.cd) 184 | << "\nad = " << int(h.ad) 185 | << "\nz = " << int(h.z) 186 | << "\nra = " << int(h.ra) 187 | << "\nq_count = " << ntohs(h.q_count) 188 | << "\nans_count = " << ntohs(h.ans_count) 189 | << "\nauth_count = " << ntohs(h.auth_count) 190 | << "\nadd_count = " << ntohs(h.add_count) 191 | << "\nrcode = " << error 192 | << '\n'; 193 | } 194 | void dns_packet::print_questions() const { 195 | for (auto q : this->questions) { 196 | std::cout << q.name << '\n'; 197 | } 198 | } 199 | void dns_packet::print_responses() const { 200 | for (const auto& r : this->responses) { 201 | std::cout << r.name << ": "; 202 | if (r.resource.type == htons(dns::A)) { 203 | in_addr a; 204 | a.s_addr = *(uint32_t*)r.data.data(); 205 | std::cout << inet_ntoa(a); 206 | } else if (r.resource.type == htons(dns::AAAA)) { 207 | struct sockaddr_in6 sa; 208 | sa.sin6_family = AF_INET6; 209 | char str[INET6_ADDRSTRLEN]; 210 | if (r.data.length() != 16) { 211 | std::cout << "invalid"; 212 | } else { 213 | std::copy(r.data.begin(),r.data.end(),sa.sin6_addr.s6_addr); 214 | const char* ret = inet_ntop(AF_INET6,&(sa.sin6_addr),str,INET6_ADDRSTRLEN); 215 | if (!ret) 216 | std::cout << "invalid"; 217 | else 218 | std::cout << str; 219 | } 220 | } 221 | std::cout << '\n'; 222 | } 223 | } 224 | void dns_packet::add_question(std::string name, dns::qtype type) { 225 | dns::QUERY q; 226 | size_t len = name.length(); 227 | 228 | if (name[len-1] != '.') 229 | q.name = name+'.'; 230 | else 231 | q.name = name; 232 | q.ques.qtype = htons(uint16_t(type)); // A records 233 | q.ques.qclass = htons(1); // internet 234 | this->questions.push_back(q); 235 | this->header.q_count = htons(this->questions.size()); 236 | } 237 | void dns_packet::add_response(uint32_t data,dns::qtype type, unsigned int ttl, uint16_t q_id) { 238 | std::string d((char*)&data,sizeof(uint32_t)); 239 | this->add_response(d,type,ttl, q_id); 240 | } 241 | void dns_packet::add_response(std::string data,dns::qtype type, unsigned int ttl, uint16_t i) { 242 | if (this->header.rcode != dns::NON_ERROR) return; 243 | this->header.rcode = dns::SERVER_FAILURE; 244 | if (i >= this->questions.size() || i < 0) { 245 | return; 246 | } 247 | dns::RES_RECORD r; 248 | dns::QUERY q = this->questions[i]; 249 | r.name = q.name; 250 | if (q.ques.qtype != htons(uint16_t(dns::ANY)) && q.ques.qtype != htons(uint16_t(type))) { 251 | return; 252 | } 253 | switch (type) { 254 | case dns::AAAA: 255 | if (data.length() != 16) 256 | return; 257 | break; 258 | case dns::A: 259 | if (data.length() != 4) 260 | return; 261 | break; 262 | default: 263 | return; 264 | }; 265 | r.resource.type = htons(uint16_t(type)); 266 | r.resource._class = htons(1); 267 | r.resource.ttl = ttl; 268 | r.resource.data_len = htons(data.length()); 269 | r.data = data; 270 | this->responses.push_back(r); 271 | this->header.ans_count = htons(this->responses.size()); 272 | this->header.rcode = dns::NON_ERROR; 273 | } 274 | void dns_packet::set_response() { 275 | this->header.qr = 1; 276 | // TODO remove these when support for auth and add resources are added 277 | this->header.auth_count = 0; 278 | this->header.add_count = 0; 279 | } 280 | /* internal static, convert dns domain into ascii form 281 | * */ 282 | std::string dns_packet::qname_to_ascii(const std::string& qname) { 283 | std::string ret; 284 | int pos = 0; 285 | int n = qname[pos]; 286 | while (n) { 287 | pos++; 288 | ret.append(qname.substr(pos,n)); 289 | ret.append(1,'.'); 290 | pos += n; 291 | if (pos > qname.length()) return ret; 292 | n = qname[pos]; 293 | } 294 | return ret; 295 | } 296 | /* internal, parse a response from bytes and add it to the responses 297 | * returns the new pos depending on consumed bytes 298 | * returns -1 with an error 299 | * */ 300 | int dns_packet::parse_response(const std::string& bytes,int pos,int len,rtype type) { 301 | dns::RES_RECORD r; 302 | if (pos + 2 + sizeof(dns::R_DATA) > len) 303 | return -1; 304 | unsigned short pname = ntohs(*(unsigned short*)(bytes.substr(pos,2).data())); 305 | if (pname & 0xc000) { // pointer format 306 | pname &= 0x3fff; // remove the flag bits 307 | if (pname > pos) return -1; 308 | size_t null = bytes.find('\0',pname); 309 | if (null == std::string::npos || null-pname > 255) return -1; 310 | r.name = qname_to_ascii(bytes.substr(pname,null-pname)); 311 | pos += 2; 312 | } else { // label format 313 | size_t null = bytes.find('\0',pos); 314 | if (null == std::string::npos || null-pos > 255) return -1; 315 | r.name = qname_to_ascii(bytes.substr(pos,null-pos)); 316 | pos = null+1; 317 | } 318 | if (pos + sizeof(dns::R_DATA) > len) return -1; 319 | r.resource = *(dns::R_DATA*)(bytes.substr(pos,sizeof(dns::R_DATA)).data()); 320 | pos += sizeof(dns::R_DATA); 321 | int data_len = ntohs(r.resource.data_len); 322 | if (pos + data_len > len) return -1; 323 | r.data = bytes.substr(pos,data_len); 324 | pos += data_len; 325 | if (type == RESPONSE) { 326 | this->responses.push_back(r); 327 | } else if (type == AUTHORITY) { 328 | this->authorities.push_back(r); 329 | } else { 330 | this->additionals.push_back(r); 331 | } 332 | return pos; 333 | } 334 | /* internal static, convert ascii domain into dns form 335 | * */ 336 | std::string dns_packet::ascii_to_qname(const std::string& domain) { 337 | std::string ret; 338 | size_t pos = 0,dot; 339 | do { 340 | dot = domain.find('.',pos); 341 | size_t len = dot == std::string::npos ? std::string::npos : dot-pos; 342 | std::string temp = domain.substr(pos,len); 343 | ret.append(1,char(temp.length())); 344 | ret.append(temp); 345 | pos += temp.length()+1; 346 | } while (dot != std::string::npos); 347 | 348 | // only append a null, if there was a no dot in the end 349 | dot = domain.rfind('.'); 350 | if (dot != domain.length()-1) 351 | ret.append(1,'\0'); 352 | return std::move(ret); 353 | } 354 | /* make bytes out of a dns_packet object 355 | * */ 356 | std::string dns_packet::str() { 357 | std::string ret; 358 | ret.append((char*)&(this->header),sizeof(this->header)); 359 | for (const auto& q : this->questions) { 360 | ret.append(ascii_to_qname(q.name)); 361 | ret.append((char*)&(q.ques),sizeof(q.ques)); 362 | } 363 | for (const auto& r : this->responses) { 364 | ret.append(ascii_to_qname(r.name)); 365 | ret.append((char*)&(r.resource),sizeof(r.resource)); 366 | ret.append(r.data); 367 | } 368 | for (const auto& r : this->authorities) { 369 | ret.append(ascii_to_qname(r.name)); 370 | ret.append((char*)&(r.resource),sizeof(r.resource)); 371 | ret.append(r.data); 372 | } 373 | for (const auto& r : this->additionals) { 374 | ret.append(ascii_to_qname(r.name)); 375 | ret.append((char*)&(r.resource),sizeof(r.resource)); 376 | ret.append(r.data); 377 | } 378 | return std::move(ret); 379 | } 380 | unsigned char dns_packet::rcode() const { 381 | return header.rcode; 382 | } 383 | 384 | /* copy constructor 385 | */ 386 | dns_packet::dns_packet(const dns_packet& obj) { 387 | header = obj.header; 388 | questions = obj.questions; 389 | responses = obj.responses; 390 | authorities = obj.authorities; 391 | additionals = obj.additionals; 392 | } 393 | -------------------------------------------------------------------------------- /src/dns_packet.h: -------------------------------------------------------------------------------- 1 | #ifndef __DNS_H 2 | #define __DNS_H 3 | #include 4 | #include 5 | #include "vec.h" 6 | 7 | namespace dns { 8 | // this is used when crerating a new dns_packet object 9 | enum dns_type { query_t, response_t, none }; 10 | enum qtype : uint16_t { A = 1, NS = 2, CNAME = 5, SOA = 6, 11 | WKS = 11, PTR = 12, MX = 15, SRV = 33, 12 | AAAA = 28, ANY = 255 }; 13 | enum rcode : uint8_t { FORMAT_ERROR = 1, SERVER_FAILURE = 2, NAME_ERROR = 3, 14 | NOT_IMPLEMENTED = 4, REFUSED = 5, NON_ERROR = 0, PARSING_ERROR = 8 }; 15 | //DNS header structure 16 | typedef struct DNS_HEADER 17 | { 18 | unsigned short id; // identification number 19 | 20 | unsigned char rd :1; // recursion desired 21 | unsigned char tc :1; // truncated message 22 | unsigned char aa :1; // authoritive answer 23 | unsigned char opcode :4; // purpose of message 24 | unsigned char qr :1; // query/response flag 25 | 26 | unsigned char rcode :4; // response code 27 | unsigned char cd :1; // checking disabled 28 | unsigned char ad :1; // authenticated data 29 | unsigned char z :1; // its z! reserved 30 | unsigned char ra :1; // recursion available 31 | 32 | unsigned short q_count; // number of question entries 33 | unsigned short ans_count; // number of answer entries 34 | unsigned short auth_count; // number of authority entries 35 | unsigned short add_count; // number of resource entries 36 | } DNS_HEADER; 37 | 38 | 39 | //Constant sized fields of query structure 40 | typedef struct QUESTION 41 | { 42 | unsigned short qtype; 43 | unsigned short qclass; 44 | } QUESTION; 45 | 46 | //Constant sized fields of the resource record structure 47 | #pragma pack(push, 1) 48 | typedef struct R_DATA 49 | { 50 | unsigned short type; 51 | unsigned short _class; 52 | unsigned int ttl; 53 | unsigned short data_len; 54 | } R_DATA; 55 | #pragma pack(pop) 56 | 57 | //Structure of a response record 58 | //the name is not in dns format, just ascii 59 | typedef struct RES_RECORD 60 | { 61 | std::string name; 62 | struct R_DATA resource; 63 | std::string data; 64 | RES_RECORD(const RES_RECORD& obj) { 65 | name = obj.name; 66 | resource = obj.resource; 67 | data = obj.data; 68 | } 69 | RES_RECORD() {}; 70 | } RES_RECORD; 71 | 72 | //Structure of a Query 73 | //the name is not in dns format, just ascii 74 | typedef struct 75 | { 76 | std::string name; 77 | struct QUESTION ques; 78 | } QUERY; 79 | 80 | } 81 | 82 | // for comparing the DNS_HEADER 83 | bool operator==(const dns::DNS_HEADER& lhs, const dns::DNS_HEADER& rhs); 84 | bool operator!=(const dns::DNS_HEADER& lhs, const dns::DNS_HEADER& rhs); 85 | bool operator==(const dns::QUERY& lhs, const dns::QUERY& rhs); 86 | bool operator!=(const dns::QUERY& lhs, const dns::QUERY& rhs); 87 | bool operator!=(const dns::RES_RECORD& lhs, const dns::RES_RECORD& rhs); 88 | bool operator==(const dns::RES_RECORD& lhs, const dns::RES_RECORD& rhs); 89 | 90 | /* the actual dns_packet class */ 91 | class dns_packet { 92 | static std::string ascii_to_qname(const std::string&); 93 | static std::string qname_to_ascii(const std::string&); 94 | enum rtype { RESPONSE, AUTHORITY, ADDITIONAL }; 95 | int parse_response(const std::string&,int,int,rtype); 96 | protected: 97 | dns::DNS_HEADER header; 98 | public: 99 | // dns_type has effect on only the query flag for the header, 100 | // query flag defaults to 0, with dns::none 101 | dns_packet(unsigned short id, dns::dns_type type = dns::none); 102 | // this creates a dns_packet from bytes 103 | dns_packet(const std::string&); 104 | dns_packet(const dns_packet&); 105 | 106 | vec questions; 107 | vec responses; 108 | vec authorities; 109 | vec additionals; 110 | 111 | void add_question(std::string name, dns::qtype type = dns::ANY); 112 | void add_response(uint32_t data,dns::qtype type,unsigned int ttl = 0, uint16_t q_id = 0); 113 | void add_response(std::string data,dns::qtype type,unsigned int ttl = 0, uint16_t q_id = 0); 114 | void print_header() const; 115 | void print_questions() const; 116 | void print_responses() const; 117 | unsigned char rcode() const; 118 | // sets the response flag 119 | void set_response(); 120 | // returns the query in byte form 121 | std::string str(); 122 | }; 123 | #endif 124 | -------------------------------------------------------------------------------- /src/hsocket.cpp: -------------------------------------------------------------------------------- 1 | #include "hsocket.h" 2 | #ifdef WIN32 3 | #include 4 | #include 5 | #else 6 | #include 7 | #include 8 | #include 9 | #include 10 | #define INVALID_SOCKET 0 11 | #endif 12 | #include 13 | #include 14 | #include "ip_utility.h" 15 | 16 | // amount of sockets 17 | int hsocket::socket_n = 0; 18 | 19 | /* Constructor for the hsocket. 20 | * At the moment this does not support TCP fully 21 | * so only call it with hsocket::UDP 22 | */ 23 | hsocket::hsocket(hsocket::socket_t type) : type(type) { 24 | #ifdef WIN32 25 | // Only initiate Winsock2 if there are no sockets open 26 | if (socket_n == 0) { 27 | WSADATA wsaData; 28 | int iResult = WSAStartup(MAKEWORD(2,2),&wsaData); 29 | if (iResult != 0) { 30 | s = INVALID_SOCKET; 31 | return; 32 | } 33 | } 34 | #endif 35 | socket_n++; 36 | int t = SOCK_DGRAM; 37 | if (type == hsocket::TCP) 38 | t = SOCK_STREAM; 39 | s = ::socket(AF_INET,t,0); 40 | send_len = 0; 41 | } 42 | void hsocket::init(uint16_t port, uint32_t address, struct sockaddr_in& addr) { 43 | addr = {0}; 44 | addr.sin_family = AF_INET; 45 | addr.sin_port = htons(port); 46 | addr.sin_addr.s_addr = address; 47 | } 48 | 49 | /* hsocket::good() 50 | * used to see if socket is still good, or connnected (TCP). 51 | * In case of an error the value 52 | * of the socket is set to INVALID_SOCKET 53 | */ 54 | int hsocket::good() { 55 | return s != INVALID_SOCKET; 56 | } 57 | 58 | /* hsocket::connect(port,addr) 59 | * With UDP sockets this is used to specify the address 60 | * that the packets are sent to through the socket. 61 | * UDP is actually connectionless, but this is called connect 62 | * so that the use would be consistent with TCP and UDP sockets. 63 | */ 64 | void hsocket::connect(uint16_t port,const std::string& addr) { 65 | connect(port,ipton(addr)); 66 | } 67 | /* With this connect, the addr has to be in network byte order. 68 | */ 69 | void hsocket::connect(uint16_t port,uint32_t addr) { 70 | init(port,addr,send_addr); 71 | send_len = sizeof(send_addr); 72 | } 73 | /* hsocket::bind(port,addr) 74 | * starts listening on the port and interface 75 | */ 76 | void hsocket::bind(uint16_t port,const std::string& addr) { 77 | bind(port,ipton(addr)); 78 | } 79 | void hsocket::bind(uint16_t port,uint32_t addr) { 80 | init(port,addr,recv_addr); 81 | ::bind(s,(struct sockaddr*)&recv_addr,sizeof(recv_addr)); 82 | } 83 | void hsocket::close() { 84 | if (s != INVALID_SOCKET) { 85 | shutdown(s,2); 86 | s = INVALID_SOCKET; 87 | socket_n--; 88 | #ifdef WIN32 89 | // We should cleanup if this is the last open socket 90 | if (socket_n == 0) 91 | WSACleanup(); 92 | #endif 93 | } 94 | } 95 | hsocket& hsocket::operator<<(const std::string& data) { 96 | if (type == UDP) { 97 | sendto(s,data.data(),data.length(),0,(struct sockaddr*)&(send_addr),sizeof(send_addr)); 98 | } else { 99 | } 100 | return *this; 101 | } 102 | /* used to set the socket into either blocking or nonblocking mode 103 | * e.g. s << hsocket::NONBLOCKING 104 | */ 105 | hsocket& hsocket::operator<<(const block_mode_t mode) { 106 | #ifdef WIN32 107 | u_long nMode = (mode == NONBLOCKING) ? 1 : 0; 108 | if (ioctlsocket (s, FIONBIO, &nMode) == SOCKET_ERROR) { 109 | this->close(); 110 | } 111 | #else 112 | int flags = fcntl(s,F_GETFL); 113 | flags = (mode == NONBLOCKING) ? flags | O_NONBLOCK : flags & ~O_NONBLOCK; 114 | if (fcntl(s, F_SETFL, flags) != 0) { 115 | this->close(); 116 | } 117 | #endif 118 | return *this; 119 | } 120 | hsocket& hsocket::operator<<(const timeout t) { 121 | #ifdef WIN32 122 | DWORD timeout = t.t * 1000; 123 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); 124 | #else 125 | struct timeval timeout; 126 | timeout.tv_sec = t.t; 127 | timeout.tv_usec = 0; 128 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); 129 | #endif 130 | return *this; 131 | } 132 | hsocket& hsocket::operator>>(std::string& data) { 133 | char recieved[8190]; 134 | struct sockaddr_in dummy; 135 | socklen_t dummy_len = sizeof(struct sockaddr_in); 136 | int len = recvfrom(s,recieved,8190,0,(sockaddr*)&dummy,&dummy_len); 137 | if (len <= 0) return *this; 138 | if (!send_len) { 139 | send_addr = dummy; 140 | send_len = dummy_len; 141 | } else if (send_addr.sin_addr.s_addr != dummy.sin_addr.s_addr) { 142 | std::cout << "recieved data from " << std::hex << dummy.sin_addr.s_addr << ", expected from " << send_addr.sin_addr.s_addr << "\n"; 143 | return *this; 144 | } else { 145 | send_addr.sin_port = dummy.sin_port; 146 | } 147 | data.append(recieved,len); 148 | return *this; 149 | } 150 | -------------------------------------------------------------------------------- /src/hsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef __SOCKET_H 2 | #define __SOCKET_H 3 | #ifdef WIN32 4 | #include 5 | #include 6 | #include 7 | #else 8 | #define SOCKET int 9 | #include 10 | #endif 11 | #include 12 | /* When compiling for Windows, the first socket 13 | * opened runs WSAStartup, and when the last of 14 | * the currently active sockets closes, it runs 15 | * WSACleanup. 16 | */ 17 | 18 | class hsocket { 19 | public: 20 | class timeout { 21 | public: 22 | timeout(uint32_t t) : t(t) {}; 23 | uint32_t t; 24 | }; 25 | enum socket_t { TCP, UDP }; 26 | enum block_mode_t { NONBLOCKING, BLOCKING }; 27 | hsocket(socket_t); 28 | hsocket& operator<<(const block_mode_t); 29 | hsocket& operator<<(const timeout t); 30 | hsocket& operator<<(const std::string&); 31 | hsocket& operator>>(std::string&); 32 | int good(); 33 | // addr has to be in network byte order 34 | void connect(uint16_t port,uint32_t addr); 35 | void connect(uint16_t port,const std::string& addr); 36 | void close(); 37 | // addr has to be in network byte order 38 | void bind(uint16_t port,uint32_t addr); 39 | void bind(uint16_t port,const std::string& addr); 40 | private: 41 | // amount of open sockets 42 | static int socket_n; 43 | socket_t type; 44 | SOCKET s; 45 | /* If using UDP the first to connect to a listening 46 | * socket is marked as the only one to listen to. 47 | * Kind of simulating a connection with UDP. 48 | * send_addr contains the address to listen to. 49 | */ 50 | struct sockaddr_in send_addr; 51 | socklen_t send_len; 52 | struct sockaddr_in recv_addr; 53 | static void init(uint16_t,uint32_t,struct sockaddr_in&); 54 | 55 | }; 56 | #endif 57 | -------------------------------------------------------------------------------- /src/ip_utility.cpp: -------------------------------------------------------------------------------- 1 | #include "ip_utility.h" 2 | #include 3 | #ifdef WIN32 4 | #include 5 | #include 6 | #include 7 | // For some reason these are missing from the mingw windows headers 8 | extern "C" WINSOCK_API_LINKAGE const char* WSAAPI inet_pton(int af, const char* src, void* dest); 9 | extern "C" WINSOCK_API_LINKAGE const char* WSAAPI inet_ntop(int af, const void* src, 10 | char *dst, socklen_t size); 11 | #else 12 | #include 13 | #endif 14 | 15 | uint32_t ipton(const std::string& ipstr) { 16 | struct sockaddr_in sa; 17 | inet_pton(AF_INET,ipstr.c_str(), &(sa.sin_addr)); 18 | return sa.sin_addr.s_addr; 19 | // TODO ipv6 20 | } 21 | std::string ntoipstr(const std::string& ipstr) { 22 | std::string ret; 23 | if (ipstr.length() != 4) return ret; 24 | struct sockaddr_in sa; 25 | char str[INET_ADDRSTRLEN] = {0}; 26 | sa.sin_addr.s_addr = *(uint32_t*)ipstr.c_str(); 27 | inet_ntop(AF_INET,&(sa.sin_addr),str,INET_ADDRSTRLEN); 28 | ret = str; 29 | return ret; 30 | } 31 | std::string iptonstr(const std::string& ipstr) { 32 | struct sockaddr_in sa; 33 | inet_pton(AF_INET,ipstr.c_str(), &(sa.sin_addr)); 34 | uint32_t ip = sa.sin_addr.s_addr; 35 | std::string ret((char*)&ip,sizeof(ip)); 36 | return ret; 37 | // TODO ipv6 38 | } 39 | -------------------------------------------------------------------------------- /src/ip_utility.h: -------------------------------------------------------------------------------- 1 | #ifndef _IP_UTILITY_H 2 | #define _IP_UTILITY_H 3 | #include 4 | /** 5 | * Transforms an IP address in a string into a uint32_t into the network byte order. 6 | */ 7 | uint32_t ipton(const std::string& ipstr); 8 | std::string iptonstr(const std::string& ipstr); 9 | std::string ntoipstr(const std::string& ipstr); 10 | #endif 11 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | CC=g++ 2 | CFLAGS=-c 3 | LD=g++ 4 | LFLAGS=--std=c++14 5 | OBJECTS=ip_utility.o hsocket.o dns_packet.o message.o tunnel_dns.o convert_utility.o 6 | EXT=app 7 | WINE= 8 | 9 | 10 | all: tests bin 11 | 12 | win: CC=/usr/bin/x86_64-w64-mingw32-g++ 13 | win: CFLAGS=-c -DWIN32_LEAN_AND_MEAN 14 | win: LD=/usr/bin/x86_64-w64-mingw32-g++ 15 | win: LDFLAGS=--std=c++14 -lws2_32 -lmsvcrt -lkernel32 -Lsubsystem,console -static-libstdc++ -static-libgcc -Wl,-Bstatic -lstdc++ -lpthread 16 | win: EXT=exe 17 | win: WINE=wine 18 | 19 | win: all 20 | 21 | winbin: CC=/usr/bin/x86_64-w64-mingw32-g++ 22 | winbin: CFLAGS=-c -DWIN32_LEAN_AND_MEAN 23 | winbin: LD=/usr/bin/x86_64-w64-mingw32-g++ 24 | winbin: LDFLAGS=--std=c++14 -lws2_32 -lmsvcrt -lkernel32 -Lsubsystem,console -static-libstdc++ -static-libgcc -Wl,-Bstatic -lstdc++ -lpthread 25 | winbin: EXT=exe 26 | winbin: WINE=wine 27 | 28 | winbin: bin 29 | 30 | 31 | bin: server.${EXT} client.${EXT} 32 | 33 | tests: test.${EXT} 34 | ${WINE} ./test.${EXT} 35 | 36 | test.${EXT}: ${OBJECTS} test.o 37 | ${LD} ${OBJECTS} test.o -o test.${EXT} ${LDFLAGS} 38 | 39 | client.${EXT}: ${OBJECTS} client.o 40 | ${LD} ${OBJECTS} client.o -o client.${EXT} ${LDFLAGS} 41 | 42 | server.${EXT}: ${OBJECTS} server.o 43 | ${LD} ${OBJECTS} server.o -o server.${EXT} ${LDFLAGS} 44 | 45 | test.o: tests/main.cpp 46 | ${CC} ${CFLAGS} tests/main.cpp -o test.o 47 | 48 | server.o: server.cpp 49 | ${CC} ${CFLAGS} server.cpp -o server.o 50 | 51 | client.o: client.cpp 52 | ${CC} ${CFLAGS} client.cpp -o client.o 53 | 54 | %.o : %.cpp %.h 55 | ${CC} ${CFLAGS} $< -o $@ 56 | 57 | PHONY.: clean win winbin tests 58 | 59 | clean: 60 | rm ${OBJECTS} test.o client.o server.o 61 | -------------------------------------------------------------------------------- /src/message.cpp: -------------------------------------------------------------------------------- 1 | #include "message.h" 2 | #ifdef WIN32 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | /* Basic constructor 9 | */ 10 | message::message(message_type type, std::string data) : type(type), data(data) { 11 | } 12 | 13 | /* Parses a message from bytes 14 | */ 15 | message::message(std::string &bytes) { 16 | if (bytes.length() < 3) { 17 | this->type = M_ERROR; 18 | return; 19 | } 20 | this->type = (message_type)bytes[0]; 21 | if (this->type != HEARTBEAT && this->type != OK) { 22 | this->type = M_ERROR; 23 | return; 24 | } 25 | uint16_t data_len = *(uint16_t*)&(bytes[1]); 26 | data_len = ntohs(data_len); 27 | if (data_len + 3 > bytes.length()) { 28 | this->type = M_ERROR; 29 | return; 30 | } 31 | this->data = bytes.substr(3,data_len); 32 | bytes.erase(0,data_len+3); 33 | } 34 | 35 | /* Converts a message into bytes, with the following form: 36 | * |type|data length| data | 37 | * |8bit| 16bit | nbits| 38 | * Data length is in network byte order. 39 | */ 40 | std::string message::str() const { 41 | std::string ret; 42 | uint16_t len = this->data.length(); 43 | uint16_t network_len = htons(len); 44 | 45 | ret.append(1,this->type); 46 | ret.append((char*)&network_len,sizeof(uint16_t)); 47 | ret.append(this->data,0,len); 48 | return ret; 49 | } 50 | 51 | bool operator!=(const message& lhs, const message& rhs) { 52 | if (lhs.type != rhs.type) 53 | return true; 54 | if (lhs.data != rhs.data) 55 | return true; 56 | return false; 57 | } 58 | bool operator==(const message& lhs, const message& rhs) { 59 | return !(lhs != rhs); 60 | } 61 | -------------------------------------------------------------------------------- /src/message.h: -------------------------------------------------------------------------------- 1 | #ifndef _MESSAGE_H 2 | #define _MESSAGE_H 3 | #include 4 | 5 | class message { 6 | public: 7 | static const unsigned int MAXLEN = UINT16_MAX; 8 | enum message_type : uint8_t { HEARTBEAT, OK, M_ERROR }; 9 | message_type type; 10 | std::string data; 11 | 12 | message(message_type,std::string data = ""); 13 | 14 | message(std::string&); // construct a message from bytes 15 | std::string str() const; // convert a message into bytes 16 | }; 17 | bool operator!=(const message& lhs, const message& rhs); 18 | bool operator==(const message& lhs, const message& rhs); 19 | #endif 20 | -------------------------------------------------------------------------------- /src/pque.h: -------------------------------------------------------------------------------- 1 | #ifndef _PQUE_H 2 | #define _PQUE_H 3 | #include 4 | /* A priority que, where the smallest element is always returned when popped. 5 | * The que is implemented as an array with insert, remove and pop operations. 6 | */ 7 | 8 | template 9 | class pque { 10 | T* m_data; 11 | size_t m_size; 12 | size_t m_capacity; 13 | public: 14 | pque(size_t n = 5); 15 | ~pque(); 16 | void insert(const T&); 17 | T pop() const; 18 | void remove(); 19 | size_t size() const; 20 | void print() const; 21 | }; 22 | 23 | template 24 | pque::pque(size_t n) : m_size(0), m_capacity(n) { 25 | m_data = new T[n]; 26 | } 27 | 28 | template 29 | pque::~pque() { 30 | delete[] m_data; 31 | } 32 | 33 | template 34 | void pque::insert(const T& obj) { 35 | m_size++; 36 | if (m_size > m_capacity) { 37 | size_t new_capacity = m_capacity*2; 38 | T* temp = new T[new_capacity]; 39 | std::copy(m_data,m_data+m_capacity,temp); 40 | delete[] m_data; 41 | m_data = temp; 42 | m_capacity = new_capacity; 43 | } 44 | m_data[m_size-1] = obj; 45 | size_t last = m_size, current = m_size/2; 46 | while(current > 0) { 47 | if (m_data[current-1] > m_data[last-1]) { 48 | std::swap(m_data[current-1],m_data[last-1]); 49 | last = current; 50 | current = current/2; 51 | } else { 52 | break; 53 | } 54 | } 55 | } 56 | 57 | /* Returns the element at the root of the heap (smallest). 58 | * Does not remove the element, for that you have to call remove. 59 | */ 60 | template 61 | T pque::pop() const { 62 | if (m_size > 0) 63 | return m_data[0]; 64 | throw "Index out of bounds"; 65 | } 66 | 67 | /* Removes the smallest element in the heap and repairs the order. 68 | * Returns void. 69 | */ 70 | template 71 | void pque::remove() { 72 | if (m_size < 1) 73 | return; 74 | std::swap(m_data[0],m_data[--m_size]); 75 | size_t current = 0; 76 | // Loop while there's leaves left 77 | while((current+1)*2-1 < m_size) { 78 | size_t l_index = (current+1)*2-1; 79 | size_t r_index = (l_index < m_size-1) ? l_index+1 : l_index; 80 | size_t min_child = (m_data[l_index] < m_data[r_index]) ? l_index : r_index; 81 | if (m_data[current] < m_data[min_child]) break; 82 | 83 | std::swap(m_data[min_child],m_data[current]); 84 | current = min_child; 85 | } 86 | } 87 | 88 | /* Prints the current array of data. 89 | */ 90 | template 91 | void pque::print() const { 92 | for (size_t i = 0; i < m_size; i++) { 93 | std::cout << m_data[i] << '\n'; 94 | } 95 | } 96 | 97 | /* Returns the current size of the heap. 98 | */ 99 | template 100 | size_t pque::size() const { 101 | return m_size; 102 | } 103 | #endif 104 | -------------------------------------------------------------------------------- /src/server.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WIN32 2 | #include 3 | #include 4 | #define usleep Sleep 5 | #else 6 | #include 7 | #endif 8 | #include 9 | #include 10 | #include "hsocket.h" 11 | #include "dns_packet.h" 12 | #include "message.h" 13 | #include "tunnel_dns.h" 14 | #define RESP_LIM 10 15 | message make_message(std::string& data, message::message_type t); 16 | void handle_incoming(tunnel_dns& tun_in, hsocket& s, dns_packet& last); 17 | void handle_outgoing(tunnel_dns& tun_out, hsocket& s, dns_packet& last); 18 | void handle_stdin(std::string& buffer); 19 | 20 | int main(int argc, char** argv) { 21 | if (argc < 3) { 22 | std::cout << "Usage: " << argv[0] << " \n"; 23 | return -1; 24 | } 25 | hsocket s(hsocket::UDP); 26 | 27 | std::string domain = "example.org"; 28 | tunnel_dns tun_in(tunnel::INCOMING, dns::query_t, dns::A, domain); 29 | tunnel_dns tun_out(tunnel::OUTGOING, dns::response_t, dns::A, domain); 30 | tun_out.set_response_limit(RESP_LIM); 31 | message heartbeat(message::HEARTBEAT); 32 | 33 | std::string data_out,data_in; 34 | s.bind(std::stoi(argv[2]),argv[1]); 35 | 36 | s << hsocket::NONBLOCKING; 37 | dns_packet last(1,dns::none); 38 | while (1) { 39 | usleep(10); 40 | #ifndef WIN32 41 | // async IO does not work like this in Windows 42 | // TODO thread or WaitForSingleObject solution 43 | handle_stdin(data_out); 44 | #endif 45 | handle_incoming(tun_in,s,last); 46 | tun_in >> data_in; 47 | message m(data_in); 48 | if (m.type == message::OK) { 49 | std::cout << m.data << '\n'; 50 | } 51 | if (!data_out.empty()) { 52 | m = make_message(data_out,message::OK); 53 | tun_out << m.str(); 54 | } 55 | if (last.rcode() == dns::NON_ERROR && tun_out.bytes_available() == 0) { 56 | tun_out << heartbeat.str(); 57 | } 58 | handle_outgoing(tun_out,s,last); 59 | } 60 | s.close(); 61 | return 0; 62 | } 63 | 64 | message make_message(std::string& data,message::message_type t) { 65 | unsigned int maxlen = message::MAXLEN; 66 | message m(t,data.substr(0,maxlen)); 67 | data.erase(0,maxlen); 68 | return m; 69 | } 70 | 71 | void handle_outgoing(tunnel_dns& tun_out, hsocket& s, dns_packet& last) { 72 | if (last.rcode()!=dns::NON_ERROR || !tun_out.bytes_available()) 73 | return; 74 | 75 | for (int i = 0; i < RESP_LIM; i++) { 76 | std::string ip; 77 | tun_out >> ip; 78 | if (ip.empty()) 79 | break; 80 | last.add_response(ip,dns::A); 81 | } 82 | last.set_response(); 83 | s << last.str(); 84 | last = dns_packet(1,dns::none); 85 | } 86 | void handle_incoming(tunnel_dns& tun_in, hsocket& s, dns_packet& last) { 87 | std::string data; 88 | s >> data; 89 | if (data.empty()) 90 | return; 91 | dns_packet d(data); 92 | if (d.rcode() != dns::NON_ERROR || d.questions.size() == 0) 93 | return; 94 | last = std::move(d); 95 | std::string qname = last.questions[0].name; 96 | tun_in << qname; 97 | } 98 | 99 | void handle_stdin(std::string& buffer) { 100 | struct timeval tv; 101 | fd_set fds; 102 | FD_ZERO (&fds); 103 | FD_SET (STDIN_FILENO, &fds); 104 | tv.tv_sec = 0; 105 | tv.tv_usec = 100; 106 | int result = select (STDIN_FILENO + 1, &fds, NULL, NULL, &tv); 107 | if (result != 0) { 108 | std::string tmp; 109 | std::getline(std::cin, tmp); 110 | buffer.append(tmp); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/tests/main.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WIN32 2 | #include 3 | #include 4 | #define usleep Sleep 5 | #else 6 | #include 7 | #endif 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "../ip_utility.h" 13 | #include "../hsocket.h" 14 | #include "../dns_packet.h" 15 | #include "../message.h" 16 | #include "../tunnel_dns.h" 17 | #include "../convert_utility.h" 18 | #include "../pque.h" 19 | #include "../vec.h" 20 | 21 | /* A subclass for testing purposes, 22 | * where the header of the dns_packet 23 | * is exposed through a member function 24 | */ 25 | class dns_test_t : public dns_packet { 26 | public: 27 | dns::DNS_HEADER get_header(){ return this->header; } 28 | dns_test_t(unsigned short id, dns::dns_type type) : dns_packet(id,type) {}; 29 | dns_test_t(const std::string& str) : dns_packet(str) {}; 30 | }; 31 | 32 | 33 | // UDP socket tests 34 | std::string test_udp (int& success); 35 | std::string test_nonblocking_udp (int& success); 36 | 37 | // DNS packet tests 38 | std::string test_dns_to_str_questions (int& success); 39 | std::string test_dns_to_str_answers (int& success); 40 | std::string test_dns_query (int& success); 41 | 42 | // message class tests 43 | std::string test_message_to_str (int& success); 44 | 45 | // convert utility tests 46 | std::string test_convert_util (int& success); 47 | 48 | // tunnel_dns tests 49 | std::string test_tunnel_dns_out_q (int& success); 50 | std::string test_tunnel_dns_in_out_q (int& success); 51 | std::string test_tunnel_dns_out_r (int& success); 52 | 53 | // pque tests 54 | std::string test_pque (int& success); 55 | 56 | // vec tests 57 | std::string test_vec (int& success); 58 | 59 | int main(int argc, char** argv) { 60 | std::cout << "Running tests...\n"; 61 | std::vector tests = { 62 | test_udp, test_nonblocking_udp, 63 | test_dns_to_str_questions, test_dns_to_str_answers, test_dns_query, 64 | test_message_to_str, 65 | test_convert_util, 66 | test_tunnel_dns_out_q, 67 | test_tunnel_dns_in_out_q, 68 | test_tunnel_dns_out_r, 69 | test_pque, 70 | test_vec }; 71 | 72 | int succeeded = tests.size(); 73 | for ( auto test : tests) { 74 | int success = 0; 75 | std::string test_name = test(success); 76 | if (success) { 77 | std::cout << "\e[38;5;40mSuccess: "; 78 | } else { 79 | std::cout << "\e[38;5;196mFail: "; 80 | succeeded--; 81 | } 82 | std::cout << test_name << "\e[39m\n"; 83 | } 84 | if (succeeded == tests.size()) { 85 | std::cout << "All tests passed!\n"; 86 | return 0; 87 | } else { 88 | std::cout << succeeded << " tests passed, " << tests.size()-succeeded << " tests failed\n"; 89 | return 1; 90 | } 91 | } 92 | 93 | /* Tests are defined here 94 | * 95 | */ 96 | 97 | // hsocket tests start 98 | 99 | std::string test_udp (int& success) { 100 | hsocket s(hsocket::UDP); 101 | s.bind(1337,"127.0.0.1"); 102 | hsocket sender(hsocket::UDP); 103 | sender.connect(1337,"127.0.0.1"); 104 | 105 | std::string data = "udp_listen_test"; 106 | std::string received; 107 | 108 | sender << data; 109 | s >> received; 110 | 111 | s.close(); 112 | sender.close(); 113 | success = data == received; 114 | return "UDP blocking test"; 115 | } 116 | std::string test_nonblocking_udp (int& success) { 117 | hsocket listener(hsocket::UDP); 118 | hsocket connector(hsocket::UDP); 119 | listener.bind(1234,"127.0.0.1"); 120 | listener << hsocket::NONBLOCKING; 121 | connector << hsocket::NONBLOCKING; 122 | connector.connect(1234,"127.0.0.1"); 123 | success = 0; 124 | int sent = 0; 125 | std::string recieved; 126 | std::string data = "Test data 123123"; 127 | while (1) { 128 | usleep(100); 129 | listener >> recieved; 130 | if (!sent) { 131 | connector << data; 132 | sent = 1; 133 | } else { 134 | if (recieved.length()) 135 | break; 136 | } 137 | } 138 | listener.close(); 139 | connector.close(); 140 | success = recieved == data; 141 | return "UDP nonblocking test"; 142 | } 143 | 144 | // dns tests start 145 | 146 | std::string test_dns_to_str_questions (int& success) { 147 | // create a dns query packet with two questions 148 | dns_test_t first (1337,dns::query_t); 149 | first.add_question("helsinki.fi", dns::A); 150 | first.add_question("helsinki.fi", dns::ANY); 151 | 152 | // create the second from the byte representation of the first 153 | std::string d = first.str(); 154 | dns_test_t second(d); 155 | 156 | success = 1; 157 | // compare the two 158 | if (first.get_header() != second.get_header()) { 159 | std::cout << "testing headers failed\n"; 160 | first.print_header(); 161 | second.print_header(); 162 | success = 0; 163 | } 164 | 165 | if (first.questions.size() == second.questions.size()) { 166 | for (int i = 0 ; i < 2; ++i) { 167 | if (first.questions[i] != second.questions[i]) { 168 | first.print_questions(); 169 | second.print_questions(); 170 | success = 0; 171 | break; 172 | } 173 | } 174 | } else { 175 | success = 0; 176 | } 177 | return "DNS to str test with questions"; 178 | } 179 | std::string test_dns_to_str_answers (int& success) { 180 | // create a dns query packet with two questions 181 | dns_test_t first (1337,dns::query_t); 182 | first.add_question("helsinki.fi", dns::A); 183 | // add response 184 | first.add_response(ipton("127.0.0.1"),dns::A); 185 | 186 | // create the second from the byte representation of the first 187 | std::string d = first.str(); 188 | dns_test_t second(d); 189 | 190 | success = 1; 191 | // compare the two 192 | if (first.get_header() != second.get_header()) { 193 | std::cout << "testing headers failed\n"; 194 | first.print_header(); 195 | second.print_header(); 196 | success = 0; 197 | } 198 | 199 | if (first.questions.size() == second.questions.size()) { 200 | for (int i = 0 ; i < first.questions.size(); ++i) { 201 | if (first.questions[i] != second.questions[i]) { 202 | first.print_questions(); 203 | second.print_questions(); 204 | success = 0; 205 | break; 206 | } 207 | } 208 | } else if (first.responses.size() == second.responses.size()) { 209 | for (int i = 0 ; i < first.responses.size(); ++i) { 210 | if (first.responses[i] != second.responses[i]) { 211 | first.print_responses(); 212 | second.print_responses(); 213 | success = 0; 214 | break; 215 | } 216 | } 217 | } else { 218 | success = 0; 219 | } 220 | return "DNS to str test with questions and answers"; 221 | } 222 | /* 223 | * Asks google dns 8.8.8.8 for an A record of helsinki.fi. 224 | * expected result is 128.214.222.24. 225 | */ 226 | std::string test_dns_query (int& success) { 227 | // title 228 | std::string ret = "DNS real dns query for helsinki.fi"; 229 | 230 | // form a network byte order data string 231 | std::string expected_ip = iptonstr("128.214.222.24"); 232 | hsocket s(hsocket::UDP); 233 | // Googles DNS 234 | s.connect(53,"8.8.8.8"); 235 | // create the packet 236 | dns_test_t query(1337,dns::query_t); 237 | query.add_question("helsinki.fi",dns::A); 238 | std::string recieved; 239 | int counter = 0; 240 | // try to send 2 times 241 | s << hsocket::timeout(1); 242 | while (recieved.empty()) { 243 | s << query.str(); 244 | s >> recieved; 245 | counter++; 246 | if (counter > 2) { 247 | success = 0; 248 | std::cout << "Error: timeout, retry limit for DNS query to 8.8.8.8 exceeded (> 2)\n"; 249 | return ret; 250 | } 251 | } 252 | // check the response 253 | success = 0; 254 | dns_test_t resp(recieved); 255 | 256 | if (resp.get_header().rcode != dns::NON_ERROR) { 257 | std::cout << "Error: query failed\n"; 258 | resp.print_header(); 259 | return ret; 260 | } 261 | if (!resp.responses.size()) { 262 | std::cout << "no responses from 8.8.8.8\n"; 263 | return ret; 264 | } 265 | 266 | for (auto r : resp.responses) { 267 | if (ntohs(r.resource.type) == dns::A) { 268 | success |= r.data == expected_ip; 269 | } 270 | } 271 | 272 | return ret; 273 | } 274 | 275 | 276 | std::string test_message_to_str (int& success) { 277 | std::string ret = "Message to str test"; 278 | 279 | message hbeat(message::HEARTBEAT,"heartbeat_test"); 280 | std::string bytes = hbeat.str(); 281 | message result(bytes); 282 | success = 1; 283 | if (result != hbeat) { 284 | std::cout << "HEARTBEAT message failed\n"; 285 | success = 0; 286 | } 287 | 288 | message ok(message::OK, "oktest"); 289 | bytes = ok.str(); 290 | message ok_result(bytes); 291 | if (ok_result != ok) { 292 | std::cout << "OK message failed\n"; 293 | success = 0; 294 | } 295 | 296 | message not_ok(message::M_ERROR, "error_test"); 297 | bytes = not_ok.str(); 298 | message not_ok_result(bytes); 299 | if (not_ok == not_ok_result) { 300 | std::cout << "ERROR message failed\n"; 301 | success = 0; 302 | } 303 | 304 | return ret; 305 | } 306 | 307 | std::string test_convert_util (int& success) { 308 | std::string ret = "Convert utility test"; 309 | std::string data = "0123456789ABCDEFHIJKLMNOPQRSTUVWXYZ"; 310 | std::string result = to_hex(data,data.length()); 311 | success = (data == from_hex(result)) ? 1 : 0; 312 | if (!success) { 313 | std::cout << "Result was " << from_hex(result) << '\n'; 314 | } 315 | return ret; 316 | } 317 | 318 | 319 | std::string test_tunnel_dns_out_q (int& success) { 320 | std::string ret = "Tunnel DNS outgoing query test"; 321 | tunnel_dns tun(tunnel::OUTGOING,dns::query_t,dns::A,"helsinki.fi"); 322 | std::string data = "AAAA"; 323 | tun << data; 324 | std::string output; 325 | tun >> output; 326 | success = (output == "41414141.helsinki.fi") ? 1 : 0; 327 | if (!success) { 328 | std::cout << "Output was " << output << '\n'; 329 | } 330 | data = "AAA AAA AAA AAA AAA AAA AAA AAAA"; 331 | tun << data; 332 | output.clear(); 333 | tun >> output; 334 | // longer output so it gets split into two labels 335 | if (output != "414141204141412041414120414141204141412041414120414141204141.4141.helsinki.fi") 336 | success = 0; 337 | if (!success) { 338 | std::cout << "Output was " << output << '\n'; 339 | } 340 | return ret; 341 | } 342 | std::string test_tunnel_dns_in_out_q (int& success) { 343 | std::string ret = "Tunnel DNS outgoing and incoming query test"; 344 | tunnel_dns tun_out(tunnel::OUTGOING,dns::query_t,dns::A,"helsinki.fi"); 345 | tunnel_dns tun_in(tunnel::INCOMING,dns::query_t,dns::A,"helsinki.fi"); 346 | std::string data = "This is a test", dns_data, result; 347 | 348 | tun_out << data; 349 | tun_out >> dns_data; 350 | 351 | tun_in << dns_data; 352 | tun_in >> result; 353 | 354 | success = (data == result) ? 1 : 0; 355 | if (!success) { 356 | std::cout << "Output was " << result << " instead of " << data << '\n'; 357 | } 358 | return ret; 359 | } 360 | std::string test_tunnel_dns_out_r (int& success) { 361 | std::string ret = "Tunnel DNS outgoing response test"; 362 | tunnel_dns tun(tunnel::OUTGOING, dns::response_t, dns::A,"helsinki.fi"); 363 | std::string data = "test", result; 364 | std::vector responses; 365 | std::vector results; 366 | 367 | results.push_back("0.4.116.101"); 368 | results.push_back("1.115.116.0"); 369 | 370 | tun.set_response_limit(10); 371 | tun << data; 372 | for (int i = 0; i < 10; i++) { 373 | std::string temp; 374 | tun >> temp; 375 | if (temp.empty()) break; 376 | std::string ip = ntoipstr(temp); 377 | responses.push_back(ip); 378 | } 379 | success = (responses == results) ? 1 : 0; 380 | if (!success) { 381 | for (auto a : responses) { 382 | std::cout << "IP " << a << '\n'; 383 | } 384 | } 385 | return ret; 386 | } 387 | 388 | std::string test_pque (int& success) { 389 | std::string ret = "Pque test"; 390 | 391 | std::vector v,vres; 392 | v.push_back("8test"); 393 | v.push_back("3test"); 394 | v.push_back("5test"); 395 | v.push_back("2test"); 396 | v.push_back("0test"); 397 | v.push_back("3test"); 398 | v.push_back("1test"); 399 | v.push_back("atest"); 400 | 401 | // new queue 402 | pque Q(1); 403 | for (auto s : v) 404 | Q.insert(s); 405 | while(Q.size() > 0) { 406 | vres.push_back(Q.pop()); 407 | Q.remove(); 408 | } 409 | std::sort(v.begin(),v.end(),std::less()); 410 | 411 | success = vres == v; 412 | if (!success) { 413 | for (auto s : v) 414 | std::cout << s << '\n'; 415 | std::cout << '\n'; 416 | for (auto s : vres) 417 | std::cout << s << '\n'; 418 | } 419 | return ret; 420 | } 421 | 422 | std::string test_vec (int& success) { 423 | std::string ret = "Vec test"; 424 | vec v(1); 425 | v.push_back("1test"); 426 | v.push_back("2test"); 427 | v.push_back("3test"); 428 | v.push_back("4test"); 429 | std::vector res ({ "1test","2test","3test","4test" }); 430 | success = res.size() == v.size(); 431 | for (int i = 0; i < std::min(res.size(),v.size()); i++) { 432 | success &= v[i] == res[i]; 433 | } 434 | return ret; 435 | 436 | } 437 | 438 | -------------------------------------------------------------------------------- /src/tunnel.h: -------------------------------------------------------------------------------- 1 | #ifndef _TUNNEL_H 2 | #define _TUNNEL_H 3 | #include 4 | 5 | class tunnel { 6 | public: 7 | enum tunnel_type { INCOMING, OUTGOING }; 8 | virtual tunnel& operator<<(const std::string&) = 0; 9 | virtual tunnel& operator>>(std::string&) = 0; 10 | virtual size_t bytes_available() const = 0; 11 | protected: 12 | tunnel_type t_type; 13 | tunnel(tunnel_type t_type) : t_type(t_type) {}; 14 | }; 15 | #endif 16 | -------------------------------------------------------------------------------- /src/tunnel_dns.cpp: -------------------------------------------------------------------------------- 1 | #include "tunnel_dns.h" 2 | #include "dns_packet.h" 3 | #include "convert_utility.h" 4 | 5 | /* Creates a tunnel, which transforms raw data into 6 | * domain names or dns responses, or vice versa. 7 | * For example: 8 | * tunnel_dns my_tunnel(tunnel::OUTGOING, dns::query_t, dns::A) 9 | */ 10 | tunnel_dns::tunnel_dns(tunnel_type t_type, dns::dns_type d_type, dns::qtype q_type, std::string domain) : tunnel(t_type),d_type(d_type),q_type(q_type),domain(domain),response_limit(1),response_count(0),response_len(0) { 11 | } 12 | size_t tunnel_dns::bytes_available() const { 13 | return data.length(); 14 | } 15 | 16 | tunnel_dns& tunnel_dns::operator<<(const std::string& d) { 17 | if (t_type == OUTGOING) { 18 | data.append(d); 19 | if (d_type == dns::response_t) 20 | response_count = 0; 21 | } else { 22 | this->dns_to_data(d); 23 | } 24 | return *this; 25 | } 26 | /* Note: this does not append to the string. 27 | * This replaces a given string with the dns data. 28 | */ 29 | tunnel_dns& tunnel_dns::operator>>(std::string& d) { 30 | if (t_type == OUTGOING) { 31 | d = data_to_dns(); 32 | } else { 33 | d.append(data); 34 | data.clear(); 35 | } 36 | return *this; 37 | } 38 | 39 | /* Converts data from buffer into a dns record or domain name. 40 | * At the moment only questions of A type are supported. 41 | * Only used in OUTGOING tunnels. 42 | */ 43 | std::string tunnel_dns::data_to_dns() { 44 | std::string ret; 45 | if (this->data.empty()) 46 | return ret; 47 | if (q_type != dns::A) 48 | return ret; 49 | 50 | if (d_type == dns::query_t) { 51 | for (int i = 0; i < 2; i++) { 52 | ret.append(to_hex(this->data,MAX_DNS_LABEL)); 53 | ret.append(1,'.'); 54 | this->data.erase(0,MAX_DNS_LABEL); 55 | if (data.empty()) 56 | break; 57 | } 58 | ret.append(this->domain); 59 | } else { 60 | // Max amount of data is calculated like this because there is 61 | // metadata for more information check dns_protocol document 62 | // (which doesn't exist yet) TODO 63 | int data_left = response_limit*4 - response_limit - 1; 64 | 65 | if (response_count > 0) 66 | data_left -= (response_count-1)*3 - 2; 67 | 68 | if (data_left > data.length()) 69 | data_left = data.length(); 70 | 71 | if (data_left == 0) return ret; 72 | 73 | if (response_count == 0) { 74 | ret.append(1,(char)response_count++); 75 | ret.append(1,(char)data_left); 76 | ret.append(data.substr(0,2)); 77 | 78 | while (ret.length() < 4) ret.append(1,'\0'); 79 | data.erase(0,2); 80 | } else { 81 | ret.append(1,(char)response_count++); 82 | ret.append(data.substr(0,3)); 83 | 84 | while (ret.length() < 4) ret.append(1,'\0'); 85 | data.erase(0,3); 86 | } 87 | if (response_count >= response_limit) 88 | response_count = 0; 89 | } 90 | return ret; 91 | } 92 | 93 | /* Converts data from dns form to raw data and appends it 94 | * to the data buffer. 95 | * At the moment only A type is supported 96 | * Only used in INCOMING tunnels. 97 | */ 98 | void tunnel_dns::dns_to_data(std::string d) { 99 | if (q_type != dns::A) 100 | return; 101 | 102 | if (d_type == dns::query_t) { 103 | size_t end = d.rfind(domain); 104 | if (end == std::string::npos) return; 105 | std::string subdomain = d.substr(0,end); 106 | 107 | size_t pos = 0, dot = std::string::npos; 108 | while ((dot = subdomain.find('.',pos)) != std::string::npos) { 109 | std::string hex_data = subdomain.substr(pos,dot-pos); 110 | data.append(from_hex(hex_data)); 111 | pos = dot+1; 112 | } 113 | } else { 114 | if (d[0] != response_count) 115 | return; 116 | 117 | if (d[0] == 0) { 118 | response_len = d[1]; 119 | 120 | int length = (response_len < 2) ? response_len : 2; 121 | data.append(d.substr(2,length)); 122 | response_len -= length; 123 | } else { 124 | int length = (response_len < 3) ? response_len : 3; 125 | data.append(d.substr(1,length)); 126 | response_len -= length; 127 | } 128 | response_count++; 129 | if (response_count >= response_limit || response_len <= 0) 130 | response_count = 0; 131 | } 132 | } 133 | 134 | /* Sets how many response records max per query we want to 135 | * generate. 136 | */ 137 | void tunnel_dns::set_response_limit(int limit) { 138 | response_limit = limit; 139 | } 140 | -------------------------------------------------------------------------------- /src/tunnel_dns.h: -------------------------------------------------------------------------------- 1 | #ifndef _TUNNEL_DNS_H 2 | #define _TUNNEL_DNS_H 3 | #include "tunnel.h" 4 | #include "dns_packet.h" 5 | 6 | class tunnel_dns : protected tunnel { 7 | static const int MAX_DNS_LABEL = 30; 8 | const dns::dns_type d_type; 9 | const dns::qtype q_type; 10 | std::string data; 11 | std::string domain; 12 | std::string data_to_dns(); 13 | void dns_to_data(std::string); 14 | int response_limit; 15 | int response_count; 16 | int response_len; 17 | public: 18 | tunnel_dns(tunnel_type, dns::dns_type,dns::qtype,std::string); 19 | virtual tunnel_dns& operator<<(const std::string&); 20 | virtual tunnel_dns& operator>>(std::string&); 21 | virtual size_t bytes_available() const; 22 | void set_response_limit(int); 23 | }; 24 | #endif 25 | -------------------------------------------------------------------------------- /src/vec.h: -------------------------------------------------------------------------------- 1 | #ifndef __VEC_H 2 | #define __VEC_H 3 | #include 4 | 5 | template 6 | class vec { 7 | T* m_data; 8 | size_t m_size; 9 | size_t m_capacity; 10 | public: 11 | typedef const T* const_iterator; 12 | typedef T* iterator; 13 | iterator begin() { return m_data; } 14 | iterator end() { return m_data+m_size; }; 15 | const_iterator begin() const { return m_data; } 16 | const_iterator end() const { return m_data+m_size; }; 17 | const T& operator[](size_t); 18 | vec& operator=(const vec&); 19 | vec& operator=(vec&); 20 | 21 | size_t size() const; 22 | void push_back(T el); 23 | void clear(); 24 | vec(size_t n = 10); 25 | vec(const vec& obj); 26 | ~vec(); 27 | }; 28 | 29 | template 30 | vec::vec(const vec& obj) : m_data(nullptr) { 31 | m_size = m_capacity = obj.m_size; 32 | m_data = new T[m_capacity]; 33 | std::copy(obj.m_data,obj.m_data+obj.m_size,m_data); 34 | } 35 | 36 | template 37 | vec& vec::operator=(vec& obj) { 38 | if (m_data != nullptr) 39 | delete [] m_data; 40 | m_size = obj.m_size; 41 | m_capacity = obj.m_capacity; 42 | m_data = obj.m_data; 43 | obj.m_data = nullptr; 44 | obj.m_size = obj.m_capacity = 0; 45 | return *this; 46 | } 47 | 48 | template 49 | vec& vec::operator=(const vec& obj) { 50 | if (m_data != nullptr) 51 | delete [] m_data; 52 | m_size = m_capacity = obj.m_size; 53 | m_data = new T[m_capacity]; 54 | std::copy(obj.m_data,obj.m_data+obj.m_size,m_data); 55 | return *this; 56 | } 57 | 58 | template 59 | const T& vec::operator[](size_t i) { 60 | if (i < m_size) { 61 | return m_data[i]; 62 | } 63 | return m_data[0]; 64 | } 65 | 66 | template 67 | void vec::clear() { 68 | m_size = 0; 69 | } 70 | 71 | template 72 | vec::vec(size_t n) : m_size(0), m_capacity(n), m_data(nullptr) { 73 | if (n == 0) { 74 | m_capacity = 1; 75 | } 76 | m_data = new T[m_capacity]; 77 | } 78 | 79 | template 80 | vec::~vec() { 81 | if (m_data != nullptr) 82 | delete[] m_data; 83 | } 84 | 85 | template 86 | void vec::push_back(T el) { 87 | if (++m_size > m_capacity) { 88 | if (m_capacity == 0) 89 | m_capacity = 1; 90 | size_t new_capacity = m_capacity*2; 91 | T* temp = new T[new_capacity]; 92 | if (m_data) { 93 | std::copy(m_data,m_data+m_size-1,temp); 94 | delete[] m_data; 95 | } 96 | m_data = temp; 97 | m_capacity = new_capacity; 98 | } 99 | m_data[m_size-1] = el; 100 | } 101 | 102 | template 103 | size_t vec::size() const { 104 | return m_size; 105 | } 106 | #endif 107 | --------------------------------------------------------------------------------