Cloudflare Tunnel で利用するエフェメラルポート範囲を Windows Server で設定する

# Cloudflare Tunnel のユースケース
Cloudflare Zero Trust では Cloudflare Tunnel を使って社内ネットワークを接続する等のユースケースを実現することができます。

https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/private-net/cloudflared/

>プライベートネットワークには、サーバーとクライアントという2つの主要コンポーネントがあります。サーバーのインフラストラクチャ（単一のアプリケーション、複数のアプリケーション、ネットワークセグメントのいずれでも）はCloudflare TunnelによってCloudflareのグローバルネットワークに接続されます。これはサーバー上でcloudflaredデーモンを実行することで行われる。
>
>クライアント側では、エンドユーザーはCloudflare WARPクライアントを使用してCloudflareのグローバルネットワークに接続します。WARPクライアントは、社内のMDMツールを使用してわずか数分で組織全体にロールアウトできます。ユーザーがCloudflare Tunnelを通じて利用可能になったIPに接続すると、WARPはCloudflareのネットワークを通じて対応するトンネルに接続を送信します。

>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/15e86864-f49b-10d7-c4b7-f5292ecac836.png)

# Cloudflare Tunnel システム要件
システム要件としては、以下のように各ホストあたり 50000 ポートの割り当てにより、ホストあたり4,000人程度の処理を担えるとしています。

https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/deploy-tunnels/system-requirements/

> Cloudflare Tunnelのスループットは主にシステムソフトウェアで設定されたポート数によって制限されます。したがって、cloudflaredサーバーのサイズを決める際に最も重要な要素は、TCPとUDPトラフィックの予想スループットを反映するようにマシンで利用可能なポートのサイズを決めることです。
>...
>ほとんどのユースケースでは、以下のベースライン構成を推奨する：
ネットワークロケーションごとに2台の専用ホストマシンでcloudflaredレプリカを実行する。2台のホストを使用することで、サーバー側の冗長性とトラフィックバランシングが可能になります。
各ホストには最低4GBのRAMと4つのCPUコアを搭載します。
各ホスト上のcloudflaredプロセスに50,000ポートを割り当てる。
この設定は通常、8,000人のWARPユーザー（ホストあたり4,000人）のトラフィックを処理するのに十分です。


# Windows Server におけるエフェメラルポート

以下の既定範囲が割り当てられています。

https://learn.microsoft.com/ja-jp/troubleshoot/windows-server/networking/default-dynamic-port-range-tcpip-chang

>インターネット割り当て番号機関 (IANA) の推奨事項に準拠するために、Microsoft は Windows Vista と Windows Server 2008 の送信接続の動的クライアント ポート範囲を増やしました。 新しい既定の開始ポートは 49152 で、新しい既定のエンド ポートは 65535 です。 これは、既定のポート範囲 1025 から 5000 を使用した以前のバージョンの Windows の構成からの変更です。

```bat:cmd
> netsh int ipv4 show dynamicport tcp

Protocol tcp Dynamic Port Range
---------------------------------
Start Port      : 49152
Number of Ports : 16384
```

# ポート枯渇問題
同時セッション数が増え、エフェメラルポート範囲からの割り当てが不足すると、接続が失敗します。

https://learn.microsoft.com/ja-jp/troubleshoot/windows-client/networking/tcp-ip-port-exhaustion-troubleshooting

> 同じブラウザーが複数の Web サイトへの多数の接続を作成しているシナリオでは、ブラウザーが試行している新しい接続に対して、エフェメラル ポートが使用されます。 しばらくすると、接続が失敗し始め、このエラーの可能性が高くなる可能性が 1 つあります。これは、ブラウザーが使用可能なすべてのポートを使用して外部に接続を行い、使用可能なポートがなくなったため、接続を確立するための新しい試行が失敗するためです。 マシン上のすべてのポートが使用されると、ポート枯渇と呼ばれます。

# エフェメラルポートの確認と変更

以下の記事が参考になります。

https://qiita.com/yasushi-jp/items/29bba4503c33a8c8df02#32-windows%E3%81%A7%E3%81%AE%E3%82%A8%E3%83%95%E3%82%A7%E3%83%A1%E3%83%A9%E3%83%AB%E3%83%9D%E3%83%BC%E3%83%88%E3%81%AE%E5%A4%89%E6%9B%B4

>IPv4のエフェメラルポートを変更すると、同時にIPv6のエフェメラルポートも変更されるようです。

```bat:cmd
netsh int ipv4 show dynamicport tcp
netsh int ipv4 show dynamicport udp
netsh int ipv6 show dynamicport tcp
netsh int ipv6 show dynamicport udp
```

https://learn.microsoft.com/ja-jp/troubleshoot/windows-server/networking/default-dynamic-port-range-tcpip-chang

>設定できるポートの最小範囲は 255 です。 設定できる最小開始ポートは 1025 です。 最大エンド ポート (構成されている範囲に基づく) は、65535 を超えることはできません。 Windows Server 2003 の既定の動作を複製するには、開始ポートとして 1025 を使用し、TCP と UDP の両方の範囲として 3976 を使用します。 これにより、開始ポートは 1025、終了ポートは 5000 になります。

