Автор Гілка: Кастомний DNS сервер на С  (Прочитано 2454 раз)

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Доброго дня усім_) Підкажіть будь-ласка на які таскі можна розбити задачу написання свого DNS сервера (хочу зрозуміти логіку роботи програми). Ось напиклад:
1. Створюємо сокет для вхідних конектів (AF_INET,SOCK_STREAM,INADDR_ANY, номер порту )
2. Якось парсим те що прийшло (запити до DNS мають певний формат).
3. Створюємо сокет який відправить запит до вторичного DNS (по протоколу UDP).
4. Отримуємо відповідь від вторичного DNS (отримуєм IP запитуваного нами домену).
5. Відправляєм ці данні DNS клієнту який робив запит
6. Що відбувається далі? Що DNS клієнт робить з отриманим IP? Який алгоритм отримання DNS клієнтом  веб сторінки по даному  ip, наприклад якщо це браузер?
От якесь таке уявлення на даний момент у мене сформувалося. Я розумію що воно хибне скоріше всього.
[/size]
« Змінено: 2017-02-24 22:55:28 від Free Rick »

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #1 : 2017-02-25 00:15:19 »
Залежить від того, що ви хочете робити. Зазвичай DNS-сервери бувають трьох типів (або можуть поєднувати їх):
  • Резолвер
  • Авторитетний
  • Слейв
Резолвер, або кеш — це найближче до того, про що ви говорите. Це те, що використовують провайдери інтернета, коли видають користувачам налаштування. Це сервер, що приймає від клієнта (браузера) запит, дивиться, чи є вже цей запит у нього в кеші, якщо немає — робить повний ланцюжок запитів (або скорочений, якщо щось з ланцюжка уже є у нього в кеші) — до кореневих серверів, і аж до NS доменної зони. Тобто для домена linux.org.ua — спочатку звертається до кореневих серверів за NS зони ua, потім до NS зони ua за NS зони org.ua, потім до NS зони org.ua за NS зони linux.org.ua, і вже до NS зони linux.org.ua — за IP цього домена.

Авторитетний — відповідає лише на запити щодо того домена, що він обслуговує, на все інше він відмовляється відповідати. Такі сервери використовують хостинг-провайдери та реєстратори доменних імен. Тобто для linux.org.ua — це ns1.h02.hvosting.ua.

Слейв — це теж авторитетний сервер, але він синхронізує свої дані з основним авторитетним сервером. Для стійкості системи DNS потнібно, щоб домен обслуговувало принаймні два NS, при цьому зрозуміло, що лише один з них буде авторитетним, а інший буде слейвом, що з ним синхронізується. Також використовується хостерами. В нашому випадку — ns2.h02.hvosting.ua.

Загалом я з серверною стороною структур dns не працював, але клієнтська сторона повністю є в стандартних заголовках C. Серверну — або використовувати заголовки демонів на кшталт bind, або писати свої структури за RFC протоколу.

Взагалі не думаю, що варто писати dns-сервер — їх зараз повно, і є з чого вибрати, як за якістю, так і за функціональністю. Ваш проект матиме дуже сильних конкурентів.

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #2 : 2017-02-25 00:17:55 »
Щодо отримання сторінки — то це вже не DNS, а HTTP починається.

Браузер створює з'єднання з IP на 80 порт, і віддає HTTP-запит та заголовок Host для вказання того, який саме сайт потрібен, якщо на одному IP крутиться декілька сайтів. Якось так:

GET / HTTP/1.0
Host: linux.org.ua


Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #3 : 2017-02-25 02:25:36 »

Щодо отримання сторінки — то це вже не DNS, а HTTP починається.

