域名系统 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 客户端程序实例

cpp 复制代码
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/socket.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>

int sock;
struct sockaddr_in sa;

/*
generate_question:解析域名数据
*/
void generate_question(char*dns_name,char*buf,int*len)
{
    char*pos = dns_name;//指向域名当前位置
    char*ptr = buf;
    int n = 0;
    *len = 0;
    while(1)
    {
            n = strlen(pos) - (strstr(pos , ".") ? strlen(strstr(pos , ".")) : 0);//在 pos 中查找"."子串
        //返回"."在 pos 中第一次出现的地址,若没有找到返回 NULL
        printf("%d\n", n);
        *ptr++ = (unsigned char)n;
        memcpy(ptr,pos,n);
        printf("%lu\n",strlen(ptr));
        printf("%s\n", ptr);
        *len += n + 1;
        ptr += n;
        if(!strstr(pos , "."))
        {
            *ptr = (unsigned char)0;
            ptr ++;
            *len += 1;
            break;
        }
        pos += n + 1;
        printf("%s\n", pos);
    }
}

/*
send_dns_request:解析域名数据,并发送数据包
*/
void send_dns_request(char * dns_name)
{
    unsigned char request[256] = {0}; //保存整个请求报文
    unsigned char *ptr = request;
    unsigned char question[128];//存储域名解析数据
    int question_len;
    //产生请求(把字符串表示的域名转换成 DNS 要求的格式)
    generate_question(dns_name , question ,&question_len);
    printf("%s\n", question);
    printf("%d\n", question_len);
    *((unsigned short*)ptr) = 1; //会话标识 ID
    ptr += 2;
    *((unsigned short*)ptr) = htons(0x0100); //flags
    ptr += 2;
    *((unsigned short*)ptr) = htons(1); //Quetions 问题数,通常为 1
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    *((unsigned short*)ptr) = 0;
    ptr += 2;
    //把域名(www.baidu.com)装换为相应的问题格式保存在此处,以\0 结尾
    memcpy(ptr , question , question_len);
    ptr += question_len;
    *((unsigned short*)ptr) = htons(1); //获取 IPV4 地址,查询类型
    ptr += 2;
    *((unsigned short*)ptr) = htons(1); //指互联网地址,查询类,一般为 1,表明是 Internet 数据
    int re = sendto(sock, request,question_len+16 ,0, (structsockaddr*)&sa, sizeof(sa));
    printf("re = %d\n",re);
    int i;
    for(i=0;i<re;i++)//以 16 进制格式打印出来
    {
        printf("%02X ",(unsigned char)request[i]);
    } 
    printf("===================\n");
}

/*
    recv_dns_response:获取域名所对应的 IP 地址,并打印
*/
void recv_dns_response()
{
    struct sockaddr_in src_addr;
    socklen_t addrlen = sizeof(src_addr);
    char buf[255] = {0};
    int r = recvfrom(sock, buf, 255, 0, (struct sockaddr*)&src_addr,&addrlen);
    printf("r == %d\n", r);
    if(r > 0)//收到大于 0 的数据
    {
        int i;
        for(i=r-4;i<r;i++)//以 16 进制格式打印出来
        {
            if(i != r-1)
                printf("%d.",(unsigned char)buf[i]);
            else
                printf("%d\n",(unsigned char)buf[i]);
        } 
    }
}

//例./main www.baidu.com
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Usage : %s <domain name>\n",argv[0]);
        return -1;
    }
    //step 1: 创建一个套接字
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == -1)
    {
        perror("socket error:");
        return -1;
    }

    //step 2: 绑定一个地址(ip+端口号)
    memset(&sa, 0,sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(53); //按"网络字节序"来保存一个整数
    sa.sin_addr.s_addr = inet_addr("114.114.114.114");
    //发送解析请求
    send_dns_request(argv[1]);
    //接收分析结果
    recv_dns_response();
    close(sock);
    return 0;
}


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

域名解析函数(gethostbyname)

【头文件】

cpp 复制代码
#include <netdb.h>
#include <sys/socket.h>
extern int h_errno;

【函数原型】