```bat:cmd
netsh int ipv4 set dynamicport tcp start=60000 num=255
netsh int ipv4 set dynamicport udp start=60000 num=255
netsh int ipv6 set dynamicport tcp start=60000 num=255
netsh int ipv6 set dynamicport udp start=60000 num=255
```

# Cloudflare Tunnel 用のエフェメラルポート範囲設定
以下のように各ホストごとに 50000 ポート分を割り当てるようにします。

```bat:cmd
netsh int ipv4 set dynamicport tcp start=11000 num=50000
netsh int ipv4 set dynamicport udp start=11000 num=50000
netsh int ipv6 set dynamicport tcp start=11000 num=50000
netsh int ipv6 set dynamicport udp start=11000 num=50000
```

# 参考：ポート枯渇の再現
以下のエフェメラルポート範囲設定とした上で、ポート枯渇の状態を再現します。

```bat:cmd
netsh int ipv4 set dynamicport tcp start=60000 num=255
netsh int ipv4 set dynamicport udp start=60000 num=255
netsh int ipv6 set dynamicport tcp start=60000 num=255
netsh int ipv6 set dynamicport udp start=60000 num=255
```

## TCPView による初期状態確認
TCPView を使って GUI でポートの使用と解放が確認できます。

https://learn.microsoft.com/ja-jp/sysinternals/downloads/tcpview

>TCPView は、ローカルおよびリモート アドレスや TCP 接続の状態など、システム上のすべての TCP および UDP エンドポイントの詳細な一覧を表示する Windows プログラムです。 TCPView では、エンドポイントを所有するプロセスの名前も報告されます。 TCPView には、Windows に付属する Netstat プログラムのより有益で便利なサブセットが用意されています。 TCPView のダウンロードには、同じ機能を持つコマンド ライン バージョンである Tcpvcon が含まれています。

Cloudflare Tunnel サーバーに特に通信が来ていない場合では、以下のような状態です。

1秒ごとにポート状態を自動更新する設定にできます。

`TCP Listen 127.0.0.1` となっているポートでは、メトリクスの取得が可能で後ほど使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/138060c4-6499-3404-e02f-dfebbe974ffa.png)

## ポート枯渇テスト
PowerShell 7 で導入された `ForEach-Object -Parallel` を使って同時並行で TCP 接続を行います。

https://learn.microsoft.com/ja-jp/powershell/scripting/whats-new/migrating-from-windows-powershell-51-to-powershell-7?view=powershell-7.4

>クラウド、オンプレミス、およびハイブリッド環境向けに設計された PowerShell 7 には、強化された機能と新機能が組み込まれています。
> - Windows PowerShell とのサイド バイ サイドのインストールおよび実行
> - 既存の Windows PowerShell モジュールとの強化された互換性
> - 三項演算子や `ForEach-Object -Parallel` などの新しい言語機能
> ...

以下のコマンドでは、WARP クライアントから `cloudflared` を経由して到達可能な　RFC 1918 IP レンジ `10.200.0.0/20` に対して同時並行で TCP 接続を行います。

```powershell:Powershell 7 (x64)
0..15 | ForEach-Object -Parallel { 
    $item = $_
    1..255 | ForEach-Object -Parallel { 
        Invoke-WebRequest -Uri "https://10.200.$using:item.$_" -TimeoutSec 60;
        sleep 1; 
    } -ThrottleLimit 255
} -ThrottleLimit 2
```

## TCPView によるポート枯渇状態確認
今回は `60000` 〜 `60254` までの割り当てとしているため、 `60254` 以上は割り当てられないことがわかります。

また、ポート使用としては（`Local Address`、`Local Port`、`Remote Address`、`Remote Port`）の組み合わせで消費されることがわかります。

> TCPトラフィックはほぼ瞬時にポートを使用、解放する。つまり、50,000の利用可能なポートを持つcloudflaredインスタンスに過負荷をかけるには、1秒間に50,001のTCPリクエストを継続的に生成する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/4839f459-4356-4e44-453f-a1cd4fea2fa8.png)

## netstat によるポート枯渇状態確認

`netstat -anob` コマンドでも、ポート枯渇状態のスナップショットを確認できます。

