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_INET | IPv4 因特网域 |
AF_INET6 | IPv6 因特网域 |
AF_UNIX | UNIX 域 |
AF_PACKET | PACKET 域 |
- 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 字节的缓冲区用于存放返回的服务主机名。为了给 host 和 serv 分配空间 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