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