Linuxネットワークプログラミングの勉強をきちんとしましょうということなんだけど、とりあえず調べたことをメモっておく。
Haskell に mkSocket
という関数がある。
https://hackage.haskell.org/package/network-3.1.2.0/docs/Network-Socket.html#v:mkSocket
今は引数がファイルディスクリプタ CInt
だけとなっているが、 2.x
系の頃の引数はもっと複雑だった。
https://hackage.haskell.org/package/network-2.2.3/docs/Network-Socket.html#v:mkSocket
この中に Family という引数があって、これは AF_INET
とか AF_INET6
、 AF_UNIX
と言ったようなソケットの種類を表す値なんだけど、 Server::Starter のようにファイルディスクリプタが渡されている場合にどうやってこれらの値を決めるかが今回の疑問点。
実際、拙作の hs-server-starter では、 Server::Starter
から渡された環境変数の値 0.0.0.0:80=3
というような値を雑にパースしてこれらの値を推測していたのだけど、 IPv6 アドレスの判定が雑過ぎて完全にバグっていた。
じゃあ、どうやって直せばいいかってところなんだけど、 Starlet のコードを見ると、渡ってきたファイルディスクリプタはすべて IPv4 であると見なしてコードが組まれているように見えた。これに従えば、 AF_INET
決め打ちにしても差し支えないように思える。
https://metacpan.org/source/KAZUHO/Starlet-0.31/lib/Plack/Handler/Starlet.pm#L26-30
現に、以下のように改修をしても、テストやサンプルアプリは問題なく動いた(もちろん、 network-2.x
系を使って試している)。
https://github.com/hiratara/hs-server-starter/pull/10/files
じゃあ、この family
は何に使っているのかということで、例えば以下にある getSocketName
を見てみる。
https://hackage.haskell.org/package/network-2.2.3/docs/src/Network-Socket.html#getSocketName
withNewSockAddr
を見ると、 sizeOfSockAddrByFamily
でサイズを求めるのに使っている。直前の定義を見ると AF_INET = 16
AF_INET6 = 28
AF_UNIX = 110
などと family
ごとにサイズが違っている。
そして、このサイズを元に struct sockaddr
を確保して c_getsockname
を呼ぶことになる。この getsockname
を調べると、
https://man7.org/linux/man-pages//man2/getsockname.2.html
The returned address is truncated if the buffer provided is too small;
つまり、渡した struct sockaddr
のサイズが足りなくても、渡した文のサイズで切られて返されるだけということである。そういう意味では AF_INET
は一番サイズが短いので、 AF_UNIX
などを採用しておいたほうが害が少ないのかもしれない。
family
はこのようにシステムコールを呼ぶのに必要な struct sockaddr
のサイズを決めるためにしか使われておらず、実際はシステムコールによって返された struct sockaddr
を以下の関数 peekSockAddr
で解釈して使うことになる。 struct sockaddr
は family
によって形式が違うが、先頭は sa_family_t
となっているので、これを見ることで family
を判別できる。
https://hackage.haskell.org/package/network-2.2.3/docs/src/Network-Socket-Internal.html#peekSockAddr
ちなみに、 network-3.x
系では family
などを渡さなくて良くなっているが、それは十分に大きい struct sockaddr
を確保しているからである。
https://hackage.haskell.org/package/network-3.1.2.0/docs/src/Network.Socket.Types.html
-- sizeof(struct sockaddr_storage) which has enough space to contain -- sockaddr_in, sockaddr_in6 and sockaddr_un. sockaddrStorageLen :: Int sockaddrStorageLen = 128