hana_shinのLinux技術ブログ

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

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



1 TCP_CORKオプションとは?

ソケットの送信バッファにMSS(Maximum Segment Size)まで送信データを貯めてから、データを送信する機能です。MSSとは、MTU(Maximum Transmission Unit)からTCP/IPヘッダサイズを引いた値になります。MTUは、イーサネット環境では1500(byte)です。また、TCP/IPヘッダサイズは各々20(byte)です。さらにTCPには、TCPタイムスタンプオプション(12byte)が付くのが一般的です。したがって、イーサネット環境では一般的にMSSは1448(byte)になります。本環境でもMSSは1448(byte)です。

2 検証環境

2.1 ネットワーク構成

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

                            192.168.2.0/24
client(eth0) -------------------------------------------- (eth0) server
            .105                                     .100

2.2 版数

サーバ、クライアントともに下記版数です。

[root@server ~]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

2.3 MTUの確認

ipコマンドを実行すると、eth0のMTUは1500(byte)であることがわかります。

[root@server ~]# ip l show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:78:10:73 brd ff:ff:ff:ff:ff:ff

3 テストプログラム(TP)作成

3.1 テストプログラム

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

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

int main(int argc, char *argv[])
{
    int lfd, cfd;
    socklen_t len;
    struct sockaddr_in sv, cl;
    char buf[32];
    ssize_t n;

    lfd = socket(AF_INET, SOCK_STREAM, 0);

    sv.sin_family = AF_INET;
    sv.sin_port = htons(11111);
    sv.sin_addr.s_addr = INADDR_ANY;

    bind(lfd, (struct sockaddr *)&sv, sizeof(sv));
    listen(lfd, 5);
    memset(buf, 0, sizeof(buf));
    len = sizeof(cl);
    cfd = accept(lfd, (struct sockaddr *)&cl, &len);

    while (1) {
        n = read(cfd, buf, sizeof(buf));
        if(n > 0)
            fprintf(stderr,"%zd, %s\n", n, buf);
        else if(n == 0) {  //EOF
            close(cfd);
            return 0;
        }
        else {
            perror("read");
            return 1;
        }
    }
    close(lfd);
    return 0;
}

クライアントで動作するTPを作成します。なお、エラー処理は見やすくするため意図的に省略しています。

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

int main(int argc, char *argv[])
{
    struct sockaddr_in server;
    int sock, i, val;
    ssize_t n;
    char buf[10]="0123456789";

    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.2.100");

    val = atoi(argv[1]);
    setsockopt(sock, IPPROTO_TCP, TCP_CORK, &val, sizeof(val));
    connect(sock, (struct sockaddr *)&server, sizeof(server));

    for(i=0; i<1000; i++) {
        n = write(sock, buf, sizeof(buf));
        if(n == -1) {
            perror("write");
            return 1;
        }
    }
    close(sock);
    return 0;
}

3.2 コンパイル

サーバ側のTPをコンパイルします。

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

クライアント側のTPをコンパイルします。

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

4 事前準備

サーバでポート番号を開放します。なお、firewall-cmdコマンドの使い方は、firewall-cmdの使い方 - hana_shinのLinux技術ブログを参照してください。

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

開放したポート番号を確認します。11111番ポートが開放されたことがわかります。

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

5 動作確認

5.1 TCP_CORKオプションが有効の場合

TCP_CORKが有効だと、ソケットの送信バッファにMSSまでのデータを貯めてから送信します。tcpdumpを使って、そのことを確認してみます。

サーバでTPを実行します。

[root@server ~]# ./sv

lsofコマンドを実行します。svプロセスが11111番ポートでListenしていることがわかります。なお、lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c sv -a -i4 -a -P
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sv      2453 root    3u  IPv4  35002      0t0  TCP *:11111 (LISTEN)

サーバでtcpdumpコマンドを実行します。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i eth0 port 11111 -nn

クライアントでTPを実行します。TPの引数には、TCP_CORK有効(=1)を指定します。

[root@client ~]# ./cl 1

tcpdumpコマンドの実行結果を確認します。lenの部分に★が付いた6つのパケットのlenは全て1448(byte)になっています。また、●が付いた1つのパケットはlenが1312(byte)になっています。lenの合計を計算すると、1448×6 + 1312 =10000となります。TPの送信バイト数と一致していることがわかります。

