# gethostbyname函数实现分析
Date: 2019-05-11


### **DNS ：**

      DNS是一个分层级 （hierarchical ），分布式（decentralized）的网络数据库，完成主机名称和IP地址之间的相互映射。DNS名称空间包含一个树状结构，树根没有命名， 下面是树的最高层为顶级域名， 顶级域名包括： 顶级域名（gTLD），国家代码顶级域名（ccTLD）， 国际化国家设施顶级域名（infrastructure TLD）， 下面一层是权威域名服务器。其中权威域名服务器对DNS请求进行应答返回IP地址。

<figure>

![](/wp-content/uploads/2019/05/1280px-Domain_name_space.svg_.png)

<figcaption>

[图片来自DNS wiki](https://en.wikipedia.org/wiki/Domain_Name_System)

</figcaption>

</figure>

### **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      \\

         (const char \*name, struct hostent \*host, char \*buffer, \\

            size\_t buflen, int \*errnop, int \*h\_errnop); 

#### 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  

函数调用链

\_nss\_dns\_gethostbyname3\_r ->

gethostbyname3\_context->

\_\_res\_context\_search->       

getanswer\_r->

##### **files方式：**

   nss/nss\_files/files-host.c

函数调用链：

\_nss\_files\_gethostbyname3\_r->

gethostbyname3\_multi         

#####    **nscd**

          nscd/nscd\_gethst\_r.c

          函数调用链：

          \_\_nscd\_gethostbyname\_r->

          nscd\_gethst\_r

> 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

####         验证过程：

调整   /etc/nsswitch.conf 

hosts: files \[SUCCESS=merge\] dns

./gethostbyname www.baidu.com  
www.baidu.com was not resolved 22 （EINVAL）

####          问题分析： 

         1）源码：

在getXXbyYY\_r. c 处理逻辑中是有merge 操作的， 调用了

\_\_merge\_einval函数， 目前这个函数直接返回  EINVAL。 

         2） 文档：

**merge** _\[SUCCESS=merge\]_ is used between two database entries. When a group is located in the first of the two group entries, processing will continue on to the next one.

```
....
```

          nswitch.conf merge目前只支持 group database

### 参考文档

[DNS wiki](https://en.wikipedia.org/wiki/Domain_Name_System#Domain_name_space)

[man gethostbyname](http://man7.org/linux/man-pages/man3/gethostbyname.3.html)

[man  nsswitch.conf](http://man7.org/linux/man-pages/man5/nsswitch.conf.5.html)

[探探gethostbyname](https://www.felix021.com/blog/read.php?2104)

[gethostbyname与DNS](https://lfckop.github.io/gethostbyname-and-DNS/)

