ある朝,「出先のノートPCから家の母艦PCを操作したい(=sshで潜りたい)」とふと思いたった.
固定グローバルIPを持ちかつsshで潜れる機器が家のネットワークにあれば,以下のようにローカルポートをフォワードしてやれば良いが,あいにく家にはそんな高いものは存在しない.(DDNS?知らんな)
# (pc1):ノートPC/(pc2):母艦PC/(pc3):固定グローバルIPを持つPC
(pc1)$ ssh -L (ローカルポート):(pc2):22 (username)@(pc3)
# ex. ssh -L 22222:192.168.0.2:22 hogehoge@110.110.110.220
そこでなんとなく契約していたVPSを中継サーバにして,ノートPC/母艦PCのsshトンネルを結ぶことにした.
# (pc1):ノートPC/(pc2):母艦PC/(pc3):中継サーバ
(pc3:port20022)--<ssh tunnel>--(pc2:port22) (母艦-中継サーバのsshトンネル)
(pc1:port20022)--<ssh tunnel>--(pc3:port20022) (ノート-中継サーバのsshトンネル)
# トンネルを作った上で...
(pc1) $ ssh -p 20022 (username)@localhost
前提
ネットワーク前提を整理すると以下のようになる.pc[123]は前述の構成図参照.
- pc1(ノートPC)/pc2(母艦PC)はprivateアドレスのみ持ち,かつ所属するネットワーク内のグローバルIP持ちの機器にsshで潜れない.
- pc3(中継サーバ)はpc1/pc2にsshで潜れない. (潜れるならばローカルポートフォワードでOKなので)
- pc1/pc2はpc3にsshで潜れる.
母艦-中継サーバのsshトンネル
このトンネルでは,中継サーバの20022番ポートを母艦の22番ポート(sshのデフォルトポート)にフォワードする.
方向が(sshサーバ)->(sshクライアント)のため,リモートポートフォワードを使用する.
(pc2) $ ssh -R 20022:localhost:22 (username)@(pc3)
ノート-中継サーバのsshトンネル
このトンネルでは,ノートの20022番ポートを中継サーバの20022番ポートにフォワードする.
方向が(sshクライアント)->(sshサーバ)のため,ローカルポートフォワードを使用する.
(pc1) $ ssh -L 20022:(pc3):20022 (username)@(pc3)
ノートからのssh接続 with GatewayPortsの変更
準備完了.で,ノートPCの20022番ポートにアクセスで一件落着…と思いきや...
(pc1) $ ssh -p 20022 (username)@localhost
ssh_exchange_identification: Connection closed by remote host
この原因は中継サーバ上でlistenしているポートを確認することで判明する.
中継サーバ上で,20022番(母艦の22番ポートにフォワードされている)はループバックアドレス(localhost)にバインドされているため,
(中継サーバ):20022にフォワードしても上手く接続できない.
(pc3) $ netstat -lt | grep 20022
tcp 0 0 localhost:20022 *:* LISTEN
tcp 0 0 localhost:20022 *:* LISTEN
これを解決するためには,sshdのGatewayPortsの設定を変える必要がある.
GatewayPorts=no(デフォルト値)では,リモートポートフォワード時のポートが常にループバックアドレスにバインドされるため,
GatewayPorts=yesと設定して,任意のアドレスにバインドするように変更する.
(pc3) $ sudo vi /etc/ssh/sshd_config
…
GatewayPorts yes
…
(pc3) $ sudo /etc/init.d/sshd restart
sshd を停止中: [ OK ]
sshd を起動中: [ OK ]
(pc2) $ ssh -R 20022:localhost:22 (username)@(pc3)
# *(任意のアドレス)にバインドされていることを確認
(pc3) $ netstat -lt | grep 20022
tcp 0 0 *:20022 *:* LISTEN
これでようやくノートから母艦へsshで潜ることができる.
(pc1) $ ssh -p 20022 (username)@localhost
Password:
あるいはGatewayPorts=clientspecified として接続時にバインドするアドレスを指定してもよい.
(pc3) $ sudo vi /etc/ssh/sshd_config
…
GatewayPorts clientspecified
…
(pc3) $ sudo /etc/init.d/sshd restart
sshd を停止中: [ OK ]
sshd を起動中: [ OK ]
# 20022番ポートの指定の前に,バインドするIP or FQDNを指定する.
(pc2) $ ssh -R (pc3):20022:localhost:22 (username)@(pc3)
# リモートポートフォワード時に指定したアドレスにバインドされていることを確認
(pc3) $ netstat -lt | grep 20022
tcp 0 0 (pc3):20022 *:* LISTEN
これでもさきほどと同様,sshで潜れるようになる.
(pc1) $ ssh -p 20022 (username)@localhost
Password:
ノートからのssh接続 with GatewayPortsデフォルトのまま
ここまでで要求を満たせたため,ほっとしてコーヒーを飲んでいたところ,「GatewayPortsのデフォルト設定(no)でできるのに,わざわざ設定変更する男の人って…」という恒例の声が横から聞こえてきた.
どうも「うわ…私の○○…」よろしく,口元に手をあてての発言らしい.(注: この記事を書いている時点で,我が家に人間は一人のみ)
今後の力関係のためにも,デフォルト設定のまま要求を満たす方法を模索していたところ,何とか見つけた.
GatewayPorts=noだと,リモートポートフォワード時にループバックアドレスにバインドされてしまうならば,ループバックアドレスを指定してローカルポートをフォワードしてやればよいだけだった.
(pc2) $ ssh -R 20022:localhost:22 (username)@(pc3) # pc3(中継サーバ)上ではlocalhost:20022がlistenになる
(pc1) $ ssh -L 20022:localhost:20022 (username)@(pc3) # 20022:(pc3):20022ではなく20022:localhost:20022
(pc1) $ ssh -p 20022 (username)@localhost
Password:
ポイントはlocalhostがsshコマンドを実行している端末を表しているわけではないということ.
これを押さえないと混乱する.
# 下記のlocalhostはpc1(ssh実行端末)を表すわけではない.
# pc1上の20022番ポートを,pc3(中継サーバ)上でlistenしている""localhost"":20022にフォワードする.
(pc1) $ ssh -L 20022:localhost:20022 (username)@(pc3)
参考
(ref1). http://www.slideshare.net/tohakushi/ssh-13118950
(ref2). $ man sshd_config