LINUX 入门 6
day10 20240505 耗时:41min
day10 20240506 耗时:155min
课程链接地址
第6章 DNS协议与请求
1 DNS协议分析与项目介绍
-
自己去看教程 快速扫了一下,还是结合实践去看概念有感觉
回答以下几个问题:
- dns作用
- dns分层
- 服务类型:授权型、递归型recursion
- dns协议——结构,服务器去解析,解析过程,报文格式
DNS互联网开发重要组件,domain name system:翻译域名domain name为IP地址
windows下win+R cmd nslookup www.baidu.com 查看网址的ip 14.215.177.38和39
我貌似不行,一直timeout,第二天试了以下又行了
-
wireshark 对dns解析
自己官网装一下就行,下载巨慢,用迅雷下就行
选Ethernet以太网 这个网卡网络适配器,but好像没有,wlan有,敲网址以后
前两条query,后一条response
看协议报文格式——recursion递归型查询,头部正文等等,去看教程,有点印象,看过的八股里有
2 DNS请求头定义与域名查询原则
域名查询 类似 Huffman树结构+多栈
0voice.com的name:60voice3com
// 查协议报文格式
struct dns_header{
// 长度固定
unsigned short id;
unsigned short flags;
unsigned short questions;
unsigned short answer;
unsigned short authority;
unsigned short additional;
};
struct dns_question {
// 长度不固定
int length;
unsigned short qtype;
unsigned short qclass;
unsigned char *name; //
};
3 DNS header填充与函数实现
- wireshark看到的一坨——物理层、数据链路层、网络层、传输层、DNS层
- query和response的transaction ID不变
- htons:将主机字节顺序转换为网络字节顺序的函数,其中 “htons” 代表 “host to network short”。在网络编程中,字节顺序是一个重要的概念,因为不同的计算机体系结构可能采用不同的字节顺序(即大端序12 | 34和小端序13 | 24),而网络协议通常要求使用特定的字节顺序进行通信,以确保数据在不同计算机之间正确解释。
// 2 client send to dns server 填充header
int dns_create_header(struct dns_header *header){
// 先判断传进来的参数,习惯!!!
if(header ==NULL) return -1;
memset(header, 0, sizeof(struct dns_header)); //接着,使用 memset 函数将传入的 header 指针所指向的内存区域全部设置为 0。这个操作通常用于初始化内存,确保结构体中的所有字段都被正确初始化为 0。
// random, 下面两行一起产生的,多线程,不安全,先srandom产生种子,在其他地方random()调用会出问题
srandom(time(NULL)); //提供一个随机种子,限定随机值的范围; 现在距离1970年的秒数,所以随机值很大!// srandom(100); //产生0-99的值!
header->id = random();
header->flags = htons(0x100);
header->questions = 1; //每次查一个DNS
}
4 DNS question填充与函数实现
不要开2倍速,听也听不清,很暴躁,越听越暴躁 1.25可以
-
size_t:
size_t
是一种用于表示内存大小的数据类型,通常用于与内存相关的操作,比如数组索引、内存分配等。它的大小在不同的系统中可能有所不同,但通常被定义为无符号整数类型。在 C 语言中,
size_t
类型通常是typedef
为unsigned int
或者unsigned long
的别名,取决于具体的编译器和系统架构。在 32 位系统中,通常为unsigned int
,而在 64 位系统中通常为unsigned long
。 -
strncpy(a,b,长度) strcpy()遇到\0才结束 b给a
// hostname:www.0voice.com
// name:3www6voice3com0
int dns_create_question(struct dns_question *question, const char *hostname){
if(question == NULL || hostname == NULL) return -1;
memset(question, 0, sizeof(struct dns_question));
question->name = (char*)malloc(strlen(hostname));
if(question->name == NULL) return -2;
question->length = strlen(hostname) + 2;
question->qtype = htons(1); //变成A
question->qclass = htons(1);
// name转换,一段段截
const char delim[2] = "."; //字符数组2个.
char *qname = question->name;
char *hostname_dup = strdup(hostname); //复制duplicate,自带malloc,返回一个动态的非常量!!
char *token = strtok(hostname_dup,delim); //截取token, 按分隔符delim截
while(token != NULL){
// 第一组www
size_t len = strlen(token);//取这段长度,size_t: 是一种用于表示内存大小的数据类型,
*qname = len;
qname++; //第一个放长度
strncpy(qname, token, len + 1);//最后的\0结束符也copy
qname += len;//指针移到结尾
// 更新token,后面2组0voice com
token = strtok(NULL, delim); //0voice.com第一次截要hostname_dup,第二次就接着截,所以线程不安全,因为保存了上一次截取的会错乱
}
free(hostname_dup);
}
5 DNS协议 UDP编程的实现
如何发射到DNS服务器(改不了),只能改client端。DNS类似广播,UDP:基于无连接,只管发不管收到吗
- AF_INET是一种常见的网络地址家族,它用于IPv4(Internet Protocol version 4)地址。在网络编程中,AF_INET常用于创建和处理IPv4套接字(socket)。它指示使用IPv4地址格式来进行通信。
- SOCK_DGRAM是一种套接字类型,用于在网络编程中进行数据报式(Datagram)通信。该类型的套接字提供了无连接、不可靠的数据传输服务。每个发送的数据包被视为一个独立的实体,可能会以任意顺序到达目标地址,并且也不能保证可靠交付或按顺序交付。UDP(User Datagram Protocol)就是使用SOCK_DGRAM套接字类型进行通信的协议之一。与TCP相比,UDP具有更低的开销和延迟,适用于对实时性要求较高但不需要可靠性和顺序性保证的应用场景。
sendto(fd, request, length, 0, servaddr, addr_len);
-
fd
:套接字文件描述符。 -
request
:要发送的数据报内容。 -
length
:要发送的数据报长度。 -
0
:标志位,用于指定额外的选项。在此处设置为0表示没有额外选项。 -
servaddr
:目标地址信息,通常是一个结构体类型,包含目标IP地址和端口号等信息。 -
addr_len
:目标地址信息结构体的大小。
UDP编程比较格式化,没啥为什么比较固定了
// 3 udp
int dns_client_commit(const char *domain){
// AF_INET是一种常见的网络地址家族,它用于IPv4(Internet Protocol version 4)地址。在网络编程中,AF_INET常用于创建和处理IPv4套接字(socket)。它指示使用IPv4地址格式来进行通信。
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); //client发的forward fd
if(sockfd < 0) return -1;
// 结构体的定义这么多年基本这么写,比较固定
struct sockaddr_in servaddr = {0};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(DNS_SERVER_PORT);
servaddr.sin_addr = inet_addr(DNS_SERVER_IP);
int ret = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
printf("connect : %d\n", ret);
struct dns_header header = {0};
dns_create_header(&header);
struct dns_question = {0};
dns_create_question(&question, domain);
char request[1024] = {0};
int length = dns_build_request(&header, &question, request);
// request
int slen = sendto(fd, request, length, 0, servaddr, sizof(struct sockaddr));
// recvfrom在这里阻塞,send和recieve在一起
char response[1024] = {0};
struct sockaddr_in addr;
size_t addr_len = sizeof(struct sockaddr_in);
int n = recvfrom(sockfd, response,sizeof(response), 0, (struct sockaddr*)&addr, (socklen_t*)&addr_len);
printf("recvfrom: %d, %s\n", n , response);
return n;
}
6 DNS build_requestion的实现
connect()类似连接前开路的,在tcp三次握手有用,udp没那么有用,可能在sendto前用
云里雾里的有点,太多库函数和数据类型 <sys/socket.h> <netinet/in.h> <unistd.h>不认识
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#define DNS_SERVER_PORT 53
#define DNS_SERVER_IP "114.114.114.114"
// 协议包组织打包,已有header question合并到request
int dns_build_request(struct dns_header *header, struct dns_question *question, char *request, int rlen){
if(header == NULL || question == NULL || request == NULL) return -1;
memset(request, 0, rlen);
// header-->request
memcpy(request, header, sizeof(struct dns_header)); //header复制到request
int offset = sizeof(struct dns_header);
// question-> request
memcpy(request + offset,question->name, question->length); // request +offset这里
offset += question->length;
memcpy(request+offset, &question->qtype, sizeof(question->qtype));
offset += sizeof(question->qtype);
memcpy(request+offset, &question->qclass, sizeof(question->qclass));
offset += sizeof(question->qclass);
return offset;
}
int main(int argc, char* argv[]){
if(argc <2) return -1;
dns_client_commit(argv[1]);
}
7 调试
爆了一堆错,一个个改
gcc -o dns dns.c
./dns www.0voice.com
我打印了connect : 0
没有打印接收recvfrom的东西,说明连接服务器是成功了
记住一句话
按DNS协议发送数据,就会按DNS协议返回数据,所以根本没法数据
问题处在:dns_create_header
里
// header->questions = 1; //每次查一个DNS
header->questions = htons(1); // 要转成网络字节序
输出
recvfrom: 48, %ځ�
%ځ�www0voicecom�
m+�y
5ffffffdaffffff81ffffff80010100003777777630766f6963653636f6d00101ffffffc0c01010016d042bffffff8b791b
8 DNS相应response解析
这里没敲,只看了操作和效果
is_pointer是否一帧,dns_parse_name和dns_parse_response就是翻译上面一坨乱码字符和16进制的形式
UDP好处,比TCP好处
- UDP传输快,对带宽无限制,比如迅雷下载 整个局域网就你下
- UDP响应速度快,游戏里服务器对客户端响应快!还有下载文件的时候