LoginSignup
13
11

More than 5 years have passed since last update.

NTEmacs 24.x から TRAMP で多段 ssh 接続するただ一つの方法(たぶん)

Last updated at Posted at 2015-10-19

TL;DR

一段目だけ plink を使い、後は ssh すべし

経緯

業務上、踏み台経由でリモートホストのファイルを編集する必要に迫られ、Emacs なら TRAMP があるから簡単にできるだろうと思って着手したものの、案外手間取ったのでメモ。探し方が悪いのか、そのものズバリの記事を見つけられませんでした。

皆さんスイスイ接続できてるのか、はたまた Windows から多段 ssh のニーズが乏しいのか、はて。

必要なもの

以下の構成で動作確認してます。

  • plink 0.65
  • GNU Emacs 24.4.1 (x86_64-pc-mingw32) of 2014-11-10 on NTEMACS64

NTEmacs の TRAMP は PuTTY と連携するので、あらかじめ plink へのパスを通しておいてください。ssh そのものは、インストールしておかなくても大丈夫です(あっても害はありませんが)。

多段構成の想定

ここでは仮に、以下のような多段接続を想定します。

localhost(windows) -> proxy1 -> proxy2 -> target

さて、target のファイルを手元で編集するにはどうすればいいでしょうか?

ファイル名のルール(おさらい)

TRAMP 知ってる人には釈迦に説法ですが、ファイル名は以下の形式に従います。

/method:user@host#port:path

  • method: 接続方法を指定する。ftp, ssh, scp, plink, pscp, su など。
  • user: 接続ユーザーを指定する。
  • host: 接続先ホストを指定する。
  • port: 接続先ポート番号を指定する。のっぴきならない場合に。
  • path: 編集するファイル名を指定する。

接続方法やユーザーなどを省略するとデフォルト値が使われたり、入力を促されたりします。

これで、ローカルファイルを開くのと同じように、C-x C-f /ssh:foo@bar:.zshrc でリモートファイルを開くことができます。この透過性を一度でも体験すれば Emacs から離れられなくなること請け合いの魅力的な機能です。その昔は Ange-FTP で・・・なんて話はどうでもいいですね。

蛇足ながら、多段接続しない場合には、method に scp や pscp などの外部メソッドを指定するとパフォーマンスが向上します。多段接続をサポートする ssh や plink などの内部メソッドはファイル転送時にエンコード処理がはさまるため、パフォーマンス面では不利です。

多段接続の設定例

以下の設定を追加しておくことで、C-x C-f /user@target:pathtarget のファイルを開けるようになります。

