Zenn
🐸

sshの既存の接続を外からコントロールしてみよう

2025/02/14に公開

sshの既存の接続を外からコントロールしてみよう

今回の話の前提として SSH Multiplexing がそもそもどんな機能なのか知らないと面白くないので軽くおさらいしておく。

SSH Multiplexing

OpenSSH には既存のSSHセッションに相乗りすることで、同じホストに対して新たにTCP接続したり認証を行うことなく新規SSHセッションを開始することが出来る機能があります。この機能は man では multiplexing という単語で説明されてますが、一般には SSH Connection Sharing とか、日本語だと「SSH接続共有」や「SSH多重化」などと呼ばれたりします。

この機能を使うと追加の新規接続が爆速になりとても便利なので自分は以下のような設定を行っています。

~/.ssh/config
# 何度も接続する場合、既存接続が再利用されてSSHの開始が高速化
ControlMaster auto
ControlPath ~/.ssh/mux-%C
ControlPersist 30m

各設定を簡単に説明するとこんな感じ

  • ControlMaster 接続共有の親になるかどうか(autoだと既存接続があれば活用し無ければ自分が親になる) ((ほげ))
  • ControlPath 接続共有のためのUNIXソケットのパス指定(%Cは接続先毎にユニークなハッシュ値)
  • ControlPersist 親の接続が終了した後も多重化専用セッションとして裏でプロセスを生かしたままにするか

通常はこの設定をしておくだけで十分な恩恵が得られると思う。

-O オプションを試してみよう

既存のSSHセッションを外部から制御するために、ssh コマンドには -O オプションが用意されています。このオプションを使うと、既に確立されている接続に対して様々な操作を行うことができます。

基本的な使い方

-O オプションの基本的な構文は以下の通り。

# ssh_config の ControlPath 設定で一意に決まるなら -S 指定は不要
ssh -O command remote

# -S 指定を使う場合、接続先はソケットに対して一意になるのでホスト名は不要なので空文字でOK(sshコマンドはホスト名が必須引数なのでとりあえず空文字を渡してるが引数と解釈されない値なら何でも良い)
ssh -O command -S path/to/control_sock ''

ここで使える command には以下のものがあります:

  • check - マスタープロセスが動いているかチェック
  • forward - ポートフォワーディングの追加
  • cancel - ポートフォワーディングの削除
  • exit - マスタープロセスの終了要求
  • stop - 新規の多重化要求の受付を停止
  • proxy - 既存の接続を使って新しいセッションを確立

実際の使用例

接続状態の確認

# マスタープロセスが動いているか確認
ssh -O check remote

ポートフォワーディングの動的な追加と削除

# 8080ポートへのフォワーディングを追加
ssh -O forward -L 8080:localhost:80 remote
# DynamicForward(SOCKS5プロキシ)を追加する
ssh -O forward -D 9494 remote

# 追加したフォワーディングを削除
ssh -O cancel -L 8080:localhost:80 remote
ssh -O cancel -D 9494 remote

ちなみに、既存のSSHセッションに対して ssh -O forward -g -L 8081:localhost:80 remote のように外部からの接続許可を追加したりすることはできない。可能なのは追加/削除のみでフォワードに関する設定だとしてもその他の設定についてはマスターセッションの確立後に変えることは出来ない。つまり、最初に -g 付きで起動していた場合は追加のポートフォワードも全てローカル以外から接続可能になるということ。

新しいセッションの確立

# 既存の接続を使って新しいセッションを開く
ssh -O proxy remote

ssh -O proxy remote は既にマスターセッションが存在する場合の ssh remote と実質的にはほぼ同じことです。

# TCP_NODELAYに関する警告表示を抑制する
ssh -O proxy -o 'NoDelay=no' remote

proxyコマンドを使用する場合、TCP_NODELAYに関する警告が表示されることがあります。これはproxyではUNIXソケットが使われるのでTCP_NODELAYは当然利用できない事が理由です。本来は OpenSSH 側で -O proxy の時はTCP_NODELAYを試さないよう修正されるべき問題と思います。ですが利用者としては修正を期待するより目の前の邪魔な表示を消したいので -O proxy の時は常に -o 'NoDelay=no' も付けることで警告表示が抑制できます。

接続の終了

# マスタープロセスを終了
ssh -O exit remote

便利な小技

ダミーホスト名の利用

ControlPathがわかっている場合、実際のホスト名は不要。でもsshコマンドの使用としてホスト名は必須なので空文字を渡す。

ssh -O proxy -S /path/to/control_sock ''

ControlPathの特定

ssh [args...] remote に対して利用されるControlPathは ssh -G コマンドで確認できる。

ssh -G [args...] remote | grep '^controlpath '

デフォルトの設定を確認

ホスト名などを指定しないデフォルトとして実際に適用される設定を確認する際も空のホスト名を指定する方法が使える。

ssh -G ''

活用シーン

この機能を知ってると色んな活用方法が思いつく。

~/.ssh/mux-XXX の接続先がどこなのか確認するなど

普段から ControlMaster auto をデフォルトにして接続共有を活用してると ControlPath ~/.ssh/mux-%C の設定で作られたコントロールソケットが沢山作成される。特にそれを気にする事はないと思うけど、たまにこのソケットってどこ宛の接続なんだろ?と気になることがある。

# コントロールソケットの先のリモートサーバで hostname コマンドを実行する
ssh -O proxy -S ~/.ssh/mux-XXXXX '' hostname

そんなときは、ソケットファイルを指定して -O proxy を実行してやればリモートのホスト名が分からなくても、無認証でSSH接続が出来るので接続した先で状況確認すればそれがどのホストでどんなユーザ名で接続してるかとかの情報が確認できる。

自動化スクリプトでの動的なポートフォワーディングを実現する

いい感じのを作ろうと思えば作れそう。少なくとも手段は提供されてるから自分次第。

ポートフォワード用のコントロールソケットが生きてるか -O check して、無ければバックグラウンドで起動しつつ、必要なタイミングで -O forward でポート転送を足して使わなくなったら即座に -O cancel で閉じるとか。

ControlPersistの自動切断をより柔軟に管理する

ControlPersist yes で無期限にコントロールソケットを維持するのは精神衛生上よろしくない。

ControlPersist 30m としておくことで少しマシになる。

でも ControlPersist 30m だとSSHセッションが終了してから30分という意味なので、ずっとPC使ってるけどSSH以外の作業をしてるだけだと切れちゃってポート転送が使えなくなって面倒とか、逆に離席するためにPCロックした場合は早くても切断しちゃって良いかもしれない。

コントロールソケットを使って外部から制御出来るとOSの各種イベントのタイミングとかでより柔軟に制御できて嬉しいかもしれない。

Discussion

ログインするとコメントできます