您的当前位置:首页正文

DNS课程设计报告

2021-05-18 来源:我们爱旅游
DNS 服务器程序

实验报告

班级:2011211301

小组成员:曹晓欢 2011211139 杨静怡 2011211140

系统功能设计

 设计一个DNS服务器程序,读入“IP地址-域名”对照表,当客户端查询域名对应的IP地址时,用域名检索该对照表,有三种可能检索结果:

 检索结果:ip地址0.0.0.0,则向客户端返回“域名不存在”的报错消息(不良网站拦截功能)

 检索结果:普通IP地址,则向客户端返回该地址(服务器功能)  表中未检到该域名,则向因特网DNS服务器发出查询,并将结果返给客户端(中继功能)

➢ 考虑多个计算机上的客户端会同时查询,需要进行消息ID

的转换

系统和运行环境描述

Windows7 操作系统平台,VS2010 编程环境。 使用 C/C++编写 dns 中继服务器。

主要数据结构

SOCKET sockfd; Socket套接字

SOCKADDR_IN ser_addr,nser_addr;

表达地址结构信息,等价于 sockaddr 结构

typedef struct req_inform {//DNS 请求包信息 SOCKADDR_IN cli_addr; unsigned short id; //id 和 cli_addr 唯一标识一个 DNS 请求 }req_inform;

该结构唯一标示了一个来自客户端的 dns 请求。

map url_ip_table; //本地域名解析表

用来构建本地存储的 dnsrelay.txt 中域名和 IP 的映射。

map req_cache[cache_num];//id转换表 这一个 map 映射,把客户端 dns 请求映射到一个 unsigned short 上面,用它来存储 id 转换信息。Unsigned short类型的key值为新的id号,用于标识转发给外部服务器,而客户端的信息和旧id作为value存在map中,与key形成映射。待服务器发回应答包,根据包中id号,即可找到原id与客户端信息。

#define cache_num 2//转换表数目

#define cache_size 500//每个转换表容量

int idThen_max=cache_num*cache_size; //总条目数 int cur_cache=0; int idThen=0;

cache_num 指定了 id 转换表的个数,cache_size 是每个 id 转换表的大小,

cur_cache 指向是当前正在装入的 id 转换表, idThen 是一个从 0 到 1000 一直循环的被映射到的 id 号。这样的设计可循环利用id转换表,并及时清除旧记录。

具体流程是:

生成 id 转换的 item(idThen,struct req_inform 的一个变量) 把 id 转换的 item 加入到 req_cache[cur_cache]中 如果 req_cache[cur_cache]已经达到 cache_size {

cur_cache 指向下一个 id 转换表,并将其清空 }

idThen 加 1

函数划分(模块划分)

int get_url_ip_table(map &iptable) //失败返回-1 从文件中读入 url和ip的映射表。

int init();

用来初始化 ser_addr、nser_addr、sockfd,以及对 sockfd 绑定到本地的 ser_addr 的 地址上。

int is_req(char * buffer);

用来判断收到的包是上级服务器的回答包,还是来自客户端的请求包。

void get_url(char * buf,string & url); 从请求包中提取 URL。

void ask_next_server(char * buffer,struct sockaddr_in req_addr,int buffer_size); 询问上级 dns 服务器。

void create_respose(char *buffer,struct sockaddr_in req_addr,int buffer_size,string ip);

根据 url-ip 表中找到的结果自己构造响应。

void deal_req(char * buffer,struct sockaddr_in req_addr,int buffer_size); 客户端请求包的处理

void deal_res(char * buffer,int buffer_size);

上级服务器回答包的处理。

int main(); 主函数

软件流程图

开始 失败 读入url-ip表 成功 结束 失败 初始化 结束 成功 接收数据包 获取DNS报头 请求包 包类型 应答包 DNS 检索映射表,修改ID 本地解析 发送至客户端 本地找到 NO YES 向上级服务器询问 构造相应包

是否结束

是 结束

源代码

#include #include #include #include #include #include #include

#include \"WinSock2.h\"//socket 头文件

#pragma comment(lib, \"ws2_32.lib\") // 链接 Ws2_32.lib 库,不必再setting 里设置

using namespace std;

//id 转换表参数定义

#define cache_num 2//转换表数目

#define cache_size 500//每个转换表容量

int idThen_max=cache_num*cache_size; //总条目数 int cur_cache=0; int idThen=0;

SOCKET sockfd;

SOCKADDR_IN ser_addr,nser_addr;/* 使用 sockaddr_in 表达地址结构

信息,等价于 sockaddr 结构*/ const char * nx_ip=\"211.68.71.4\"; const char * file_name=\"dnsrelay.txt\";

typedef struct req_inform {//DNS 请求包信息 SOCKADDR_IN cli_addr; unsigned short id;//id 和 cli_addr 唯一标识一个 DNS 请求 }req_inform;

map url_ip_table;//本地域名解析表

map req_cache[cache_num];//id转换表 /*

struct dns_ans_add

{//构造响应包所要添加的信息除了请求包外,包括指针、类型、生存时间、类、资源大小、资源等等 unsigned short url_pointer; unsigned short type; unsigned short clas_s; unsigned long ttl; unsigned short sourse_size; unsigned long sourse; }; */

int get_url_ip_table(map &iptable) //失败返回-1 { string tmpip,tmpurl; fstream fs; fs.open(\"dnsrelay.txt\"); if(!fs.is_open()) { cout<<\"txt can't open.\"<>tmpip;

}

}

fs>>tmpurl; iptable.insert(pair(tmpurl,tmpip)); }

