HTTP Client の動作フロー その6 : getaddrinfo()

名前解決には、gethostbyname()ではなく、getaddrinfo()を使えとのことだったので、getaddrinfo()について調べてみた。

getaddrinfo(3)

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

getaddrinfo() は、(インターネットのホストとサービスを識別する) node と service を渡すと、一つ以上の addrinfo 構造体を返す。それぞれの addrinfo 構造体には、 bind(2) や connect(2) を呼び出す際に指定できるインターネットアドレスが格納されている。 getaddrinfo() 関数は、 getservbyname(3) と getservbyport(3) の機能をまとめて一つのインターフェースにしたものであるが、 これらの関数と違い、 getaddrinfo() はリエントラントであり、 getaddrinfo() を使うことでプログラムは IPv4IPv6 の違いに関する依存関係を なくすことができる。

getaddrinfo() は成功すると 0 を返し、失敗すると以下の非 0 のエラーコードのいずれかを返す。

getaddrinfo() 関数は、 addrinfo 構造体のメモリ確保を行い、 addrinfo 構造体のリンクリストを初期化し、 res にリストの先頭へのポインタを入れて返す。 このとき、各構造体のネットワークアドレスは node と service に一致し、 hints で課されたすべての制限を満たすものとなる。 リンクリストの要素は ai_next フィールドにより連結される。

getaddrinfo() が用いる addrinfo 構造体は以下のフィールドを含む。

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

node

node には、数値形式のネットワークアドレス (IPv4 の場合は inet_aton(3) でサポートされているドット区切りの数字による表記、 IPv6 の場合は inet_pton(3) でサポートされている 16 進数の文字列形式) もしくは ネットワークホスト名を指定する。 ネットワークホスト名を指定した場合には、そのネットワークアドレスが検索され、 名前解決が行なわれる。

service

service により、返される各アドレス構造体のポート番号が決まる。 この引き数がサービス名 (services(5) 参照) の場合、対応するポート番号に翻訳される。 この引き数には 10 進数も指定することができ、 この場合にはバイナリへの変換だけが行われる。 service が NULL の場合、返されるソケットアドレスのポート番号は 初期化されないままとなる。

実例