Ага... зрозумів. Дякую що відгукнулися. То на 53 порті у мене в Ubuntu авторитетний слухає DNS клієнтів? Я просто початківець і хочу попасти до одної компанії на стажування. І це моє тестове завдання. Мені треба написати DNS проксі-сервер який має чорний список доменних імен і не пускає на них DNS клієнтів. Я це розумію так: що треба написати свій авторитетний DNS сервер який (якщо доменне ім'я одобрене) звертатиметься до резолвера або слейва і повертатиме IP. І я так поки думаю щоб не використовувати iptables він має уже від початку знаходитись на 53 порті. До речі я вже дещо написав але тепер не можу запустити його на 53, каже що порт уже зайнятий. Коли переписую щоб слухала з термінала то програма повертає IP-шнікі доменів а щоб слухала з порту то не можу протестити. Не знаю як звільнити 53 порт_((
     І ще одне. А браузер в основному по UDP протоколу запитує DNS сервера адресу домена? але скрізь кажуть що і по UDP і по TCP. То це значить що в програмі  для прослухавування конектів потрібно створити по сокету на кожен протокол. А таке можливо? 
« Змінено: 2017-02-25 02:54:43 від Free Rick »

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #4 : 2017-02-25 03:01:10 »
На 53 порті у вас скоріше за все слухає або systemd-resolved, або unbound (локальний резолвер). Точно дізнатися можна від рута запустивши команду

Код: Bash
  1. netstat -lpon

і вивчивши її вивід.

Ні, в даному випадку у вас резолвер, але урізаний, який не резолвить сам, а передає запити резолверу вище рівнем. Можна сказати, четвертий тип — "чистий кеш", такі теж можуть використовуватися великими провайдерами для того, щоб кешувати якмога більше, але не перевантажувати один сервер всіма запитами від всіх клієнтів.

Тобто клієнт звертається до вашого сервера, він приймає з'єднання, розбирає запит, перевіряє по списку, якщо в списку — віддає помилку (порожній результат), якщо ні — передає запит резолверу вище (тобто в цьому разі робить те саме, що зробив браузер, звертаючись до нього самого), очікує відповіді від нього, і пересилає відповідь назад клієнту.

Edit:
Точніше у вас не "чистий кеш", а саме проксі, бо він у вас нічого не кешує (хоча міг би).

P.S. Yeah, abusing admin powers :)

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #5 : 2017-02-25 03:08:31 »
А можуть два резолвера (мій та той який на 53) слухати 53? напевно ні, тому треба на 53 залишити тільки мій, так? У мене dnsmasq  там
« Змінено: 2017-02-25 03:19:05 від Free Rick »

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #6 : 2017-02-25 03:14:21 »
Ні. Вони можуть слухати на одному порті, але на різних інтерфейсах, і скоріше за все те, що у вас — слухає на 127.0.0.1, принаймні я не думаю, що на десктопі з коробки ставиться загальнодоступний кеш. Тоді можете спробувати прибіндитися до свого мережевого IP. Але хто його знає, може слухати і на 0.0.0.0 (тобто всіх інтерфейсах), а фільтрувати вже на рівні демона.

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #7 : 2017-02-25 03:22:04 »
у мене dnsmasq  там (на 127.0.1.1:53) Мені прийдеться його вимкнути щоб свій запустити?
« Змінено: 2017-02-25 03:33:36 від Free Rick »

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #8 : 2017-02-25 03:29:37 »
dnsmasq — це такий собі комбайн. Залежно від того, як його налаштовано — він може бути резолвером, авторитетним сервером, dhcp-сервером, bootp-сервером, і tftp-сервером. Тому не можу сказати, що саме він у вас робить. Але радив би спочатку спробувати прибіндитися до айпішки комп'ютера, бо є вірогідність що з його вимкненням у вас пропаде системний резолвер dns.

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #9 : 2017-02-25 03:37:33 »
А, ну іще не забувайте про те, що у вас сервер, тобто він має обробляти запити постійно — тобто вам потрібно або форкати воркерів, що будуть обробляти запит користувача (один воркер на запит, що для активного dns може швидко вичерпати ресурси системи), або працювати в асинхронному режимі (тобто вам не вдасться використати системні функції для того, щоб передавати запит реальному резолверу — вони блокуючі (програма припиняє своє виконання доки фукнція не поверне результат)).

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #10 : 2017-02-25 03:39:20 »
... спочатку спробувати прибіндитися до айпішки комп'ютера, бо є вірогідність що з його вимкненням у вас пропаде системний резолвер dns.
Не зрозумів_))))