cpp 复制代码
struct hostent *gethostbyname(const char *name);

【函数功能】
使用域名或主机名获取地址
【参数含义】
**[name]:**待解析的域名或主机名
【返回值】
失败返回 NULL 指针
成功返回的非空指针指向如下的 hostent 结构体指针

cpp 复制代码
struct hostent
{
    char *h_name; /* 主机正式名称 */
    char **h_aliases; /* 别名列表。 */
    int h_addrtype; /* 主机地址类型。*/
    int h_length; /* 地址的长度。 */
    char **h_addr_list; /*来自名称服务器的地址列表。 */
    #ifdef __USE_MISC
    # define h_addr h_addr_list[0] /* 地址,用于向后兼容。*/
    #endif
};

【示例】

cpp 复制代码
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
extern int h_errno;

int main(int argc, char **argv)
{
    if (argc != 2) {
        printf("Use example: %s www.google.com\n", *argv);
        return -1;
    }
    char *name = argv[1];
    struct hostent *hptr;
    hptr = gethostbyname(name);
    if (hptr == NULL) {
        printf("gethostbyname error for host: %s: %s\n", name, 
        hstrerror(h_errno));
        return -1;
    }
    //输出主机的规范名
    printf("\tofficial: %s\n", hptr->h_name);
    //输出主机的别名
    char **pptr;
    char str[INET_ADDRSTRLEN];
    for (pptr=hptr->h_aliases; *pptr!=NULL; pptr++) {
        printf("\ttalias: %s\n", *pptr);
    }
    //输出 ip 地址
    switch (hptr->h_addrtype) {
        case AF_INET:
            pptr = hptr->h_addr_list;
            for (; *pptr!=NULL; pptr++) {
                printf("\taddress: %s\n",inet_ntop(hptr->h_addrtype, hptr->h_addr, str, sizeof(str)));
        }
            break;
        default:
            printf("unknown address type\n");
            break;
    }
    return 0;
}

获取本地主机名(gethostname)

【头文件】

cpp 复制代码
#include <unistd.h>

【函数原型】

cpp 复制代码
int gethostname(char *name, size_t len);

【函数功能】
获取本地主机名
【参数含义】
**[name]:**保存获取的主机名
**[len]:**naem 的最大长度
【返回值】
成功返回 0,失败返回-1
【示例】

cpp 复制代码
char buf[256] = {0};
int t = gethostname(buf,256);
if(t == 0)
{
    printf("%s\n",buf);
}

设置本地主机名(sethostname)

【头文件】

cpp 复制代码
#include <unistd.h>

【函数原型】

cpp 复制代码
int sethostname(const char *name, size_t len);

【函数功能】
设置本地主机名
【参数含义】
**[name]:**设置的主机名
**[len]:**naem 的最大长度
【返回值】
成功返回 0,失败返回-1
【示例】
sethostname ( "jiuyue" , 6 );

相关推荐
betazhou4 分钟前
基于Linux环境实现Oracle goldengate远程抽取MySQL同步数据到MySQL
linux·数据库·mysql·oracle·ogg
什么半岛铁盒11 分钟前
Linux信号的保存
linux·运维·网络
百锦再18 分钟前
大数据技术的主要方向及其应用详解
大数据·linux·网络·python·django·pygame
2301_8035545232 分钟前
vim,gcc/g++,makefile,cmake
linux·编辑器·vim
惜.己1 小时前
Linux常用命令(十四)
linux·运维·服务器
linkingvision2 小时前
H5S 视频监控AWS S3 对象存储
linux·运维·aws·视频监控s3对象存储
BillKu2 小时前
服务器多JAR程序运行与管理指南
运维·服务器·jar
QQ2740287562 小时前
BlockMesh Ai项目 监控节点部署教程
运维·服务器·web3
belldeep2 小时前
WSL 安装 Debian 12 后,Linux 如何安装 vim ?
linux·debian·vim
wqqqianqian3 小时前
国产linux系统(银河麒麟,统信uos)使用 PageOffice自定义Word模版中的数据区域
linux·word·自定义·pageoffice·数据区域