Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

Haskellの古いmkSocketの引数のfamily

Linuxネットワークプログラミングの勉強をきちんとしましょうということなんだけど、とりあえず調べたことをメモっておく。

HaskellmkSocket という関数がある。

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_INET6AF_UNIX と言ったようなソケットの種類を表す値なんだけど、 Server::Starter のようにファイルディスクリプタが渡されている場合にどうやってこれらの値を決めるかが今回の疑問点。

実際、拙作の hs-server-starter では、 Server::Starter から渡された環境変数の値 0.0.0.0:80=3 というような値を雑にパースしてこれらの値を推測していたのだけど、 IPv6 アドレスの判定が雑過ぎて完全にバグっていた。

https://github.com/hiratara/hs-server-starter/blob/f47d198aab92135e6546191f82f44bec60ccaf5f/src/Network/ServerStarter/Socket.hs#L83-L101

じゃあ、どうやって直せばいいかってところなんだけど、 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 ごとにサイズが違っている。

https://hackage.haskell.org/package/network-2.2.3/docs/src/Network-Socket-Internal.html#withNewSockAddr

そして、このサイズを元に 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 sockaddrfamily によって形式が違うが、先頭は 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