Даниленко Миха́йло

  • Гість
Re: Кастомний DNS сервер на С
« Відповідей #11 : 2017-02-25 04:17:27 »
Є вірогідність, що ваш браузер і інші програми на комп'ютері використовують dnsmask для отримання dns. Тобто ваш комп'ютер працює в ролі кешуючого резолвера для самого себе. Якщо його вимкнути — треба буде або змінювати /etc/resolv.conf та прописувати DNS-сервери вашого провайдера, або у вас може перестати працювати розв'язання dns-адрес.

Отож я б рекомендував у вашому демоні спробувати слухати порт на конкретній адресі (з вашої локальної мережі чи на тій, що вам видає провайдер, якщо ви напряму приєднані до інтернету). Наприклад, у мене це 192.168.0.35 — адреса в моїй локальній мережі.

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #12 : 2017-02-28 04:51:53 »
Залишу це тут для наступних поколінь_))) Але потрібно дописати одну функцію (це додасть розуміння того що робиться в коді). Тут є все щоб зробити свій DNS.


//**************************************************************//
//             SERVER END UDP/IP APPLICATION                //
//                          DNS by Parfeniuk B.O.                        //
//***************************************************************************//
#include <stdio.h>                          // Standard input and output
#include <sys/socket.h>                     // For socket(), connect(), send(), and recv()
#include <arpa/inet.h>                      // For sockaddr_in and inet_addr()
#include <stdlib.h>                         // For atoi() and exit()
#include <string.h>                         // For memset()
#include <unistd.h>                         // For close()
#include <netdb.h>                          // For gethostbyname()
#include <time.h>                           // For time_t


//List of DNS Servers registered on the system
char dns_servers[10][100];
int dns_server_count = 0;
//Types of DNS resource records


#define T_A 1 //Ipv4 address
#define T_NS 2 //Nameserver
#define T_CNAME 5 // canonical name
#define T_SOA 6 /* start of authority zone */
#define T_PTR 12 /* domain name pointer */
#define T_MX 15 //Mail server


//Function Prototypes
void ngethostbyname (unsigned char* , int);
void ChangetoDnsNameFormat (unsigned char*,unsigned char*);
unsigned char* ReadName (unsigned char*,unsigned char*,int*);
void get_dns_servers();




/*** The structure for DNS header ***/
typedef struct
{
unsigned short id;       // identification number
unsigned char rd :1;     // recursion desired
unsigned char tc :1;     // truncated message
unsigned char aa :1;     // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1;     // query/response flag
unsigned char rcode :4;  // response code
unsigned char cd :1;     // checking disabled
unsigned char ad :1;     // authenticated data
unsigned char z :1;      // its z! reserved
unsigned char ra :1;     // recursion available
unsigned short q_count;  // number of question entries
unsigned short ans_count; // number of answer entries
unsigned short auth_count; // number of authority entries
unsigned short add_count; // number of resource entries
} DNS_HEADER;


/*** The structure for the query ***/
typedef struct
{
unsigned short qtype;
unsigned short qclass;
} QUESTION;


/*** The structure for Resource Record ***/
#pragma pack(push, 1)
typedef struct
{
unsigned short type;
unsigned short _class;
unsigned int ttl;
unsigned short data_len;
} R_DATA;
#pragma pack(pop)
/*** Pointers to resource record contents***/
typedef struct
{
unsigned char *name;
R_DATA *resource;
unsigned char *rdata;
} RES_RECORD;
/*** Structure of a Query***/
typedef struct
{
unsigned char *name;
QUESTION *ques;
} QUERY;
//-------------------------------------------------------------//
//*****************     MAIN FUNCTION      ********************//
//-------------------------------------------------------------//


