listen()のbacklogの設定値、キューの数値を確認する
Apacheのポートベースのバーチャルホストで特定のホストだけ死活監視が失敗する事がありました。 ssコマンドで表示されるRecv-Qがbacklogのキューの数値だと思っていたのですが ss(8) - Linux manual pageを見ても不確かだったのでソースを確認したいと思います。
以下、対象サーバーの5秒間隔のss -intlコマンドの出力結果になります。
State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:30443 *:* LISTEN 38 128 *:10443 *:* LISTEN 0 128 *:40443 *:* LISTEN 0 128 *:20443 *:* State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:30443 *:* LISTEN 127 128 *:10443 *:* LISTEN 0 128 *:40443 *:* LISTEN 0 128 *:20443 *:* State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:30443 *:* LISTEN 129 128 *:10443 *:* LISTEN 0 128 *:40443 *:* LISTEN 0 128 *:20443 *:* State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:30443 *:* LISTEN 129 128 *:10443 *:* LISTEN 0 128 *:40443 *:* LISTEN 0 128 *:20443 *:* State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:30443 *:* LISTEN 126 128 *:10443 *:* LISTEN 0 128 *:40443 *:* LISTEN 0 128 *:20443 *:*
10443ポートでListenしているRecv-Qが最大で(Send-Q)+1まで増加しています。
このRecv-QとSend-Qの値が何を指しているのか確認します。
ss.cのsock_state_print()が出力しています。
- iproute2-4.11.0/misc/ss.c
793 static void sock_state_print(struct sockstat *s) 794 { 795 const char *sock_name; 796 static const char * const sstate_name[] = { 797 "UNKNOWN", 798 [SS_ESTABLISHED] = "ESTAB", 799 [SS_SYN_SENT] = "SYN-SENT", 800 [SS_SYN_RECV] = "SYN-RECV", 801 [SS_FIN_WAIT1] = "FIN-WAIT-1", 802 [SS_FIN_WAIT2] = "FIN-WAIT-2", 803 [SS_TIME_WAIT] = "TIME-WAIT", 804 [SS_CLOSE] = "UNCONN", 805 [SS_CLOSE_WAIT] = "CLOSE-WAIT", 806 [SS_LAST_ACK] = "LAST-ACK", 807 [SS_LISTEN] = "LISTEN", 808 [SS_CLOSING] = "CLOSING", 809 }; 810 811 switch (s->local.family) { 812 case AF_UNIX: 813 sock_name = unix_netid_name(s->type); 814 break; 815 case AF_INET: 816 case AF_INET6: 817 sock_name = proto_name(s->type); 818 break; 819 case AF_PACKET: 820 sock_name = s->type == SOCK_RAW ? "p_raw" : "p_dgr"; 821 break; 822 case AF_NETLINK: 823 sock_name = "nl"; 824 break; 825 default: 826 sock_name = "unknown"; 827 } 828 829 if (netid_width) 830 printf("%-*s ", netid_width, 831 is_sctp_assoc(s, sock_name) ? "" : sock_name); 832 if (state_width) { 833 if (is_sctp_assoc(s, sock_name)) 834 printf("`- %-*s ", state_width - 3, 835 sctp_sstate_name[s->state]); 836 else 837 printf("%-*s ", state_width, sstate_name[s->state]); 838 } 839 840 printf("%-6d %-6d ", s->rq, s->wq); 841 }
s->rqとs->wqには、parse_diag_msg()によってidiag_rqueueとidiag_wqueueの値がそれぞれ入ります。
2343 static void parse_diag_msg(struct nlmsghdr *nlh, struct sockstat *s) 2344 { 2345 struct rtattr *tb[INET_DIAG_MAX+1]; 2346 struct inet_diag_msg *r = NLMSG_DATA(nlh); 2347 2348 parse_rtattr(tb, INET_DIAG_MAX, (struct rtattr *)(r+1), 2349 nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); 2350 2351 s->state = r->idiag_state; 2352 s->local.family = s->remote.family = r->idiag_family; 2353 s->lport = ntohs(r->id.idiag_sport); 2354 s->rport = ntohs(r->id.idiag_dport); 2355 s->wq = r->idiag_wqueue; 2356 s->rq = r->idiag_rqueue; 2357 s->ino = r->idiag_inode; 2358 s->uid = r->idiag_uid; 2359 s->iface = r->id.idiag_if; 2360 s->sk = cookie_sk_get(&r->id.idiag_cookie[0]); 2361 2362 s->mark = 0; 2363 if (tb[INET_DIAG_MARK]) 2364 s->mark = rta_getattr_u32(tb[INET_DIAG_MARK]); 2365 if (tb[INET_DIAG_PROTOCOL]) 2366 s->raw_prot = rta_getattr_u8(tb[INET_DIAG_PROTOCOL]); 2367 else 2368 s->raw_prot = 0; 2369 2370 if (s->local.family == AF_INET) 2371 s->local.bytelen = s->remote.bytelen = 4; 2372 else 2373 s->local.bytelen = s->remote.bytelen = 16; 2374 2375 memcpy(s->local.data, r->id.idiag_src, s->local.bytelen); 2376 memcpy(s->remote.data, r->id.idiag_dst, s->local.bytelen); 2377 }
それぞれの値の意味はman(7)sock_diagによると
http://man7.org/linux/man-pages/man7/sock_diag.7.html
idiag_rqueue For listening sockets: the number of pending connections. For other sockets: the amount of data in the incoming queue. idiag_wqueue For listening sockets: the backlog length. For other sockets: the amount of memory available for sending.
idiag_rqueue (Recv-Q)は、accept()を待っているキューの数
idiag_wqueue (Send-Q)は、backlogのサイズ
という事なので期待していた数値で間違いなさそうです。
backlogが溢れた時の動作については、How TCP backlog works in Linuxに詳しく解説されてますが
1. LINUX_MIB_LISTENOVERFLOWS, LINUX_MIB_LISTENDROPSのカウンタをインクリメントしtcp_abort_on_overflow = 0だった場合何もしない。
2. ACK, SYNをdropする
となります。
カウンタの値は以下のコマンドで確認できます。
$ nstat -s |grep -i listen TcpExtListenOverflows 32449 0.0 TcpExtListenDrops 32449 0.0 $ nstat -s TcpExtListenOverflows #kernel TcpExtListenOverflows 32449 0.0 [mnishikizawa@web01 ~ RADIAN-MUSASHI]$ nstat -s TcpExtListenDrops #kernel TcpExtListenDrops 32449 0.0 [mnishikizawa@web01 ~ RADIAN-MUSASHI]$
死活監視の失敗原因は、backlog溢れによるtimeoutで間違いなさそうなので今回はsomaxconnを増やして対応することにしました。