在实现了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类来完成:
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; } }
//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); } }
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; }
202.108.22.5 www.baidu.com 74.125.128.199 www.google.com.hk