pjlib系列之IP地址解析

pjlib中对地址解析的封装在addr_resolv.h定义,实现在addr_resolv_sock.c和sock_common.c

根据域名获取ip(仅支持IPv4)

struct hostent *gethostbyname(const char *hostname)

struct hostent{
    char *h_name;  //official name
    char **h_aliases;  //alias list
    int  h_addrtype;  //host address type
    int  h_length;  //address lenght
    char **h_addr_list;  //address list
}

从该结构体可以看出,不只返回 IP 地址,还会附带其他信息,各位读者只需关注最后一个成员 h_addr_list。下面是对各成员的说明:

  • h_name:官方域名(Official domain name)。官方域名代表某一主页,但实际上一些著名公司的域名并未用官方域名注册。
  • h_aliases:别名,可以通过多个域名访问同一主机。同一 IP 地址可以绑定多个域名,因此除了当前域名还可以指定其他域名。
  • h_addrtype:gethostbyname() 不仅支持 IPv4,还支持 IPv6,可以通过此成员获取IP地址的地址族(地址类型)信息,IPv4 对应 AF_INET,IPv6 对应 AF_INET6。
  • h_length:保存IP地址长度。IPv4 的长度为 4 个字节,IPv6 的长度为 16 个字节。
  • h_addr_list:这是最重要的成员。通过该成员以整数形式保存域名对应的 IP 地址。对于用户较多的服务器,可能会分配多个 IP 地址给同一域名,利用多个服务器进行均衡负载。


hostent 结构体变量的组成如下图所示

注意,gethostbyname只会搜索A记录,即IPv4地址。

从地址查找主机名

#include <netdb.h>
 
struct hostent *gethostbyaddr (const char *addr, socklen_t len, int family);
//返回:成功为非空指针,出错为NULL并设置h_errno

参数addr实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针;len参数是这个结构的大小:地域IPv4地址为4,family参数为AF_INET

以上两个函数只支持IPv4,如果要支持IPv6,则要使用getaddrinfo

根据域名获取ip(支持IPv6)

getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构而不是一个地址列表,这些sockaddr结构随后可由套接字函数直使用

#include <netdb.h>
 
int getaddrinfo (const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result) ;
 //返回:若成功为0,出错为非0

struct addrinfo {
   int          ai_flags;           /* AI_PASSIVE, AI_CANONNAME */
   int          ai_family;          /* AF_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;       /* ptr to canonical name for host */
   struct sockaddr    *ai_addr;     /* ptr to socket address structure */
   struct addrinfo    *ai_next;     /* ptr to next structure in linked list */
};

getaddrinfo函数的输入参数hints也为addrinfo类型。hints参数可以的一个空指针,也可以指向addrinfo结构的指针,调用者可在这个结构中填入关于期望返回的信息类型的暗示。举例来说,如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。

hints结构中调用者可以设置的成员有:

  • ai_flags(零个或多个或在一起的AI_xxx值)
  • ai_family(某个AF_xxx值)
  • ai_socktype(某个SOCK_xxx值)
  • ai_protocol

其中ai_flags成员可用的标志值及其含义 例如有 AI_PASSIVE(套接字将用于被动打开),AI_CANONNAME(告知getaddrinfo函数返回主机的规范名字)等等

如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串联起来的addrinfo结构链表。可以导致返回多个addrinfo结构的情形有以下2个:

  • 如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
  • 如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。

我们必须先分配一个hints结构,把它清零后填写需要的字段,再调用getaddrinfo然后遍历一个链表逐个尝试每个返回地址。

最后调用freeaddrinfo释放内存。

pjlib中addr_resolv_sock.c基本是对这两个函数的封装,其它还有3个接口在sock_common.c。

获取指定目标地址的本地地址

