TCPIP 网络编程-07-域名地址转换

访问服务端的时候不能总是通过 IP 访问而是通过域名,于是就有了域名与地址转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <netdb.h>

/**
* @brief 通过传递字符串格式的域名获取IP地址
* @return 成功时返回hostent结构体地址,失败时返回 NULL指针
*/
struct hostent *gethostbyname(const char* hostname);

struct hostent
{
char * h_name; //官网域名,代表着某一个主页(可能很多注明的域名并未注册官方域名)
char ** h_aliases; //通过多个域名访问同一个主页。同一个IP可以绑定多个域名,所以除了官方域名还可以指定其他域名
int h_addrtype; //地址类型 IPv4则是AF_INET
int h_length; //保存IP地址长度(单位B)。若是IPv4地址,则是4; IPv6 则是16
char ** h_addr_list; //以证书形式保存域名对应的IP地址,可能多个IP分配给同一个域名
}

/**
* 利用IP地址获取域名
* addr 含有IP地址信息的in_addr结构体指针。为了同时传递IPv4地址之外的其他细心,该变量类型声明为char指针
* len 第一个参数的字节数,IPv4时为4,IPv6 时为16
* family 传递协议族信息,IPv4时为AF_INET,IPv6 时为AF_INET6
* 成功返回hostent结构体变量地址值,失败返回NULL指针
*/
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);

有了上面的函数解析 示例地址:https://github.com/XBoom/network-ip.git 中的 apps/socket/10/ 来看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root]#./client www.naver.com
[10/client.c:12 main info] www.naver.com get host name:
[10/client.c:13 main info] h_name:www.naver.com.nheos.com
[10/client.c:14 main info] h_aliases:
[10/client.c:17 main info] [0]:www.naver.com
[10/client.c:19 main info] type:IPv4
[10/client.c:20 main info] h_length:4
[10/client.c:21 main info] h_addr_list:
[10/client.c:24 main info] [0]:223.130.200.104
[10/client.c:24 main info] [1]:223.130.200.107
[root]#./server 223.130.200.104
[10/server.c:14 main error] gethostbyaddr failed Unknown host
[root]#./server 223.130.200.107
[10/server.c:14 main error] gethostbyaddr failed Unknown host

并不能总是通过 IP 地址获取到域名

通过抓去端口 53 的报文(DNS)可以得到

image-20240113080658112

通过 UDP 去发送 DNS 请求,一次返回多个 IP 地址,而无法获取域名则如下返回 No such name

image-20240113080753643

需要注意的就是 inet_ntoa(*(struct in_addr *)addr_info->h_addr_list[i]) 为什么是一个结构体,而要使用 char* 进行存储

image-20240113081503692

这是因为 这个结构体并不一定有 IPv4,也有可能是 IPv6;另外在定义这个标准之前还没有 void * 标准,所以使用了 char* 定义

不过目前比较常用的是 getaddrinfo 更加灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
// node 参数是要解析的主机名或 IP 地址,可以为 NULL。
// service 参数是服务名或端口号的字符串表示,可以为 NULL。
// hints 参数是一个指向 struct addrinfo 结构的指针,用于提供解析的提示信息。
// res 参数是一个指向 struct addrinfo 结构链表的指针,用于保存解析结果。

struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
int ai_family; /* PF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
socklen_t ai_addrlen; /* length of ai_addr */
char *ai_canonname; /* canonical name for hostname */
struct sockaddr *ai_addr; /* binary address */
struct addrinfo *ai_next; /* next structure in linked list */
};

最终输出结果为

1
2
3
getaddrinfo: 
[10/client.c:55 main info] ip: 182.61.200.7
[10/client.c:55 main info] ip: 182.61.200.6

最后还有两个小知识点

  1. hstrerror(h_errno)h_errno 是一个全局错误码,而 hstrerror 用来解析 host相关的错误

  2. inet_ntop(Internet Network TO Presentation)二进制字符串转换

    1
    2
    3
    4
    5
    6
    7
    #include <arpa/inet.h>

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
    // af 是地址族,可以是 AF_INET(IPv4)或 AF_INET6(IPv6)。
    // src 是指向存储二进制形式地址的指针。
    // dst 是一个用于存储字符串形式地址的缓冲区。
    // size 是缓冲区的大小

参考文档

  1. 《TCPIP 网络编程》