DNS 简介

更新时间:
2024-03-14
下载文档

DNS 简介

DNS 是域名系统(Domain Name System)的缩写,是因特网的一项核心服务,它作为可以将域名和 IP 地址相互映射的一个分布式数据库,使人能够更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 地址。

#include <sys/socket.h>
#include <netdb.h>
int   getaddrinfo(const char *nodename, const char *servname,
                  const struct addrinfo *hints, struct addrinfo **res);
void  freeaddrinfo(struct addrinfo *ai);

函数 getaddrinfo 原型分析:

  • 此函数成功返回 0,失败返回非 0 值。
  • 参数 nodename 是地址字符串。
  • 参数 servname 是服务名。
  • 参数 hints 输入地址信息。
  • 输出参数 res 返回结果地址信息。

函数 freeaddrinfo 原型分析:

  • 参数 ai 是由 getaddrinfo 函数返回的地址信息结构。

getaddrinfo 函数返回一个或更多的 addrinfo 结构的地址信息,这些地址结构可通过调用 freeaddrinfo 函数进行释放,SylixOS 中 addrinfo 结构,如下所示:

struct addrinfo {
    int                 ai_flags;         /* Input flags.                           */
    int                 ai_family;        /* Address family of socket.              */
    int                 ai_socktype;      /* Socket type.                           */
    int                 ai_protocol;      /* Protocol of socket.                    */
    socklen_t           ai_addrlen;       /* Length of socket address.              */
    struct sockaddr    *ai_addr;          /* Socket address of socket.              */
    char               *ai_canonname;     /* Canonical name of service location.    */
    struct addrinfo    *ai_next;          /* Pointer to next in list.               */
};

下面是 addrinfo 结构成员含义:

  • ai_flags:输入标志,如下表所示。
标志说明
AI_PASSIVE套接字地址用于监听绑定
AI_CANONNAME需要一个规范的名字(与别名相对)
AI_NUMERICHOST以数字形式指定主机地址
AI_NUMERICSERV将服务指定为数字端口号
  • ai_family:套接字地址族。
  • ai_socktype:套接字类型,如下表所示。
套接字类型说明
SOCK_DGRAM数据报套接口,固定长度,无连接的,不可靠的报文传递
SOCK_RAW原始套接口,IP 协议的数据报接口
SOCK_SEQPACKET有序分组套接口,固定长度的、有序的、可靠的、面向连接的报文传递
SOCK_STREAM字节流套接口,有序的、可靠的、双向的、面向连接的字节流
  • ai_protocol:协议,如下表所示。
协议域说明
AF_UNSPEC未指定
AF_INETIPv4 因特网域
AF_INET6IPv6 因特网域
AF_UNIXUNIX 域
AF_PACKETPACKET 域
  • ai_addrlen:套接字地址长度。
  • ai_addr:套接字地址。
  • ai_canonname:规范的名字。

可以提供一个可选的 hints 来选择符合特定条件的地址, hints 是一个用于过滤地址的模板,包括 ai_family、ai_flags、ai_protocol 和 ai_socktype 字段,剩余的字段在 SylixOS 分为两种情况,一是如果应用程序链接了外部 c 库(libcextern),则剩余的整数字段必须为 0,指针字段必须为空;二是如果应用程序没有链接外部 c 库,则剩余字段不做要求。

#include <sys/socket.h>
#include <netdb.h>
int   getnameinfo(const struct sockaddr *addr,    socklen_t len,
                  char *host,  socklen_t hostlen,
                  char *serv,  socklen_t servlen, int flag);

函数 getnameinfo 原型分析:

  • 此函数成功返回 0,失败返回非 0 错误值。
  • 参数 addr 是套接字地址。
  • 参数 len 是套接字地址长度。
  • 输出参数 host 返回主机名。
  • 参数 hostlen 是参数 host 缓冲区长度。
  • 输出参数 serv 返回服务主机名。
  • 参数 servlen 是参数 serv 缓冲区长度。
  • 参数 flag 是控制标志,如下表所示。

getnameinfo 函数将一个地址转换成一个主机名和服务名,如果 host 非 NULL,则指向一个长度为 hostlen 字节的缓冲区用于存放返回的主机名,同样,如果 serv 非 NULL,则指向一个长度为 servlen 字节的缓冲区用于存放返回的服务主机名。为了给 hostserv 分配空间 SylixOS 包含了以下常值:

