hana_shinのLinux技術ブログ

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

ソケットオプションの使い方(SO_SNDBUF編)



1 はじめに

SO_SNDBUF,SO_RCVBUFは、ソケットの送受信バッファの最大サイズの設定、取得をするソケットオプションです。ここでは、SO_SNDBUFについて説明をします。

オプション 意味
SO_SNDBUF ソケットの送信バッファーの最大サイズを設定、取得をする
SO_RCVBUF ソケットの受信バッファーの最大サイズを設定、取得をする

2 ソースコードの説明

送信バッファの最大サイズは、以下のsock_setsockopt関数で設定します。sock_setsockopt関数はsetsockoptシステムコールの延長で呼び出すカーネル関数です。第4引数(optval)は、ソケットオプションによって意味が異なります。SO_SNDBUFソケットオプションの場合、送信バッファサイズの格納領域へのポインタになります。

 846 int sock_setsockopt(struct socket *sock, int level, int optname,
 847                     char __user *optval, unsigned int optlen)
-snip-
 866         if (get_user(val, (int __user *)optval))
 867                 return -EFAULT;
-snip-
 899         case SO_SNDBUF:
 900                 /* Don't error on this BSD doesn't and if you think
 901                  * about it this is right. Otherwise apps have to
 902                  * play 'guess the biggest size' games. RCVBUF/SNDBUF
 903                  * are treated in BSD as hints
 904                  */
 905                 val = min_t(u32, val, sysctl_wmem_max);
 906 set_sndbuf:
 907                 /* Ensure val * 2 fits into an int, to prevent max_t()
 908                  * from treating it as a negative value.
 909                  */
 910                 val = min_t(int, val, INT_MAX / 2);
 911                 sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
 912                 sk->sk_sndbuf = max_t(int, val * 2, SOCK_MIN_SNDBUF);
 913                 /* Wake up sending tasks if we upped the value. */
 914                 sk->sk_write_space(sk);
 915                 break;

ソースコードの変数について説明をします。

変数,定数 意味
val 送信バッファサイズを保存する変数です。866行目でソケット利用者が指定した送信バッファサイズをvalに保存しています
sysctl_wmem_max システムで利用できる送信バッファの最大サイズを保存するカーネルパラメータ(net.core.wmem_max)です。私の環境では、この値は212992でした。905行目を確認すると、ソケット利用者が指定できる送信バッファサイズの最大値は、カーネルパラメータの値より小さくなることがわかります
sk_sndbuf 送信バッファの最大サイズを保存する変数です。912行目で送信バッファの最大サイズをsk_sndbufに保存しています
SOCK_MIN_SNDBUF 4608バイトの固定値です


ソースコードを確認すると、ソケット利用者が指定する送信バッファの最大サイズ(val)とカーネルが割り当てる送信バッファの最大サイズ(sk_sndbuf)は以下の関係になることがわかります。

簡単にグラフが描けるツールが見つからなかったのでwindowsのペイントを使いました。いいツールがあったら教えてください。

3 検証環境

3.1 ネットワーク構成

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

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

3.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.3 カーネルパラメータのデフォルト値

wmem_maxは、送信バッファの最大サイズを保持するカーネルパラメータです。デフォルト値は212992であることがわかります。

[root@client ~]# sysctl -n net.core.wmem_max
212992

4 事前準備

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

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

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

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

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

TPを作成します。なお、エラー処理は見やすくするため意図的に省略しています。また、ソケットオプションの動作確認を目的としているため、実用的なプログラムにはなっていません。

・サーバ側:ncコマンドを使います。なお、ncコマンドのインストール方法、使い方は、ncコマンドの使い方(ネットワーク実験の幅が広がるなぁ~) - hana_shinのLinux技術ブログを参照してください。

・クライアント側:以下のTPをコンパイルして使用します。TPはデフォルトの送信バッファの最大サイズを表示したあと、ソケット利用者が指定した送信バッファの最大サイズを設定します。そして、設定した送信バッファの最大サイズを表示します。TPの第1引数には、送信バッファの最大サイズを指定します。なお、見やすくするため、不要な異常処理は意図的に省略しています。

[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, sendbuff;
  struct sockaddr_in server;
  socklen_t optlen;

  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));

  optlen = sizeof(sendbuff);
  getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&sendbuff, (socklen_t *)&optlen);
  printf("send buffer size(before) = %d\n",sendbuff);

  sendbuff = atoi(argv[1]);
  setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&sendbuff, (socklen_t )optlen);

  getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&sendbuff, (socklen_t *)&optlen);
  printf("send buffer size(after) = %d\n",sendbuff);

  close(sock);
  return 0;
}

TPをコンパイルします。

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

6 検証結果

TPで指定する送信バッファサイズを変化させて、そのとき、カーネルが割り当てる送信バッファサイズが冒頭で記述したグラフのようになるかを検証してみたいと思います。

サーバでncコマンドを実行します。

[root@server ~]# nc -kl 11111

クライアントでTPを実行します。このとき、送信バッファの最大サイズとして2303を指定します。実行結果を確認すると、カーネルが割り当てた送信バッファの最大サイズは4608であることがわかります。

[root@client ~]# ./cl 2303
send buffer size(before) = 87040
send buffer size(after) = 4608

送信バッファの最大サイズとして2304を指定すると、カーネルが割り当てた送信バッファの最大サイズは4608であることがわかります。

[root@client ~]# ./cl 2304
send buffer size(before) = 87040
send buffer size(after) = 4608

送信バッファの最大サイズとして2305を指定すると、カーネルが割り当てた送信バッファの最大サイズは4610であることがわかります。

[root@client ~]# ./cl 2305
send buffer size(before) = 87040
send buffer size(after) = 4610

以降、実行結果を記載しておきます。

[root@client ~]# ./cl 2306
send buffer size(before) = 87040
send buffer size(after) = 4612
[root@client ~]# ./cl 212990
send buffer size(before) = 87040
send buffer size(after) = 425980
[root@client ~]# ./cl 212991
send buffer size(before) = 87040
send buffer size(after) = 425982
[root@client ~]# ./cl 212992
send buffer size(before) = 87040
send buffer size(after) = 425984
[root@client ~]# ./cl 212993
send buffer size(before) = 87040
send buffer size(after) = 425984

Z 参考情報

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