UNIX Domain Socket の SO_SNDBUF, SO_RCVBUF についての覚書

macOSLinuxUNIX Domain Socket の SO_SNDBUF, SO_RCVBUF について調べていた。

経緯

会社で @kurotaky のトラブルシューティングの相談を受けた際の覚書。

下記のように UNIX Domain Socket を挟んで IPC しているコードがあり、macOSLinux で挙動が違っているのを調べていた。Linux では Go Ethereum が送ってくるデータを全部読み出せるが、macOS だと一部 ( 8192 バイト 注1 ) しか読み出せないとのことだった。

Ruby のコード <----- UNIX Domain Socket -----> Go Ethereum

色々調べてみたところ、macOS / LinuxUNIX Domain Socket のデフォルトの SO_SNDBUF, SO_RCVBUF (注2) が違うことで、Ruby 側で 1回の recvmsg(2) で取り出せるデータのサイズが異なっているのが問題だったようだ。

UNIX Domain Socket の SO_SNDBUF / SO_RCVBUF のサイズをとる

socket の SO_SNDBUF をとるには getsockopt(2) を呼び出す

man7.org

Ruby でかくとこんなんかな?

#!/usr/bin/env ruby

require 'socket'

sender, receiver = UNIXSocket.pair

# あるいは SO_RCVBUF
opt = receiver.getsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF)
p opt

SO_SNDBUF のサイズ: macOS の場合

手元の macOS のバージョンは以下の通り

$ uname -a
Darwin PM-* 21.4.0 Darwin Kernel Version 21.4.0: Mon Feb 21 20:35:58 PST 2022; root:xnu-8020.101.4~2/RELEASE_ARM64_T6000 arm64

検証コードを実行すると 8192 だった

$ ./test.rb
#<Socket::Option: UNIX SOCKET SNDBUF 8192>

macOS では、sysctl net.local.stream.sendspace で SO_SNDBUF のデフォルトのサイズを変更することができた

$ sudo sysctl -w net.local.stream.sendspace=16384
net.local.stream.sendspace: 8192 -> 16384

sysctl を変更後は 16384 になる

$ ruby test.rb
#<Socket::Option: UNIX SOCKET SNDBUF 16384>

Linux の場合

multipass で起動した Linux 5.4 系で試す

$ uname -a
Linux primary 5.4.0-105-generic #119-Ubuntu SMP Mon Mar 7 18:50:13 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux

Linux では 212992 だった

$ ruby test.rb
#<Socket::Option: UNIX SOCKET SNDBUF 212992>

Linux では sysctl net.core.wmem_default でデフォルトのサイズを変えることができる

$ sysctl net.core.wmem_default
net.core.wmem_default = 212992

$ sudo sysctl -w net.core.wmem_default=8192

変更すると 8192 になった

$ ruby test.rb
#<Socket::Option: UNIX SOCKET SNDBUF 8192>

UNIX Domain Socket と SO_SNDBUF と送信でのブロック

UNIX Domain Socket で、SO_SNDBUF を超えるサイズのデータを送ろうとするとブロックする (注意: ソケットがブロッキングモードの場合) 。読み手がバッファのデータを読み出さない限りブロックし続ける

#!/usr/bin/env ruby

require 'socket'

sender, receiver = UNIXSocket.pair
warn "try to sendmsg morethan 8192 bytes" 

sender.sendmsg "@" * 8192

# ここでブロックする
warn "try to sendmsg 1 bytes" 
sender.sendmsg "@" * 1

warn "done sendmsg"

検証コードを実行した結果

$ ruby ~/hoge.rb
try to sendmsg morethan 8192 bytes

感想

しばらくシステムプログラミングのレイヤから遠ざかっていたので、リハリビになった。ブロックする挙動のこともすっかり忘れていた

macOSカーネルnet.local.stream.sendspace を参照しているあたりのコードを調べたかったが、どこ調べたらいいかわからん。↓ ではないのかなぁ

github.com

Linux だと net/unix/af_unix.c を読むといい

参照

wiki.geant.org

geth.ethereum.org

同僚の 🕶 が書いたエントリで net.core.wmem_default, net.core.rmem_default の存在を思い出します

ryuichi1208.hateblo.jp

net.ipv4.tcp_rmem と net.ipv4.tcp_wmem については以前調べた拙著のエントリがあります

tech.pepabo.com

追記

  • 注1 8192 バイトであることはエントリを公開後 追記した
  • 注2 UNIX Domain Socket では SO_SNDBUF ソケットオプションは UNIX ドメインソケットで効果を持つが、 SO_RCVBUF は効果がない。 と man に書いてある
  • ブロックを検証するコードが間違っていたので修正した

関連のエントリ

hiboma.hatenadiary.jp