~/.emacs.d/init.el
(with-eval-after-load 'tramp
  (setq tramp-default-method "ssh")
  (add-to-list 'tramp-default-proxies-alist
               '("proxy2" nil "/plink:proxy1:"))
  (add-to-list 'tramp-default-proxies-alist
               '("target" nil "/proxy2:"))
  nil)

tramp-default-method はファイル名に method が指定されなかった場合のデフォルト値になります。tramp-default-proxies-alist は、(HOST USER PROXY) のリストです。これは、USER@HOST に接続する場合は PROXY を経由しろ、という指定になります。

ポイントは、一段目にだけ /plink: を明示的に指定し、二段目以降は未指定にすることです。

わざわざ設定を書かずに多段したいものぐさな人は、C-x C-f /plink:proxy1|proxy2|user@target:path でファイルを開けば OK です。この時にも、一段目にだけ /plink を指定する必要があります。

多段接続の仕組み

TRAMP の多段接続は結局のところ、コマンドラインで

$ ssh -t user@host1 ssh -t user@host2

としているのと同じです。Windows では ssh のかわりに plink を使って

C:\>plink -t user@host1 ssh -t user@host2

とできればいいわけですね。踏み台に接続した後はそのホスト上で ssh を普通に叩きます。Windows だからと言って安直に

(setq tramp-default-method "plink")

と設定してしまうと、いざ多段接続するときに

C:\>plink -t user@host1 plink -t user@host2

と残念なことになってしまいます。plink が踏み台である host1 に存在することはまずないでしょうから、これでは繋がりません。

そこで、tramp-default-method には ssh を指定しておき、一段目の踏み台もしくは、直接 ssh する時だけ /plink: を指定すれば良いのです。

あるいは、tramp-default-methodplink にしておき、接続方法として常に /ssh: を指定する、という手もあります。この場合には直接接続時に /plink: を省略できます。

多段が多いのか、はたまた直接が多いのかによって、どちらをデフォルトにするか決めれば良いでしょう。

はまりポイント - 多段で無限ループ

tramp-default-proxies-alist で、HOSTUSERPROXY と一致する正規表現を指定した場合には無限ループします。私はこれではまりました。例えば、(nil nil "/plink:foo@bar") などは、無限ループ確定です。

その理由は、多段接続用のリストを作る tramp-compute-multi-hops を見ればわかります。

tramp-sh.elから抜粋
    ;; Look for proxy hosts to be passed.
    (setq choices tramp-default-proxies-alist)
    (while choices
      (setq item (pop choices)
            proxy (eval (nth 2 item)))
      (when (and
             ;; Host.
             (string-match (or (eval (nth 0 item)) "")
                           (or (tramp-file-name-host (car target-alist)) ""))
             ;; User.
             (string-match (or (eval (nth 1 item)) "")
                           (or (tramp-file-name-user (car target-alist)) "")))
        (if (null proxy)
            ;; No more hops needed.
            (setq choices nil)
          ;; Replace placeholders.
          (setq proxy
                (format-spec
                 proxy
                 (format-spec-make
                  ?u (or (tramp-file-name-user (car target-alist)) "")
                  ?h (or (tramp-file-name-host (car target-alist)) ""))))
          (with-parsed-tramp-file-name proxy l
            ;; Add the hop.
            (add-to-list 'target-alist l)
            ;; Start next search.
            (setq choices tramp-default-proxies-alist)))))

処理はまず、接続先リストの先頭からホストとユーザーを取り出し、それらが経由すべき踏み台の条件と一致するかを調べます。もし一致した場合には、その踏み台を接続先リストの先頭に追加し、もう一度踏み台リストの先頭から調べ直します。

つまり、(nil nil "/plink:foo@bar") のような条件はどんな PROXY とも一致するので、自分で自分にヒットしてまた自分を追加する・・・という一人上手を延々と続けることになるわけです。わーお!

考察 - 汎用的な多段接続を実現するには

無限ループに関連して、現状の TRAMP では、どう頑張ってもできない設定があります。ドメイン名を利用した、汎用的な多段接続設定です(まあ、そんなニーズはそもそもないのかもしれませんが)。仮に、以下のような多段接続が必要だとします。

localhost -> *.local.domain
localhost -> proxy@gw.local.domain -> *.hop.domain
localhost -> proxy@gw.local.domain -> proxy@gw.hop.domain -> *.step.domain
localhost -> proxy@gw.local.domain -> proxy@gw.hop.domain -> proxy@gw.step.domain -> *.jump.domain

これを、以下のように設定できれば直観的です。

(setq tramp-default-method "ssh")
(add-to-list 'tramp-default-proxies-alist
             '("local\\.domain" nil "/plink:%u@%h:"))
(add-to-list 'tramp-default-proxies-alist
             '("hop\\.domain" nil "/proxy@gw.local.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("step\\.domain" nil "/proxy@gw.hop.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("jump\\.domain" nil "/proxy@gw.step.domain:"))

しかし、これは無限ループします。なぜなら、/plink:%u@%h:*.local.domain と一致してしまうからです。せっかくなので(≒現実逃避)、どうすればこういった設定が可能になるかを考察してみます。

要件としては、一段目のルールに対して「これ以上多段生成しなくて良い」ということを指定できれば良いはずです。TRAMP では PROXYnil を指定することで多段生成を抑制することができますが、残念ながら今回のようなケースには全く無力です。例えばこう書いてみたとしても:

(add-to-list 'tramp-default-proxies-alist
             '("local\\.domain" nil "/plink:%u@%h:"))
(add-to-list 'tramp-default-proxies-alist
             '("hop\\.domain" nil "/proxy@gw.local.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("step\\.domain" nil "/proxy@gw.hop.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("jump\\.domain" nil "/proxy@gw.step.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("local\\.domain" nil nil)) ;; prevent multi-hops

*.local.domain への接続は PROXYnil を指定したルールが最初にヒットしてしまうため、常に失敗することは明らかです。

そこでシンプルに考えて、tramp-default-proxies-alist の各要素を (HOST USER PROXY NO-MORE-HOPS) という風に拡張し、NO-MORE-HOPSt なら PROXY を接続先リストの先頭に追加して多段生成ループを抜ける、というロジックを考えてみます。

    ;; Look for proxy hosts to be passed.
    (setq choices tramp-default-proxies-alist)
    (while choices
      (setq item (pop choices)
            proxy (eval (nth 2 item))
            no-more-hops (eval (nth 3 item)))
      (when (and
             ;; Host.
             (string-match (or (eval (nth 0 item)) "")
                           (or (tramp-file-name-host (car target-alist)) ""))
             ;; User.
             (string-match (or (eval (nth 1 item)) "")
                           (or (tramp-file-name-user (car target-alist)) "")))
        (if (null proxy)
            ;; No more hops needed.
            (setq choices nil)
          ;; Replace placeholders.
          (setq proxy
                (format-spec
                 proxy
                 (format-spec-make
                  ?u (or (tramp-file-name-user (car target-alist)) "")
                  ?h (or (tramp-file-name-host (car target-alist)) ""))))
          (with-parsed-tramp-file-name proxy l
            ;; Add the hop.
            (add-to-list 'target-alist l)
            ;; Start next search or break if no more hops needed
            (setq choices (if no-more-hops nil
                            tramp-default-proxies-alist))))))

これで設定は以下のように書けます。

(add-to-list 'tramp-default-proxies-alist
             '("local\\.domain" nil "/plink:%u@%h:" t)) ;; prevent multi-hops any more
(add-to-list 'tramp-default-proxies-alist
             '("hop\\.domain" nil "/proxy@gw.local.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("step\\.domain" nil "/proxy@gw.hop.domain:"))
(add-to-list 'tramp-default-proxies-alist
             '("jump\\.domain" nil "/proxy@gw.step.domain:"))

既存の挙動と互換性を維持したまま、機能拡張できそうです。しかも、一段目に接続する時も /plink: を指定する必要がなくなり、利便性が向上しています。

でも、tramp-default-proxies-alist をこんなふうに拡張してしまうのは、バッドノウハウを増やすだけのような罪悪感があり、気が引けます。パッチを送るべきかどうか、悩むところです。

まとめ

NTEmacs で多段 ssh 接続する方法をまとめました。無限ループにはまって、ずいぶん寄り道してしまいましたが、モヤモヤを解消できてスッキリしました。Emacs との腐れ縁はまだまだ続きそうです。

参考

13
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11