DNS :
DNS是一个分层级 (hierarchical ),分布式(decentralized)的网络数据库,完成主机名称和IP地址之间的相互映射。DNS名称空间包含一个树状结构,树根没有命名, 下面是树的最高层为顶级域名, 顶级域名包括: 顶级域名(gTLD),国家代码顶级域名(ccTLD), 国际化国家设施顶级域名(infrastructure TLD), 下面一层是权威域名服务器。其中权威域名服务器对DNS请求进行应答返回IP地址。
DNS协议:
dns协议包含两部分:
1. 对DNS特定名称查询的查询、响应协议
2. 名称服务器用户交换数据库记录的协议(区域传输)
应用访问DNS通过地址解析器(resolver), 通常通过TCP,UDP协议通讯是需要将主机名转换为IPv4, IPv6地址。
gethostbname:
需要socket通过域名方式完成连接 , 可以通过API gethostbyname 函数完成。gethostbyname 函数是 glibc 库中提供一个域名解析的函数。 调用方法也相对简单,存在问题是范围结果是 static 类型变量,存在数据被覆盖的情况。
函数定义:
#include <netdb.h> extern int h_errno; struct hostent *gethostbyname(const char *name);
The hostent structure is defined in <netdb.h> as follows: struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ } #define h_addr h_addr_list[0] /* for backward compatibility */
DEMO程序:
#include <sys/types.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
struct hostent *hp=NULL;
struct in_addr ip_addr;
char str[64];
if (argc <= 1){
printf("argc <1 \n");
exit(EXIT_FAILURE);
}
hp = gethostbyname(argv[1]);
if (!hp){
printf("%s was not resolved %d\n", argv[1], errno);
exit(EXIT_FAILURE);
}
char **pptr;
for (pptr = hp->h_addr_list; *pptr!=NULL; pptr++) {
printf("HostName: %s was resolved to :%s \n",
argv[1], inet_ntop(hp->h_addrtype, *pptr, str, sizeof(str)));
}
exit(EXIT_SUCCESS);
}
gethostbyname 的glibc2.29版本实现:
下载glibc2.29 版本学习了下gethostbyname 代码:
1. 函数声明
netdb.h
#define DECLARE_NSS_PROTOTYPES(service)
extern enum nss_status _nss_ ## service ## _gethostbyname_r \
2. 函数定义:
gethostbyname实现比较特殊 使用 nss/getXXbyYY.c, nss/getXXbyYY_r.c 两个文件, 通过宏定义的方式定义了模板,inet/gethstbynm.c ,inet/gethstbynm_r.c nscd/nscd_gethst_r.c中设置了相关参数信息,与模板组合完成函数定义。
3.函数实现:
getXXbyYY.c :实现函数主题流程
1). 生成锁
2). 申请buffer空间
3). 判断 传入主机名称是否IP地址
4). 调用 INTERNAL,针对gethostbyname内部函数指定为 gethostbyname_r,调用过程中出现ERANGE时, 通过realloc 2倍方式动态扩大内存;
5). 释放锁;
getXXbyYY_r.c : 函数具体实现方法
1). 判断 传入主机名称是否IP地址,并处理
2). 判断是否支持nscd(编译时通过USE_NSCD指定),存在从nscd中获取
3). 初始化处理动作查询表。也是通过宏定义 DB_LOOKUP_FCT,指定第一个处理方法
4). 动态库方法: 通过 DL_CALL_FCT调用方法
静态库方法: 通过function.def定义的DEFINE_GETBY
# define DEFINE_GETBY(h,nm,ky) \
{ #h”_get”#nm”by”#ky”_r”, _nss_##h##_get##nm##by##ky##_r },
static struct fct_tbl { const char *fname; void *fp; } *tp, tbl[] =
5). 根据返回结果是否成功,成功 并判断是否需要合并处理,将结果累计存储到mergebuf中,否则进行失败处理。
6). 如果存在多个方法通过__nss_next2 指向一下方法通过4,5调用
4. 域名查询方法初始化与nsswith.conf:
XXX-lookup.c, nss/nsswitch.c: 进行解析式的具体方法列表创建,查找返回
1). 宏定义在nss/XXX-lookup.c nss/hosts-lookup.c完成hosts(域名查询) 方法定义
2). __nss_database_lookup 通过nss_parse_file解析nsswitch.conf文件, 完成 service_user列表初始化,指定开始执行的方法 。
3) .__nss_lookup根据支持的action, 通过动态加载模式,__nss_lookup_function, 找到指定的函数并返回
5. dns,files nscd方式实现:
域名解析顺序可以通过 /etc/nsswitch.conf 文件中的hosts选项进行设置, 先使用 files方式 (/etc/hosts文件中的配置信息),还是使用dns 方式 (通过/etc/resolv.conf 配置域名服务器通过域名解析进行)
dns方式:
resolv/nss_dns/dns-host.c
函数调用链
files方式:
nscd
sudo apt update
sudo apt install nscd
跟踪gethostbyname的域名解析过程:
环境:
OS:Ubuntu 17.10 (lsb_release -a) 4.13.0-21-generic (uname -a)
glibc : GNU C Library (Ubuntu GLIBC 2.26-0ubuntu2.1) stable release version 2.26 (libc.so.6 )
gcc: gcc version 7.2.0 (Ubuntu 7.2.0-8ubuntu3.2)
准备:
1)demo编译:
gcc gethostbyname.c -o gethostbyname
2) 配置文件:
/etc/nsswitch.conf 中
hosts: files dns
/etc/resolv.conf
nameserver 223.5.5.5
service nscd status: inactive
strace ./gethostbyname www.baidu.com > gethostbyname.default.log 2>&1
1 execve("./gethostbyname", ["./gethostbyname", "www.baidu.com"], [/* 22 vars */]) = 0
...
//载入resolv.conf host.conf nsswitch.conf 文件
26 stat("/etc/resolv.conf", {st_mode=S_IFREG|0644, st_size=241, ...}) = 0
27 open("/etc/host.conf", O_RDONLY|O_CLOEXEC) = 3
28 fstat(3, {st_mode=S_IFREG|0644, st_size=92, ...}) = 0
29 read(3, "# The \"order\" line is only used "..., 4096) = 92
30 read(3, "", 4096) = 0
31 close(3) = 0
32 open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
33 fstat(3, {st_mode=S_IFREG|0644, st_size=241, ...}) = 0
34 read(3, "# This file is managed by man:sy"..., 4096) = 241
35 read(3, "", 4096) = 0
36 close(3) = 0
...
//ncsd
uname({sysname="Linux", nodename="vm-134", ...}) = 0
38 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
39 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
40 close(3) = 0
41 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
42 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
43 close(3) = 0
...
44 open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
45 fstat(3, {st_mode=S_IFREG|0644, st_size=497, ...}) = 0
46 read(3, "# /etc/nsswitch.conf\n#\n# Example"..., 4096) = 497
47 read(3, "", 4096) = 0
48 close(3)
// 链接动态库根据nsswitch 查找相关函数 这里由于配置了files先查找libnss_files.so.2动态库中指定函数
54 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 3
55 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360!\0\0\0\0\0\0"..., 832) = 832
56 fstat(3, {st_mode=S_IFREG|0644, st_size=47608, ...}) = 0
...
64 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
65 fstat(3, {st_mode=S_IFREG|0644, st_size=348, ...}) = 0
66 read(3, "127.0.0.1\tlocalhost\n127.0.1.1\twe"..., 4096) = 348
67 read(3, "", 4096) = 0
68 close(3)
// 没有结果,通过dns方式查找相关函数, 最终走的是res_search等相关方法,
74 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_dns.so.2", O_RDONLY|O_CLOEXEC) = 3
75 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\20\0\0\0\0\0\0"..., 832) = 832
76 fstat(3, {st_mode=S_IFREG|0644, st_size=27016, ...}) = 0
...
82 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libresolv.so.2", O_RDONLY|O_CLOEXEC) = 3
83 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0p8\0\0\0\0\0\0"..., 832) = 832
84 fstat(3, {st_mode=S_IFREG|0644, st_size=97136, ...}) = 0
// 可以看到基于UDP协议进行了域名查询
93 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
94 connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("223.5.5.5")}, 16) = 0
95 poll([{fd=3, events=POLLOUT}], 1, 0) = 1 ([{fd=3, revents=POLLOUT}])
96 sendto(3, "B\217\1\0\0\1\0\0\0\0\0\0\3www\5baidu\3com\0\0\1\0\1", 31, MSG_NOSIGNAL, NULL, 0) = 31
97 poll([{fd=3, events=POLLIN}], 1, 5000) = 1 ([{fd=3, revents=POLLIN}])
98 ioctl(3, FIONREAD, [90]) = 0
99 recvfrom(3, "B\217\201\200\0\1\0\3\0\0\0\0\3www\5baidu\3com\0\0\1\0\1\300"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("223.5.5. 5")}, [28->16]) = 90
100 close(3) = 0
启动 nscd 后(service nscd)
//nscd 走的是TCP协议
38 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
39 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = 0
40 sendto(3, "\2\0\0\0\r\0\0\0\6\0\0\0hosts\0", 18, MSG_NOSIGNAL, NULL, 0) = 18
41 poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])
42 recvmsg(3, {msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base="hosts\0", iov_len=6}, {iov_base="\310O\3\0\0\0\0\0", iov_len=8}], msg_iovlen=2, msg_control=[{ cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, cmsg_data=[4]}], msg_controllen=20, msg_flags=MSG_CMSG_CLOEXEC}, MSG_CMSG_CLOEXEC) = 14
43 mmap(NULL, 217032, PROT_READ, MAP_SHARED, 4, 0) = 0x7fb454ef8000
44 close(4) = 0
45 close(3) = 0
46 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
47 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = 0
48 sendto(3, "\2\0\0\0\4\0\0\0\16\0\0\0www.baidu.com\0", 26, MSG_NOSIGNAL, NULL, 0) = 26
49 poll([{fd=3, events=POLLIN|POLLERR|POLLHUP}], 1, 5000) = 1 ([{fd=3, revents=POLLIN|POLLHUP}])
50 read(3, "\2\0\0\0\1\0\0\0\21\0\0\0\1\0\0\0\2\0\0\0\4\0\0\0\2\0\0\0\0\0\0\0", 32) = 32
51 readv(3, [{iov_base="www.a.shifen.com\0", iov_len=17}, {iov_base="\16\0\0\0", iov_len=4}, {iov_base="o\rd[o\rd\\", iov_len=8}], 3) = 29
52 read(3, "www.baidu.com\0", 14) = 14
53 close(3) = 0
54 fstat(1, {st_mode=S_IFREG|0664, st_size=3702, ...}) = 0
55 write(1, "HostName: www.baidu.com was reso"..., 112HostName: www.baidu.com was resolved to :111.13.100.91
nsswitch.conf merge action:
概述及疑问:
nsswitch.conf 文件大致格式如下:
database1: service1[STATUS=ACTION], service2
…..
可以配置 database , 如hosts ,passwd等,通过调整它们的service 顺序,来设置哪个services先被查询, 如果存在多个services,可以通过service查询结果状态设置不通的action 目前包含 return, continue, merge。
这个验证了下merge action
验证过程:
www.baidu.com was not resolved 22 (EINVAL)
问题分析:
1)源码:
在getXXbyYY_r. c 处理逻辑中是有merge 操作的, 调用了
....
Be First to Comment