EmacsのTRAMPで多段SSH越しにファイルを開いた話

きっかけ 作業機Aで書いたEmacsの設定はそのままに、B経由でCにあるファイルを直接開きたくなった。CにEmacsを立てるほどでもないし、B自体も経由するだけの踏み台。よくある構成だと思う。 「TRAMPで多段SSHすれば一発で開けるはず」という認識だけはあったので、記憶を頼りに適当な構文を書いたら普通に動かなかった。 最初に書いた(間違った)構文 C-x C-f /ssh:userB@B:/ssh:userC@C:/path/to/file RET コロン区切りでリモートパスを入れ子にすれば多段になるだろう、という思い込みで書いたが、これは単に存在しないパスとして扱われて開けない。 正しい構文: パイプ区切り 調べ直すと、現行のTRAMP(Ad-hoc multi-hops)では各ホップを | で連結する仕様になっていた。 各プロキシはファイル名部分を除いたリモートホスト指定と同じ構文で指定し、ホップごとに | で区切って、起点ホストから最終目的地までを連結する、という説明になっている1。 C-x C-f /ssh:userB@B|ssh:userC@C:/path/to/file RET パスの : は最後のホップにだけ付く。それ以前のホップは | で単純に繋げるだけでよかった。 dired でディレクトリブラウズしたい場合も同じ構文でいける。 C-x d /ssh:userB@B|ssh:userC@C:/path/to/dir RET 一度使うと短縮形が使えるようになる TRAMPはこのアドホックな多段定義を、そのEmacsセッション中は tramp-default-proxies-alist に一時的なレコードとして追加する。そのため同じセッション内であれば、以降は /ssh:you@remotehost:/path という単純な形式だけで同じリモートホストに再接続できるようになる1。 セッションをまたいで使い回したい場合は tramp-save-ad-hoc-proxies を非nilにしておけば、設定ファイル側に多段定義そのものが保存される1。 ついでにsudoも同じノリで組める C上でrootが必要な作業をしたい場合、sudo を最後のホップとして追加するだけでよい。 C-x C-f /ssh:userB@B|ssh:userC@C|sudo:root@C:/path/to/file RET su、sudo、doas、run0 のようなメソッドを別ホスト上で実行したい場合、先頭にsshなどのメソッドを組み合わせて使う。つまりTRAMPはまず非管理者権限でそのホストに接続し、そのあとでそのホスト上で管理者権限に切り替える、という2段階の動作になっている1。 現在開いているバッファをそのままsudo権限で開き直したいだけなら、tramp-revert-buffer-with-sudo というコマンドが用意されている2。ファイルを開き直すたびにパスを書き直さなくていいので、これが一番出番が多いかもしれない。 M-x tramp-revert-buffer-with-sudo RET まとめ TRAMPの多段SSHは、以前は tramp-default-proxies-alist を事前に設定しておく必要があったが、Emacs 24以降は設定なしでその場でパイプ区切りの多段パスを書くだけで通るようになっている3。この「昔の記憶のまま新しい仕様を疑わなかった」せいで無駄にハマったので、次から多段SSH系のパスを書くときは一旦マニュアルを見に行くことにする。 Ad-hoc multi-hops (TRAMP User Manual) ↩︎ ↩︎ ↩︎ ↩︎ TRAMP 2.8.1 User Manual ↩︎ TRAMP - WikEmacs ↩︎

July 13, 2026 · 1 min

iptablesルールを可視化するツールを作った話

