ベーシックなところを知れば知るほど、色々なことが見えてきます。
ということで、今回はCでIPアドレスを解決する方法。
色々参考にしてますが、なにが古くてなにが新しいか、という点が慣れていないと分かりづらいのが難点・・。
(あと、基本的に色々略称が多くて実際なんの値なのか分かりづらいのも・・( ;´Д`))
サンプルコード
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char **args) {
char *hostname = "css-eblog.com";
struct addrinfo hints, *res;
struct in_addr addr;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
if ((err = getaddrinfo(hostname, NULL, &hints, &res)) != 0) {
printf("error %d\n", err);
return 1;
}
addr.s_addr= ((struct sockaddr_in *)(res->ai_addr))->sin_addr.s_addr;
printf("ip addres: %s\n", inet_ntoa(addr));
// 取得した情報を解放
freeaddrinfo(res);
return 0;
}
使用関数と構造体
getaddrinfo
// 宣言
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() 関数は、 gethostbyname(3) と getservbyname(3) の機能をまとめて一つのインターフェースにしたものであるが、 これらの関数と違い、 getaddrinfo() はリエントラントであり、 getaddrinfo() を使うことでプログラムは IPv4 と IPv6 の違いに関する依存関係を なくすことができる。
hints 引き数は addrinfo 構造体を指し示し、この構造体を用いて res が指すリストに入れて返すソケットアドレス構造体を選択するための基準を指定する。 hints が NULL でない場合、 hints は addrinfo 構造体を指し示し、その構造体のフィールド ai_family, ai_socktype, ai_protocol で getaddrinfo() が返すソケットアドレス集合に対する基準を指定する。
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;
};
- ai_family
このフィールドは返されるアドレスの希望のアドレスファミリーを指定する。 このフィールドに指定できる有効な値としては AF_INET と AF_INET6 がある。 また、値 AF_UNSPEC を指定すると、 getaddrinfo() は node と service で使用できるいずれかのアドレスファミリー (例えば IPv4 か IPv6) の ソケットアドレスを返すことを求められる。 - ai_socktype
このフィールドは推奨のソケット型 (例えば SOCK_STREAM や SOCK_DGRAM) を指定する。 このフィールドに 0 を指定すると、任意のソケット型のソケットアドレスを getaddrinfo() が返してよいことを意味する。 - ai_protocol
このフィールドは返されるソケットアドレスのプロトコルを指定する。 このフィールドに 0 を指定すると、任意のプロトコルののソケットアドレスを getaddrinfo() が返してよいことを意味する。 - ai_flags
このフィールドは、追加のオプション (下記) を指定する。 複数のフラグを指定する際には、それらのビット単位の OR をとって指定する。
sockaddr / sockaddr_in構造体
アドレスファミリー、IPアドレス、ポート番号を格納。
// Cの宣言
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
- sin_family
アドレスファミリー(通常はAF_INET(2)を指定) - sin_port
ポート番号(htonsでネットワークバイトオーダーに変換しておく) - sin_addr
IPアドレスを格納したin_addr構造体 - sin_zero
0で初期化しておく
inet_ntoa
// 宣言
char *inet_ntoa(struct in_addr in);
inet_ntoa() 関数は、ネットワークバイトオーダで渡されたインターネットホストアドレス in を、 IPv4 のドット区切りの 10 進数表記の文字列に変換する。 文字列は静的に割当てられたバッファーに格納されて返されるので、 この後でこの関数を再度呼び出すと文字列は上書きされる。
ちなみに逆の操作はinet_aton
を使います。
//宣言
int inet_aton(const char *cp, struct in_addr *inp);
inet_aton() は、インターネットホストのアドレス cp を、 IPv4 の数値とドットによる表記から (ネットワークバイトオーダの) バイナリ値へ 変換し、変換結果を inp が指している構造体に格納する。 アドレスが有効な場合 0 以外を返し、そうでない場合は 0 を返す。
in_addr
in_addr
は以下のように定義されています。
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
メモ
-
ちなみに、IPv6の登場により、
gethostbyname
ではなくgetaddrinfo
を使うようです。 -
あと、参考にしていたサンプルに
bzero
という、0で埋める関数がありましたが、廃止予定のためmemset
を使うことが推奨されているようです。 -
inet_ntoa
を使おうとして「warning: implicit declaration of function 'inet_ntoa' is invalid in C99
」というエラーが出たら、#include <arpa/inet.h>
を読み込ませてあげると解決します。
余談
ちなみに、登場するAF_INET
は Address Family の頭文字。
似たようなものにPF_INET
がありますが、こちらは Protocol Family の頭文字のようです。
PFのほうはsocket関数に送るもの、AFのほうはconnect関数で使うアドレス指定、ということのよう。ただ、Internetではアドレス指定がひとつしかないのでどちらも変わらず使えるみたいです。