阅读 25

Nginx网络epoll多进程系列:应用层协议实现系列(四)——DNS服务器之设计与实现

blog.csdn.net/zhaoxy_thu/…

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

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

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

[cpp]  view plain  copy

  1. void Server::init(int &port) {  
  2.     struct sockaddr_in servAddr;  
  3.     memset(&servAddr, 0, sizeof(servAddr));  
  4.     //protocol domain  
  5.     servAddr.sin_family = AF_INET;  
  6.     //default ip  
  7.     servAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  8.     //port  
  9.     servAddr.sin_port = htons(port);  
  10.     //create socket  
  11.     if ((m_socketfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {  
  12.         printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);  
  13.         return;  
  14.     }  
  15.     unsigned value = 1;  
  16.     setsockopt(m_socketfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));  
  17.     //bind socket to port  
  18.     if (bind(m_socketfd, (struct sockaddr *)&servAddr, sizeof(servAddr))) {  
  19.         printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);  
  20.         return;  
  21.     }  
  22.     //dynamically allocating a port  
  23.     if (port == 0) {  
  24.         socklen_t namelen = sizeof(servAddr);  
  25.         if (getsockname(m_socketfd, (struct sockaddr *)&servAddr, &namelen) == -1) {  
  26.             printf("getsockname error: %s(errno: %d)\n",strerror(errno),errno);  
  27.             return;  
  28.         }  
  29.         port = ntohs(servAddr.sin_port);  
  30.     }  
  31.     std::cout<<"server running on port:"<<port<<std::endl;  
  32. }  
  33.   
  34. void Server::run() {  
  35.     char buff[512];  
  36.     struct sockaddr_in clientAddr;  
  37.     socklen_t len = sizeof(struct sockaddr_in);  
  38.     while (1) {  
  39.         int n = (int)recvfrom(m_socketfd, buff, sizeof(buff), 0, (struct sockaddr *)&clientAddr, &len);  
  40.         if (n <= 0) continue;  
  41.         m_query.decode(buff, n);  
  42.         std::cout<<m_query.to_string();  
  43.           
  44.         m_resolver.process(m_query, m_response);  
  45.         memset(buff, 0, sizeof(buff));  
  46.         n = m_response.encode(buff);  
  47.         std::cout<<m_response.to_string();  
  48.         sendto(m_socketfd, buff, n, 0, (struct sockaddr *)&clientAddr, len);  
  49.           
  50.         std::cout<<std::endl;  
  51.     }  
  52. }  


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