fs.close(); return 0;

int init() //失败返回-1 {

WSAData wsa;//socket 初始化

if (WSAStartup(MAKEWORD(2,2), &wsa) !=0)//Winsock 服务的初始化 { WSACleanup(); }

cout<<\"winsock初始失败\"<for(int i=0;isockfd=socket(AF_INET,SOCK_DGRAM,0);//创建 socket 套接字 ser_addr.sin_family=AF_INET;//sin_family 指代协议族

ser_addr.sin_port=htons(53);//使用 53 端口即 DNS 服务器端口

ser_addr.sin_addr.s_addr=INADDR_ANY;//0.0.0.0,sin_addr 存储 IP 地址

nser_addr.sin_family=AF_INET; nser_addr.sin_port=htons(53);

nser_addr.sin_addr.s_addr=inet_addr(nx_ip);// 返 回 值 作Internet 地址

if (bind(sockfd, (SOCKADDR*)&ser_addr, sizeof(ser_addr))==-1) {

cout << \"绑定端口失败\" << endl; return -1; } else cout << \"绑定端口成功\" << endl; return 0; }

int is_req(char * buffer) { unsigned short flags=0;

memcpy(&flags,buffer+2,2);//取标志位信息 if(flags==0x0001)//3 号位 00 2 号位 01 请求包 (0001为网络顺序) 本地:0100 return 1; else return 0; }

void get_url(char * buf,string &url) {//从请求包中提取 URL int index=0; unsigned char num,i; url.clear();

num=buf[index++]; //第一节字段长度 while(num) //当num=0为结尾跳出 { for(i=0;iurl.push_back(buf[index++]);//push_back 字符串之后插入一个字符 num=buf[index++]; if(num!=0) //说明没结束 url.push_back('.'); } }

void ask_next_server(char * buffer,sockaddr_in req_addr,int buffer_size) { struct req_inform tem; cout<<\"本地域名解析表未找到,询问外部服务器... \"<(idThen ,tem));//增加本id转换项,至映射表项 if(req_cache[cur_cache].size()>=cache_size) //当前本映射表已满,跳到另一张表 { cur_cache=(cur_cache+1)%cache_num; //指向新的一张表 req_cache[cur_cache].clear(); //新表的历史记录清空

}

idThen=(idThen+1)%idThen_max; //下一个转发请求的id号 }

void create_respose(char *buffer,sockaddr_in req_addr,int buffer_size,string ip) { char ans[1000];//响应包 unsigned short flags=0x8081;//响应包标志位 8180 (8180为x86顺序) unsigned short ans_num=0x0100;//正常的响应数设置响应个数 1 cout<<\"在本地域名解析表缓存中找到。。 \"<{//若是屏蔽,则直接返回 ans_num=0x0000; //重置回复数为0,屏蔽 memcpy(&ans[6], &ans_num, sizeof(unsigned short)); if(sendto(sockfd,ans,buffer_size,0,(struct sockaddr*)&req_addr,sizeof(struct sockaddr))==-1) {

cout<<\"发送至客户端失败! \"<{//构造响应包 char c_ip[50]; ip.copy(c_ip,ip.length()); c_ip[ip.length()]='\\0'; printf(\"该请求包所要找的的ip是 %s... \\n\ memcpy(ans+6,&ans_num,sizeof(unsigned short)) ;//修改、添加信息,包括响应数、多出的响 应字段等等

//构造DNS响应部分 int curLen = 0; char answer[16];

unsigned short Name = htons(0xc00c);

memcpy(answer, &Name, sizeof(unsigned short)); curLen += sizeof(unsigned short);

unsigned short TypeA = htons(0x0001);

memcpy(answer+curLen, &TypeA, sizeof(unsigned short));

curLen += sizeof(unsigned short);

unsigned short ClassA = htons(0x0001);

memcpy(answer+curLen, &ClassA, sizeof(unsigned short)); curLen += sizeof(unsigned short);

unsigned long timeLive = htonl(0x7b);

memcpy(answer+curLen, &timeLive, sizeof(unsigned long)); curLen += sizeof(unsigned long);

unsigned short IPLen = htons(0x0004);

memcpy(answer+curLen, &IPLen, sizeof(unsigned short)); curLen += sizeof(unsigned short);

unsigned long IP = (unsigned long) inet_addr(c_ip); memcpy(answer+curLen, &IP, sizeof(unsigned long)); curLen += sizeof(unsigned long); curLen += buffer_size;

//请求报文和响应部分共同组成DNS响应报文存入sendbuf memcpy(ans+buffer_size, answer, curLen); if(sendto(sockfd,ans,curLen,0,(struct sockaddr*)&req_addr,sizeof(struct sockaddr))==-1) { cout<<\"发送客户端失败!\"<void deal_req(char * buffer,struct sockaddr_in req_addr,int buffer_size) { string url; map::iterator uit_iter; printf(\"收到请求包,正在处理... \\n\"); get_url(buffer+12,url); //包头12个字节 printf(\"请求解析的url是:\"); cout<if(uit_iter==url_ip_table.end()) { ask_next_server(buffer,req_addr,buffer_size); //未找到,向上级服务器询问 }

else //自己构造响应 { cout<<\"在本地缓存找到ip:\"+uit_iter->second<second); } }

void deal_res(char * buffer,int buffer_size) { unsigned short cur_id,i; map::iterator miter; struct req_inform cur_inform;

printf(\"从外部服务器得到响应包...\"); memcpy(&cur_id,buffer,2);

for(i=0;i{//检索映射表找到相匹配的 id 信息,从而正确返回 dns 请求回复 miter=req_cache[i].find(cur_id); if(miter!=req_cache[i].end()) {

cur_inform=miter->second; //获得之前dns请求信息 memcpy(buffer,&(cur_inform.id),2); //得到旧id复制到buffer,修改id if(sendto(sockfd,buffer,buffer_size,0,(struct sockaddr*) &(cur_inform.cli_addr),sizeof(struct sockaddr))==-1) printf(\" 发送至客户端失败! \\n\"); else printf(\"成功发送至客户端! \\n\"); return ; } } printf(\"could not be found in the cache... \\n\"); }

int main() { char recv_buffer[512]; SOCKADDR_IN recv_addr; int recv_size;

printf(\"读入url-ip表...\");

if(get_url_ip_table(url_ip_table)==-1) {

perror(\"读入url-ip表错误! \\n\"); return 0; }

printf(\"缓存一共有 %d 条记录\\n\中表项 //初始化 socket 和 sockaddr_in,bind 端口 printf(\"初始化socket...\"); if(init()==-1) return 0;

printf(\"初始化成功... \\n\"); printf(\"dns服务器启动... \\n\"); while(1) { int recv_len=sizeof(sockaddr); if((recv_size=recvfrom(sockfd,recv_buffer,sizeof(recv_buffer),0,(sockaddr *)&recv_addr,&recv_len))==-1) { perror(\"接收错误包! \\n\"); continue; } else if(is_req(recv_buffer)) }

{//处理请求包 deal_req(recv_buffer,recv_addr,recv_size); } else {//处理响应包 deal_res(recv_buffer,recv_size); } printf(\"\\n\\n\"); }

return 0;

测试用例以及运行结果

(1)首先先把本地连接中的DNS服务器地址改成本地的127.0.0.1

此时无法正常使用网页:

(2)开启本实验设计的DNS服务器:

正常浏览网页:

能正常ping通网址:

服务器显示信息:

能正常使用nslookup命令查ip地址: a.本地解析表存在的记录:

服务器信息:

b.查询本地解析表中,被屏蔽的记录

服务器信息:

C.本地没有记录,转发出去:

服务端信息:

遇到并解决的问题

1. Id转换问题

构造一个map映射:

map req_cache[cache_num];

把客户端 dns 请求映射到一个 unsigned short 上面,用它来存储 id 转换表。 id 转换表相关的参数如下: #define cache_num 2//转换表数目

#define cache_size 500//每个转换表容量

int idThen_max=cache_num*cache_size; //总条目数 int cur_cache=0; int idThen=0;

cache_num 指定了 id 转换表的个数,cache_size 是每个 id 转换表的大小,cur_cache 指向是当前正在装入的 id 转换表, idThen 是一个从 0 到 1000 一直循环的被映射到的 id 号。 这个设计的作用是更方便转换包的id信息,为处理在本地映射表找不到相应ip的情况打下基础。由于实验中只用了一个socket,这样的映射设计,能及时处理各种包,遇到转发的情况不用阻塞等待回复,提高工作效率。采用循环map表记录信息,可以是及时清理掉已处理或超时的转发包信息。 具体流程:

生成 id 转换的 item

把 id 转换的 item 加入到 req_cache[cur_cache]中 If ( req_cache[cur_cache]已经达到 cache_size ) {

cur_cache 指向下一个 id 转换表,并将其清空 }

idThen ++

2. 阻塞问题

解决阻塞: While(1){

recv 一个包

if ( 是客户端请求包 ) {

处理客户端请求包(deal_req) } else {

处理回答包(deal_res) }

通过while死循环,阻塞等待接收包触发。接收到一个包,就及时处理,在返回recv语句阻塞,等待下一个包的到来。

3. 识别IPV4和IPV6

由于一开始没有对于IPV4和IPV6的判断导致域名转换结果输出两个相同的IP。之后对程序做了改进,增加了对于IPV4和IPV6的判断,即拿出报文相关字段进行判别。 DNS数据包格式如下图所示:

其中查询问题部分:

两字节的查询类型若为1则表示IPV4,28表示IPV6,对该部分进行判别即可区分IPV4和IPV6。

实验总结

本次实验要求设计一个DNS服务器程序。通过完成本地域名解析、中继功能以及不良网站拦截等基本的三个功能来实现用户访问外部服务器的需求。一开始的时候对RFC没有一个清晰地概念,后来查阅了大量相关资料,对DNS的基本原理和实现方法有了比较全面和深刻的理解,这对后期的程序编写起到了非常大的帮助作用。

通过本次网络实验,对小型网络应用程序的实现有了一定程度的理解,更加深入的了解了 SOCKET 工作机制,熟悉了 SOCKET 编程的方法,对于编程能力的提高有很大帮助。同时,我们也对于 DNS 的工作过程有了一定的深入理解,清晰的了解了DNS报文格式和各个部分相应的功能,收获颇丰。

在验收时,经老师提醒,发现我们同时处理了IPV4与IPV6的请求包,并未准确区分它们之前的处理,导致结果有问题,会出现两个一样的应答包。经过修改代码,在处理请求包的请求前,先对包的查询类型做判断,若为IPV6查询包应转发给外部服务器应答(因本地未有对应的IPV6的地址信息),再提取url进行查询处理。这件事告诉我们,包的结构和请求流程必须清楚,不能遗漏可能情况,这样才能正确设计好的DNS服务器程序。

因篇幅问题不能全部显示,请点此查看更多更全内容