int main(int argc, char *argv[]) {
unsigned char hostname[100];
    int serverSock;                         // Socket descriptor for server
                           // Socket descriptor for client


    struct sockaddr_in serverAddr;          // Structure variable for Local address


    if (argc != 4)                          // Test for correct number of arguments
    {
        printf("\n\t\tNumber of command line parametes aren't proper. Terminating DNS service!");
    }


    /* Create socket for incoming connections */
    if ((serverSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) //UDP packet for DNS queries
     {
        printf("\n\t\tERROR opening socket. Terminating DNS service!");
     //   exit(1);
    }


    // Zero out structure
    serverAddr.sin_family = AF_INET;                // Internet address family
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); // Any incoming interface
    serverAddr.sin_port = htons(atoi(argv[1]));        // Local port 53


    /* Bind to the local address */
    if (bind(serverSock, (struct sockaddr *) &serverAddr, sizeof(serverAddr)) < 0){
        printf("bind() failed");
        }


 printf("\n\n\t\tWelcome to the DNS Doisser System \t\t\n");
    printf("\n\t\tDNS Doisser listening on Port Number is: %s\n",argv[1]);




    /* Mark the socket so it will listen for incoming connections */
    if (listen(serverSock, 5) < 0)


    printf("\n");
    while(1){}
 //Now get the ip of this hostname , A record
    ngethostbyname(hostname , T_A);




}
//-------------------------------------------------------------//
//*****************   MAIN FUNCTION  ENDS  ********************//
//-------------------------------------------------------------//


/*
 * Perform a DNS query by sending a packet
 * */
