まずは公式ドキュメントをご覧ください。
IPv6 is only supported on Docker daemons running on Linux hosts.
残念!
Docker Desktop for Macなどでローカル開発をしているときに、ローカルで立ち上げたプロセスからDocker内にあるコンテナに通信したいことは割りとよくあるユースケースだと思う。
こういうときは、基本的には宛先をIPv4のLoopback Addressである127.0.0.1
に向けてあげて、 IPv6を使わないようにしてあげるとよい。
localhost
を使ってしまうと、名前解決でIPv6のLoopback Addressに名前解決されるケースがあり、そうなればIPv6に対して接続しようとしてしかしIPv4でしかlisten(2)
されていないのでコケる。
しかし、世の中には親切(?)にも127.0.0.1
をlocalhost
に正規化するようなコードが存在する。*1
困る。
ローカルの問題なので/etc/hosts
を手で書き換えてlocalhost
からIPv6に解決されないようにすればOK!という感じではあるのだが、たとえばチームで開発する基盤としてのローカル開発環境だとしてそれをチームメンバーに強いるにはトラブルの元になりそうでちょっともにょる。
なにより、プロジェクトローカルな事情で環境の設定を変えてしまうのは目的に対してtoo muchな策であり、副作用が大きくあんまり良い解決策とはいえないだろう。
では、nginxのTCP Reverse Proxy Server機能をつかってIPv6で待ち受けたやつをIPv4にProxyしてあげればよかろう。 と思いきや、nginxはDockerで建てるには楽だがローカルで建てるにはだるい!
ということで、Perlさえあればどこでも動く、簡易なTCP Reverse Proxy Serverを書きました。 PerlはだいたいのOSに最初から入ってるのでお手軽に使えるし、コピーしてプロジェクトのリポジトリに同梱しちゃえば各環境でのインストールも不要。お手軽!
簡易、というのは、素直なTCP Reverse Proxyとは異なりTCPパケットレベルでProxyしているわけではなくて、いったんTCPのペイロードを受け取ってからそれをまたTCPで流すということをやっているため。
たとえば、ACKが返ってきたからといって、それがReverse Proxyされた先でもACKされているとは限らないので、厳密にはL4 Proxyと言ってはいけない代物ではある。が、おそらく実用上はそんなに問題はないでしょう。というか、SOCK_RAWで頑張るには目的に対して大変すぎる……。
ニッチなケースで役に立つと思うのでお試しください。
裏話
もともと、これをIO::Socket::IPで書いて、IO::Socket::INETと違ってIO::Socket::IPではIPv4もIPv6も使えて便利!というLTをShonan.pm #1でやったのですが、実際にはIO::Socket::IPでpeeraddrやsockaddrなどをうまく取得できず(undefになる)、あきらめて標準関数とHash::Util::FieldHashを使って実装しなおしたという微妙な経緯があります。(そんなわけで。Shonan.pm #1のLT資料は動くでしょうって前提の間違いが微妙に散りばめられており、公開しておりません。あしからず。)
FAQ(追記)
ncでいいのでは?
2つの課題があって、ひとつはncではこれと同等のことを実現することは実はあんまり楽ではないこと。
たとえば、proxy modeを使うにしても送信側のproxy対応が必要になって(アプリケーションが小さければ楽かもしれないが少なくとも自分の関わっていたプロダクトでは対応箇所がどうしても多くなってしまって)面倒で、具体的にはhttpではなくgrpcなやつとかもいたりするから単にHTTP_PROXY環境変数を入れておしまいとはならないからsocks proxyを使わねばならず、まあ面倒。
nc -l
とかを繋げたワンライナーっぽいので素朴にやることも考えられるが、複数のリクエストをそれも並行して扱えないとあんまり意味がない場面(まあまああると思う)ではまともにつかえないのでこれもやはり無し。
もうひとつは移植性の問題があるのでスクリプトみたいな形にまとめて使いづらいこと。
*1:厳密にはそうではないかもしれないが、外形的な挙動としてはそのような挙動に見えるケースに当たったことがある。