常值说明
NI_MAXHOST返回的主机字符串的长度(参数 hostlen)1025
NI_MAXSERV返回的服务字符串的长度(参数 servlen)32

DNS 定义了一个用于查询和响应的报文格式。如下图所示是这个报文的总体格式。

下图 DNS 格式封装 16 bit 的标志字段被划分为若干子字段:

  • QR 是 1bit 字段:0 表示查询报文,1 表示响应报文。
  • opcode 是一个 4 bit 字段:通常值为 0(标准查询),其他值为 1(反向查询)和 2(服务器状态请求)。
  • AA 是 1bit 标志,表示“授权回答(authoritative answer)”。该名字服务器是授权于该域的,这个比特位在应答的时候才有意义。
  • TC 是 1bit 字段,表示“可截断的(truncated)”,用来指示报文比允许的长度还要长,例如:使用 UDP 时,它表示当应答的总长度超过 512 字节时,只返回前 512 个字节。
  • RD 是 1bit 字段表示“期望递归(recursion desired)”。该比特能在一个查询中设置,并在响应中返回。这个标志告诉名字服务器必须处理这个查询,也称为一个递归查询。如果该位为 0,且被请求的名字服务器没有一个授权回答,它就返回一个能解答该查询的其他名字服务器列表,这称为迭代查询。在后面的例子中,我们将看到这两种类型查询的例子。
  • RA 是 1bit 字段,表示“可用递归”。如果名字服务器支持递归查询,则在响应中将该比特设置为 1。大多数名字服务器都提供递归查询,除了某些根服务器。
  • zero 的 3bit 字段必须为 0。
  • rcode 是一个 4bit 的应答码字段,如下表所示。
标志说明
NI_NUMERICHOST返回主机地址的数字形式,而非主机名
NI_NUMERICSERV返回服务地址的数字形式(端口号),而非名字
NI_DGRAM服务基于数据报而非基于流
NI_NUMERICSCOPE对于 IPv6,返回范围 ID 的数字形式,而非名字

在大多数查询中,查询问题段包含着问题,例如:指定问什么。这个段包含了“问题数段”问题,每个问题格式,如下图所示;

  • 查询名被编码为一些 labels 序列,每个 labels 包含一个字节表示后续字符串长度,以及这个字符串,以 0 长度和空字符串来表示名字结束。
  • 查询类型用 16 bit 表示,取值可以为任何可用的类型值,以及通配符来表示所有的资源记录。
  • 查询类用 16bit 表示,如下表所示。
应答码说明
0没有错误
1报文格式错误(服务器不能理解请求的错误)
2服务器失败(因为服务器的原因导致没办法处理这个错误)
3名字错误(只有对授权域名解析服务器有意义,指出解析的域名不存在)
4没有实现
5拒绝(服务器由于设置的策略拒绝给出应答)
6-15保留值

回答、授权以及额外信息段都共用相同的格式:资源记录,格式如下图所示。

下图资源记录格式

  • 域名是资源记录包含的域名。
  • 类型表示 16bit 的资源记录类型。
  • 类表示 16bit 资源记录类。
  • 生存时间表示资源记录可以缓存的时间,如果为 0 只能被传输不能被缓存。
  • 资源数据长度表示数据长度。

DNS 查询过程如下:

客户机发出域名解析请求,并将该请求发送给本地域名服务器;

当本地的域名服务器收到请求后,就先查询本地的缓存,如果有该记录项,则本地的域名服务器就直接把查询的结果返回;

如果本地的缓存中没有该记录,则本地域名服务器就直接把请求发给根域名服务器,并查询自己的缓存,如果没有该记录,则返回相关的下级的域名服务器的地址;

重复第 3 歩,直到找到正确的记录。

下面程序展示了 getaddrinfo 函数的使用方法,本程序仅实现与 IPv4 一起工作的那些协议(ai_family 为 AF_INET)的地址信息,程序将输出限制在 AF_INET 协议族,也即在提示中设置 ai_family 字段为 AF_INET。