[cpp]  view plain  copy

  1. //encode an address seperated by '.' like 'www.google.com'  
  2. //to address seperated by substring length like '3www6google3com'  
  3. void encode_address(char *addr, char *&buff) {  
  4.     string address(addr);  
  5.     int pos, current = 0;  
  6.     while ((pos = (int)address.find('.')) != string::npos) {  
  7.         address.erase(0, pos+1);  
  8.         *buff++ = pos;  
  9.         memcpy(buff, addr+current, pos);  
  10.         buff += pos;  
  11.         current += pos+1;  
  12.     }  
  13.     *buff++ = address.size();  
  14.     memcpy(buff, addr+current, address.size());  
  15.     buff += address.size();  
  16.       
  17.     *buff++ = 0;  
  18. }  
  19.   
  20. //encode the message to the buffer  
  21. void Message::encode_header(char *&buff) {  
  22.     MHeader header = {0};  
  23.     header.hId = m_id;  
  24.     header.hFlags += ((m_qr?1:0)<<15);  
  25.     header.hFlags += (m_opcode<<11);  
  26.     header.hFlags += (m_aa<<10);  
  27.     header.hFlags += (m_tc<<9);  
  28.     header.hFlags += (m_rd<<8);  
  29.     header.hFlags += (m_ra<<7);  
  30.     header.hFlags += m_rcode;  
  31.     header.queryCount = m_qdCount;  
  32.     header.answCount = m_anCount;  
  33.     header.authCount = m_nsCount;  
  34.     header.addiCount = m_arCount;  
  35.       
  36.     header.hId = htons(header.hId);  
  37.     header.hFlags = htons(header.hFlags);  
  38.     header.queryCount = htons(header.queryCount);  
  39.     header.answCount = htons(header.answCount);  
  40.     header.authCount = htons(header.authCount);  
  41.     header.addiCount = htons(header.addiCount);  
  42.       
  43.     memcpy(buff, &header, sizeof(MHeader));  
  44.     //offset  
  45.     buff += sizeof(MHeader);  
  46. }  
  47.   
  48. //encode the questions of message to the buffer  
  49. void Message::encode_questions(char *&buff) {  
  50.     //encode each question  
  51.     for (int i=0; i<m_qdCount; i++) {  
  52.         MQuestion question = m_questions[i];  
  53.         encode_address(question.qName, buff);  
  54.           
  55.         uint16_t nQType = htons(question.qType);  
  56.         memcpy(buff, &nQType, sizeof(uint16_t));  
  57.         buff+=sizeof(uint16_t);  
  58.           
  59.         uint16_t nQClass = htons(question.qClass);  
  60.         memcpy(buff, &nQClass, sizeof(uint16_t));  
  61.         buff+=sizeof(uint16_t);  
  62.     }  
  63. }  
  64.   
  65. //encode the answers of the message to the buffer  
  66. void Message::encode_answers(char *&buff) {  
  67.     //encode each answer  
  68.     for (int i=0; i<m_anCount; i++) {  
  69.         MResource resource = m_answers[i];  
  70.         encode_address(resource.rName, buff);  
  71.           
  72.         uint16_t nRType = htons(resource.rType);  
  73.         memcpy(buff, &nRType, sizeof(uint16_t));  
  74.         buff+=sizeof(uint16_t);  
  75.           
  76.         uint16_t nRClass = htons(resource.rClass);  
  77.         memcpy(buff, &nRClass, sizeof(uint16_t));  
  78.         buff+=sizeof(uint16_t);  
  79.           
  80.         uint32_t nTTL = htonl(resource.rTTL);  
  81.         memcpy(buff, &nTTL, sizeof(uint32_t));  
  82.         buff+=sizeof(uint32_t);  
  83.           
  84.         uint16_t nRDLen = htons(resource.rdLength);  
  85.         memcpy(buff, &nRDLen, sizeof(uint16_t));  
  86.         buff+=sizeof(uint16_t);  
  87.           
  88.         if (MT_A == resource.rType) {  
  89.             memcpy(buff, resource.rData, sizeof(uint32_t));  
  90.             buff+=sizeof(uint32_t);  
  91.         }  
  92.     }  
  93. }  
  94.   
  95. //decode the message header from the buffer  
  96. void Message::decode_header(const char *&buff) {  
  97.     MHeader header;  
  98.     memcpy(&header, buff, sizeof(MHeader));  
  99.     //network order to host order  
  100.     header.hId = ntohs(header.hId);  
  101.     header.hFlags = ntohs(header.hFlags);  
  102.     header.queryCount = ntohs(header.queryCount);  
  103.     header.answCount = ntohs(header.answCount);  
  104.     header.authCount = ntohs(header.authCount);  
  105.     header.addiCount = ntohs(header.addiCount);  
  106.     //id  
  107.     m_id = header.hId;  
  108.     //flags  
  109.     m_qr = header.hFlags&QR_MASK;  
  110.     m_opcode = header.hFlags&OPCODE_MASK;  
  111.     m_aa = header.hFlags&AA_MASK;  
  112.     m_tc = header.hFlags&TC_MASK;  
  113.     m_rd = header.hFlags&RD_MASK;  
  114.     m_ra = header.hFlags&RA_MASK;  
  115.     m_rcode = header.hFlags&RCODE_MASK;  
  116.     //count  
  117.     m_qdCount = header.queryCount;  
  118.     m_anCount = header.answCount;  
  119.     m_nsCount = header.authCount;  
  120.     m_arCount = header.addiCount;  
  121.     //offset  
  122.     buff+= sizeof(MHeader);  
  123. }  
  124.   
  125. //decode the questions of the message from the buffer  
  126. void Message::decode_questions(const char *&buff) {  
  127.     //reset  
  128.     m_questions.clear();  
  129.     //decode each question  
  130.     for (int i=0; i<m_qdCount; i++) {  
  131.         MQuestion question = {0};  
  132.         //name  
  133.         while (1) {  
  134.             uint len = *buff++;  
  135.             if (len==0) break;  
  136.             if (strlen(question.qName)!=0) strcat(question.qName, ".");  
  137.             memcpy(question.qName+strlen(question.qName), buff, len);  
  138.             buff+=len;  
  139.         }  
  140.         //type  
  141.         question.qType = ntohs(*((uint16_t *)buff));  
  142.         buff+=sizeof(uint16_t);  
  143.         //class  
  144.         question.qClass = ntohs(*((uint16_t *)buff));  
  145.         buff+=sizeof(uint16_t);  
  146.         //add to list  
  147.         m_questions.push_back(question);  
  148.     }  
  149. }  


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

