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です。
0 件のコメント:
コメントを投稿