DNS服务器程序
目的:设计一个DNS服务器程序,读入“域名-IP地址”对照表(一个文件),当客户端查询域名对应的IP地址时,用域名检索该对照表,得到三种检索结果。
基本内容:设计一个DNS服务器程序,读入“域名-IP地址”对照表(一个文件),当客户端查询域名对应的IP地址时,用域名检索该对照表,得到三种检索结果。
实验方法:C语言编程。
系统的功能设计
读入“IP地址-域名”对照表,当客户端查询域名对应的IP地址时,用域名检索该对照表。
不良网站拦截功能:
检索结果为ip地址0.0.0.0,则向客户端返回“域名不存在”的报错消息
服务器功能:
检索结果为普通IP地址,则向客户返回这个地址
中继功能:
表中未检到该域名,则向因特网DNS服务器发出查询,并将结果返给客户端
需要进行消息ID的转换,以满足多个计算机上的客户端会同时查询。
主要的数据结构:
//DNS报文首部 12字节
typedef struct DNSHeader
{
unsigned short ID; //标志
unsigned short Flags; //标识
unsigned short QuestionNum; //问题数
unsigned short AnswerNum; //资源记录数
unsigned short AuthorNum; //授权资源记录数
unsigned short AdditionNum; //额外资源记录数
} DNSHDR, * pDNSHDR;
//DNS域名解析表结构
typedef struct translate
{
char * IP; //IP地址
char * domain; //域名
} Translate;
//ID转换表结构
typedef struct IDChange
{
unsigned short oldID; //原有ID
BOOL done; //标记是否完成解析
SOCKADDR_IN client; //请求者套接字地址
} IDTransform;
模块划分(函数划分)
//加载本地txt文件
int InitialDNSTable(char* path)
//获取DNS请求中的域名
void GetUrl(char* recvbuf, int recvnum)
//判断能不能在本中找到DNS请求中的域名,找到返回下标
int IsFind(char* url, int num)
//将请求ID转换为新的ID,并将信息写入ID转换表中
unsigned short ReplaceNewID(unsigned short OldID, SOCKADDR_IN temp, BOOL ifdone)
//打印 时间 newID 功能 域名 IP
void PrintInfo(unsigned short newID, int find)
//socket通信模块,内置于主函数
int main()
软件流程图
测试用例以及运行结果
首先将本地连接中的DNS服务器地址改为本地的127.0.0.1
此时无法正常使用网页。
开启设计好的DNS服务器
即可正常浏览网页。
而且能正常PING通网址:
此时服务器端显示信息:
此外还能正常使用nslookup命令查ip地址:
本地解析表存在的记录且并不屏蔽:
服务器信息:
本地解析表存在记录但返回IP为0.0.0.0,即屏蔽:
服务器信息:
本地没有记录,转发出去:
服务端信息:
调试中遇到并解决的问题
在代码编写过程中,踩了很多坑。
- 初次采用了两个socket共用一个端口,天真地将其简单化为收——发的简单循环过程中。在后续的调试过程中,认识到两个socket、共用端口造成的阻塞和丢失消息,具体表现为:当客户端socket从客户端拿到消息、发给服务端并等待服务端回复时,此时处于阻塞状态;而服务端socket也处于阻塞状态,所以服务端没有办法快速地处理客户端信息并给予应答,在这段没有办法应答的时间内,很有可能客户端判断超时,效率低下;且客户端socket在漫长地等待服务端socket回信的同时,客户端这边源源不断地发起请求都会被客户端socket错失掉。发现这个问题后,我们先是尝试了加入其它socket,具体尝试为客户端一个收socket、一个发socket;服务端一个收socket、一个发socket,但这样的方法在实现中出现了大量的bug,迫于时间压力只能放弃。我们又尝试了利用多线程。但过于频繁的请求发起使用多线程,又会造成大量问题。在验收前期,我们通过讨论才意识到:我们其实可以使用一个socket,在收发过程中判断报文的类型是请求还是响应,同时记录报文的ID和IP还有PORT,然后对其进行精准转发,这样其实就可以更好地解决上述问题。但是,由于时间压力和自身编程能力水平的限制,我们最终只能采取非阻塞对这种情况进行稍许的改善。
- 编程优秀习惯的养成。本次课程设计要求全C语言,熊宇同学编写完程序后交给马锐文同学修订,马锐文同学发现了熊宇同学代码中使用了部分C++内容,例如string库、iomanip库。迅速对其修改并检查对程序的影响,充分发挥了团队协作的优势。
源代码(613行)
“definition.h”
#ifndef DEFINITION_H_INCLUDED
#define DEFINITION_H_INCLUDED
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <iomanip>
#include <WinSock2.h>
#include <windows.h>
#include <time.h>
#define BUFSIZE 1024 //最大报文缓存大小
#define PORT 53 //53端口号
#define DEF_DNS_ADDRESS "223.5.5.5" //ipconfig/all 得知外部服务器dns地址
#define LOCAL_DNS_ADDRESS "127.0.0.1" //本地DNS服务器地址
#define AMOUNT 1500//最大ID转换表大小
#define NOTFOUND 32767 //没有找到
#define LENGTHOFURL 64 //0~63
//DNS报文首部 12字节
typedef struct DNSHeader
{
unsigned short ID; //标志
unsigned short Flags; //标识
unsigned short QuestionNum; //问题数
unsigned short AnswerNum; //资源记录数
unsigned short AuthorNum; //授权资源记录数
unsigned short AdditionNum; //额外资源记录数
} DNSHDR, * pDNSHDR;
//DNS域名解析表结构
typedef struct translate
{
char * IP; //IP地址
char * domain; //域名
} Translate;
//ID转换表结构
typedef struct IDChange
{
unsigned short oldID; //原有ID
BOOL done; //标记是否完成解析
SOCKADDR_IN client; //请求者套接字地址
} IDTransform;
#endif // DEFINITION_H_INCLUDED
“functions.h”
#ifndef FUNCTIONS_H_INCLUDED
#define FUNCTIONS_H_INCLUDED
#pragma once
int InitialDNSTable(char* path); //加载本地txt文件
void GetUrl(char* recvbuf, int recvnum); //获取DNS请求中的域名
int IsFind(char* url, int num);//判断能不能在本中找到DNS请求中的域名,找到返回下标
unsigned short ReplaceNewID(unsigned short OldID, SOCKADDR_IN temp, BOOL ifdone); //将请求ID转换为新的ID,并将信息写入ID转换表中
void PrintInfo(unsigned short newID, int find); //打印 时间 newID 功能 域名 IP
#endif // FUNCTIONS_H_INCLUDED
<functions.c>
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#include <time.h>
#include <process.h>
#include "definition.h"
#pragma comment(lib, "Ws2_32.lib") //加载 ws2_32.dll
extern Translate DNSTable[AMOUNT]; //DNS域名解析表
extern IDTransform IDTransTable[AMOUNT]; //ID转换表
extern int IDcount; //转换表中的条目个数
extern char Url[LENGTHOFURL]; //域名
extern SYSTEMTIME TimeOfSys; //系统时间
extern int Day, Hour, Minute, Second, Milliseconds;//保存系统时间的变量
//加载本地txt文件
int InitialDNSTable(char* path)
{
int i = 0, j = 0;
int num = 0;
char* Temp[AMOUNT];//char型指针1500数组
FILE* fp = fopen(path, "ab+");
if (!fp)
{
printf("Open file failed.\n");
exit(-1);
}
char* reac;
while (i < AMOUNT - 1)//实现把每一行分开的操作
{
Temp[i] = (char*)malloc(sizeof(char)*200);
//Temp[200];
//fscanf(fp, "%*c%*[^\n]", IPTemp[i]);
if (fgets(Temp[i],1000,fp)== NULL)//如果错误或者读到结束符,就返回NULL;
break;
else
{
//reac = strchr(Temp[i], '\n'); //查找换行符
//if (reac) //如果find不为空指针
// *reac = '\0';
;//printf("%s", Temp[i]);
}
i++;
}
if (i == AMOUNT - 1)
printf("The DNS record memory is full.\n");
for (j; j < i; j++)//用来把刚分好的TEMP【i】再次切割成IP和domain
{
char* ex1 = strtok(Temp[j], " ");
char* ex2 = strtok(NULL, " ");
if (ex2 == NULL)
{
printf("The record is not in a correct format.\n");
}
else
{
DNSTable[j].IP = ex1;
DNSTable[j].domain = ex2;
//DNSTable[j].IP[strlen(DNSTable[j].IP) - 1] = 0;
//DNSTable[j].domain[strlen(DNSTable[j].domain) - 1] = '\0';
//printf("%d\n%s\n%s\n",j, DNSTable[j].IP, DNSTable[j].domain);
//printf("%d %s %s\n", j, Temp[j], DNSTable[j].domain);
num++;
}
}
//printf("%d\n", num);
//
fclose(fp);
printf("Load records success.\n");
return num;
}
//获取DNS请求中的域名
void GetUrl(char* recvbuf, int recvnum)
{
char urlname[LENGTHOFURL];
int i = 0, j, k = 0;
memset(Url, 0, LENGTHOFURL); //全用0初始化
memcpy(urlname, &(recvbuf[sizeof(DNSHDR)]), recvnum - 12); //获取请求报文中的域名表示,要去掉DNS报文首部的12字节
int len = strlen(urlname);
//域名转换
while (i < len)
{
if (urlname[i] > 0 && urlname[i] <= 63)
for (j = urlname[i], i++; j > 0; j--, i++, k++)
Url[k] = urlname[i];
if (urlname[i] != 0)
{
Url[k] = '.';
k++;
}
}
Url[k] = '\0';
}
//判断能不能在本中找到DNS请求中的域名,找到返回下标
int IsFind(char* url, int num)
{
int find = NOTFOUND;
char* domain;
char* NUrl;
NUrl = (char*)malloc(sizeof(char)*210);
strcpy(NUrl, url);
strcat(NUrl, "\r\n");
//printf("URL: %sabab\n", url);
//printf("%sabab\n", DNSTable[20].domain);
for (int i = 0; i < num+2; i++)
{
if (DNSTable[i].domain)
{
domain = DNSTable[i].domain;
/*printf("%s", domain);
printf("%s", NUrl);
printf("%d\n", strcmp(domain, NUrl));
printf("wwwwwwwwwwwwwwwwwwwwwwwwwwww\n");*/
if (strcmp(domain, NUrl) == 0)
{
//printf("!~!FOUND!~!\n");
find = i;
break;
}
}
}
//printf("find:%d\n", find);
return find;
}
//将请求ID转换为新的ID,并将信息写入ID转换表中
unsigned short ReplaceNewID(unsigned short OldID, SOCKADDR_IN temp, BOOL ifdone)
{
srand(time(NULL)); //随机数种子
IDTransTable[IDcount].oldID = OldID;
IDTransTable[IDcount].client = temp;
IDTransTable[IDcount].done = ifdone;
IDcount++; //ID转换表数目要更新~
return (unsigned short)(IDcount - 1); //以表中下标作为新的ID
}
//打印 时间 newID 功能 域名 IP
void PrintInfo(unsigned short newID, int find)
{
//打印时间
GetLocalTime(&TimeOfSys);
//输出指定长度的字符串, 超长时不截断, 不足时左对齐:
//printf("%-ns", str); --n 为指定长度的10进制数值
int Btime;
int Ltime;
Btime = ((((TimeOfSys.wDay - Day) * 24 + TimeOfSys.wHour - Hour) * 60 + TimeOfSys.wMinute - Minute) * 60) + TimeOfSys.wSecond - Second;
Ltime = abs(TimeOfSys.wMilliseconds - Milliseconds);
printf("%d.%d %d", Btime, Ltime, newID);
printf(" ");
//在表中没有找到DNS请求中的域名
if (find == NOTFOUND)
{
//中继功能
printf("中继");
printf(" ");
//打印域名
printf("%s",Url);
printf(" ");
//打印IP
printf("\n");
}
//在表中找到DNS请求中的域名
else
{
if (strcmp(DNSTable[find].IP, "0.0.0.0") == 0) //不良网站拦截
{
//屏蔽功能
printf("屏蔽");
printf(" ");
//打印域名(加*)
//打印域名
printf("***%s", Url);
printf(" ");
//打印IP
printf("%s\n", DNSTable[find].IP);
}
//检索结果为普通IP地址,则向客户返回这个地址
else
{
//服务器功能
printf("Local服务器");
printf(" ");
//打印域名
printf("***%s", Url);
printf(" ");
//打印IP
printf("%s\n", DNSTable[find].IP);
}
}
}
<main.c>
//#define _CRT_SECURE_NO_WARNINGS
//#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <windows.h>
#include <time.h>
#include <process.h>
#include "definition.h"
#include "functions.h"
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
Translate DNSTable[AMOUNT]; //DNS域名解析表
IDTransform IDTransTable[AMOUNT]; //ID转换表
int IDcount = 0; //转换表中的条目个数
char Url[LENGTHOFURL]; //域名
SYSTEMTIME TimeOfSys; //系统时间
int Day, Hour, Minute, Second, Milliseconds;//保存系统时间的变量
int main()
{
//参考:https://wenku.baidu.com/view/ed7d64c852d380eb62946df4.html
//初始化 DLL
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET servSock = socket(AF_INET, SOCK_DGRAM, 0);
SOCKET localSock = socket(AF_INET, SOCK_DGRAM, 0);
//将套接口都设置为非阻塞
int unBlock = 1;
ioctlsocket(servSock, FIONBIO, (u_long FAR*) &unBlock);//将外部套街口设置为非阻塞
ioctlsocket(localSock, FIONBIO, (u_long FAR*) &unBlock);//将本地套街口设置为非阻塞
//绑定套接字
SOCKADDR_IN serverName, clientName, localName; //本地DNS、外部DNS和请求端三个网络套接字地址
localName.sin_family = AF_INET;
localName.sin_port = htons(PORT);
localName.sin_addr.s_addr = inet_addr(LOCAL_DNS_ADDRESS);
serverName.sin_family = AF_INET;
serverName.sin_port = htons(PORT);
serverName.sin_addr.s_addr = inet_addr(DEF_DNS_ADDRESS);
//绑定本地服务器地址
if (bind(localSock, (SOCKADDR*)&localName, sizeof(localName)))
{
printf("Bind 53 port failed.\n");
exit(-1);
}
else
printf("Bind 53 port success.\n");
char sendBuf[BUFSIZE]; //发送缓存
char recvBuf[BUFSIZE]; //接收缓存
char* Path;
Path=(char*)malloc(sizeof(char)*100);
int recordNum; //txt文件有效行数
int iLen_cli, iSend, iRecv;
strcpy(Path, "C:\\Users\\mrw29\\Desktop\\dnsrelay.txt");
recordNum = InitialDNSTable(Path);
//保存系统时间
GetLocalTime(&TimeOfSys);
Day = TimeOfSys.wDay;
Hour = TimeOfSys.wHour;
Minute = TimeOfSys.wMinute;
Milliseconds = TimeOfSys.wMilliseconds;
int find;
unsigned short NewID;
unsigned short* pID;
//下面是服务器的具体操作
while (1)
{
iLen_cli = sizeof(clientName);
memset(recvBuf, 0, BUFSIZE); //将接收缓存先置为全0
//接收DNS请求
//函数:int recvfrom(int s, void* buf, int len, unsigned int flags, struct sockaddr* from, int* fromlen);
//函数说明:recv()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, 参数len 为可接收数据的最大长度.
//参数flags 一般设0, 其他数值定义请参考recv().参数from 用来指定欲传送的网络地址, 结构sockaddr 请参考bind().参数fromlen 为sockaddr 的结构长度.
iRecv = recvfrom(localSock, recvBuf, sizeof(recvBuf), 0, (SOCKADDR*)&clientName, &iLen_cli);
//错误反馈
if (iRecv == SOCKET_ERROR)
{
//printf("Recvfrom Failed: %s\n", strerror(WSAGetLastError()));
continue; //强制开始下一次循环
}
else if (iRecv == 0)
{
break; //没东西,跳出循环0
}
else
{
GetUrl(recvBuf, iRecv); //获取域名
find = IsFind(Url, recordNum); //在域名解析表中查找
//printf("We have get the url: %s\n", Url);
//printf("%d\n", find);
//开始分情况讨论
//在域名解析表中没有找到
if (find == NOTFOUND)
{
//printf("We dont find this url, will get a new ID and forward to SERVER.\n");
//ID转换
//pID = new (unsigned short);
pID = (unsigned short*)malloc(sizeof(unsigned short*));
memcpy(pID, recvBuf, sizeof(unsigned short)); //报文前两字节为ID
NewID = htons(ReplaceNewID(ntohs(*pID), clientName, FALSE));
memcpy(recvBuf, &NewID, sizeof(unsigned short));
//打印 时间 newID 功能 域名 IP
PrintInfo(ntohs(NewID), find);
//把recvbuf转发至指定的外部DNS服务器
iSend = sendto(servSock, recvBuf, iRecv, 0, (SOCKADDR*)&serverName, sizeof(serverName));
if (iSend == SOCKET_ERROR)
{
//printf("sendto Failed: %s\n", strerror(WSAGetLastError()));
continue;
}
else if (iSend == 0)
break;
//delete pID; //释放动态分配的内存
free(pID);
clock_t start, stop; //定时
double duration = 0;
//接收来自外部DNS服务器的响应报文
start = clock();
iRecv = recvfrom(servSock, recvBuf, sizeof(recvBuf), 0, (SOCKADDR*)&clientName, &iLen_cli);
while ((iRecv == 0) || (iRecv == SOCKET_ERROR))
{
iRecv = recvfrom(servSock, recvBuf, sizeof(recvBuf), 0, (SOCKADDR*)&clientName, &iLen_cli);
stop = clock();
duration = (double)(stop - start) / CLK_TCK;
if (duration > 5)
{
printf("Long Time No Response From Server.\n");
goto ps;
}
}
//ID转换
pID = (unsigned short*)malloc(sizeof(unsigned short*));
memcpy(pID, recvBuf, sizeof(unsigned short)); //报文前两字节为ID
int GetId = ntohs(*pID); //ntohs的功能:将网络字节序转换为主机字节序
unsigned short oID = htons(IDTransTable[GetId].oldID);
memcpy(recvBuf, &oID, sizeof(unsigned short));
IDTransTable[GetId].done = TRUE;
//char* urlname;
//memcpy(urlname, &(recvBuf[sizeof(DNSHDR)]), iRecv - 12); //获取请求报文中的域名表示,要去掉DNS报文首部的12字节
//char* NewIP;
//打印 时间 newID 功能 域名 IP
PrintInfo(ntohs(NewID), find);
//从ID转换表中获取发出DNS请求者的信息
clientName = IDTransTable[GetId].client;
//printf("We get a answer from SERVER, now we give it back to client.\n");
//把recvbuf转发至请求者处
iSend = sendto(localSock, recvBuf, iRecv, 0, (SOCKADDR*)&clientName, sizeof(clientName));
if (iSend == SOCKET_ERROR)
{
//printf("sendto Failed: %s\n\n", strerror(WSAGetLastError()));
continue;
}
else if (iSend == 0)
break;
free(pID); //释放动态分配的内存
}
//在域名解析表中找到
else
{
//printf("We have find this url.\n");
//获取请求报文的ID
pID = (unsigned short*)malloc(sizeof(unsigned short*));
memcpy(pID, recvBuf, sizeof(unsigned short));
//转换ID
unsigned short nID = ReplaceNewID(ntohs(*pID), clientName, FALSE);
//printf("We have get a new ID, now we will create an answer.\n");
//打印 时间 newID 功能 域名 IP
PrintInfo(nID, find);
//参考:https://blog.csdn.net/weixin_34192993/article/details/87949701
//构造响应报文头
memcpy(sendBuf, recvBuf, iRecv); //拷贝请求报文
unsigned short AFlag = htons(0x8180); //htons的功能:将主机字节序转换为网络字节序,即大端模式(big-endian) 0x8180为DNS响应报文的标志Flags字段
memcpy(&sendBuf[2], &AFlag, sizeof(unsigned short)); //修改标志域,绕开ID的两字节
//修改回答数域
if (strcmp(DNSTable[find].IP, "0.0.0.0") == 0)
AFlag = htons(0x0000); //屏蔽功能:回答数为0
else
AFlag = htons(0x0001); //服务器功能:回答数为1
memcpy(&sendBuf[6], &AFlag, sizeof(unsigned short)); //修改回答记录数,绕开ID两字节、Flags两字节、问题记录数两字节
int curLen = 0; //不断更新的长度
//构造DNS响应部分
//参考:http://c.biancheng.net/view/6457.html
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);
//TTL四字节
unsigned long timeLive = htonl(0x7b); //生存时间
memcpy(answer + curLen, &timeLive, sizeof(unsigned long));
curLen += sizeof(unsigned long);
unsigned short RDLength = htons(0x0004); //资源数据长度
memcpy(answer + curLen, &RDLength, sizeof(unsigned short));
curLen += sizeof(unsigned short);
unsigned long IP = (unsigned long)inet_addr(DNSTable[find].IP); //inet_addr为IP地址转化函数
memcpy(answer + curLen, &IP, sizeof(unsigned long));
curLen += sizeof(unsigned long);
curLen += iRecv;
//请求报文和响应部分共同组成DNS响应报文存入sendbuf
memcpy(sendBuf + iRecv, answer, curLen);
//printf("Create Over, give it to client.\n");
clock_t Nstart, Nstop; //clock_t为clock()函数返回的变量类型
double Nduration;
//发送DNS响应报文
Nstart = clock();
iSend = sendto(localSock, sendBuf, curLen, 0, (SOCKADDR*)&clientName, sizeof(clientName));
//if (iSend == SOCKET_ERROR)
//{
// //printf("sendto Failed: %s\n", strerror(WSAGetLastError()));
// Nstop = clock();
// Nduration = (double)(Nstop - Nstart) / CLK_TCK;
// if (Nduration > 1)
// goto ps;
// else
// continue;
//}
//else if (iSend == 0)
// break;
free(pID); //释放动态分配的内存
//printf("\nThis loop is over, thanks.\n\n");
}
}
ps:;
}
closesocket(servSock);
closesocket(localSock);
WSACleanup(); //释放ws2_32.dll动态链接库初始化时分配的资源
system("pause");
return 0;
}
WireShark调试笔记
Nslookup www.baidu.com
从3890->3895
请求报文1
响应报文1
请求报文2
响应报文2
请求报文3
响应报文3
Nslookup 555.265.com
从2797->2829
请求报文1
响应报文1
请求报文2
响应报文2
Nslookup content.googleapis.com
从3190->3191
请求报文
响应报文
Nslookup
从86->87
请求报文
响应报文
♻️ 资源
大小: 6.16MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87388351
版权归原作者 神仙别闹 所有, 如有侵权,请联系我们删除。