#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
void family (struct addrinfo *ai)
{
    fprintf(stdout, "-------------show family-------------\n");
    switch (ai->ai_family) {
    case AF_INET:
        fprintf(stdout, "inet.\n");
        break;
    case AF_INET6:
        fprintf(stdout, "inet6.\n");
        break;
    case AF_UNIX:
        fprintf(stdout, "unix domain.\n");
        break;
    case AF_PACKET:
        fprintf(stdout, "packet domain.\n");
        break;
    case AF_UNSPEC:
        fprintf(stdout, "unspec.\n");
        break;
    default:
        fprintf(stderr, "unknown %d\n", ai->ai_family);
    }
}
void type (struct addrinfo *ai)
{
    fprintf(stdout, "-------------show socktype-----------\n");
    switch (ai->ai_socktype) {
    case SOCK_STREAM:
        fprintf(stdout, "stream.\n");
        break;
    case SOCK_DGRAM:
        fprintf(stdout, "datagram.\n");
        break;
    case SOCK_RAW:
        fprintf(stdout, "raw.\n");
        break;
    case SOCK_SEQPACKET:
        fprintf(stdout, "seqpacket.\n");
        break;
    default:
        fprintf(stderr, "unknown %d.\n", ai->ai_socktype);
    }
}
void protocol (struct addrinfo *ai)
{
    fprintf(stdout, "-------------show protocol-----------\n");
    switch (ai->ai_protocol) {
    case 0:
        fprintf(stdout, "default.\n");
        break;
    case IPPROTO_TCP:
        fprintf(stdout, "TCP.\n");
        break;
    case IPPROTO_UDP:
        fprintf(stdout, "UDP.\n");
        break;
    case IPPROTO_RAW:
        fprintf(stdout, "RAW.\n");
        break;
    default:
        fprintf(stderr, "unknown %d.\n", ai->ai_protocol);
    }
}
void flags (struct addrinfo *ai)
{
    fprintf(stdout, "-------------show flags--------------\n");
    if (ai->ai_flags == 0) {
        fprintf(stdout, " 0 \n");
    } else {
        if (ai->ai_flags & AI_PASSIVE) {
            fprintf(stdout, " AI_PASSIVE \n");
        }
        if (ai->ai_flags & AI_CANONNAME) {
            fprintf(stdout, " AI_CANONNAME \n");
        }
        if (ai->ai_flags & AI_NUMERICHOST) {
            fprintf(stdout, " AI_NUMERICHOST \n");
        }
        if (ai->ai_flags & AI_NUMERICSERV) {
            fprintf(stdout, " AI_NUMERICSERV \n");
        }
    }
}
int main (int argc, char *argv[])
{
    struct addrinfo       hints;
    struct addrinfo      *aip, *ailist;
    int                   ret;
    struct sockaddr_in   *inetaddr;
    const char           *addr = NULL;
    char                  buf[INET_ADDRSTRLEN];
    if (argc < 2) {
        fprintf(stderr, "%s [host]\n", argv[0]);
        return  (-1);
    }
    bzero(&hints, sizeof(hints));
    hints.ai_family      = AF_INET;
    hints.ai_socktype    = 0;
    hints.ai_flags       = AI_CANONNAME;
    ret = getaddrinfo(argv[1], NULL, &hints, &ailist);
    if (ret != 0) {
        fprintf(stderr, "getaddrinfo %s.\n", strerror(ret));
        return  (-1);
    }
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {
        family(aip);
        type(aip);
        protocol(aip);
        flags(aip);
        fprintf(stdout, "host: %s\n", 
aip->ai_canonname ? aip->ai_canonname : "=");
        if (aip->ai_family == AF_INET) {
            inetaddr = (struct sockaddr_in *)aip->ai_addr;
            addr = inet_ntop(AF_INET, &inetaddr->sin_addr, buf, INET_ADDRSTRLEN);
            fprintf(stdout, "ADDR[IPv4]: %s\n", addr ? addr : "NULL");
        }
    }
    freeaddrinfo(ailist);
    return  (0);
}

在 SylixOS Shell 下运行程序,显示如下结果:

# ./Function_Getaddrinfo sylixos
-------------show family-------------
inet.
-------------show socktype-----------
unknown 0.
-------------show protocol-----------
default.
-------------show flags--------------
 0 
host: sylixos.com
ADDR[IPv4]: 127.0.0.1
文档内容是否对您有所帮助?
有帮助
没帮助