2014年1月28日火曜日

somaxconnの上限値は65535

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の定義ですが

net/core/sysctl_net_core.c

static struct ctl_table netns_core_table[] = {
 {
  .procname = "somaxconn",
  .data  = &init_net.core.sysctl_somaxconn,
  .maxlen  = sizeof(int),
  .mode  = 0644,
  .proc_handler = proc_dointvec
 },

include/net/netns/core.h

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に引き渡されます。

net/ipv4/af_inet.c

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に渡されています。この値の宣言を見ると

include/net/sock.h

struct sock {
(途中省略)
        unsigned short          sk_ack_backlog;
        unsigned short          sk_max_ack_backlog;

unsigned shortとなっています。intをunsigned shortにそのまま代入してますね。なので、一番上に書いたようにオーバーフローします。黙ってオーバーフローします。net.core.somaxconnの値は正しくセットされるのが余計にたちが悪いですね。

ところでbacklog=0ってなんでしょうか?全くacceptできないように思えますが、実はキューの長さは

include/net/sock.h

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 件のコメント:

コメントを投稿