hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

ssコマンドのRecv-Q,Send-Qの意味について(LISTEN状態のとき)



1 はじめに

ssコマンドを実行すると、Recv-QとSend-Q という項目が表示されます。本記事では、LISTEN状態のときのRecv-QとSend-Qの意味について実験を通して確認してみます。ESTABLISHED状態のときの、Recv-QとSend-Qの意味については、ssコマンドのRecv-Q,Send-Qの意味について(ESTABLISHED状態のとき) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ss -antpo4
State          Recv-Q         Send-Q                  Local Address:Port                   Peer Address:Port          Process
LISTEN         0              128                           0.0.0.0:22                          0.0.0.0:*              users:(("sshd",pid=842,fd=5))
ESTAB          0              0                      192.168.122.68:22                    192.168.122.1:51132          users:(("sshd",pid=1504,fd=5),("sshd",pid=1486,fd=5)) timer:(keepalive,49min,0)

ssコマンドの使い方は、ssコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。


それぞれの意味は以下になります。

項目 意味
Recv-Q TCPコネクションは確立済したけど、まだアプリケーションがacceptシステムコールを実行していないため、backlog(*)に保留されているTCPコネクション確立済の要求数を表します。最大値は、なぜかbacklog最大値+1の値になります。別途調査予定。
Send-Q ソケットのbacklog(*)の最大値を表します

(*) backlogは、TCPコネクション確立済の要求をキューイングするキューです。アプリケーションがacceptシステムコールを実行すると、このキューから要求が取り出されます。キュー長の最大値はlistenシステムコールの第2引数で指定します。しかし、カーネルパラメータ(net.core.somaxconn)が示すシステム上限値を超えることはできません。

2 検証環境

2.1 ネットワーク構成

サーバとクライアントの2台構成です。図中のeth0はNICの名前です。

                           192.168.122.0/24
client(eth0) ------------------------------------------(eth0) server
           .177                                       .68

2.2 版数

サーバとクライアントのAlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

3 事前準備

クライアントからサーバの11111番ポートにアクセスできるようにするため、TCPの11111番ポートを解放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# firewall-cmd --add-port=11111/tcp

設定を確認します。TCPの11111番ポートへのアクセスを許可するルールが追加されたことがわかります。

[root@server ~]# firewall-cmd --list-ports
11111/tcp

4 テストプログラム(以降TP)の作成

サーバ、クライアントで実行するTPを作成します。ソースコードを見やすくするため、意図的に異常処理は省略しています。また、ソケットオプションの動作確認を目的としているため、実用的なプログラムにはなっていません。

4.1 サーバ側

TPの引数には、backlogの最大値を指定します。

[root@server ~]# cat sv.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
  int lfd, cfd, status, backlog, on=1;
  socklen_t len;
  struct sockaddr_in sv, cl;
  pid_t pid;

  lfd = socket(AF_INET, SOCK_STREAM, 0);
  sv.sin_family = AF_INET;
  sv.sin_port = htons(11111);
  sv.sin_addr.s_addr = INADDR_ANY;

  backlog=atoi(argv[1]);

  setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
  bind(lfd, (struct sockaddr *)&sv, sizeof(sv));
  listen(lfd, backlog);

  while (1) {
    len = sizeof(cl);
    cfd = accept(lfd, (struct sockaddr *)&cl, &len);
    printf("accept\n");
    if((pid=fork()) == 0){  //child
      close(lfd);
      printf("child PID=%d\n", getpid());
      close(cfd);
      exit(0);
    }
    else{    // parent
      close(cfd);
      waitpid(pid, &status, 0);
      exit(0);
    }
  }
}

コンパイルします。

[root@server ~]# gcc -Wall -o sv sv.c

4.2 クライアント側

connectシステムコールを実行して、サーバとTCPコネクションを確立します。TCPコネクション確立後は、データの送受信はしません。TCPコネクション確立後、600秒スリープします

[root@client ~]# cat cl.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
  int sock;
  struct sockaddr_in server;

  sock = socket(AF_INET, SOCK_STREAM, 0);
  server.sin_family = AF_INET;
  server.sin_port = htons(11111);
  server.sin_addr.s_addr = inet_addr("192.168.122.68");

  connect(sock, (struct sockaddr *)&server, sizeof(server));
  sleep(600);
}

コンパイルします。

[root@client ~]# gcc -Wall -o cl cl.c

5 Send-Qの確認

5.1 TPの引数に1を指定した場合

サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に1を指定していることがわかります。また、acceptシステムコールが完了していないので、TCPコネクションの確立待ちであることがわかります。TCPのコネクションが確立すると、acceptシステムコールが復帰します。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# strace -ttT -f -e trace=network ./sv 1
21:52:03.856722 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000225>
21:52:03.858151 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000264>
21:52:03.859615 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000492>
21:52:03.860493 listen(3, 1)            = 0 <0.000830>
21:52:03.862325 accept(3,

ssコマンドを実行すると、backlogの最大値が1であることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State      Recv-Q     Send-Q         Local Address:Port          Peer Address:Port     Process
LISTEN     0          1                    0.0.0.0:11111              0.0.0.0:*         users:(("sv",pid=2411,fd=3))

5.2 TPの引数に2を指定した場合

サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に2を指定していることがわかります。

[root@server ~]# strace -ttT -f -e trace=network ./sv 2
21:52:37.688721 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000677>
21:52:37.690257 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000526>
21:52:37.691767 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.001065>
21:52:37.694699 listen(3, 2)            = 0 <0.000927>
21:52:37.696554 accept(3,

ssコマンドを実行すると、backlogの最大値が2であることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State      Recv-Q     Send-Q         Local Address:Port          Peer Address:Port     Process
LISTEN     0          2                    0.0.0.0:11111              0.0.0.0:*         users:(("sv",pid=2417,fd=3))

5.3 TPの引数に256を指定した場合

サーバでTPを実行します。このとき、straceコマンドを実行すると、listenシステムコールの第2引数に256を指定していることがわかります。

[root@server ~]# strace -ttT -f -e trace=network ./sv 256
21:53:13.642364 socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3 <0.000374>
21:53:13.643762 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 <0.000485>
21:53:13.644835 bind(3, {sa_family=AF_INET, sin_port=htons(11111), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 <0.000340>
21:53:13.646043 listen(3, 256)          = 0 <0.000609>
21:53:13.647567 accept(3,

ssコマンドを実行すると、backlogの最大値がlistenシステムコールに指定した256ではなく128であることがわかります。これは、カーネルパラメータ(システムの最大値)のbacklog最大値が128(後述)だからです。ソケットオプションの最大値はカーネルパラメータの最大値を超えることはできません。

[root@server ~]# ss -antop4 'sport == :11111'
State      Recv-Q     Send-Q         Local Address:Port          Peer Address:Port     Process
LISTEN     0          128                  0.0.0.0:11111              0.0.0.0:*         users:(("sv",pid=2422,fd=3))

カーネルパラメータを確認すると、backlog最大値が128であることがわかります。

[root@server ~]# sysctl -n net.core.somaxconn
128

6 Recv-Qの確認

6.1 TPの引数に1を指定した場合

サーバでTPを実行します。この時、引数に1を指定してbacklog最大値を1に設定します。

[root@server ~]# ./sv 1

psコマンドを実行してプロセスの状態を確認すると、svプロセスがスリープ(S)していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C sv -o comm,pid,ppid,state
COMMAND             PID    PPID S
sv                 2440    1489 S

Ctrl+Zを押下して、svプロセスを停止状態にします。プロセスを停止する理由は、svプロセスがacceptシステムコールを実行してbacklogよりTCPコネクション確立済の要求を取り出さないようにするためです。要求を取り出さないようにすることで、backlogに要求を溜めていきます。

[root@server ~]# ./sv 1
^Z
[1]+  停止                  ./sv 1

psコマンドを実行してプロセスの状態を確認すると、svプロセスが停止(T)していることがわかります。この状態でTCPコネクションが確立しても、acceptシステムコールは復帰しません。

[root@server ~]# ps -C sv -o comm,pid,ppid,state
COMMAND             PID    PPID S
sv                 2440    1489 T

クライアントでTPを実行します。

[root@client ~]# ./cl

サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が1つあることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 1       1               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2440,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35698

クライアントでTPを実行(2回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が2つあることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 2       1               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2440,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35698
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35700

クライアントでTPを実行(3回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行すると、backlogに保留している要求が3つではなく2つあることがわかります(期待値)。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 2       1               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2440,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35698
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35700

6.2 TPの引数に2を指定した場合

サーバでTPを実行します。この時、引数に1を指定してbacklog最大値を2に設定します。

[root@server ~]# ./sv 2

psコマンドを実行して、プロセスの状態を確認すると、スリープ状態であることがわかります。

[root@server ~]# ps -C sv -o comm,pid,ppid,state
COMMAND             PID    PPID S
sv                 2451    1489 S

Ctrl+Zを押下して、svプロセスを停止状態にします。

[root@server ~]# ./sv 2
^Z
[1]+  停止                  ./sv 2

クライアントでTPを実行(1回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が1つあることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 1       2               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2451,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35704

クライアントでTPを実行(2回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が2つあることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 2       2               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2451,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35706
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35704

クライアントでTPを実行(3回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行します。TCPコネクション確立済だけど、まだacceptシステムコールが実行されていないため、backlogに保留されている要求が3つあることがわかります。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 3       2               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2451,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35706
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35708
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35704

クライアントでTPを実行(4回目)します。

[root@client ~]# ./cl

サーバでssコマンドを実行すると、backlogに保留している要求が4つではなく3つあることがわかります(期待値)。

[root@server ~]# ss -antop4 'sport == :11111'
State  Recv-Q  Send-Q    Local Address:Port       Peer Address:Port  Process
LISTEN 3       2               0.0.0.0:11111           0.0.0.0:*      users:(("sv",pid=2451,fd=3))
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35706
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35708
ESTAB  0       0        192.168.122.68:11111   192.168.122.177:35704

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

How TCP backlog works in Linux