筑波大学 システム情報系 情報工学域 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
https://www.coins.tsukuba.ac.jp/~syspro/2024/2024-07-10/echo-server-nofork-fdopen.html
あるいは、次のページから手繰っていくこともできます。
https://www.coins.tsukuba.ac.jp/~syspro/2024/
http://www.coins.tsukuba.ac.jp/~yas/
主要部分は、講義資料のメインのページにある。 このページにあるもの、その他の細かい部分である。
120: #define HOST_NAME_MAX 256 121: void 122: print_my_host_port( int portno ) 123: { 124: char hostname[HOST_NAME_MAX+1] ; 125: 126: gethostname( hostname,HOST_NAME_MAX ); 127: hostname[HOST_NAME_MAX] = 0 ; 128: printf("run telnet %s %d \n", hostname, portno ); 129: } 130:gethostname() システムコールで自分自身のホスト名を取り出している。(正 式には、gethostname() の結果とインターネット的なホスト名(IPアドレスと 対応している))が一致していないことがある。
2024年7月現在、coins では、同じホスト名で IPv4 と IPv6 の両方のアドレス を登録している。多くのサービスでは、両方のアドレスでサービスを提供して いるが、一方のアドレスでのみサービスを提供していることもある。 そのような場合、片方のアドレスで接続を試みて時間切れを起こした後に別の アドレスで接続を試みるとしう処理が入るために、応答時間が増大することが ある。
131: void 132: tcp_sockaddr_print( int com ) 133: { 134: struct sockaddr_storage addr ; 135: socklen_t addr_len ; /* macOS: __uint32_t, Linux: unsigned int */ 136: 137: addr_len = sizeof( addr ); 138: if( getsockname( com, (struct sockaddr *)&addr, &addr_len )<0 ) 139: { 140: perror("tcp_peeraddr_print"); 141: return; 142: } 143: printf("[%d] accepting (fd==%d) to ",getpid(),com ); 144: sockaddr_print( (struct sockaddr *)&addr, addr_len ); 145: printf("\n"); 146: } 147:自分自身のアドレス(IPアドレスとポート番号)は、getsockname() システムコー ルで得られる。得たアドレスを、sockaddr_print() に渡して表示している。
148: void 149: tcp_peeraddr_print( int com ) 150: { 151: struct sockaddr_storage addr ; 152: socklen_t addr_len ; /* macOS: __uint32_t, Linux: unsigned int */ 153: 154: addr_len = sizeof( addr ); 155: if( getpeername( com, (struct sockaddr *)&addr, &addr_len )<0 ) 156: { 157: perror("tcp_peeraddr_print"); 158: return; 159: } 160: printf("[%d] connection (fd==%d) from ",getpid(),com ); 161: sockaddr_print( (struct sockaddr *)&addr, addr_len ); 162: printf("\n"); 163: } 164:通信相手のアドレス(IPアドレスとポート番号)は、getpeername() システムコー ルで得られる。得たアドレスを、sockaddr_print() に渡して表示している。
165: void 166: sockaddr_print( struct sockaddr *addrp, socklen_t addr_len ) 167: { 168: char host[BUFFERSIZE] ; 169: char port[BUFFERSIZE] ; 170: 171: if( getnameinfo(addrp, addr_len, host, sizeof(host), 172: port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 ) 173: return; 174: if( addrp->sa_family == PF_INET ) 175: printf("%s:%s", host, port ); 176: else 177: printf("[%s]:%s", host, port ); 178: } 179:IP アドレスは、IPv4 では、32 ビットであり、 IP アドレスは、IPv6 では、128 ビットである。 ポート番号は、16 ビットである。 この関数は、getnameinfo() ライブラリ関数を用いてホスト名とポート番号の 文字列表現に変換している。この時、NUMERIC と指定しいるので、 IPv4 では、8ビットごと区切り、各8ビットを10進数で表記し、ドット「.」で結合する。 IPv6 では、16ビットごとに区切り、各16ビットを16進数で表記し、コロン「:」で結合する。
getnameinfo() が使われている部分では、以前は、gethostbyaddr() が使われ ていた。gethostbyaddr() は、IPv6 には対応していない。
180: #define PORTNO_BUFSIZE 30 181: 182: int 183: tcp_acc_port( int portno, int ip_version ) 184: { 185: struct addrinfo hints, *ai; 186: char portno_str[PORTNO_BUFSIZE]; 187: int err, s, on, pf; 188: 189: switch( ip_version ) 190: { 191: case 4: 192: pf = PF_INET; 193: break; 194: case 6: 195: #if !defined(IPV6_V6ONLY) 196: fprintf(stderr,"Sorry, IPV6_V6ONLY is not supported in this system.\n"); 197: goto error0; 198: #endif /*IPV6_V6ONLY*/ 199: pf = PF_INET6; 200: break; 201: case 0: 202: case 46: 203: case 64: 204: pf = PF_INET6; /* pf = 0; in macOS */ 205: break; 206: default: 207: fprintf(stderr,"bad IP version: %d. 4 or 6 is allowed.\n", 208: ip_version ); 209: goto error0; 210: } 211: snprintf( portno_str,sizeof(portno_str),"%d",portno ); 212: memset( &hints, 0, sizeof(hints) ); 213: ai = NULL; 214: hints.ai_family = pf ; 215: hints.ai_flags = AI_PASSIVE; 216: hints.ai_socktype = SOCK_STREAM ; 217: if( (err = getaddrinfo( NULL, portno_str, &hints, &ai )) ) 218: { 219: fprintf(stderr,"bad portno %d? (%s)\n",portno, 220: gai_strerror(err) ); 221: goto error0; 222: } 223: if( (s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0 ) 224: { 225: perror("socket"); 226: goto error1; 227: } 228: 229: #ifdef IPV6_V6ONLY 230: if( ai->ai_family == PF_INET6 && ip_version == 6 ) 231: { 232: on = 1; 233: if( setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY,&on,sizeof(on)) < 0 ) 234: { 235: perror("setsockopt(,,IPV6_V6ONLY)"); 236: goto error1; 237: } 238: } 239: #endif /*IPV6_V6ONLY*/ 240: 241: if( bind(s,ai->ai_addr,ai->ai_addrlen) < 0 ) 242: { 243: perror("bind"); 244: fprintf(stderr,"Port number %d\n", portno ); 245: goto error2; 246: } 247: on = 1; 248: if( setsockopt( s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ) < 0 ) 249: { 250: perror("setsockopt(,,SO_REUSEADDR)"); 251: goto error2; 252: } 253: if( listen( s, 5 ) < 0 ) 254: { 255: perror("listen"); 256: goto error2; 257: } 258: freeaddrinfo( ai ); 259: return( s ); 260: 261: error2: 262: close( s ); 263: error1: 264: freeaddrinfo( ai ); 265: error0: 266: return( -1 ); 267: } 268:
この関数の引数は、ポート番号(整数値)と IP のバージョン(4、6、46、64、または0の整数値)を取る。 まず、IPのバージョンに応じて、Protocol Family (PF_) を選択している。 socket() の引数で、PF_INET/PF_INET6 の変りに、AF_INET/AF_INET6 と書いて もよい。ここでは、Protocol を選んでいるので、PF_ が正しいが、実際には、 PF_INET と AF_INET は同じであり、また、多くの教科書で混在されて使われ いる。
次に、getaddrinfo() を使って、socket() システム・コール、および、 bind() システム・コールに渡すためのパラメタを整えている。 hints.ai_family には、 PF_INET か PF_INET6 を設定している。 hints.ai_socktype に SOCK_STREAM を設定している。これにより、TCP を意味 する。ポート番号は、引数で与えられたもの(を、文字列に変換したもの)を指 定している。
hints.ai_flags には、AI_PASSIVE を指定し、 getaddrinfo() の第一引数とし て NULL を指定している。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。IPv4 の時代には、IPアドレスとして INADDR_ANY という定数を指定することが一般的であった。
次に、クライアント側と同様に、ソケットを、socket() システムコールで作成 している。 PF_INET と SOCK_STREAMの組み合わせ または、 PF_INET6 と SOCK_STREAMの組み合わせ なので、TCP を使うことを意味する。
IPv6 の場合(ip_version==6の場合)、setsockopt() で IPV6_V6ONLY というオ プションを有効にしている。これで、IPv6 が指定された時には、IPv6 のみで 接続要求を受け付ける。このオプションをつけないと、 IPv4 でも要求を受け付ける。この場合、クライアントのアドレスは、 IPv4射影アドレス(IPv4-mapped address) になる。
ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスとポート番号は、 getaddrinfo() で返されたものを使っている。
次に、setsockopt() で、SO_REUSEADDR オプションを設定している。これによ り、同じ IP アドレスとポート番号で(連続して)サービスを提供することがや りやすくなる。ただし、可能性は低いが、前のプロセスと新しいプロセスで、 通信内容が混じってしまう危険性がある。
次に、listen() システムコールにより、要求受け付けを開始する。第2引数 は、最大何個のクライアントを接続要求待ちで待たせるか(待ち行列の長さ) を指定する。 重たいサーバを設計する時には、キューの長さを調節する。Apache (WWW サー バ) などでは、500 程度になっていることがある。
注意:このプログラムには 複数のクライアントに対してサービスを同時に提供できない という問題がある。
$ host azalea16.coins.tsukuba.ac.jp
azalea16.coins.tsukuba.ac.jp has address 130.158.231.16
azalea16.coins.tsukuba.ac.jp has IPv6 address 2001:2f8:3a:1711::231:16
$ hostname
azalea16
$ ifconfig enp0s31f6
enp0s31f6: flags=4163 mtu 1500
inet 130.158.231.16 netmask 255.255.254.0 broadcast 130.158.231.255
inet6 fe80::6e3c:8cff:fe16:82f2 prefixlen 64 scopeid 0x20
inet6 2001:2f8:3a:1711::231:16 prefixlen 64 scopeid 0x0
ether 6c:3c:8c:16:82:f2 txqueuelen 1000 (イーサネット)
RX packets 2748591 bytes 889438993 (889.4 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 1040201 bytes 536689353 (536.6 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 19 memory 0x70800000-70820000
$ ip addr show dev enp0s31f6
3: enp0s31f6: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 6c:3c:8c:16:82:f2 brd ff:ff:ff:ff:ff:ff
inet 130.158.231.16/23 brd 130.158.231.255 scope global noprefixroute enp0s31f6
valid_lft forever preferred_lft forever
inet6 2001:2f8:3a:1711::231:16/64 scope global noprefixroute
valid_lft forever preferred_lft forever
inet6 fe80::6e3c:8cff:fe16:82f2/64 scope link noprefixroute
valid_lft forever preferred_lft forever
$
このホストは、次の IP アドレスを持っている。
次の例では、echo-server-nofork-fdopen は、IPv4 のホストから接続要求を受 け付けている。IPv6 のサーバは、::ffff で始まるIPv4射影アドレスからアク セスされたものとしてサービスを提供している。
$ ./echo-server-fork-fdopen 1231
run telnet azalea16 1231
[631651] accepting (fd==3) to [::]:1231
[631651] accepting incoming connections (acc==3) ...
[631651] connection (fd==4) from [::ffff:130.158.231.16]:55366
[631651] accepting incoming connections (acc==3) ...
[632001] received (fd==4) 5 bytes, [abc
]
[632001] received (fd==4) 5 bytes, [def
]
[632001] received (fd==4) 5 bytes, [ghi
]
[632001] connection (fd==4) closed.
^C
$
クライアント側は、IPv4 でアクセスしている。
$ host azalea16.coins.tsukuba.ac.jp
azalea16.coins.tsukuba.ac.jp has address 130.158.231.16
azalea16.coins.tsukuba.ac.jp has IPv6 address 2001:2f8:3a:1711::231:16
$ telnet -4 azalea16.coins.tsukuba.ac.jp 1231
Trying 130.158.231.16...
Connected to azalea16.coins.tsukuba.ac.jp.
Escape character is '^]'.
abc
abc
def
def
ghi
ghi
^]
telnet> quit
Connection closed.
$
このように、IPv6 で意図せず IPv4 でもサービスを提供するとセキュリ ティ上の問題が生じることがある。たとえば、あるホストからのアクセスを禁 止しようとして、通常の IPv6 のアドレスで禁止する設定をしていたとしても、 IPv4 からアクセスされた時にはIPv4射影アドレスを通じて接続を許可してしま うことがある。