きっかけ 暇つぶしとインフラの勉強を兼ねて、何か作りたいと思っていた。最初に思いついたのは「iptablesのルールを抽出して、何が許可されてて何が拒否されてるのかをUI的に見せるアプリ」だった。 思いつきはよかったが、実際に手を動かしてみると想像より何段階も難しかった(動いたのはほぼClaude)。この記事はその過程の記録。 言語化しづらい分野だったが、ふんわりとした説明でも個人的にはかなり納得のいくものを生成してくれたので満足ではある。 作ったもの iptables-rel GitHub - wasuken/iptables-rel · GitHub 既存ツールは無かったのか 作り始める前に軽く調べた。同じ発想のツールは既にいくつかある。 gressgraph は iptables -L -vx の出力をグラフ化するツールで、開発者自身が小規模なルールセットでしか検証していないと明言している。iptable_vis はAWKスクリプトでiptablesの出力を読み、blockdiagでフローチャートを生成する。星が700超えていて、それなりに使われているようだった。 ただ、これらは基本的に「チェーンのジャンプ構造」をそのままグラフにする方向性で、IPアドレスやインターフェースの意味を解釈して「ホストAからホストBへの通信」という粒度に変換するような作り方はしていなかった。個人の環境に合わせて意味づけをするなら、これは自分で作った方が早いと判断した。 最初の設計: グラフ、失敗 D3.jsのforce-directedグラフで、ノードをホスト、エッジをルールとして表示するプロトタイプを最初に作った。動くには動いたが、実際に見てみると「関連はわかるけど、間のルールがわからない」という致命的な問題があった。エッジの上に小さくラベルを乗せるだけでは、プロトコルやポート、チェーンの情報を表現しきれない。 次に表形式(ホストごとにルールを一覧表示)を試したが、今度は逆に「どこと繋がってるか」の直感性が失われた。 最終的に落ち着いたのは、グラフとテーブルのハイブリッドだった。ノード間の関係は力学モデルのグラフで表示し、エッジをクリックすると、その2ノード間の全ルールが表形式でサイドバーに展開される。これで「全体像」と「詳細」を両立できた。 インターフェースでつなげる 実データを流し込んでみて気づいたのが、FORWARDチェーンのルールをホスト同士のエッジとしてマッピングすると意味が壊れるケースがあることだった。-i eth0 -o wg0 のようなルールは、2つのホスト間の通信を表しているのではなく、1台のマシンの中で「あるインターフェースから別のインターフェースへ中継してよいか」を表している。 ここでモデルを作り直した。ホストをそのまま1つのノードにするのではなく、ホスト:eth0 ホスト:wg0 のようにインターフェース単位でノードを分割し、ルールでない「そもそも物理的/論理的に繋がっている」という関係は、クリックできない点線のtopologyエッジとして別扱いにした。実線はルール、点線は構造。この区別を入れてから、ようやく「何が起きているか」が読み取れるグラフになった。 パーサ: 再帰的チェーン解決 iptables-save の出力は、INPUT や FORWARD のようなビルトインチェーンから、ユーザー定義のチェーンへ -j でジャンプしていく構造になっている。UFWの設定などはこのジャンプが何段にもネストしていて、素直に1行ずつ読むだけでは意味のあるグラフにならない。 実装したパーサは、ビルトインチェーンを起点に、ACCEPT / DROP / REJECT / DNAT / MASQUERADE のような終端アクションに到達するまで再帰的にチェーンを辿る。テストケースとして、意図的に未定義のチェーンへジャンプするルールや、循環参照(AがBを呼び、BがAを呼ぶ)を仕込んで、無限ループせずに unresolved として記録されることを確認した。完全なNetfilterのセマンティクスを再現しているわけではなく、あくまでヒューリスティックな近似であることは、公開ページのフッターにも明記している。 エリア表示: 凸包から矩形へ 同じサブネットに属するノードを視覚的にグルーピングしたくなった。最初は d3.polygonHull() を使って、ノード群を囲む凸包を薄い背景として描画する方式を試した。技術的には動いたが、見た目が思っていたより有機的すぎて、期待していた「かっちりした枠」の印象にならなかった。 矩形(bounding box)に切り替えた。同じグループのノード座標からmin/maxを取り、パディングを足して角丸の<rect>を描画するだけなので、実装としては凸包より単純になった。CIDRが入れ子になっているケース(/24の中に/28があるなど)は、prefix長が短いほど背面・大きいパディング・薄い色、長いほど前面・小さいパディング・濃い色にすることで、二重の枠として表現した。 インターフェースのノードについては、エリアの境界をノードの座標だけで決めると、繋がっているエッジが箱の外からいきなり生えているように見えて不自然だった。接続エッジの中間点までを座標の計算対象に含めることで、エッジが箱の内側から伸びているように見せている。 公開、しかし・・・ Cloudflare Pagesにデプロイした。ビルドコマンドとアウトプットディレクトリを指定するだけで、静的サイトとして問題なく配信できた。バックエンドは無く、ルールのパースは全部ブラウザ内で完結する構成にした。 一個ドメイン持ってたので、サブドメイン割り当てて公開し、最初デプロイしたとき問題なく表示された。 ところが微修正を加えてPushし、自動デプロイが走った直後、突然サイトにアクセスできなくなった。Cloudflare Pagesが自動で割り当てる *.pages.dev のドメインからは問題なくアクセスできる一方、独自サブドメインだけが ERR_NAME_NOT_RESOLVED で弾かれる状態だった。 ...

July 12, 2026 · 1 min