应用层协议实现系列——DNS服务器之设计与实现

Posted zhaoxy2850

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了应用层协议实现系列——DNS服务器之设计与实现相关的知识,希望对你有一定的参考价值。

在实现了HTTP、FTP服务器后,本人开始尝试实现DNS服务器,DNS协议的内容相比HTTP和FTP协议要多一些,但经过一番折腾之后,还是把自己的DNS服务器完成了,同时在自己的DNS服务器上实现了DNS劫持,即用户使用该DNS服务器后,访问例如www.taobao.com会加载另一个网站的内容。在这里把实现的过程分享给大家。

在实现DNS服务器之前,先介绍一下DNS协议。DNS协议主要用于实现域名和ip地址之间的转换,举个例子,当我们在浏览器输入www.baidu.com时,浏览器会向DNS服务器查询www.baidu.com对应的ip地址,如220.181.111.86,收到查询结果后,再通过HTTP协议访问ip对应的主机获取网页内容。大家可以试试,在地址栏输入www.baidu.com和220.181.111.86的效果是一样的。当然www.baidu.com对应的ip地址会有很多个,这么设计主要是为了实现负载均衡,但那是后话了。

DNS协议作为一个应用层的协议是通过UDP和TCP实现的,当DNS报文的长度小于512字节时,会使用UDP,否则使用TCP。熟悉TCP和UDP协议的朋友可以猜出来,这么做的目的主要是为了效率,DNS报文的长度不同于HTTP和TCP,一般都比较小,也就是说三次握手协议的通讯量相对于报文长度不能忽略不计。因此DNS协议在大多数情况下都采用UDP进行通讯,本文中也只实现了UDP的通信。

然后说明一下DNS服务器的种类和DNS的解析流程。互联网上的所有域名及其对应的ip地址都是存放在无数台大大小小的DNS服务器中的,DNS服务器主要分为以下几类:根DNS服务器、顶级域名DNS服务器、权威DNS服务器和本地DNS服务器。举个例子,当浏览器需要查询一个域名(www.baidu.com)时,会向本地DNS服务器发起一个请求,本地DNS服务器会在数据库中查找,如果没有找到则向根DNS服务器(.)发起一个请求,根域名服务器将根据所查询的域名,指定一个顶级域名服务器(.com)并将其ip地址返回给本地DNS服务器,本地DNS服务器再向顶级域名服务器(.com)发起相同的请求,顶级域名服务器指定该域名对应的权威DNS服务器(baidu.com)并将其ip返回给本地DNS服务器。本地DNS服务器再向权威DNS服务器(baidu.com)发起同样的请求,权威DNS服务器找到www.baidu.com对应的ip地址并将其返回给本地DNS服务器。本地DNS服务器得到ip地址后,再返回给浏览器打开。这就完成了一次域名查找。域名查找的过程又可以分为递归查找和迭代查找,有兴趣的朋友可以自己谷歌。

下面来具体说下DNS报文的格式。DNS报文主要由5个部分组成,包括HeaderQuestionAnswerAuthorityAdditional,一般请求报文只包括HeaderQuestion两部分,而应答报文在没有错误的情况下,至少包括HeaderQuestionAnswer三个部分。Header部分主要用于说明DNS报文的id、是请求还是应答、查询类型、是否截断、递归设置、状态码以及问题答案的数量等;Question部分主要用于列出需要查询的域名即类型;Answer部分主要用于列出对Question部分相应的回答,包括有效时间、数据长度、答案的数据等;Authority部分主要列出其他权威的DNS服务器,如果客户端需要的话可以直接查询;Additional部分主要是一些附加信息,如给出Authority部分权威服务器对应的ip地址等。至于每个部分的具体格式在这里不作说明,有兴趣的可以看看这篇博文

在了解了以上内容之后,我们就可以开始实现DNS服务器了。本文的DNS服务器设计参考了这个链接,在这里对该作者表示感谢。

首先是Socket通信中的创建UDP套接字并监听,主要通过Server类来完成:

