最初にことわっておくと、Linux には SO_REUSEPORT はない(追記: Kernel 3.9 で追加された)。Windows (Winsock) にもないし、Linux に近い環境を構成する Cygwin にもない。(Solaris はどうだったかな…) 今現在簡単に入手できる OS のうち SO_REUSEPORT があるのは、大まかに BSD に分類される OS、つまり {Free,Open,Net}BSD の系統と Mac OS X だけである。

BSD の場合

BSD においては、AF_INET / AF_INET6 なソケットに対する SO_REUSEADDR と SO_REUSEPORT は以下の意味になる。(AF_UNIX などその他のアドレスファミリに関しては割愛)

SO_REUSEADDR
  • connected socket が残っている状態で、そのソケットの local 側のアドレスポートへの bind を許す。TCP サーバにおいて特に重要。
  • ポートが同じでアドレスが異なる bind を許す。
  • マルチキャストアドレスに bind する場合、アドレスもポートも同一な bind (これを完全重複 bind という)を許す。ただし、先に bind した方も後に bind する方も SO_REUSEADDR を付けておく必要がある。
SO_REUSEPORT
SO_REUSEADDR の効果に加え、アドレスの種類によらず、アドレスもポートも同一な bind を許す。ただし、先に bind した方も後に bind する方も SO_REUSEPORT を付けておく必要がある。

どちらの場合であっても、マルチキャストアドレスに bind するのでない限り、後に bind する方は root であるか、先に bind した方と同じ UID (EUID) である必要がある。なお、マルチキャストアドレスに bind する場合、一方が SO_REUSEADDR で他方が SO_REUSEPORT だと、どちらが先かで挙動が異なるようである(ので、統一するか両方付けるべき)。

歴史的に言うと、SO_REUSEPORT は BSD でマルチキャストをサポートする際に複数のプログラムが同一のマルチキャストアドレスとポートを listen するために導入された。その時点では SO_REUSEADDR の3つ目の挙動は存在しなかったのであろう。ところが、前述のとおり現在は SO_REUSEADDR でマルチキャストアドレスへの完全重複 bind ができるようになってしまっている(何故そうなったのかは調べていない)ため、その目的でどうしても SO_REUSEPORT が必要になることは(たぶん)ない。SO_REUSEPORT が必要になるのは、マルチキャストアドレス以外(ワイルドカードやユニキャストアドレス)で完全重複 bind するときである。(たまに必要になる)

Linux の場合

では SO_REUSEPORT の無い世界ではどういうことになるかというと、SO_REUSEADDR の挙動を変更することで対処したようである。つまり、BSD の場合の SO_REUSEADDR の3番目のところが、アドレスの種類によらなくなる。BSD はこの挙動の変更は insecure だと考えて SO_REUSEPORT を導入したのではないかと勝手に思っている。

ついでに言うと、Linux の場合は UID による制限も存在しないようなので、乗っ取り放題である。いいのだろうかそんなんで… (他の OS でどうなっているかは未調査)

結局のところどうすべきか

移植性を考える&できるだけ読みづらくしない のであれば、恐らく以下のようにすべきだろう。

まず完全重複 bind の必要が無いケースで SO_REUSEADDR が必要な場合(こちらは自明)

    const int on = 1;
    fd = socket(...);
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    bind(fd, ...);

完全重複 bind が必要な場合

    const int on = 1;
    fd = socket(...);
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
#ifdef SO_REUSEPORT
    setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
#endif
    bind(fd, ...);

両方付けるのがポイント。

なお、bind した「後」に受信パケットがどう吸い込まれるかに関しては、また別の話であり調査していない。今回自分が困った事例に関して言うと、完全重複 bind した後に即座に connect するので、どう吸い込まれるかは自明なのである。(気が向いたら調べるかも)

参考文献: 「詳解 TCP/IP Vol.2 実装」 (”TCP/IP Illustrated” の和訳本)

Trackback

only 1 comment untill now

  1. Linux Kernel 3.9 の SO_REUSEPORT

    Linux Kernel 3.9 には SO_REUSEPORT が追加されているそうです。SO_REUSEPORT でググると日本語のページの中で一番上に出てくる当blog(2013-06-02現在)としては調査しないわけにはいきません Linux の …

Add your comment now