[cpp]  view plain  copy

  1. void Resolver::init(const std::string &fileName) {  
  2.     ifstream fstream(fileName);  
  3.     char hostStr[128];  
  4.     while (fstream.getline(hostStr, sizeof(hostStr))) {  
  5.         stringstream sstream;  
  6.         sstream<<hostStr;  
  7.         Host host;  
  8.         sstream>>host.ipAddr;  
  9.         sstream>>host.name;  
  10.         m_hosts.push_back(host);  
  11.     }  
  12. }  
  13.   
  14. void Resolver::process(const Query &query, Response &response) {  
  15.     //clear  
  16.     response.m_questions.clear();  
  17.     response.m_answers.clear();  
  18.     //find host and generate answers  
  19.     vector<Response::MQuestion> questions = query.getQuestions();  
  20.     for (vector<Response::MQuestion>::iterator qIter = questions.begin(); qIter != questions.end(); ++qIter) {  
  21.         Response::MQuestion question = *qIter;  
  22.         Response::MResource resource;  
  23.         for (vector<Host>::iterator hIter = m_hosts.begin(); hIter != m_hosts.end(); hIter++) {  
  24.             Host host = *hIter;  
  25.             //if find  
  26.             if (question.qName == host.name && question.qType == Message::MT_A) {  
  27.                 strcpy(resource.rName, question.qName);  
  28.                 resource.rType = question.qType;  
  29.                 resource.rClass = question.qClass;  
  30.                 resource.rTTL = 10;  
  31.                 resource.rdLength = sizeof(uint32_t);  
  32.                 memcpy(resource.rIp, host.ipAddr.c_str(), host.ipAddr.size());  
  33.                   
  34.                 struct sockaddr_in adr_inet;  
  35.                 memset(&adr_inet, 0, sizeof(adr_inet));  
  36.                 inet_aton(host.ipAddr.c_str(), &adr_inet.sin_addr);  
  37.                 memcpy(resource.rData, &adr_inet.sin_addr.s_addr, sizeof(uint32_t));  
  38.                   
  39.                 response.m_answers.push_back(resource);  
  40.                   
  41.                 break;  
  42.             }  
  43.         }  
  44.           
  45.         response.m_questions.push_back(question);  
  46.     }  
  47.       
  48.     response.m_id = query.getID();  
  49.     response.m_qr = 1;  
  50.     response.m_opcode = query.getOpcode();  
  51.     response.m_aa = 0;  
  52.     response.m_tc = 0;  
  53.     response.m_rd = 0;  
  54.     response.m_ra = 0;  
  55.     if (response.m_answers.size()!=response.m_questions.size()) {  
  56.         response.m_rcode = Message::MC_SERVER_ERROR;  
  57.     }else {  
  58.         response.m_rcode = Message::MC_NO_ERROR;  
  59.     }  
  60.       
  61.     response.m_qdCount = (int)response.m_questions.size();  
  62.     response.m_anCount = (int)response.m_answers.size();  
  63.     response.m_nsCount = 0;  
  64.     response.m_arCount = 0;  
  65. }  


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

[plain]  view plain  copy

  1. 202.108.22.5        www.baidu.com  
  2. 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请求。

\

如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:)

个人博客: blog.csdn.net/zhaoxy2850

本文地址: blog.csdn.net/zhaoxy_thu/…

转载请注明出处,谢谢!

\

\

\

文章分类
代码人生
文章标签