【SSH多重化&ポートフォワード最強まとめ】既に接続したSSHにポートフォワードを追加する方法は？

# 前提条件: ControlMaster の設定
`.ssh/config` に以下を書いておく (参考: [ssh config最強設定](https://qiita.com/keiya/items/dec9a1142ac701b19bd9))

```
##### コントロールマスター接続で永続化するかどうか #####
# 何度も接続したり、複数コネクションを張る場合、既存接続が再利用され、
# SSHの開始が高速になります
# スリープの多用や、ノートPCなどネットワークの場所移動が多い場合は ControlPersist を10秒(10)〜数分(1m)単位の短めにしてください
ControlMaster auto
ControlPersist 10 # モバイル用
# ControlPersist 1h # スリープしない有線接続の据え置き機であれば、1h〜2hなど大きな値でも可能
ControlPath ~/.ssh/%C
```

これを書くことで、 establishしたssh接続に対して `ctl_cmd` が送れるようになります。

# ctl_cmd を送ってみる
## ControlMaster に慣れる
まずは普通に接続します（裏側で、ControlMasterを使った接続になっています）

```
ssh [ホスト名]
```

接続されたら、configの `ControlPath` で設定したように `~/.ssh/` 内になんらかのソケットファイルが作られているはずです。

ssh がつながったらすぐに切断し、

```
exit
```

すぐに(10秒以内)に**同じ**ホストに再接続してみてください。

```
ssh [ホスト名]
```

**前回のsshより接続が早くないですか？**これは `ControlPersist 10` と設定したので、10秒間は裏側でsshが接続されたままになっているからです。時間を伸ばして `1h` などにすると1時間つながったままになります。こうすると、githubなどが裏側でつながったままになり、ssh接続のネゴシエーションのオーバーヘッドがなく高速にpushできます。

## 各種ctl_cmd
[ssh の man](https://linux.die.net/man/1/ssh) にはこう書いてあります。

```
     -O ctl_cmd
             Control an active connection multiplexing master process.  When the -O option is
             specified, the ctl_cmd argument is interpreted and passed to the master process.
             Valid commands are: ``check'' (check that the master process is running), ``forward''
             (request forwardings without command execution), ``cancel'' (cancel forwardings),
             ``exit'' (request the master to exit), and ``stop'' (request the master to stop
             accepting further multiplexing requests).
```

つまり、ControlMasterを設定すると
- `ssh -O check [ホスト名]`
- `ssh -O forward [ホスト名]`
- `ssh -O exit [ホスト名]`
- `ssh -O stop [ホスト名]`
という ctl_cmd が使えるようになるのです。例えば、


### check - ControlMasterのチェック
[ホスト名]に接続された状態で、checkを実行すると
（sshを接続した**同じ**ホスト名でやってみてください。）

```
$ ssh -O check [ホスト名]

Master running (pid=10415)
```

このように表示されるはずです。これ以外の表示だとつながっていないかもしれません。

### exit - ControlMasterの切断
exit を実行すると、切断されます。 `ControlPersist` で指定した時間を待つ前に一旦接続を切りたいときはこれを実行します。
ControlMasterを運用していると、ログアウトしても裏側ではつながっているので、切断したい時はこのコマンドが必要になります。

```
$ ssh -O exit devv

Exit request sent.
```

### forward - 既に接続したSSHにポートフォワードを追加
タイトルの通り、「既に接続したSSHにポートフォワードを追加」することができます。ポートフォワードは接続先のホストにトンネルを掘ることができ、ファイアウォールで直接80番に繋げなくてもSSH越しに80番に繋ぐことができるものです。詳しくは説明しませんが、便利なのでぜひ知っておくべきです。

#### `-L` - LocalForward
例えば、接続先ホストの80番をローカル(このコマンドを叩いているPC)の8080に紐付けるコマンドは以下になります。これをすると、`[ホスト名]` に既につながっている状態でも、ポートフォワードを追加できます。

```
ssh -O forward -L8080:localhost:80 [ホスト名]
```

このコマンドを実行したあと、ブラウザに `http://localhost:8080` をすると接続先の `80` 番につながることができます。

`-L`、ローカルフォワードは基本的な静的ポートフォワーディングで、あらかじめ決められたポートをあらかじめ決められたホストの特定のポートにフォワードします。

#### `-D` - DynamicForward: SOCKSプロキシの生成

一方、 `-D`、ダイナミックフォワードをすると SOCKS プロキシになります：

```
ssh -O forward -D11111 [ホスト名]
```

上記を実行し、Firefoxのプロキシ設定や、OSのプロキシ設定でこれにつなげてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19148/45c98272-0b1c-2ed3-44a9-0ba13f65f817.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19148/423f1a6a-0e48-458e-8985-51ffd959694e.png)

すると、**SSH接続先を経由してインターネットに出ることができます**。IPアドレスが確認できるサイトなどを使って確認してみてください。このテクニックは、監視されているPCからSSHトンネルを経由することで、監視されていないSSHサーバーから監視を逃れてインターネットを使うことや、気晴らしに別のIPアドレスを使いたい時などに応用できます。

SOCKSに対応したアプリケーションなら、例えばメールソフトなども使うことができると思います。

#### `-R` - RemoteForward

実は`-R`もあります。これはリモートフォワードやリバーストンネルというもので、例えばポートを開けられない会社や学校の制限されたネットワークから、あらかじめ自分の持っているVPSへ「リバーストンネル」を作るSSH接続をしておくと、VPSから会社や学校の端末に**逆に**アクセスできる代物です。

会社や学校のPCの電源を入れたまま家に帰っておけば、家から会社や学校のネットワークにアクセスすることすらできます。会社や学校のファイアウォールは外部からの接続を弾いているのにもかかわらず、です。もちろんファイアウォールを破ったわけではなく、最初に内部から外部へトンネルを掘っているからできることです。

そんなに使う機会はないと思いますが、知っておくと何かと役に立つのでリバーストンネルの作り方はググってください。

# 「最初から」ポートフォワードをするには？
## コマンドライン
上記は既に接続されたSSHに対し、後からポートフォワードをする方法でした。**最初からポートフォワードをした状態でSSH接続するには以下のようにします**。

```
ssh -L8080:localhost:80 [ホスト名]
ssh -D11111 [ホスト名]
# など
```

`-O` の部分が消えただけです。

## 長いコマンドを入力するのが嫌な場合
最初から `~/.ssh/config` に以下のように書きます。複数書けます。

```
Host dev
  HostName dev.example.com
  User angelus

  #LocalForward 3000 localhost:3000
  LocalForward 8080 localhost:80
  #LocalForward 1080 localhost:1080
  DynamicForward 11111
```

こうすると、

```
ssh dev
```

とするだけで、 `dev.example.com` にトンネルを張った状態でつながります。

[最強のconfigの書き方はこちらで詳しく説明しています](https://qiita.com/keiya/items/dec9a1142ac701b19bd9)。

## localhost の部分を変える
上記でやたら出てきた「localhost」とはなんでしょうか。それはトンネルをSSHサーバー「そのもの」の特定のポートに繋げるという意味です。この部分を、SSHサーバーが属する同じネットワークの他のIPアドレスにすることもできます。

例えば、SSHサーバーが動いているネットワークの `192.168.0.1` につなぎたいとしましょう。

`ssh -L8080:192.168.0.1:80 [ホスト名]`

こうすると、手元のPCの 8080 が　`192.168.0.1`　の 80 番につながっているということになります。応用すると、遠く離れた場所からルータの設定を変えたりということができるはずです。リバーストンネルでも同様です。

`192.168.0.2` がWindowsで、リモートデスクトップが動いている場合、こうしてリモートデスクトップを繋ぐこともできます。


```
ssh -L13389:192.168.0.2:3389 [ホスト名]
```

## トンネルを掘った端末以外でポートを使いたい
ここでさらに欲望が生まれてきました。手元のPCでSSHトンネルを掘ったはいいですが、そのPC以外でトンネルに接続したい場合はどうしますか？

```
ssh -g -L13389:192.168.0.2:3389 [ホスト名]
```

`-g` オプションを使います。こうすると、SSHトンネルを作った手元のPC(1)の 13389 が同一ローカルネットワーク上からアクセスできるようになります。「手元のPC(2)」からSSH接続先の `192.168.0.2` の 3389 につなげられるわけです。
(手元のPC(1)では、外部接続可能なようにファイアウォールの設定をしてください)

# 奥が深いSSHの世界
`ControlMaster` は接続開始が速くなるだけでなく、コントロール用のソケットを介してこうしたコマンドが送れて便利なほか、複数接続をまとめるマルチプレクサでもあるので、NATのテーブルの消費を節約する効果もあるでしょう。ぜひ設定しておくことをお勧めします。

トンネルを経由してエントロピーの低いデータ(HTTP, FTP)を送るならSSHの圧縮もONにしておきましょう (`-C` または `Compression yes`)。ただし、トンネルでエントロピーの高い暗号化済みデータ(HTTPS, FTPS)などを送る場合はOFFにした方がいい場合もあります。私は開発用のRails Serverにつなげていて、平文なので圧縮は有効にしています。

暗号化は多くの場合AESかChaCha20-Poly1305が良いでしょう。

そのほかにもいろいろなオプションがあります。[sshのman](https://linux.die.net/man/1/ssh)はもちろん、[KMCの解説](https://www.kmc.gr.jp/advent-calendar/ssh/2013/12/09/tunnel2.html)などもみてみてください。

私のssh-configに関する記事も以下にリンクしておくのでみてください。

https://qiita.com/keiya/items/dec9a1142ac701b19bd9