ホスト名を引数に渡すとアドレス解決して表示
/* getaddrinfo_test.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{

        struct addrinfo hints;
        struct addrinfo *result, *rp;
	struct sockaddr_in *addr_in;
	struct sockaddr_in6 *addr_in6;
        int s;
        const char *errormess;
	char address[INET6_ADDRSTRLEN];

        if (argc < 2) {
                fprintf(stderr, "Usage: %s host\n", argv[0]);
		return 1;
        }

        //hintsを初期化
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC; //IPv4とIPv6どちらでもOK
        hints.ai_socktype = SOCK_STREAM; //TCP
        hints.ai_flags = 0;
        hints.ai_protocol = 0; //Any protocol

        //名前解決
        s = getaddrinfo(argv[1], "http", &hints, &result);

        //エラー処理
        if (s != 0) {
                errormess = gai_strerror(s);
                fprintf(stderr, "getaddrinfo: %s\n", errormess);
                return 1;
        }

        //取得した情報を表示
        for (rp = result; rp != NULL; rp = rp->ai_next) {

                printf("{\n"); 
                printf("\tai_family:\t%d\n", rp->ai_family);
                printf("\tai_socktype:\t%d\n", rp->ai_socktype);
                printf("\tai_protocol:\t%d\n", rp->ai_protocol);
                printf("\tai_addrlen:\t%zd\n", rp->ai_addrlen);
                printf("\tai_canonname:\t%s\n", rp->ai_canonname);
		

		if (rp->ai_family == AF_INET) { //IPv4

			addr_in = (struct sockaddr_in *) rp->ai_addr;
			if (inet_ntop(rp->ai_family, &addr_in->sin_addr.s_addr, address, rp->ai_addrlen) == NULL) {
				fprintf(stderr, "inet_ntop: error\n");
				return 1;
			}
			printf("\tai_addr: {\n");
			printf("\t\tsin_port:\t%d\n", ntohs(addr_in->sin_port));
			printf("\t\tsin_addr: {\n");
			printf("\t\t\ts_addr:\t%s\n", address);
			printf("\t\t}\n");
			printf("\t}\n");

		} else if (rp->ai_family == AF_INET6) { //IPv6

			addr_in6 = (struct sockaddr_in6 *) rp->ai_addr;
			if (inet_ntop(rp->ai_family, &addr_in6->sin6_addr.s6_addr, address, rp->ai_addrlen) == NULL) {
				fprintf(stderr, "inet_ntop: error\n");
				return 1;
			}
			printf("\tai_addr: {\n");
			printf("\t\tsin6_port:\t%d\n", ntohs(addr_in6->sin6_port));
			printf("\t\tsin6_addr: {\n");
			printf("\t\t\ts6_addr:\t%s\n", address);
			printf("\t\t}\n");
			printf("\t}\n");
		}

                printf("}\n");

        }

        return 0;

}

実行例

$ ./getaddrinfo_test yahoo.co.jp
{
	ai_family:	2
	ai_socktype:	1
	ai_protocol:	6
	ai_addrlen:	16
	ai_canonname:	(null)
	ai_addr: {
		sin_port:	80
		sin_addr: {
			s_addr:	124.83.187.140
		}
	}
}
{
	ai_family:	2
	ai_socktype:	1
	ai_protocol:	6
	ai_addrlen:	16
	ai_canonname:	(null)
	ai_addr: {
		sin_port:	80
		sin_addr: {
			s_addr:	203.216.243.240
		}
	}
}
$
$ ./getaddrinfo_test localhost
{
	ai_family:	10
	ai_socktype:	1
	ai_protocol:	6
	ai_addrlen:	28
	ai_canonname:	(null)
	ai_addr: {
		sin6_port:	80
		sin6_addr: {
			s6_addr:	::1
		}
	}
}
{
	ai_family:	2
	ai_socktype:	1
	ai_protocol:	6
	ai_addrlen:	16
	ai_canonname:	(null)
	ai_addr: {
		sin_port:	80
		sin_addr: {
			s_addr:	127.0.0.1
		}
	}
}
ホスト名を解決してソケットを開きコネクトする例
/*  getaddrinfo_socket_connect.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

        struct addrinfo hints;
        struct addrinfo *result, *rp;
        int s, sfd;
        const char *errormess;

        if (argc < 2) {
                fprintf(stderr, "Usage: %s host\n", argv[0]);
		return 1;
        }

        //hintsを初期化
        memset(&hints, 0, sizeof(struct addrinfo));
        hints.ai_family = AF_UNSPEC; //IPv4とIPv6どちらでもOK
        hints.ai_socktype = SOCK_STREAM; //TCP
        hints.ai_flags = 0;
        hints.ai_protocol = 0; //Any protocol

        //名前解決
        s = getaddrinfo(argv[1], "http", &hints, &result);

        //エラー処理
        if (s != 0) {
                errormess = gai_strerror(s);
                fprintf(stderr, "getaddrinfo: %s\n", errormess);
                return 1;
        }

        //ホストに接続
        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;	
		}

		//接続できかなった場合はソケットを閉じて次のアドレスでリトライ
		close(sfd);

        }

	//すべてのアドレスで接続に成功しなかった場合は終了
	if (rp == NULL) {
		fprintf(stderr, "Could not connect\n");
		return 1;
	}
	
	//resultのメモリ領域を開放
	freeaddrinfo(result);

	//接続後30秒待機して接続を切る
	sleep(30);
	close(sfd);	

        return 0;

}

実行例

$ ./ getaddrinfo_socket_connect localhost & netstat -anptl | grep  getaddrinfo_socket_connect
[2] 7886
(一部のプロセスが識別されますが, 所有していないプロセスの情報は
表示されません。それら全てを見るにはルートになる必要があります.)
tcp        0      0 127.0.0.1:44619         127.0.0.1:80            ESTABLISHED 7886/getaddrinfo_socket_connect

ESTABLISHED!!