在实现了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
- 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报文的解析和生成:
[cpp] view plain copy
- //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文件)中查找,并根据结果生成相应的应答报文。
[cpp] view plain copy
- 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地址和域名。例如:
[plain] view plain copy
- 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请求。
\
如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:)
个人博客: blog.csdn.net/zhaoxy2850
本文地址: blog.csdn.net/zhaoxy_thu/…
转载请注明出处,谢谢!
\
\
\