/**
 * Get the interface IP address to send data to the specified destination.
 *
 * @param af	    The desired address family to query. Valid values
 *		    are pj_AF_INET() or pj_AF_INET6().
 * @param dst	    The destination host.
 * @param itf_addr  On successful resolution, the address family and address
 *		    part of this socket address will be filled up with the host
 *		    IP address, in network byte order. Other parts of the socket
 *		    address should be ignored.
 * @param allow_resolve   If \a dst may contain hostname (instead of IP
 * 		    address), specify whether hostname resolution should
 * 	            be performed. If not, default interface address will
 *  		    be returned.
 * @param p_dst_addr If not NULL, it will be filled with the IP address of
 * 		    the destination host.
 *
 * @return	    PJ_SUCCESS on success, or the appropriate error code.
 */
PJ_DECL(pj_status_t) pj_getipinterface(int af,
                                       const pj_str_t *dst,
                                       pj_sockaddr *itf_addr,
                                       pj_bool_t allow_resolve,
                                       pj_sockaddr *p_dst_addr);

比如你本机有很多个地址,其中一个地址用来跟百度通讯,该接口可以根据百度的地址(目的地址),获取到对应的本地地址。

该函数创建一个socket,然后connect到目标地址,接着通过pj_sock_getsockname就可以返回此连接的本地ip,如果通过pj_sock_getpeername则获取到的就是我们传入的dst。关于UDP使用connect,其实就是限制UDP只在一个连接上收发数据,而不是每次可以指定不同地址“乱发”。

如果目标地址dst是IPv4的1.1.1.1或者是IPv6的1::1时,则获取的是默认接口(网卡)的ip,而默认接口就是默认路由的接口。关于1.1.1.1这个地址,我查了一下,大概意思是这个地址代表受限广播地址 可以作为目的地址 但不可作为源地址 ,是用来被动测试的,1.1.1.1是路由器或交换机自己设置的IP。后来被Cloudflare这个机构占了,实际上还是不太理解这个IP的使用场景。

获取主机地址

/**
 * Resolve the primary IP address of local host. 
 *
 * @param af	    The desired address family to query. Valid values
 *		    are pj_AF_INET() or pj_AF_INET6().
 * @param addr      On successful resolution, the address family and address
 *		    part of this socket address will be filled up with the host
 *		    IP address, in network byte order. Other parts of the socket
 *		    address are untouched.
 *
 * @return	    PJ_SUCCESS on success, or the appropriate error code.
 */
PJ_DECL(pj_status_t) pj_gethostip(int af, pj_sockaddr *addr)

因为可以从多个函数获取到ip,并且一台机器可以有多网卡,每个网卡有多个ip,那么到底选择哪个ip,需要使用权重。

	/* Weighting to be applied to found addresses */
	WEIGHT_HOSTNAME	= 1,	/* hostname IP is not always valid! */
	WEIGHT_DEF_ROUTE = 2,
	WEIGHT_INTERFACE = 1,
	WEIGHT_LOOPBACK = -5,
	WEIGHT_LINK_LOCAL = -4,
	WEIGHT_DISABLED = -50,

其中有3个类型的IP是负数,那些ip是不能正常用来跟其它机器通讯的,这些ip是回环、本地链路、空。hostname表示从机器名称获取的ip,Interface是从网卡获取的ip,def_route是默认路由默认网卡的ip。使用候选数组cand_addr、候选权重cand_weight、候选个数cand_cnt,三个变量来存储所有权重的ip。

1、通话pj_gethostname和pj_getaddrinfo,根据机器名获取ip。

2、通过pj_getdefaultipinterface获取给默认路由的默认网卡ip。

3、通话pj_enum_ip_interface枚举所有接口的ip。

从这三类选择权重最大的ip作为主机ip,如果找不到有效ip,则使用回环ip作为主机ip。

网络socket封装

本来sock相关的准备再写一篇,但由于没有太多有深度的东西,所以这里一笔概括。主要是在sock_bsd.c和sock_common封装了诸如socket创造,发送,接收等,和iton等工具的封装,select的封装单独在一个文件sock_select.c。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值