[root@server ~]# tcpdump -i eth0 port 11111 -nn
-snip-
10:44:09.311769 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [S], seq 1386691091, win 29200, options [mss 1460,sackOK,TS val 1306121 ecr 0,nop,wscale 7], length 0
10:44:09.311844 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [S.], seq 975134360, ack 1386691092, win 28960, options [mss 1460,sackOK,TS val 1146026 ecr 1306121,nop,wscale 7], length 0
10:44:09.312674 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], ack 1, win 229, options [nop,nop,TS val 1306124 ecr 1146026], length 0
10:44:09.313059 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], seq 1:1449, ack 1, win 229, options [nop,nop,TS val 1306124 ecr 1146026], length 1448 ★
10:44:09.313124 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 1449, win 249, options [nop,nop,TS val 1146027 ecr 1306124], length 0
10:44:09.313495 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], seq 1449:2897, ack 1, win 229, options [nop,nop,TS val 1306124 ecr 1146026], length 1448 ★
10:44:09.313814 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 2897, win 272, options [nop,nop,TS val 1146027 ecr 1306124], length 0
10:44:09.314021 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], seq 2897:4345, ack 1, win 229, options [nop,nop,TS val 1306125 ecr 1146027], length 1448 ★
10:44:09.314046 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 4345, win 295, options [nop,nop,TS val 1146028 ecr 1306125], length 0
10:44:09.314240 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], seq 4345:5793, ack 1, win 229, options [nop,nop,TS val 1306126 ecr 1146027], length 1448 ★
10:44:09.314284 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 5793, win 317, options [nop,nop,TS val 1146028 ecr 1306126], length 0
10:44:09.314545 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [P.], seq 5793:7241, ack 1, win 229, options [nop,nop,TS val 1306126 ecr 1146028], length 1448 ★
10:44:09.314583 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 7241, win 340, options [nop,nop,TS val 1146028 ecr 1306126], length 0
10:44:09.315009 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], seq 7241:8689, ack 1, win 229, options [nop,nop,TS val 1306126 ecr 1146028], length 1448 ★
10:44:09.315195 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [.], ack 8689, win 362, options [nop,nop,TS val 1146029 ecr 1306126], length 0
10:44:09.315569 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [FP.], seq 8689:10001, ack 1, win 229, options [nop,nop,TS val 1306127 ecr 1146028], length 1312 ●
10:44:09.319247 IP 192.168.2.100.11111 > 192.168.2.105.53204: Flags [F.], seq 1, ack 10002, win 385, options [nop,nop,TS val 1146033 ecr 1306127], length 0
10:44:09.320480 IP 192.168.2.105.53204 > 192.168.2.100.11111: Flags [.], ack 2, win 229, options [nop,nop,TS val 1306131 ecr 1146033], length 0

5.2 TCP_CORKオプションが無効の場合

サーバでTPを実行します。

[root@server ~]# ./sv

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

[root@server ~]# tcpdump -i eth0 port 11111 -nn

クライアントでTPを実行します。TPの引数には、TCP_CORK無効(=0)を指定します。

[root@client ~]#./cl 0

lenの部分を確認すると、1448(byte)のものもありますが、1448以外の長さのものが大部分であることがわかります。つまり、送信バッファにMSSサイズ分のデータが貯まる前に、データを送信していることがわかります。

[root@server ~]# tcpdump -i eth0 port 11111 -nn
-snip-
10:50:24.844911 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [S], seq 1982540112, win 29200, options [mss 1460,sackOK,TS val 1681655 ecr 0,nop,wscale 7], length 0
10:50:24.844959 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [S.], seq 3204141593, ack 1982540113, win 28960, options [mss 1460,sackOK,TS val 1521559 ecr 1681655,nop,wscale 7], length 0
10:50:24.845601 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521559], length 0
10:50:24.845682 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 1:11, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521559], length 10
10:50:24.845694 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 11, win 227, options [nop,nop,TS val 1521560 ecr 1681657], length 0
10:50:24.846079 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], seq 11:1459, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521559], length 1448
10:50:24.846113 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 1459:1461, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 2
10:50:24.846160 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 1459, win 249, options [nop,nop,TS val 1521560 ecr 1681657], length 0
10:50:24.846240 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 1461, win 249, options [nop,nop,TS val 1521560 ecr 1681657], length 0
10:50:24.846478 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], seq 1461:2909, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 1448
10:50:24.846495 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 2909, win 272, options [nop,nop,TS val 1521560 ecr 1681657], length 0
10:50:24.846553 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 2909:2951, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 42
10:50:24.846563 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 2951, win 272, options [nop,nop,TS val 1521560 ecr 1681657], length 0
10:50:24.846944 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 2951:3351, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 400
10:50:24.847789 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 3351, win 295, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.847845 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], seq 3351:6247, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 2896
10:50:24.847858 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 6247, win 340, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.847936 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], seq 6247:7695, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521560], length 1448
10:50:24.847950 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 7695, win 362, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.848017 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 7695:7701, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521562], length 6
10:50:24.848030 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 7701, win 362, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.848382 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 7701:8181, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521562], length 480
10:50:24.848427 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 8181, win 385, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.848523 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], seq 8181:9629, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521562], length 1448
10:50:24.848537 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 9629, win 408, options [nop,nop,TS val 1521562 ecr 1681657], length 0
10:50:24.848720 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [P.], seq 9629:9631, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521562], length 2
10:50:24.848733 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [.], ack 9631, win 408, options [nop,nop,TS val 1521563 ecr 1681657], length 0
10:50:24.848804 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [FP.], seq 9631:10001, ack 1, win 229, options [nop,nop,TS val 1681657 ecr 1521562], length 370
10:50:24.850268 IP 192.168.2.100.11111 > 192.168.2.105.53206: Flags [F.], seq 1, ack 10002, win 430, options [nop,nop,TS val 1521564 ecr 1681657], length 0
10:50:24.850991 IP 192.168.2.105.53206 > 192.168.2.100.11111: Flags [.], ack 2, win 229, options [nop,nop,TS val 1681657 ecr 1521564], length 0

6 まとめ

実験結果より次のことがわかりました。
TCP_CORK有効の場合:MSSまで送信データを溜めてから送信する。
TCP_CORK無効の場合:MSSまで送信データが溜まらなくても送信する。

Z 参考情報

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