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を増やして対応することにしました。