void Server::init(int &port) 
    struct sockaddr_in servAddr;
    memset(&servAddr, 0, sizeof(servAddr));
    //protocol domain
    servAddr.sin_family = AF_INET;
    //default ip
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    //port
    servAddr.sin_port = htons(port);
    //create socket
    if ((m_socketfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) 
        printf("create socket error: %s(errno: %d)\\n",strerror(errno),errno);
        return;
    
    unsigned value = 1;
    setsockopt(m_socketfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
    //bind socket to port
    if (bind(m_socketfd, (struct sockaddr *)&servAddr, sizeof(servAddr))) 
        printf("bind socket error: %s(errno: %d)\\n",strerror(errno),errno);
        return;
    
    //dynamically allocating a port
    if (port == 0) 
        socklen_t namelen = sizeof(servAddr);
        if (getsockname(m_socketfd, (struct sockaddr *)&servAddr, &namelen) == -1) 
            printf("getsockname error: %s(errno: %d)\\n",strerror(errno),errno);
            return;
        
        port = ntohs(servAddr.sin_port);
    
    std::cout<<"server running on port:"<<port<<std::endl;


void Server::run() 
    char buff[512];
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    while (1) 
        int n = (int)recvfrom(m_socketfd, buff, sizeof(buff), 0, (struct sockaddr *)&clientAddr, &len);
        if (n <= 0) continue;
        m_query.decode(buff, n);
        std::cout<<m_query.to_string();
        
        m_resolver.process(m_query, m_response);
        memset(buff, 0, sizeof(buff));
        n = m_response.encode(buff);
        std::cout<<m_response.to_string();
        sendto(m_socketfd, buff, n, 0, (struct sockaddr *)&clientAddr, len);
        
        std::cout<<std::endl;
    

以上代码并没有使用多进程,仅仅是收到一个请求后进行解析,处理并回应。代码的核心主要是在Message类,它实现了DNS报文的解析和生成:

//encode an address seperated by '.' like 'www.google.com'
//to address seperated by substring length like '3www6google3com'
void encode_address(char *addr, char *&buff) 
    string address(addr);
    int pos, current = 0;
    while ((pos = (int)address.find('.')) != string::npos) 
        address.erase(0, pos+1);
        *buff++ = pos;
        memcpy(buff, addr+current, pos);
        buff += pos;
        current += pos+1;
    
    *buff++ = address.size();
    memcpy(buff, addr+current, address.size());
    buff += address.size();
    
    *buff++ = 0;


//encode the message to the buffer
void Message::encode_header(char *&buff) 
    MHeader header = 0;
    header.hId = m_id;
    header.hFlags += ((m_qr?1:0)<<15);
    header.hFlags += (m_opcode<<11);
    header.hFlags += (m_aa<<10);
    header.hFlags += (m_tc<<9);
    header.hFlags += (m_rd<<8);
    header.hFlags += (m_ra<<7);
    header.hFlags += m_rcode;
    header.queryCount = m_qdCount;
    header.answCount = m_anCount;
    header.authCount = m_nsCount;
    header.addiCount = m_arCount;
    
    header.hId = htons(header.hId);
    header.hFlags = htons(header.hFlags);
    header.queryCount = htons(header.queryCount);
    header.answCount = htons(header.answCount);
    header.authCount = htons(header.authCount);
    header.addiCount = htons(header.addiCount);
    
    memcpy(buff, &header, sizeof(MHeader));
    //offset
    buff += sizeof(MHeader);


//encode the questions of message to the buffer
void Message::encode_questions(char *&buff) 
    //encode each question
    for (int i=0; i<m_qdCount; i++) 
        MQuestion question = m_questions[i];
        encode_address(question.qName, buff);
        
        uint16_t nQType = htons(question.qType);
        memcpy(buff, &nQType, sizeof(uint16_t));
        buff+=sizeof(uint16_t);
        
        uint16_t nQClass = htons(question.qClass);
        memcpy(buff, &nQClass, sizeof(uint16_t));
        buff+=sizeof(uint16_t);
    


//encode the answers of the message to the buffer
void Message::encode_answers(char *&buff) 
    //encode each answer
    for (int i=0; i<m_anCount; i++) 
        MResource resource = m_answers[i];
        encode_address(resource.rName, buff);
        
        uint16_t nRType = htons(resource.rType);
        memcpy(buff, &nRType, sizeof(uint16_t));
        buff+=sizeof(uint16_t);
        
        uint16_t nRClass = htons(resource.rClass);
        memcpy(buff, &nRClass, sizeof(uint16_t));
        buff+=sizeof(uint16_t);
        
        uint32_t nTTL = htonl(resource.rTTL);
        memcpy(buff, &nTTL, sizeof(uint32_t));
        buff+=sizeof(uint32_t);
        
        uint16_t nRDLen = htons(resource.rdLength);
        memcpy(buff, &nRDLen, sizeof(uint16_t));
        buff+=sizeof(uint16_t);
        
        if (MT_A == resource.rType) 
            memcpy(buff, resource.rData, sizeof(uint32_t));
            buff+=sizeof(uint32_t);
        
    


//decode the message header from the buffer
void Message::decode_header(const char *&buff) 
    MHeader header;
    memcpy(&header, buff, sizeof(MHeader));
    //network order to host order
    header.hId = ntohs(header.hId);
    header.hFlags = ntohs(header.hFlags);
    header.queryCount = ntohs(header.queryCount);
    header.answCount = ntohs(header.answCount);
    header.authCount = ntohs(header.authCount);
    header.addiCount = ntohs(header.addiCount);
    //id
    m_id = header.hId;
    //flags
    m_qr = header.hFlags&QR_MASK;
    m_opcode = header.hFlags&OPCODE_MASK;
    m_aa = header.hFlags&AA_MASK;
    m_tc = header.hFlags&TC_MASK;
    m_rd = header.hFlags&RD_MASK;
    m_ra = header.hFlags&RA_MASK;
    m_rcode = header.hFlags&RCODE_MASK;
    //count
    m_qdCount = header.queryCount;
    m_anCount = header.answCount;
    m_nsCount = header.authCount;
    m_arCount = header.addiCount;
    //offset
    buff+= sizeof(MHeader);


//decode the questions of the message from the buffer
void Message::decode_questions(const char *&buff) 
    //reset
    m_questions.clear();
    //decode each question
    for (int i=0; i<m_qdCount; i++) 
        MQuestion question = 0;
        //name
        while (1) 
            uint len = *buff++;
            if (len==0) break;
            if (strlen(question.qName)!=0) strcat(question.qName, ".");
            memcpy(question.qName+strlen(question.qName), buff, len);
            buff+=len;
        
        //type
        question.qType = ntohs(*((uint16_t *)buff));
        buff+=sizeof(uint16_t);
        //class
        question.qClass = ntohs(*((uint16_t *)buff));
        buff+=sizeof(uint16_t);
        //add to list
        m_questions.push_back(question);
    

代码中的DNS请求类Query和应答类Response都是继承于Message类,由于它们的结构类似。Resolver类中主要是针对查询的域名,在本地数据库(在这里是host文件)中查找,并根据结果生成相应的应答报文。

void Resolver::init(const std::string &fileName) 
    ifstream fstream(fileName);
    char hostStr[128];
    while (fstream.getline(hostStr, sizeof(hostStr))) 
        stringstream sstream;
        sstream<<hostStr;
        Host host;
        sstream>>host.ipAddr;
        sstream>>host.name;
        m_hosts.push_back(host);
    


void Resolver::process(const Query &query, Response &response) 
    //clear
    response.m_questions.clear();
    response.m_answers.clear();
    //find host and generate answers
    vector<Response::MQuestion> questions = query.getQuestions();
    for (vector<Response::MQuestion>::iterator qIter = questions.begin(); qIter != questions.end(); ++qIter) 
        Response::MQuestion question = *qIter;
        Response::MResource resource;
        for (vector<Host>::iterator hIter = m_hosts.begin(); hIter != m_hosts.end(); hIter++) 
            Host host = *hIter;
            //if find
            if (question.qName == host.name && question.qType == Message::MT_A) 
                strcpy(resource.rName, question.qName);
                resource.rType = question.qType;
                resource.rClass = question.qClass;
                resource.rTTL = 10;
                resource.rdLength = sizeof(uint32_t);
                memcpy(resource.rIp, host.ipAddr.c_str(), host.ipAddr.size());
                
                struct sockaddr_in adr_inet;
                memset(&adr_inet, 0, sizeof(adr_inet));
                inet_aton(host.ipAddr.c_str(), &adr_inet.sin_addr);
                memcpy(resource.rData, &adr_inet.sin_addr.s_addr, sizeof(uint32_t));
                
                response.m_answers.push_back(resource);
                
                break;
            
        
        
        response.m_questions.push_back(question);
    
    
    response.m_id = query.getID();
    response.m_qr = 1;
    response.m_opcode = query.getOpcode();
    response.m_aa = 0;
    response.m_tc = 0;
    response.m_rd = 0;
    response.m_ra = 0;
    if (response.m_answers.size()!=response.m_questions.size()) 
        response.m_rcode = Message::MC_SERVER_ERROR;
    else 
        response.m_rcode = Message::MC_NO_ERROR;
    
    
    response.m_qdCount = (int)response.m_questions.size();
    response.m_anCount = (int)response.m_answers.size();
    response.m_nsCount = 0;
    response.m_arCount = 0;

以上代码已经在GitHub上开源,有兴趣的朋友可以前往 下载。将这个代码运行起来后,需要Server类构造函数中的host文件路径,这个文件可以自己创建,格式与系统/etc/hosts中的相同,每行一个ip地址和域名。例如:

202.108.22.5        www.baidu.com
74.125.128.199      www.google.com.hk

另外,由于该代码中使用了1024以下的端口,运行时需要root权限,否则会提示权限不足。将该代码运行起来后,即可将其他设备的dns地址设为运行代码的机器ip,即可实现对指定的域名进行解析。如果要实现DNS劫持,将想劫持的域名指向自己设定的一个ip地址即可。注意,如果将www.baidu.com指向谷歌的ip地址会出现连接失败,由于HTTP协议会将域名发送到HTTP服务器,谷歌的HTTP服务器会拒绝host不是www.google.com.hk的HTTP请求。


如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:) 个人博客: http://blog.csdn.net/zhaoxy2850 本文地址: http://blog.csdn.net/zhaoxy_thu/article/details/25712811 转载请注明出处,谢谢!

以上是关于应用层协议实现系列——DNS服务器之设计与实现的主要内容,如果未能解决你的问题,请参考以下文章

应用层协议实现系列——HTTP服务器之http协议解析

netty系列之:在netty中使用TCP协议请求DNS服务器

应用层协议实现系列——HTTP服务器之仿nginx多进程和多路IO的实现

netty系列之:在netty中使用UDP协议请求DNS服务器

基于HTTPS协议的12306抢票软件设计与实现--水平DNS并发查询分享

服务注册发现之服务注册中心设计原理与Golang实现