【2023年10月版】socat による tcp/udp ポート転送 【PROXY】

# はじめに

わたし > 「 nginx の stream で tcp/udp のポート転送してるけど、1コマンドでできないもんかな。。。 」
AIさん > 「 つ socat 」

# 環境

Ubuntu22.04で使用。macOSもWindowsもあるらしい。
DMZのセグメントをA、内部のセグメントをBとする。
セグメントA、セグメントBにアクセスできるサーバを転送サーバとする。

```plantuml
@startuml
left to right direction
rectangle "セグメントA\n172.16.0.0/24" as segment_a {
  node "クライアント　\n172.16.0.100" as client
  interface "172.16.0.5" as socat_server_interface_a
}
rectangle "セグメントB\n10.0.0.0/24" as segment_b {
  node "転送サーバ\n10.0.0.10" as socat_server
  node "サービスサーバ\n10.0.0.11:80" as content_server
}
client --- socat_server_interface_a
socat_server_interface_a --- socat_server
socat_server --- content_server
@enduml
```

# 使い方

## インストール

```terminal
sudo apt update
sudo apt install socat
```
## 基本的な使い方

- 転送サーバの ポート 8080 で待ち受けして、別ホスト(コンテンツサーバ)の 80 へ転送する場合
- クライアントからは、172.16.0.5:8080 でサービスが提供されているように見える

```plantuml
@startuml
left to right direction
rectangle "セグメントA\n172.16.0.0/24" as segment_a {
  node "クライアント　\n172.16.0.100" as client
  interface "172.16.0.5:8080" as socat_server_interface_a
}
rectangle "セグメントB\n10.0.0.0/24" as segment_b {
  node "転送サーバ\n10.0.0.10" as socat_server
  node "コンテンツサーバ\n10.0.0.11:80" as content_server
}
client --- socat_server_interface_a
socat_server_interface_a --- socat_server
socat_server --- content_server
@enduml
```

```terminal
socat TCP-LISTEN:8080,bind=172.16.0.5,reuseaddr,fork TCP:10.0.0.11:80
```

## Wireguard等の UDPの転送が可能

- nginx だと、streamによる転送は、windows版は実験段階だそうで、udp の転送はできない。
- 以下は、wireguard(port 51820)の転送例。
- クライアントからは、172.16.0.5:51820 でサービスが提供されているように見える

```plantuml
@startuml
left to right direction
rectangle "セグメントA\n172.16.0.0/24" as segment_a {
  node "クライアント　\n172.16.0.100" as client
  interface "172.16.0.5:51820" as socat_server_interface_a
}
rectangle "セグメントB\n10.0.0.0/24" as segment_b {
  node "転送サーバ\n10.0.0.10" as socat_server
  node "Wireguardサーバ\n10.0.0.11:51820" as content_server
}
client --- socat_server_interface_a
socat_server_interface_a --- socat_server
socat_server --- content_server
@enduml
```

```terminal
socat UDP-RECVFROM:51820,bind=172.16.0.5,reuseaddr,fork UDP-SENDTO:10.0.0.11:51820
```

## SSH等の TCPの転送が可能

- 以下は、ssh(port 22)の転送例。
- クライアントからは、172.16.0.5:22 でサービスが提供されているように見える

```plantuml
@startuml
left to right direction
rectangle "セグメントA\n172.16.0.0/24" as segment_a {
  node "クライアント　\n172.16.0.100" as client
  interface "172.16.0.5:22" as socat_server_interface_a
}
rectangle "セグメントB\n10.0.0.0/24" as segment_b {
  node "転送サーバ\n10.0.0.10" as socat_server
  node "SSHサーバ\n10.0.0.11:22" as content_server
}
client --- socat_server_interface_a
socat_server_interface_a --- socat_server
socat_server --- content_server
@enduml
```

```terminal
socat TCP-LISTEN:22,bind=172.16.0.5,reuseaddr,fork TCP:10.0.0.11:22
```

## RemoteDesktop等の Windows の TCPの転送が可能

- 以下は、RDP(port 3389)の転送例。
- クライアントからは、172.16.0.5:13389 でサービスが提供されているように見える

```plantuml
@startuml
left to right direction
rectangle "セグメントA\n172.16.0.0/24" as segment_a {
  node "クライアント　\n172.16.0.100" as client
  interface "172.16.0.5:13389" as socat_server_interface_a
}
rectangle "セグメントB\n10.0.0.0/24" as segment_b {
  node "転送サーバ\n10.0.0.10" as socat_server
  node "RDPサーバ\n10.0.0.11:3389" as content_server
}
client --- socat_server_interface_a
socat_server_interface_a --- socat_server
socat_server --- content_server
@enduml
```

```terminal
socat TCP-LISTEN:13389,bind=172.16.0.5,reuseaddr,fork TCP:10.0.0.11:3389
```

## オプション

### リスニングと接続のタイプのオプション
- TCP-LISTEN: TCPプロトコルでポートをリスニングします。
- UDP-RECVFROM: UDPプロトコルでポートをリスニングし、受信したデータを転送します。
- TCP: リモートのTCPアドレスとポートに接続します。
- UDP: リモートのUDPアドレスとポートに接続し、データを送信します。

### その他のオプション
- bind=<address>: ローカルアドレスを指定してソケットをバインドします。
- reuseaddr: ソケットを再利用可能な状態にします。これにより、同じアドレスとポートを迅速に再バインドできます。
- fork: それぞれの接続ごとに新しいプロセスを生成します。これにより、複数のクライアントが同時に接続できるようになります。

### データの操作オプション
- EXEC:<command>: 指定されたコマンドを実行し、その標準入出力を使用します。
- SYSTEM:<command>: 指定されたコマンドをシェル経由で実行し、その標準入出力を使用します。

### デバッグとロギング
- -d: デバッグモードを有効にします。数字を増やすことでデバッグの詳細レベルを上げることができます（例: -ddd）。
- -v, -vv, -vvv: ログの詳細レベルを設定します。-v が最も低く、-vvv が最も高い詳細レベルです。

### タイムアウト関連オプション
- timeout=<seconds>: このオプションは、指定された秒数だけアイドル状態が続いたら接続をクローズします。
- connect-timeout=<seconds>: TCP接続を確立する際のタイムアウト時間を指定します。指定された時間内に接続が確立されない場合、操作は失敗します。

### リミット関連のオプション
- range=<start>-<end>: 接続が可能なアドレスの範囲を指定します。
- send-buffer=<size>: ソケットの送信バッファのサイズを指定します。
- recv-buffer=<size>: ソケットの受信バッファのサイズを指定します。

### ファイル記述子とプロセス制御のオプション
- setuid=<user>: 指定したユーザーIDにプロセスの所有権を設定します。
- setgid=<group>: 指定したグループIDにプロセスの所有権を設定します。

# サービス化

- サービスファイルを記述して登録するだけ
- 以下は、HTTP(port 80)の転送例
- クライアントからは、172.16.0.5:80 でサービスが提供されているように見える

## サービスファイルの記述

```/etc/systemd/system/socat-relay.service
[Unit]
Description=Socat Relay Service
After=network.target

[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:80,bind=172.16.0.5,reuseaddr,fork TCP:10.0.0.11:80
Restart=always
User=nobody
Group=nogroup
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
```

## サービスの登録

```terminal
sudo systemctl daemon-reload
sudo systemctl start socat-relay.service
sudo systemctl enable socat-relay.service
# sudo systemctl enable --now socat-relay.service でも可能
```

# さいごに

かんたんでしたね