SO_REUSEPORT 完全に理解した(い)

こんにちは!

今回は、弊研にて雑談中に SO_REUSEPORT の話題になった際に「どのようにソケットを選んで振り分けているの?」と聞かれて気になったので調査してみました(わかったとは言っていない)。

SO_REUSEPORT とは

SO_REUSEPORT は、複数のスレッド、プロセスから同じポートを bind() できるようにするためのソケットオプションです。Linuxでは、Kernel 3.9 からサポートされています。

SO_REUSEPORT を用いて複数のスレッド・プロセスで同じポートで待ち受けることにより、TCPでコネクションを張る際の accept() 部分を負荷分散できるようになります。

使い方

SO_REUSEPORT で検索をするとCのサンプルコードなど多数ヒットするため割愛します。

端的に言えば、ソケットを bind() するまえに、setsockopt()SO_REUSEPORT を設定すればよいです。

ちなみにRustで SO_REUSEPORT を利用したい場合は、libcnix でも可能ですが、socket2 の利用が一番簡単でおすすめです。

どのように調査したか

Linux Kernel のソースコードを読みました。

はじめに Interactive map of Linux kernel で読み始める箇所にあたりをつけました。今回はL4LBであるため protocols のレイヤー(一番右の列の中段あたりにあります)に絞ったのち、勘に頼ってそれらしいところを探していきました。

しばらくコードの森に迷い込んだのち、 tcp_v4_rcv()lookup: でソケットバッファのコピー先になるソケットを見つけていることがわかりました。

コールスタックをたどっていくと、lookup_reuseport() という関数が見つかります。下記に lookup_reuseport() の一部を抜粋します。

	if (sk->sk_reuseport) {
		phash = inet_ehashfn(net, daddr, hnum, saddr, sport);
		reuse_sk = reuseport_select_sock(sk, phash, skb, doff);
	}

inet_ehashfn()では、ハードウェア乱数生成器を用いて生成される乱数、送り元のアドレス、送り元のポート番号、送り先のアドレス、送り先のポート番号の5つの値からハッシュ値を計算しています。

reuseport_select_sock() では求まったハッシュ値から1つソケットを選択しています。

結論

わかったこと

Linuxの SO_REUSEPORT では、概ね乱数を用いてランダムに割り当てるソケットを選択していました。

わからなかったこと

「わかったこと」で概ねと述べたように、常にランダムにソケットを選択しているわけではありませんでした。compute_score() で謎のスコア計算をして、乱数生成によるソケットを選択をするか否かを判断しています。しかし、自分には何をしているのかサッパリわかりませんでした……(´・ω・`)

Leave a Reply