美文网首页
linux手册翻译——getaddrinfo(3)

linux手册翻译——getaddrinfo(3)

作者: 蟹蟹宁 | 来源:发表于2021-06-25 22:23 被阅读0次

\color{#A00000}{NAME}
getaddrinfo, freeaddrinfo, gai_strerror - network address and service translation
getaddrinfo(),粗略的理解为将host_name/service_name转化为host_ip/service_port。
\color{#A00000}{SYNOPSIS}

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict node, 
                const char *restrict service, 
                const struct addrinfo *restrict hints, 
                struct addrinfo **restrict res);
void freeaddrinfo(struct addrinfo *res);
const char *gai_strerror(int errcode);

\color{#A00000}{DESCRIPTION}
给定一个 nodeservice (分别标识host_name和service_name),getaddrinfo() 将返回一个或多个 addrinfo 结构,每个 addrinfo 都包含一个 Internet 地址(socker 地址,包含IP和端口号,即 ip:port),可以使用该地址进行 bind(2) 或 connect(2) 操作。注意,host_name可以是具体的名称如:dns.google,也可以是10进制点ip地址如:8.8.8.8 。前者将返回两个addrinfo,即8.8.8.8和8.8.4.4;而后者则仅仅包含8.8.8.8。可以认为是getaddrinfo()融合了gethostbyname(3)
getservbyname(3)两者的功能到一个接口中,区别是getaddrinfo() 是可重入的,并允许程序消除 IPv4 与 IPv6 的依赖关系(eliminate IPv4-versus-IPv6 dependencies.)。

addrinfo 的结构如下:

struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct           sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

关于此结构的我认为不错的介绍,可参考此文
hints 参数可以认为是一种对结果的选择标准,简单的以service为例,相同的service服务,可能使用了TCP协议或者UDP协议,他们对应了不同的端口,我们可以在hints.ai_protocol中指定,我们所关心的具体是那一个。如果hints不是NULL,那么其ai_family、ai_socktype 和ai_protocol三个字段,将用于指定返回的值的筛选条件:

  • ai_family
    指定返回地址的地址族。 有效值包括:
常量名 取值 含义
AF_INET 2 IPv4
AF_INET6 23 IPv6
AF_UNSPEC 0 协议无关
  • ai_socktype
    指定首选(preferred)的socket类型,取值:
常量名 取值 含义
SOCK_STREAM 1 数据流
SOCK_DGRAM 2 数据报

取值0表示,任何协议都可以

  • ai_protocol
    制定socket的协议,取值:
常量名 取值 含义
IPPROTO_IP 0 协议无关
IPPROTO_IPV4 4 IPv4
IPPROTO_IPV6 41 IPv6
IPPROTO_UDP 17 UDP
IPPROTO_TCP 6 TCP
  • ai_flags
    此字段指定附加选项,如下所述。 多个标志是通过按位或将它们组合在一起来指定的。

hints指向的addrinfo结构中的所有其他字段必须包含 0 或空指针,视情况而定,通常直接用mmset初始为0。

hints指定为 NULL 相当于将 ai_socktype 和 ai_protocol 设置为 0; ai_family 设置为 AF_UNSPEC; 将 ai_flags 设置为 (AI_V4MAPPED | AI_ADDRCONFIG).(POSIX 为 ai_flags 指定了不同的默认值;请参阅 NOTES.)

node

node可以是数字网络地址(对于 IPv4,即inet_aton(3) 支持的数点表示法,如127.0.0.1;对于 IPv6,即inet_pton(3) 支持的十六进制字符串格式,如 ::1 ),或网络主机名(其网络地址将会被被查找和解析)。 如果hints.ai_flags包含AI_NUMERICHOST 标志,则node必须是数字网络地址。AI_NUMERICHOST 标志用于避免长时间的的网络主机地址查找。

如果hints.ai_flags不包含AI_PASSIVE标志,且node为NULL,那么将返回一个适用于( be suitable for) 进行bind(2)操作的通配地址符(即0.0.0.0,INADDR_ANY 用于 IPv4 地址,IN6ADDR_ANY_INIT 用于 IPv6 地址),通配地址符通常用于表示可以接受任何主机的连接,通常用于服务器。如果node不为NULL,则忽略AI_PASSIVE标志。

如果hints.ai_flags包含AI_PASSIVE标志,且node为NULL,则返回的地址将适用于( be suitable for) connect(2)、sendto(2) 或sendmsg(2)。如果 node 为 NULL,则网络地址将设置为环回接口地址(即127.0.0.1,INADDR_LOOPBACK 用于 IPv4 地址,IN6ADDR_LOOPBACK_INIT 用于 IPv6 地址), 通常用于与本机服务进行通讯的应用。

service

service 用于设置返回地址的端口(设置这个词用的非常好,此函数通常用于生成一个可以进行后续操作的,如bind,socket地址,node用于设置ip,而service用于设置端口),如果此参数是服务名称(参阅 services(5)),则将其转换为相应的端口号。 此参数也可以指定为十进制数,这样话只需要将其转化为二进制即可,端口和ip在sockaddr结构中是以网络字节序而非主机字节序存储的。如果 service 为 NULL,则返回的套接字地址的端口号将保持未初始化状态。 如果在hints.ai_flags 中指定了AI_NUMERICSERV 并且service 不是NULL,则service 必须指向一个包含数字端口号的字符串。 该标志用于在已知不需要的情况下禁止调用名称解析服务。

Either node or service, but not both, may be NULL.

getaddrinfo() 函数分配并初始化一个 addrinfo 结构的链表,所有的匹配nodeservice的地址,经过hints的限制筛选后都会被添加到链表中,链表由addrinfo.ai_next字段链接,并将链表的头指针的地址赋值给res

res

链表可能有多个 addrinfo 结构的原因有多种,包括:网络主机是多宿主的,可通过多种协议访问(例如,AF_INET 和 AF_INET6); 或者为多种套接字类型(例如,一个 SOCK_STREAM 地址和另一个 SOCK_DGRAM 地址)提供相同的服务。 通常,应用程序应尝试按照地址返回的顺序使用这些地址。 getaddrinfo() 中使用的排序函数在 RFC 3484 中定义; 可以通过编辑 /etc/gai.conf(自 glibc 2.5 起可用)为特定系统调整顺序。

fhints.ai_flags 包含 AI_CANONNAME 标志,则返回列表中第一个 addrinfo 结构的 ai_canonname 字段被设置为指向主机的正式名称。此值同node参数的值,如传入8.8.8.8就是8.8.8.8,传入dns.google就是dns.google

每个返回的 addrinfo 结构的其余字段初始化如下:

  • ai_family、ai_socktype 和 ai_protocol 字段返回创建套接字时使用的参数(即,这些字段与 socket(2) 的相应参数具有相同的含义)。 例如,ai_family 可能返回 AF_INET 或 AF_INET6; ai_socktype 可能返回 SOCK_DGRAM 或 SOCK_STREAM; 并且 ai_protocol 返回套接字的协议。
  • 指向套接字地址( sockaddr *,包含了ip地址和端口信息)的指针放在 ai_addr 字段中,套接字地址的长度(以字节为单位)放在 ai_addrlen 字段中。

如果hints.ai_flags包含AI_ADDRCONFIG标志,则仅当本地系统至少配置了一个IPv4地址时,才会在res指向的列表中返回IPv4地址,并且仅当本地系统至少有一个IPv6地址时才返回IPv6地址 配置。 在这种情况下,环回地址不被视为与配置地址一样有效。 例如,此标志在仅支持 IPv4 的系统上很有用,可确保 getaddrinfo() 不会返回在 connect(2) 或 bind(2) 中总是会失败的 IPv6 套接字地址。

如果hints.ai_flags 指定了AI_V4MAPPED 标志,并且hints.ai_family 指定为AF_INET6,若找不到匹配的IPv6 地址,则在res指向的列表中返回“IPv4-mapped的IPv6 “地址。 如果在hints.ai_flags 中指定了AI_V4MAPPED 和AI_ALL,则在res 指向的列表中同时返回IPv6地址和 IPv4-mapped 的IPv6 地址。 如果未指定 AI_V4MAPPED,则忽略 AI_ALL。

freeaddrinfo() 函数释放为动态分配的链表 res 分配的内存。

Extensions to getaddrinfo() for Internationalized Domain Names

Starting with glibc 2.3.4, getaddrinfo() has been extended to selectively allow the incoming and outgoing hostnames to be transparently converted to and from the Internationalized Domain Name (IDN) format (see RFC 3490, Internationalizing Domain Names in Applications (IDNA)). Four new flags are defined:

  • AI_IDN
    If this flag is specified, then the node name given in node is converted to IDN format if necessary. The source encoding is that of the current locale.
    If the input name contains non-ASCII characters, then the IDN encoding is used. Those parts of the node name (delimited by dots) that contain non-ASCII characters are encoded using ASCII Compatible Encoding (ACE) before being passed to the name resolution functions.
  • AI_CANONIDN
    After a successful name lookup, and if the AI_CANONNAME flag was specified, getaddrinfo() will return the canonical name of the node corresponding to the addrinfo structure value passed back. The return value is an exact copy of the value returned by the name resolution function.
    If the name is encoded using ACE, then it will contain the xn-- prefix for one or more components of the name. To convert these components into a readable form the AI_CANONIDN flag can be passed in addition to AI_CANONNAME. The resulting string is encoded using the current locale's encoding.
  • AI_IDN_ALLOW_UNASSIGNED, AI_IDN_USE_STD3_ASCII_RULES
    After a successful name lookup, and if the AI_CANONNAME flag was specified, getaddrinfo() will return the canonical name of the node corresponding to the addrinfo structure value passed back. The return value is an exact copy of the value returned by the name resolution function. If the name is encoded using ACE, then it will contain the xn-- prefix for one or more components of the name. To convert these components into a readable form the AI_CANONIDN flag can be passed in addition to AI_CANONNAME. The resulting string is encoded using the current locale's encoding.

\color{#A00000}{RETURN VALUE}
如果成功,getaddrinfo() 返回 0,或以下非零错误代码之一:

  • EAI_ADDRFAMILY

指定的网络主机在请求的地址族中没有任何网络地址。

  • EAI_AGAIN
    名称服务器返回了一个临时故障指示。 稍后再试。
  • EAI_BADFLAGS
    hints.ai_flags 包含无效标志; 或者,hints.ai_flags 包含 AI_CANONNAME 并且名称为 NULL。
  • EAI_FAIL
    名称服务器返回一个永久故障指示。
  • EAI_FAMILY
    不支持请求的地址族。
  • EAI_MEMORY
    Out of memory.
  • EAI_NODATA
    指定的网络主机存在,但没有定义任何网络地址。
  • EAI_NONAME
    节点或服务未知; 或者节点和服务都为NULL; 或 AI_NUMERICSERV 在hints.ai_flags 中指定并且服务不是数字端口号字符串。
  • EAI_SERVICE
    请求的服务对于请求的套接字类型不可用。 它可能通过另一种套接字类型可用。 例如,如果服务是“shell”(仅在流套接字上可用的服务),并且hints.ai_protocol 是IPPROTO_UDP,或者hints.ai_socktype 是SOCK_DGRAM,则可能发生此错误; 或者如果 service 不是 NULL,并且hints.ai_socktype 是 SOCK_RAW(不支持服务概念的套接字类型),则可能会发生错误。
  • EAI_SOCKTYPE
    不支持请求的套接字类型。 例如,如果hints.ai_socktype 和hints.ai_protocol 不一致(例如分别是SOCK_DGRAM 和IPPROTO_TCP),就会发生这种情况。
  • EAI_SYSTEM
    其他系统错误,详情查看errno。
    gai_strerror(3) 函数将这些错误代码转换为人类可读的字符串,适用于错误报告。

\color{#A00000}{FILES}
/etc/gai.conf

\color{#A00000}{ATTRIBUTES}

Interface Attribute Value
getaddrinfo() Thread safety MT-Safe env locale
freeaddrinfo(), gai_strerror() Thread safety MT-Safe

\color{#A00000}{CONFORMING TO}
POSIX.1-2001, POSIX.1-2008. The getaddrinfo() function is documented in RFC 2553.

\color{#A00000}{NOTES}
getaddrinfo() supports the address%scope-id notation for specifying the IPv6 scope-ID.

AI_ADDRCONFIG, AI_ALL, and AI_V4MAPPED are available since glibc 2.3.3. AI_NUMERICSERV is available since glibc 2.3.4.

According to POSIX.1, specifying hints as NULL should cause ai_flags to be assumed as 0. The GNU C library instead assumes a value of (AI_V4MAPPED | AI_ADDRCONFIG) for this case, since this value is considered an improvement on the specification.

\color{#A00000}{EXAMPLES}
以下程序演示了 getaddrinfo()、gai_strerror()、freeaddrinfo() 和 getnameinfo(3) 的使用。 这些程序是 UDP 数据报的回显服务器和客户端。

Server program

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define BUF_SIZE 500

int main(int argc, char* argv[])
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, s;
    struct sockaddr_storage peer_addr;
    socklen_t peer_addr_len;
    ssize_t nread;
    char buf[BUF_SIZE];

    if (argc != 2)
    {
        fprintf(stderr, "Usage: %s port\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family    = AF_UNSPEC; /* Allow IPv4 or IPv6 */
    hints.ai_socktype  = SOCK_DGRAM; /* Datagram socket */
    hints.ai_flags     = AI_PASSIVE; /* For wildcard IP address */
    hints.ai_protocol  = 0; /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr      = NULL;
    hints.ai_next      = NULL;

    s = getaddrinfo(NULL, argv[1], &hints, &result);
    if (s != 0)
    {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    /* getaddrinfo() returns a list of address structures.
       Try each address until we successfully bind(2).
       If socket(2) (or bind(2)) fails, we (close the socket
       and) try the next address. */

    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        sfd = socket(rp->ai_family, rp->ai_socktype,
                     rp->ai_protocol);
        if (sfd == -1)
            continue;

        if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0)
            break; /* Success */

        close(sfd);
    }

    freeaddrinfo(result); /* No longer needed */

    if (rp == NULL)
    { /* No address succeeded */
        fprintf(stderr, "Could not bind\n");
        exit(EXIT_FAILURE);
    }

    /* Read datagrams and echo them back to sender. */

    for (;;)
    {
        peer_addr_len = sizeof(peer_addr);
        nread         = recvfrom(sfd, buf, BUF_SIZE, 0,
                         (struct sockaddr*)&peer_addr, &peer_addr_len);
        if (nread == -1)
            continue; /* Ignore failed request */

        char host[NI_MAXHOST], service[NI_MAXSERV];

        s = getnameinfo((struct sockaddr*)&peer_addr,
                        peer_addr_len, host, NI_MAXHOST,
                        service, NI_MAXSERV, NI_NUMERICSERV);
        if (s == 0)
            printf("Received %zd bytes from %s:%s\n",
                   nread, host, service);
        else
            fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s));

        if (sendto(sfd, buf, nread, 0,
                   (struct sockaddr*)&peer_addr,
                   peer_addr_len)
            != nread)
            fprintf(stderr, "Error sending response\n");
    }
}

Client program

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#define BUF_SIZE 500

int main(int argc, char* argv[])
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    int sfd, s;
    size_t len;
    ssize_t nread;
    char buf[BUF_SIZE];

    if (argc < 3)
    {
        fprintf(stderr, "Usage: %s host port msg...\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Obtain address(es) matching host/port. */

    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = AF_UNSPEC; /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
    hints.ai_flags    = 0;
    hints.ai_protocol = 0; /* Any protocol */

    s = getaddrinfo(argv[1], argv[2], &hints, &result);
    if (s != 0)
    {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    /* getaddrinfo() returns a list of address structures.
       Try each address until we successfully connect(2).
       If socket(2) (or connect(2)) fails, we (close the socket
       and) try the next address. */

    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        sfd = socket(rp->ai_family, rp->ai_socktype,
                     rp->ai_protocol);
        if (sfd == -1)
            continue;

        if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
            break; /* Success */

        close(sfd);
    }

    freeaddrinfo(result); /* No longer needed */

    if (rp == NULL)
    { /* No address succeeded */
        fprintf(stderr, "Could not connect\n");
        exit(EXIT_FAILURE);
    }

    /* Send remaining command-line arguments as separate
       datagrams, and read responses from server. */

    for (int j = 3; j < argc; j++)
    {
        len = strlen(argv[j]) + 1;
        /* +1 for terminating null byte */

        if (len > BUF_SIZE)
        {
            fprintf(stderr,
                    "Ignoring long message in argument %d\n", j);
            continue;
        }

        if (write(sfd, argv[j], len) != len)
        {
            fprintf(stderr, "partial/failed write\n");
            exit(EXIT_FAILURE);
        }

        nread = read(sfd, buf, BUF_SIZE);
        if (nread == -1)
        {
            perror("read");
            exit(EXIT_FAILURE);
        }

        printf("Received %zd bytes: %s\n", nread, buf);
    }

    exit(EXIT_SUCCESS);
}

相关文章

网友评论

      本文标题:linux手册翻译——getaddrinfo(3)

      本文链接:https://www.haomeiwen.com/subject/xuxoyltx.html