本記事は前回の DockerNATが一部のポートを潰しちゃう話 を読んでることを前提に書かれています。 まだの方は先にそちらを読んでからこちらをどうぞ。
TL;DR
- Windows には「ポート除外範囲」(
excludedportrange
)という指定した範囲のポートの利用を禁ずる機能がある - 実は「ポート除外範囲」には2種類ある
- エフェメラルポートの動的割当から除外する
- 特定プログラムが予約し、そのプログラム以外からの利用を禁ずる
- 2種類とも同じコマンドで確認できるのでややこしい
-
Hyper-Vをインストールする前に「ポート除外範囲」を追加するのが良い
コマンド例:
> netsh int ipv4 add excludedportrange protocol=tcp startport=2222 numberofports=1
経緯
前回の記事 を公開したところ、 ある方から以下のような情報をいただきました。
理由は知りませんが、Hyper-Vが大量のTCPポート範囲を予約するようです。CreatePersistentTcpPortReservationとかexcludedportrangeといったキーワードで調べてみてください。
— nu774 (@nu774_) October 19, 2019
まず excludedportrange
を手がかりに調べたところ TL;DR に書いた
「ポート除外範囲」なるものに行き着きました。
実験してみると DockerNAT のオン・オフに伴い、
問題にしていた 2222 番を含む範囲がポート除外範囲に追加されたり削除されたのです。
確認のためのコマンドは次の通りです。
netsh int ipv4 show excludedportrange protocol=tcp
excludedportrange
という名前から
エフェメラルポートの動的割当から除外するものだろう
という予想は容易だったのですが、
それが LISTEN すらも妨げるのはどういうことだろうと
それでもまだ疑問は残ります。
そこで次に PersistentTcpPortReservation
というものを
主にAPI方面から調べ、判明したことは以下の通りです。
CreatePersistentTcpPortReservation
でポートの範囲を指定して予約すると、トークン (64ビットの非負整数値) が得られる- この範囲のポートを普通に使おうとするとエラーになる
- ソケットに対して
WSAIoctl()
を用いて 1 で得たトークンを与えると使えるようになる - 指定した範囲は
netsh int ipv4 show excludedportrange
で表示される
これは PersistentTcpPortReservation
の単純な和訳である
「永続化TCPポート予約」に違わない動作であり、
エフェメラルポートから除外されるのも一定の理解ができます。
しかしそれがどちらも同じコマンドで確認できるのはやや面食らいました。
twitter である方から同じ意見をいただいてます。
excludedportrange のコマンドで両方確認できてしまうのでややこしいですが、動的割り当ての除外設定とポートの予約は別物です。
— Tamrin (@Tamrin007) October 19, 2019
なお netsh int ipv4 show excludedportrange protocol=tcp
の出力上、この2つは微妙に差別化されています。
> netsh int ipv4 show excludedportrange protocol=tcp
プロトコル tcp ポート除外範囲
開始ポート 終了ポート
---------- --------
80 80
1628 1727
1802 1901
1902 2001
2222 2222 *
2223 2322
4688 4787
5357 5357
50000 50059 *
* - 管理されている除外ポート。
最後に * - 管理されている除外ポート。
とある通り
*
が付いている方が「ポート除外範囲」で
それ以外は「永続化TCPポート予約」で確保されたものです。
完璧な理解まで、最後の一歩
こうして DockerNAT のオン・オフにより Docker Desktop for Windows と VirtualBox の排他的な共存(?) を自宅PCにて実現し、 さらにその背景にある動作原理を理解したわけですが 後日十分ではなかったことが判明します。 というのはオフィスのPCで同様の環境を構築しようとしたところ DockerNAT をオフにしても 2222 番を含む範囲が解放されず VirtualBox から利用できないという事態に陥りました。
そこで思い出されたのが先程のツイートに追いツイートされてたこの情報です。
なお特定のポートがHyper-Vによって予約されるのを回避するには、Hyper-Vを無効化した状態でnetsh int ip add excludedportrangeで使いたいポートの範囲を予約しておきHyper-Vを再有効化する、という手法をとると良いようです。
— nu774 (@nu774_) October 19, 2019
Docker Desktop for Windows だけではなく Hyper-V も「永続化TCPポート予約」してたんですね。 しかも予約する範囲はそれぞれ固定ではない、と推測されます。
つまり自宅PCとオフィスPCではHyper-VとDockerをインストールする際に 微妙に手順が違ったと推測されます。 おそらく自宅環境は以下の手順を踏んだと推測されます。
- VirtualBoxのVMを起動する
- Hyper-Vを有効化する
- 再起動する
- Docker Desktop for Windowsをインストールする
ステップ2でHyper-VはVirtualBoxのVMが利用している分を避けて 永続化TCPポート予約しました。 そのためVirtualBox VMが使ってる2222を含むポートは影響を受けずに済みます。 つぎにステップ4でDocker Desktop for Windows=DockerNAT がポートを予約する際はステップ3で再起動を挟んでおり、 かつHyper-Vが有効であるためVMが起動できず 2222を含んだ範囲が予約されてしまいました。
これによりDockerNATだけをオフにすれば2222が利用できる環境が 偶然できあがってしまったのです。
一方でオフィス環境においては以下の通り VMを起動せずにインストールしたため、 2222を含むVMで必要なポートをHyper-Vが予約した状態になっていました。
- Hyper-Vを有効化する
- 再起動する
- Docker Desktop for Windowsをインストールする
ここまでわかれば正しいHyper-V及び Docker Desktop for Windowsのインストール方法が 以下の通りであるとわかります。
- VM含め自分が使う可能性のあるポートを予め「ポート除外範囲」に追加する
- Hyper-Vを有効化する
- 再起動する
- Docker Desktop for Windowsをインストールする
ポート除外範囲に追加する方法は 「管理者として実行」したコンソールで以下のコマンドを実行します。
netsh int ipv4 add excludedportrange protocol=tcp startport=2222 numberofports=1
startport
と numberofports
の値は自身の用途に合わせて調整してください。
以上、これでHyper-VとVirtualBoxを 自信を持って切り替えながら利用できる環境が作れるようになりました。 機会があればぜひお試しを。