• 域名系统 DNS


    DNS 概述

            域名系统 DNS(Domain Name System)是因特网使用的命名系统,用来把便于人们使用的机器名字转换成为 IP 地址。域名系统其实就是名字系统。为什么不叫“名字”而叫“域名”呢?这是因为在这种因特网的命名系统中使用了许多的“域(domain)”,因此就出现了“域名”这个名词。“域名系统”明确地指明这种系统是应用在因特网中。我们都知道,IP 地址是由 32 位的二进制数字组成的。用户与因特网上某台主机通信时,显然不愿意使用很难记忆的长达 32 位的二进制主机地址。即使是点分十进制 IP 地址也并不太容易记忆。相反,大家愿意使用比较容易记忆的主机名字。但是,机器在处理 IP 数据报时,并不是使用域名而是使用 IP 地址。 这是因为 IP 地址长度固定,而域名的长度不固定,机器处理起来比较困难。
            例如,www.baidu.com 就是一个域名,那么域名解析的过程就是:
    www.baidu.com-->DNS 服务器(把域名转成 IP)-->转换为 IP(http://14.215.177.39/)

    DNS 协议运行在 UDP 协议上面,是一个 UDP 的“回显”程序,使用 53 号端口

    因特网的域名结构

    从语法上讲,每一个域名都是有标号(label)序列组成,而各标号之间用点(小数点)隔开。如下例子所示:

            上图这是中央电视台用于手法电子邮件的计算机的域名,它由三个标号组成,其中标号com 是顶级域名,标号 cctv 是二级域名,标号 mail 是三级域名。
            DNS 规定,域名中的标号都有英文和数字组成, 每一个标号不超过 63 个字符 ( 为了记忆方 便,一般不会超过 12 个字符 ) ,也不区分大小写字母。
            级别最低的域名写在最左边,而级别最高的字符写在最右边。由多个标号组成的完整域名总共不超过 255 个字符。
            DNS 既不规定一个域名需要包含多少个下级域名,也不规定每一级域名代表什么意思。各级域名由其上一级的域名管理机构管理,而最高的顶级域名则由 ICANN 进行管理。用这种方法可使每一个域名在整个互联网范围内是唯一的,并且也容易设计出一种查找域名的机制。

    DNS 协议

    首部格式

            DNS 请求与响应的格式是一致的,其头部分为 Header Question Answer Authority
    Additional5 部分,如下图所示:

    Header 部分是一定有的,长度固定为 12 个字节;其余 4 部分可能有也可能没有,并且长度也不一定,这个在 Header 部分中有指明。 Header 的结构如下:

    下面说明一下各个字段的含义 :
    1. 标识符:占 16
            ID:占 16 位。该值由发出 DNS 请求的程序生成,DNS 服务器在响应时会使用该 ID,这样便于请求程序区分不同的 DNS 响应。
    2. 标志:占 16
            QR:占 1 位。指示该消息是请求还是响应。0 表示请求;1 表示响应。
            OPCODE:占 4 位。指示请求的类型,有请求发起者设定,响应消息中复用该值。0 表示标准查询;1 表示反转查询;2 表示服务器状态查询。3~15 目前保留,以备将来使用。
            AA(Authoritative Answer,权威应答):占 1 位。表示响应的服务器是否是权威DNS 服务器。只在响应消息中有效。
            TC(TrunCation,截断):占 1 位。指示消息是否因为传输大小限制而被截断。
            RD(Recursion Desired,期望递归):占 1 位。该值在请求消息中被设置,响应消息复用该值。如果被设置,表示希望服务器递归查询。但服务器不一定支持递归查询。
            RA(Recursion Available,递归可用性):占 1 位。该值在响应消息中被设置或被清除,以表明服务器是否支持递归查询。
            Z:占 3 位。保留备用。
            RCODE(Response code):占 4 位。该值在响应消息中被设置。取值及含义如下:
                    0:No error condition,没有错误条件;
                    1:Format error,请求格式有误,服务器无法解析请求;
                    2:Server failure,服务器出错。
                    3:Name Error,只在权威 DNS 服务器的响应中有意义,表示请求中的域名不存在。
                    4:Not Implemented,服务器不支持该请求类型。
                    5:Refused,服务器拒绝执行请求操作。
                    6~15:保留备用。QDCOUNT:占 16 位(无符号)。指明 Question 部分的包含的实体数量。
            ANCOUNT:占 16 位(无符号)。指明 Answer 部分的包含的 RR(Resource Record)数 量。
            NSCOUNT:占 16 位(无符号)。指明 Authority 部分的包含的 RR(Resource Record)数量。
            ARCOUNT:占 16 位(无符号)。指明 Additional 部分的包含的 RR(Resource Record)数量。

    数据区域

    查询名:
            查询名部分长度不定,一般为要查询的域名(也会有 IP 的时候,即反向查询)。 此部分由一个或者多个标示符序列组成,每个标示符以首字节数的计数值来说明该标示符长度,每个名字以 0 结束。计数字节数必须是 0~63 之间。该字段无需填充字节。还是借个例子来说明更直观些,查询名为 http://gemini.tuc.noao.edu 的话,查询名字段如下
    查询类型:

    查询类:
            一般为 IN(枚举值 1) ,即 Internet 数据

    DNS 客户端程序实例

    1. #include /* See NOTES */
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. int sock;
    15. struct sockaddr_in sa;
    16. /*
    17. generate_question:解析域名数据
    18. */
    19. void generate_question(char*dns_name,char*buf,int*len)
    20. {
    21. char*pos = dns_name;//指向域名当前位置
    22. char*ptr = buf;
    23. int n = 0;
    24. *len = 0;
    25. while(1)
    26. {
    27. n = strlen(pos) - (strstr(pos , ".") ? strlen(strstr(pos , ".")) : 0);//在 pos 中查找"."子串
    28. //返回"."在 pos 中第一次出现的地址,若没有找到返回 NULL
    29. printf("%d\n", n);
    30. *ptr++ = (unsigned char)n;
    31. memcpy(ptr,pos,n);
    32. printf("%lu\n",strlen(ptr));
    33. printf("%s\n", ptr);
    34. *len += n + 1;
    35. ptr += n;
    36. if(!strstr(pos , "."))
    37. {
    38. *ptr = (unsigned char)0;
    39. ptr ++;
    40. *len += 1;
    41. break;
    42. }
    43. pos += n + 1;
    44. printf("%s\n", pos);
    45. }
    46. }
    47. /*
    48. send_dns_request:解析域名数据,并发送数据包
    49. */
    50. void send_dns_request(char * dns_name)
    51. {
    52. unsigned char request[256] = {0}; //保存整个请求报文
    53. unsigned char *ptr = request;
    54. unsigned char question[128];//存储域名解析数据
    55. int question_len;
    56. //产生请求(把字符串表示的域名转换成 DNS 要求的格式)
    57. generate_question(dns_name , question ,&question_len);
    58. printf("%s\n", question);
    59. printf("%d\n", question_len);
    60. *((unsigned short*)ptr) = 1; //会话标识 ID
    61. ptr += 2;
    62. *((unsigned short*)ptr) = htons(0x0100); //flags
    63. ptr += 2;
    64. *((unsigned short*)ptr) = htons(1); //Quetions 问题数,通常为 1
    65. ptr += 2;
    66. *((unsigned short*)ptr) = 0;
    67. ptr += 2;
    68. *((unsigned short*)ptr) = 0;
    69. ptr += 2;
    70. *((unsigned short*)ptr) = 0;
    71. ptr += 2;
    72. //把域名(www.baidu.com)装换为相应的问题格式保存在此处,以\0 结尾
    73. memcpy(ptr , question , question_len);
    74. ptr += question_len;
    75. *((unsigned short*)ptr) = htons(1); //获取 IPV4 地址,查询类型
    76. ptr += 2;
    77. *((unsigned short*)ptr) = htons(1); //指互联网地址,查询类,一般为 1,表明是 Internet 数据
    78. int re = sendto(sock, request,question_len+16 ,0, (structsockaddr*)&sa, sizeof(sa));
    79. printf("re = %d\n",re);
    80. int i;
    81. for(i=0;i//以 16 进制格式打印出来
    82. {
    83. printf("%02X ",(unsigned char)request[i]);
    84. }
    85. printf("===================\n");
    86. }
    87. /*
    88. recv_dns_response:获取域名所对应的 IP 地址,并打印
    89. */
    90. void recv_dns_response()
    91. {
    92. struct sockaddr_in src_addr;
    93. socklen_t addrlen = sizeof(src_addr);
    94. char buf[255] = {0};
    95. int r = recvfrom(sock, buf, 255, 0, (struct sockaddr*)&src_addr,&addrlen);
    96. printf("r == %d\n", r);
    97. if(r > 0)//收到大于 0 的数据
    98. {
    99. int i;
    100. for(i=r-4;i//以 16 进制格式打印出来
    101. {
    102. if(i != r-1)
    103. printf("%d.",(unsigned char)buf[i]);
    104. else
    105. printf("%d\n",(unsigned char)buf[i]);
    106. }
    107. }
    108. }
    109. //例./main www.baidu.com
    110. int main(int argc, char *argv[])
    111. {
    112. if(argc != 2)
    113. {
    114. printf("Usage : %s \n",argv[0]);
    115. return -1;
    116. }
    117. //step 1: 创建一个套接字
    118. sock = socket(AF_INET, SOCK_DGRAM, 0);
    119. if (sock == -1)
    120. {
    121. perror("socket error:");
    122. return -1;
    123. }
    124. //step 2: 绑定一个地址(ip+端口号)
    125. memset(&sa, 0,sizeof(sa));
    126. sa.sin_family = AF_INET;
    127. sa.sin_port = htons(53); //按"网络字节序"来保存一个整数
    128. sa.sin_addr.s_addr = inet_addr("114.114.114.114");
    129. //发送解析请求
    130. send_dns_request(argv[1]);
    131. //接收分析结果
    132. recv_dns_response();
    133. close(sock);
    134. return 0;
    135. }

    可以看到当按正确的报文形式发送给 DNS 服务器后,接收到的报文后面四个字节存储了解析的 IP 地址

    域名解析函数(gethostbyname)

    【头文件】
    1. #include
    2. #include
    3. extern int h_errno;
    【函数原型】
    struct hostent *gethostbyname(const char *name);
    【函数功能】
            使用域名或主机名获取地址
    【参数含义】
            [name]: 待解析的域名或主机名
    【返回值】
            失败返回 NULL 指针
            成功返回的非空指针指向如下的 hostent 结构体指针
    1. struct hostent
    2. {
    3. char *h_name; /* 主机正式名称 */
    4. char **h_aliases; /* 别名列表。 */
    5. int h_addrtype; /* 主机地址类型。*/
    6. int h_length; /* 地址的长度。 */
    7. char **h_addr_list; /*来自名称服务器的地址列表。 */
    8. #ifdef __USE_MISC
    9. # define h_addr h_addr_list[0] /* 地址,用于向后兼容。*/
    10. #endif
    11. };
    【示例】
    1. #include
    2. #include
    3. #include
    4. #include
    5. extern int h_errno;
    6. int main(int argc, char **argv)
    7. {
    8. if (argc != 2) {
    9. printf("Use example: %s www.google.com\n", *argv);
    10. return -1;
    11. }
    12. char *name = argv[1];
    13. struct hostent *hptr;
    14. hptr = gethostbyname(name);
    15. if (hptr == NULL) {
    16. printf("gethostbyname error for host: %s: %s\n", name,
    17. hstrerror(h_errno));
    18. return -1;
    19. }
    20. //输出主机的规范名
    21. printf("\tofficial: %s\n", hptr->h_name);
    22. //输出主机的别名
    23. char **pptr;
    24. char str[INET_ADDRSTRLEN];
    25. for (pptr=hptr->h_aliases; *pptr!=NULL; pptr++) {
    26. printf("\ttalias: %s\n", *pptr);
    27. }
    28. //输出 ip 地址
    29. switch (hptr->h_addrtype) {
    30. case AF_INET:
    31. pptr = hptr->h_addr_list;
    32. for (; *pptr!=NULL; pptr++) {
    33. printf("\taddress: %s\n",inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
    34. }
    35. break;
    36. default:
    37. printf("unknown address type\n");
    38. break;
    39. }
    40. return 0;
    41. }

    获取本地主机名(gethostname)

    【头文件】
    #include 
    【函数原型】
    int gethostname(char *name, size_t len);
    【函数功能】
            获取本地主机名
    【参数含义】
            [name]: 保存获取的主机名
            [len]: naem 的最大长度
    【返回值】
            成功返回 0,失败返回-1
    【示例】
    1. char buf[256] = {0};
    2. int t = gethostname(buf,256);
    3. if(t == 0)
    4. {
    5. printf("%s\n",buf);
    6. }

    设置本地主机名(sethostname)

    【头文件】
    #include 
    【函数原型】
    int sethostname(const char *name, size_t len);
    【函数功能】
            设置本地主机名
    【参数含义】
            [name]: 设置的主机名
            [len]: naem 的最大长度
    【返回值】
            成功返回 0,失败返回-1
    【示例】
    sethostname ( "jiuyue" , 6 );
  • 相关阅读:
    CMake使用记录
    C#和.NET FrameWork概述
    项目实战第四十六讲:财务经营看板
    【软件测试】PDM、PTM、IPD介绍(捣鼓一晚上的血泪知识)
    企业微信获取客户群里用户的unionid;企业微信获取客户详情
    SDN实验(三)——集线器hub的实现
    代码随想录算法训练营第六天|字符串 & 栈 & 队列
    .NetCore+Vue2.0前后端分离的个人博客项目
    数据库设计规范
    什么年代了,还在用FastQC?试试Falco吧
  • 原文地址:https://blog.csdn.net/jiu_yue_ya/article/details/134056328