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