```bat:cmd
  TCP    10.146.0.10:60006      10.200.2.44:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60007      10.200.3.40:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60008      10.200.3.39:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60009      10.200.2.45:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60010      10.200.3.41:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60011      10.200.2.46:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60012      10.200.2.48:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60013      10.200.2.47:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60014      10.200.3.42:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60015      10.200.2.50:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60016      10.200.2.49:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60017      10.200.2.52:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60018      10.200.2.51:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60019      10.200.2.53:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60020      10.200.3.43:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60021      10.200.3.44:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60170      10.200.3.1:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60171      10.200.2.2:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60172      10.200.2.1:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60173      10.200.3.2:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60174      10.200.3.3:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60175      10.200.2.4:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60176      10.200.2.3:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60177      10.200.3.4:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60178      10.200.2.5:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60179      10.200.3.5:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60180      10.200.2.6:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60181      10.200.3.6:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60182      10.200.2.7:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60183      10.200.2.9:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60184      10.200.2.8:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60186      10.200.3.7:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60187      10.200.3.9:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60188      10.200.3.8:443         SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60189      10.200.2.10:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60190      10.200.2.11:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60191      10.200.2.12:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60192      10.200.3.10:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60193      10.200.2.13:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60194      10.200.3.11:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60195      10.200.3.12:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60196      10.200.3.13:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60197      10.200.2.14:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60198      10.200.3.14:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60199      10.200.2.15:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60200      10.200.3.16:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60201      10.200.3.15:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60202      10.200.2.16:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60203      10.200.3.17:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60204      10.200.2.17:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60205      10.200.2.18:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60206      10.200.3.19:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60207      10.200.2.19:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60208      10.200.3.18:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60209      10.200.2.20:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60210      10.200.2.21:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60211      10.200.3.21:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60212      10.200.3.20:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60213      10.200.2.22:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60215      10.200.2.23:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60216      10.200.3.22:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60217      10.200.2.24:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60218      10.200.2.25:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60219      10.200.3.23:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60220      10.200.3.24:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60221      10.200.2.26:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60222      10.200.2.27:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60223      10.200.3.25:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60224      10.200.2.28:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60225      10.200.3.26:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60226      10.200.2.30:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60227      10.200.2.29:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60228      10.200.3.28:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60229      10.200.2.31:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60230      10.200.3.29:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60231      10.200.3.27:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60232      10.200.3.30:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60233      10.200.2.33:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60234      10.200.2.32:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60235      10.200.2.34:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60236      10.200.3.32:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60237      10.200.3.31:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60240      10.200.3.33:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60241      10.200.2.36:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60242      10.200.2.35:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60243      10.200.3.34:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60244      10.200.2.38:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60245      10.200.2.37:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60246      10.200.2.39:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60247      10.200.2.40:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60248      10.200.3.36:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60249      10.200.3.35:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60250      10.200.2.41:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60251      10.200.2.42:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60252      10.200.3.37:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60253      10.200.3.38:443        SYN_SENT        6864
 [cloudflared.exe]
  TCP    10.146.0.10:60254      10.200.2.43:443        SYN_SENT        6864
 [cloudflared.exe]
```

## `cloudflared` ログ
以下を参考に、より詳細なデバッグログを入手することができますが、
ポート枯渇時には、`cloudflared` のログとしては残らないようです。

https://qiita.com/khayama/items/84ab5685ab8462222034

クライアントのブラウザから見ると以下のように `ERR_CONNECTION_RESET` エラーとなることがあるため、参考にしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/4863e392-0d00-baa7-1dfa-8ad002f7bc32.png)

## `cloudflared` メトリクス
Prometheus と Grafana にも Windows 版があるので、導入してメトリクスを確認します。

https://developers.cloudflare.com/cloudflare-one/tutorials/grafana/#create-the-metrics-endpoint

### Prometheus
- Prometheus：http://localhost:9090

`global` の `scrape_interval` を `1s` に変更して、`cloudflared` のメトリクスエンドポイントを追加します。

```diff_yaml:prometheus.yml
global
-  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
+  scrape_interval: 1s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
...
+    - job_name: "cloudflared"
+      static_configs:
+        - targets: ["127.0.0.1:60110"] ## cloudflared server IP and the --metrics port configured for the tunnel
```

```bat:cmd
"C:\Program Files (x86)\prometheus\prometheus.exe" ^
--config.file="C:\Program Files (x86)\prometheus\prometheus.yml"
```

### Grafana

- Grafana：http://localhost:3000

`Data source` の `Scrape Interval` を `1s` にしておきます。

| メトリクス | 説明 |
|:-:|:-:|
| `cloudflared_tcp_active_sessions` | 任意のオリジンに対してプロキシされているTCPセッションの同時数 |
| `cloudflared_tunnel_concurrent_requests_per_tunnel` | 各トンネルを通してプロキシされるリクエストの同時数 |

以下のグラフは、上記 PowerShell 実行時のメトリクス推移になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/670aca3b-b9cd-cdd7-50f0-5b01075b0bbb.png)

以下のようにエフェメラルポート範囲を増やすと、その分 `cloudflared` で扱える同時セッション数も増える様子が確認できます。

```bat:cmd
netsh int ipv4 set dynamicport tcp start=60000 num=355
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101361/5a1fe4ad-6956-a2ab-9ff6-f56bfa5621c3.png)


# まとめ

Cloudflare Tunnel で利用するエフェメラルポート範囲は `netsh int ipv4/ipv6 set dynamicport tcp/udp` の設定により制御できることがわかりました。

Windows Server の初期設定では、ポート数の割り当てが `16384` となっているため、Cloudflare Tunnel 導入の際にはポート数の割り当てを `50000` まで拡張することで、一定のキャパシティを捌くことができます。

同時セッションの集中によりポート枯渇の可能性がある場合には、エフェメラルポート範囲を設定するとともに、Cloudflare Tunnel をホストするサーバーの台数を水平スケールする等の検討が必要です。

https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/deploy-tunnels/deploy-cloudflared-replicas/