Linuxのネットワークパラメータの一つに、net.core.somaxconnというのがあります。これはlisten(2)の第二引数backlogの上限値となっています。このsomaxconnは一見intに見えますが、実はunsigned shortの範囲の数値しか受け付けません。それを超える数値を入れると黙って切り捨てられます。つまり
- -1→65535
- 0→0
- 65535→65535
- 65536→0
- 65537→1
と同じような動作を内部的にします。なので、この値は絶対に0~65535の範囲を超えてはいけません。以下、詳しい説明です。
おことわり: この仕様はLinux 3.11以降変更されており、範囲外の数値を設定できないようになっています。ここに書いてある内容が再現するのは、Linux 3.10以前の古いカーネルのみです。
まずsysctlの定義ですが
static struct ctl_table netns_core_table[] = { { .procname = "somaxconn", .data = &init_net.core.sysctl_somaxconn, .maxlen = sizeof(int), .mode = 0644, .proc_handler = proc_dointvec },
struct netns_core { /* core sysctls */ struct ctl_table_header *sysctl_hdr; int sysctl_somaxconn; struct prot_inuse __percpu *inuse; };
以上のようにしっかりと"int"と書かれています。
さて、この値がどう使われているかというと、listen(2)でbacklogの最大値として利用された後inet_listenに引き渡されます。
int inet_listen(struct socket *sock, int backlog) { struct sock *sk = sock->sk; unsigned char old_state; int err; (途中省略) sk->sk_max_ack_backlog = backlog; err = 0; out: release_sock(sk); return err; }
という感じで、socket.sk_max_ack_backlogに渡されています。この値の宣言を見ると
struct sock { (途中省略) unsigned short sk_ack_backlog; unsigned short sk_max_ack_backlog;
unsigned shortとなっています。intをunsigned shortにそのまま代入してますね。なので、一番上に書いたようにオーバーフローします。黙ってオーバーフローします。net.core.somaxconnの値は正しくセットされるのが余計にたちが悪いですね。
ところでbacklog=0ってなんでしょうか?全くacceptできないように思えますが、実はキューの長さは
static inline bool sk_acceptq_is_full(const struct sock *sk) { return sk->sk_ack_backlog > sk->sk_max_ack_backlog; }
という感じで不等号で比較されているので、最低一つはaccept待ちのソケットが使用できます。
これはTCPのコードですが、unix domain socketでも似たような事象が起こります。
あと何年かすれば役に立たなくなる感じの知識ではありますが、今のところsomaxconnは16bitです。