void ngethostbyname(unsigned char *host , int query_type)
{
    unsigned char buf[65536],*qname,*reader;
    int i , j , stop , s;


    struct sockaddr_in a;


     RES_RECORD answers[20],auth[20],addit[20]; //the replies from the DNS server
    struct sockaddr_in dest;


   DNS_HEADER *dns = NULL;
    QUESTION *qinfo = NULL;


    printf("Resolving %s" , host);


    s = socket(AF_INET , SOCK_DGRAM , IPPROTO_UDP); //UDP packet for DNS queries


    dest.sin_family = AF_INET;
    dest.sin_port = htons(53);
    dest.sin_addr.s_addr = inet_addr(dns_servers[0]); //dns servers


    //Set the DNS structure to standard queries
  dns = (DNS_HEADER *)&buf;


    dns->id = (unsigned short) htons(getpid());
    dns->qr = 0; //This is a query
    dns->opcode = 0; //This is a standard query
    dns->aa = 0; //Not Authoritative
    dns->tc = 0; //This message is not truncated
    dns->rd = 1; //Recursion Desired
    dns->ra = 0; //Recursion not available! hey we dont have it (lol)
    dns->z = 0;
    dns->ad = 0;
    dns->cd = 0;
    dns->rcode = 0;
    dns->q_count = htons(1); //we have only 1 question
    dns->ans_count = 0;
    dns->auth_count = 0;
    dns->add_count = 0;


    //point to the query portion
    qname =(unsigned char*)&buf[sizeof( DNS_HEADER)];


    ChangetoDnsNameFormat(qname , host);
    qinfo =( QUESTION*)&buf[sizeof( DNS_HEADER) + (strlen((const char*)qname) + 1)]; //fill it


    qinfo->qtype = htons( query_type ); //type of the query , A , MX , CNAME , NS etc
    qinfo->qclass = htons(1); //its internet (lol)


    printf("\nSending Packet...");
    if( sendto(s,(char*)buf,sizeof( DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof(QUESTION),0,(struct sockaddr*)&dest,sizeof(dest)) < 0)
    {
        perror("sendto failed");
    }
    printf("Done");


    //Receive the answer
    i = sizeof dest;
    printf("\nReceiving answer...");
    if(recvfrom (s,(char*)buf , 65536 , 0 , (struct sockaddr*)&dest , (socklen_t*)&i ) < 0)
    {
        perror("recvfrom failed");
    }
    printf("Done");


    dns = (DNS_HEADER*) buf;


    //move ahead of the dns header and the query field
    reader = &buf[sizeof( DNS_HEADER) + (strlen((const char*)qname)+1) + sizeof( QUESTION)];


    printf("\nThe response contains : ");
    printf("\n %d Questions.",ntohs(dns->q_count));
    printf("\n %d Answers.",ntohs(dns->ans_count));
    printf("\n %d Authoritative Servers.",ntohs(dns->auth_count));
    printf("\n %d Additional records.\n\n",ntohs(dns->add_count));


    //Start reading answers
    stop=0;


    for(i=0;i<ntohs(dns->ans_count);i++)
    {
        answers[i].name=ReadName(reader,buf,&stop);
        reader = reader + stop;


        answers[i].resource = ( R_DATA*)(reader);
        reader = reader + sizeof( R_DATA);


        if(ntohs(answers[i].resource->type) == 1) //if its an ipv4 address
        {
            answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));


            for(j=0 ; j<ntohs(answers[i].resource->data_len) ; j++)
            {
                answers[i].rdata[j]=reader[j];
            }


            answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';


            reader = reader + ntohs(answers[i].resource->data_len);
        }
        else
        {
            answers[i].rdata = ReadName(reader,buf,&stop);
            reader = reader + stop;
        }
    }


    //read authorities
    for(i=0;i<ntohs(dns->auth_count);i++)
    {
        auth[i].name=ReadName(reader,buf,&stop);
        reader+=stop;


        auth[i].resource=( R_DATA*)(reader);
        reader+=sizeof( R_DATA);


        auth[i].rdata=ReadName(reader,buf,&stop);
        reader+=stop;
    }


    //read additional
    for(i=0;i<ntohs(dns->add_count);i++)
    {
        addit[i].name=ReadName(reader,buf,&stop);
        reader+=stop;


        addit[i].resource=( R_DATA*)(reader);
        reader+=sizeof( R_DATA);


        if(ntohs(addit[i].resource->type)==1)
        {
            addit[i].rdata = (unsigned char*)malloc(ntohs(addit[i].resource->data_len));
            for(j=0;j<ntohs(addit[i].resource->data_len);j++)
            addit[i].rdata[j]=reader[j];


            addit[i].rdata[ntohs(addit[i].resource->data_len)]='\0';
            reader+=ntohs(addit[i].resource->data_len);
        }
        else
        {
            addit[i].rdata=ReadName(reader,buf,&stop);
            reader+=stop;
        }
    }


    //print answers
    printf("\nAnswer Records : %d \n" , ntohs(dns->ans_count) );
    for(i=0 ; i < ntohs(dns->ans_count) ; i++)
    {
        printf("Name : %s ",answers[i].name);


        if( ntohs(answers[i].resource->type) == T_A) //IPv4 address
        {
            long *p;
            p=(long*)answers[i].rdata;
            a.sin_addr.s_addr=(*p); //working without ntohl
            printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
        }


        if(ntohs(answers[i].resource->type)==5)
        {
            //Canonical name for an alias
            printf("has alias name : %s",answers[i].rdata);
        }


        printf("\n");
    }


    //print authorities
    printf("\nAuthoritive Records : %d \n" , ntohs(dns->auth_count) );
    for( i=0 ; i < ntohs(dns->auth_count) ; i++)
    {


        printf("Name : %s ",auth[i].name);
        if(ntohs(auth[i].resource->type)==2)
        {
            printf("has nameserver : %s",auth[i].rdata);
        }
        printf("\n");
    }


    //print additional resource records
    printf("\nAdditional Records : %d \n" , ntohs(dns->add_count) );
    for(i=0; i < ntohs(dns->add_count) ; i++)
    {
        printf("Name : %s ",addit[i].name);
        if(ntohs(addit[i].resource->type)==1)
        {
            long *p;
            p=(long*)addit[i].rdata;
            a.sin_addr.s_addr=(*p);
            printf("has IPv4 address : %s",inet_ntoa(a.sin_addr));
        }
        printf("\n");
    }
    return;
}


