UNIX で TCP接続を accept したとき、そのソケット sd に対して
struct sockaddr_storage ss;
struct sockaddr *from = (struct sockaddr*)&ss;
socklen_t fromlen = sizeof(ss);
getpeername(sd, from, &fromlen);
などとすれば、接続元のアドレスが from に返る。 UNIXドメインソケットを listen し accept した場合は、
from->sa_family ← AF_UNIX
fromlen ← 2
という値が返るようだ。
ところが、
この返り値を getnameinfo に与えると、
fromlen で指定したサイズを超えて
UNIX ドメインソケットのファイル名を読もうとする
問題があるようだ。
たとえば次のようなテストプログラムを書いてみる:
#include <stdio.h> #include <sys/socket.h> #include <netdb.h> #include <sys/un.h> #define STRMAX 256 main() { struct sockaddr *sa; socklen_t salen; struct sockaddr_un sun; char name[STRMAX+1]; char serv[STRMAX+1]; int err; bzero(name, STRMAX+1); bzero(serv, STRMAX+1); sa = (struct sockaddr*)&sun; salen = sizeof(sun); sun.sun_family = AF_UNIX; strcpy(sun.sun_path, "/tmp/sock"); salen = sizeof(sa_family_t); err = getnameinfo(sa, salen, name, STRMAX, serv, STRMAX, 0); printf("salen: %d, err: %d, name: '%s', serv: '%s'\n", salen, err, name, serv); }
salen は 2 であるので、 sun.sun_path の部分の値は無視すべきであるのに、 serv には、"/tmp/sock" の値が返ってしまう。 sun.sun_path の部分が初期化されていなければ、 不定値が返るだろう。
実際、stone では struct sockaddr_storage を初期化せずに getpeername を呼び出して長さ 2 の struct sockaddr_un を受け取り、 これを getnameinfo の引数として与えると、 dserv に不定値が返ってきてしまった。
本来ならば、getnameinfo は長さ 0 のパス名を返すか、 あるいは UNIX ドメインソケットは扱わない、 ないしは長さが異常ということで EAI_FAMILY を返すか、 どちらかであるべきではなかろうか。 少なくとも glibc-2.3.5 と、glibc-2.2.5 では、 この問題があることを確認した。
とりあえず、stone は AF_UNIX のときは getnameinfo を呼ばずに sun_path を dserv へコピーするように修正した。
$Id: stone.c,v 2.2.2.19 2006/04/14 23:35:18 hiroaki_sengoku Exp $