地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Linux】so_reuseportに関して

背景

so_reuseportってなんやねんと思って調べた

SO_REUSEPORTとは

ローカルアドレスの再使用を有効にするソケットオプションのSO_REUSEADDRは使ったことがあったがportは使ったことがなかった。manを参照すると「重複したアドレスとポートのバインドを有効にします」と書いてある。そのままだった。

これの何が嬉しいのかというと例えば複数プロセスのソケットプログラミングを行う際にマルチプロセスモデルで書く場合にこのオプションを指定しない場合は親プロセスでListenして子プロセスでそのsocketをaccept(2)する必要がある。このオプションを使うと各子プロセスでbindすることが可能となる。

Nginxでの使い方

Socket Sharding in NGINX OSS Release 1.9.1

Nginxの場合のわかりやすい図。使わない場合は各workerがリクエストごとに接続を取得しようとする。

f:id:ryuichi1208:20210626181905p:plain

サンプルソース

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <netinet/in.h>

void child(int idx)
{
        int S, C;
        const int on = 1;
        struct addrinfo *res;
        struct addrinfo hints;
        struct sockaddr_in sin4;
        socklen_t slen;

        memset(&hints, sizeof(hints), 0);
        hints.ai_family = AF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
        getaddrinfo("127.0.0.1", "10000", &hints, &res);
        S = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        setsockopt(S, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
        bind(S, res->ai_addr, res->ai_addrlen);
        freeaddrinfo(res);
        listen(S, 100);

        slen = sizeof(sin4);
        while ((C = accept(S, (struct sockaddr *)&sin4, &slen)) >= 0) {
                close(C);
                printf("accepted by child #%d\n", idx);
        }
}

int main(void)
{
        int status;
        int i;

        for (i = 0; i < 4; i++) {
                pid_t pid = fork();
                if (pid == 0) {
                        child(i);
                        return 0;
                } else if (pid < 0) {
                        perror("fork");
                        return 1;
                }
        }

        wait(&status);

        return 0;
}

参考

【Linux】UDPのMapping Refresh

UDPでのルーターのNATテーブルの圧迫の話。自分の家もまさに該当しそうなので調べてみた。

datatracker.ietf.org

カーネルパラメータの設定だとnf_conntrack_udp_timeout_streamあたりが該当するらしい。120sがデフォルトなのでQUICで大量にブラウジングすると結構溜まっていきそう。

nf_conntrack_udp_timeout_stream - INTEGER (seconds)
    default 120

    This extended timeout will be used in case there is an UDP stream
    detected.

https://www.kernel.org/doc/Documentation/networking/nf_conntrack-sysctl.txt

netstatで見てみた

googleのページに繋いでルータから接続情報を出力してみた。確かにudpで接続している。UNREPLIEDフラグはこのパケットに対する返答はまだ一度も行われていないことを示しているらしい。パケットが返ってくるとASSUREDになるらしい。どれくらいで消えるのかは自宅のルーターでは判断できなかった。(パラメータを見る限りは120秒にはなっていた)

NATed Address                            Destination Address                      State 
udp   192.168.1.121:61684                      172.217.175.67:443                       UNREPLIED 

【Linux】io.h: No such file or directory

謎な技術だったので調べた。結論としてはisattyのようなライブラリ関数を使うかfstatでstdoutのステータスを判定すれば良さそう

#include <stdio.h>

int main(int argc, char **argv)
{
    if (isatty(fileno(stdout)))
        printf( "stdin is a terminal\n" );
    else
        printf( "stdin is a file or a pipe\n");
}

みたいに書いておけばstdoutの後続にパイプがあれば出力を抑制なり整形するみたいな処理を書くことができる。

fstatならマクロを使う。

struct stat stats;
fstat(0, &stats);
if (S_ISCHR(stats.st_mode)) {
    // Looks like a tty, so we're in interactive mode.
} else if (S_ISFIFO(stats.st_mode)) {
    // Looks like a pipe, so we're in non-interactive mode.
}