/*
 *
 * */
u_char* ReadName(unsigned char* reader,unsigned char* buffer,int* count)
{
    unsigned char *name;
    unsigned int p=0,jumped=0,offset;
    int i , j;


    *count = 1;
    name = (unsigned char*)malloc(256);


    name[0]='\0';


    //read the names in 3www6google3com format
    while(*reader!=0)
    {
        if(*reader>=192)
        {
            offset = (*reader)*256 + *(reader+1) - 49152; //49152 = 11000000 00000000
            reader = buffer + offset - 1;
            jumped = 1; //we have jumped to another location so counting wont go up!
        }
        else
        {
            name[p++]=*reader;
        }


        reader = reader+1;


        if(jumped==0)
        {
            *count = *count + 1; //if we havent jumped to another location then we can count up
        }
    }


    name[p]='\0'; //string complete
    if(jumped==1)
    {
        *count = *count + 1; //number of steps we actually moved forward in the packet
    }


    //now convert 3www6google3com0 to www.google.com
    for(i=0;i<(int)strlen((const char*)name);i++)
    {
        p=name[i];
        for(j=0;j<(int)p;j++)
        {
            name[i]=name[i+1];
            i=i+1;
        }
        name[i]='.';
    }
    name[i-1]='\0'; //remove the last dot
    return name;
}


/*
 * Get the DNS servers from /etc/resolv.conf file on Linux
 * */
void get_dns_servers()
{
    FILE *fp;
    char line[200]  ;//*p
    if((fp = fopen("/etc/resolv.conf" , "r")) == NULL)
    {
        printf("Failed opening /etc/resolv.conf file \n");
    }


    while(fgets(line , 200 , fp))
    {
        if(line[0] == '#')
        {
            continue;
        }
        if(strncmp(line , "nameserver" , 10) == 0)
        {
          //  p = strtok(line , " ");
           // p = strtok(NULL , " ");


            //p now is the dns ip
            //?
        }
    }


    strcpy(dns_servers[0] , "8.8.8.8");
    strcpy(dns_servers[1] , "208.67.220.220");
}


/*
 * This will convert www.google.com to 3www6google3com
 * got it
 * */
void ChangetoDnsNameFormat(unsigned char* dns,unsigned char* host)
{
    int lock = 0 , i;
    strcat((char*)host,".");


    for(i = 0 ; i < strlen((char*)host) ; i++)
    {
        if(host[i]=='.')
        {
            *dns++ = i-lock;
            for(;lock<i;lock++)
            {
                *dns++=host[lock];
            }
            lock++; //or lock=i+1;
        }
    }
    *dns++='\0';
}


« Змінено: 2017-02-28 04:57:21 від Free Rick »

Відсутній Free Rick

  • Новачок
  • *
  • дописів: 9
  • Карма: +0/-0
Re: Кастомний DNS сервер на С
« Відповідей #13 : 2017-02-28 04:54:12 »
P.S. На форумі є маленький баг (випадковсість напевно, недогледіли). Спробуйте від юзера додати код в месенджі. // видалити після прочитання

Відсутній Михайло Даниленко

  • Адміністратор ЩОДО
  • Літератор
  • *****
  • дописів: 1262
  • Карма: +0/-0
  • [Debian Stretch]
Re: Кастомний DNS сервер на С
« Відповідей #14 : 2017-02-28 22:47:48 »
Off-topic:
hi

В чому саме полягає баг?