2008-07-22 recvfromto, sendfromto.
仕事で使うので、NetBSD で IP_PKTINFO を使えるようにした。
そもそもこれはIPV6_PKTINFOを真似して、linuxに実装されたものなのかな。
何をするものかというと、UDP 送信時に src address を指定する方法である。
§
UDPの送受信に関しては、いろいろとめんどくさいことがよく起きる。
例えば昔のbindでよく問題になっていたのが、ある IP address に問い合わせたdns queryの返事が、
違う IP address から帰ってくる現象。
server が複数の IP address を持っている場合、普通に sendto(2) すると、
ルーティングに従って決定された出力ネットワークインターフェイスに付けられているアドレスが
src address に使われてしまうからだ。
これを回避するためには、INADDR_ANY で bind せずに、ネットワークインターフェイスに
付けられているアドレス毎にソケットを作り、bind しなければならない。
alias address が付いていた場合は、さらにそれぞれのアドレスで同じことをする。
これは、ネットワークインターフェイスやaliasが何十個もあるシステムだと、
socket を何十個も作らなければならないという悲惨なことが起きる。
さすがにこれはアホなので、BSD には IP_RECVDSTADDR というソケットオプションがあり、
INADDR_ANY で bind したソケットでも setsockopt IP_RECVDSTADDR することで、
パケット受信時に、送信先アドレスが拾えるようになる。
これには recvfrom(2) ではなく recvmsg(2) を使う。
送受信アドレスを取得できるということは、つまり recvfrom() ではなく、
recvfromto() が実現できるようになるということだ。
しかしこれでもまだ問題がある。受信時の送信先アドレスがわかったので、そのアドレスを src address にして相手に返事を返したくとも、
INADDR_ANY で bind された socket からだと送信元アドレスを選択することができないのである。
しょうがないので BSD では、返事を返す瞬間だけ socket を作って、送信元にしたいアドレスで bind して送ったりする。
やっぱりこれもアホなので、IPv6 では IPV6_PKTINFO というオプションがあり、
sendmsg(2) する時に送信元アドレスを指定できるようになっている。
つまり sendto(2) では不十分なので sendfromto() ができるように、ということだ。
これらは RFC3542 - Advanced Sockets Application Program Interface (API) for IPv6 で詳しく説明されている。
§
というわけで、冒頭のパッチは、IPv4 でも IPV6_PKTINFO のように送信元アドレスを指定できるようにするパッチである。
これで送信時も送信元アドレスが指定できるようになり、IPv4 でも sendfromto() 相当が実現できることになる。
これはLinuxには既に実装されている。が、Linux ではIPv4の受信時の送信先アドレスの取得方法がRFC2292(obsolete)
ベースの方法なのでなんだかちぐはぐである。
まとめ。
というわけで、recvfromto()、sendfromto() の実装例とサンプルプログラム。(参考: racoonのソース)
そもそもこれはIPV6_PKTINFOを真似して、linuxに実装されたものなのかな。
何をするものかというと、UDP 送信時に src address を指定する方法である。
§
UDPの送受信に関しては、いろいろとめんどくさいことがよく起きる。
例えば昔のbindでよく問題になっていたのが、ある IP address に問い合わせたdns queryの返事が、
違う IP address から帰ってくる現象。
server が複数の IP address を持っている場合、普通に sendto(2) すると、
ルーティングに従って決定された出力ネットワークインターフェイスに付けられているアドレスが
src address に使われてしまうからだ。
これを回避するためには、INADDR_ANY で bind せずに、ネットワークインターフェイスに
付けられているアドレス毎にソケットを作り、bind しなければならない。
alias address が付いていた場合は、さらにそれぞれのアドレスで同じことをする。
これは、ネットワークインターフェイスやaliasが何十個もあるシステムだと、
socket を何十個も作らなければならないという悲惨なことが起きる。
さすがにこれはアホなので、BSD には IP_RECVDSTADDR というソケットオプションがあり、
INADDR_ANY で bind したソケットでも setsockopt IP_RECVDSTADDR することで、
パケット受信時に、送信先アドレスが拾えるようになる。
これには recvfrom(2) ではなく recvmsg(2) を使う。
送受信アドレスを取得できるということは、つまり recvfrom() ではなく、
recvfromto() が実現できるようになるということだ。
しかしこれでもまだ問題がある。受信時の送信先アドレスがわかったので、そのアドレスを src address にして相手に返事を返したくとも、
INADDR_ANY で bind された socket からだと送信元アドレスを選択することができないのである。
しょうがないので BSD では、返事を返す瞬間だけ socket を作って、送信元にしたいアドレスで bind して送ったりする。
やっぱりこれもアホなので、IPv6 では IPV6_PKTINFO というオプションがあり、
sendmsg(2) する時に送信元アドレスを指定できるようになっている。
つまり sendto(2) では不十分なので sendfromto() ができるように、ということだ。
これらは RFC3542 - Advanced Sockets Application Program Interface (API) for IPv6 で詳しく説明されている。
§
というわけで、冒頭のパッチは、IPv4 でも IPV6_PKTINFO のように送信元アドレスを指定できるようにするパッチである。
これで送信時も送信元アドレスが指定できるようになり、IPv4 でも sendfromto() 相当が実現できることになる。
これはLinuxには既に実装されている。が、Linux ではIPv4の受信時の送信先アドレスの取得方法がRFC2292(obsolete)
ベースの方法なのでなんだかちぐはぐである。
まとめ。
受信時の送信先アドレス取得方法 | 送信時の送信元アドレス指定方法 | |
---|---|---|
IPv4 (*BSD) | setsockopt(IP_RECVDSTADDR) しておいてrecvmsg(2) | bind(2)のみ |
IPv4 (Linux) | setsockopt(IP_PKTINFO) しておいてrecvmsg(2) | IP_PKTINFOコントロールを指定して sendmsg(2) |
IPv6 (*BSD) | setsockopt(IPV6_RECVPKTINFO) しておいてrecvmsg(2) (RFC3542) | IPV6_PKTINFOコントロールを指定して sendmsg(2) |
IPv6 (Linux) | setsockopt(IPV6_RECVPKTINFO) しておいてrecvmsg(2) (RFC3542) | IPV6_PKTINFOコントロールを指定して sendmsg(2) |
IPv4 (NetBSD、パッチ適用後) | setsockopt(IP_RECVDSTADDR) しておいてrecvmsg(2) or setsockopt(IP_RECVPKTINFO) しておいてrecvmsg(2) or setsockopt(IP_PKTINFO) しておいてrecvmsg(2) (linux compatible API) | IP_PKTINFOコントロールを指定して sendmsg(2) |
というわけで、recvfromto()、sendfromto() の実装例とサンプルプログラム。(参考: racoonのソース)
EOF