0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Yamaha RTXのNATセッション可視化アプリをCisco IOSにも対応させた — Cisco固有コードは5%弱

0
Posted at

はじめに

瀧澤です。

前回、EgressViewという、自宅・SOHOネットワークの外向き通信を可視化するOSSを作った記事を書きました。もともとYamaha RTX専用でしたが、前回の記事の公開後、実際の利用者から「自分でコードに手を入れてCiscoルーターで動かした」という報告をいただきました。ユーザーがフォークして自力対応するほどの需要があるなら、こちらで安全に使える形を提供すべきだと思い、Cisco IOSに正式対応しました。

さらに、Ciscoを利用するSOHOで想定される冗長化や複数回線にも備えるため、Yamaha RTXとCisco IOSを任意に組み合わせて最大10台まで登録できるようにしました。

この記事は「Yamaha 対応のツールを Cisco に広げるとき、何が流用でき、何に留意が必要だったか」の記録です。結論から書くと、新たに必要な Cisco 固有コードは全体の5%弱でした。


できるようになったこと

同じグラフマップ・統計・通信ログを、Cisco の NAT テーブル由来の通信に対しても使えます。宛先ホスト名・組織名・国・脅威フィード照合(Feodo Tracker / ThreatFox / URLhaus / Spamhaus DROP)、MCP 経由の AI エージェント連携も従来どおりです。

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_61619_062c8836-6033-4151-85c6-4900a4cc98d7.avif

構成 動作
Yamaha RTX のみ 従来どおり
Cisco IOS のみ NAT変換テーブルから同等の可視化
Yamaha + Cisco 併用 同じ通信を重複計上せず、観測した全ルーターを保持
Cisco複数台 / Yamaha複数台 同一メーカーを含め、任意の組み合わせで最大10台

設定画面もこの通りです。

Yamaha RTXとCisco IOSを登録した設定画面


流用できたもの・書いたもの

分類 規模(概算) Cisco対応での扱い
保存層(SQLite、履歴、集計) 大 そのまま流用
エンリッチ(逆引きDNS、RDAP組織名、Geo) 大 そのまま流用
脅威フィード照合 中 そのまま流用
Web UI(グラフマップ、統計、通信ログ、端末一覧) 大 そのまま流用
MCP サーバー / Slack 通知・検出ログ 中 そのまま流用
Cisco 固有コード(SSH対話、パーサー、アダプタ) 841行 新規

Cisco 専用モジュールは841行で、JavaScript 全体(テスト除く18,105行)の**約4.6%**です。この比率は専用モジュールの規模であり、既存ファイルへの変更量を含む流用率ではありません。それでも、保存・分析・可視化を作り直さずに済みました。

もともと、「ルーター」を抽象化してあったのが良かったと考えています。EgressView の内部では、ルーターは「定期的に接続レコードの配列を返すもの」でしかありません。

// ルーターアダプタが返す共通の接続レコード(概念図)
{ src: '192.168.1.42', dst: '203.0.113.10', dport: 443, proto: 'tcp', srcMac: 'aa:bb:cc:11:02:00' }

この形に変換するアダプタさえ書けば、その先は機種を知らないまま動きます。「対応機種を増やすかもしれないなら、1機種目のうちにデータの正規形と境界を決めておく」——今回いちばん効いた設計判断は、Cisco 対応前に済んでいました。

ただし機種の抽象化だけでは台数を増やせません。kind: cisco という種類と routerId: cisco1 という個体を分離し、scheduler が個体ごとに独立ポーリングすることで、cisco1 / cisco2 / yamaha1 のような構成を特別扱いなしで表現しています。複数ルーターが同じ通信を観測した場合は、通信は自然キー (src, dst, dport, proto) で1件のまま、観測元だけを connection_observations テーブルに routerId 単位で保持します。


留意点 — CLI の出力の仕方

設計レベルの困難ではなく「出力の仕方」への対処でした。対処自体は数行ですが、どれも失敗が沈黙する(エラーにならず、止まる・0件になる)ため、注意深い対応が必要です。

# Yamaha との違い 症状 対処
1 --More-- ページャー 出力が途中で止まりタイムアウト 冒頭で terminal length 0 + --More-- 検出時にスペース送信
2 enable の Password: 応答 プロンプト待ち正規表現に永遠にマッチしない 「何を待つか」を差し替え可能にし、Password: とプロンプト両方を待つ
3 enable 失敗時も一部コマンドは通る 接続は「成功」なのにデータが永遠に0件 # への遷移を検証し、% Invalid input をエラー扱い
4 MAC 表記が aabb.cc11.0200 形式 同じ端末が二重登録される パーサー入口でコロン区切りへ正規化
5 ポーリングループが複数になる 差分配信の漏れ・二重カウント 個体ごとに状態を分離し、観測元を junction table に保存

なお、2と3は privilege 15 のユーザーでテストしていると気づけません。ログイン直後から特権モードに着地し、enable の分岐が一度も実行されないためです。「手元では動くのにユーザーの環境では動かない」の典型でした。


留意点 — NATテーブルの細かな差

同じ「NAT テーブル」でも、載っている情報が違います。

情報 Yamaha RTX Cisco IOS
コマンド show nat descriptor address N detail show ip nat translations
プロトコル TCP / UDP / ICMP / GRE TCP / UDP / ICMP
LAN側IP・ポート あり Inside local としてあり
通信先IP・ポート あり Outside global としてあり
セッションの残存時間 実測TTLを取得 verbose の left から取得(未対応機は既定値)
セッション生成時刻 なし verbose の create から取得

Cisco は inside/outside × local/global の「NAT の前後」を軸にした語彙、Yamaha は「LAN 側とその通信先」という素直な形で代わりに実測 TTL を持つ——情報の多寡ではなく軸が違うのです。アダプタの仕事は形式変換だけでなく「共通の正規形に何を残すか」の判断で、EgressView では「どの端末がどこへ出ているか」を核にし、片方にしかない情報はオプション扱いにしました。共通レコードを両機種の和集合にすると、3機種目でまた壊れるからです。

この違いは機種固有の強みにもなります。show ip nat translations verbose の create / left を取り込み、Cisco 版ではセッション開始時刻を NAT エントリ生成の推定時刻まで遡れるようになり、TTL も既定値から実測値になりました。verbose 未対応機は自動フォールバックし、Yamaha 側のコードには一切手を入れていません。


3機種目を見据えたリファクタリング

対応作業と同時に、次の機種を簡単に追加できるように4つの改善を行いました。

① アダプタ契約のコード化 — 実装すべき22メソッドを契約として定義し、起動時に検証。3機種目の実装漏れは起動した瞬間にエラーメッセージ付きでわかります。

// router-interface.js(抜粋)
const REQUIRED_METHODS = ['configure', 'connect', 'fetchSessions', /* ...計22 */];
function validateRouterPoller(adapter) {
  for (const name of REQUIRED_METHODS) {
    if (typeof adapter[name] !== 'function') {
      throw new TypeError(`adapter "${adapter.kind}" is missing method: ${name}`);
    }
  }
}

② SSH 対話ロジックの状態機械(ステートマシン)化 — enable 遷移は SSH コールバックに埋まった「実機がないとテストできない」コードでした。I/O を注入できる純粋な状態遷移関数に切り出し、「enable パスワードが間違っていた場合」のような再現しにくいケースもユニットテストで固定しています。

③ ポーリング状態のポーラー単位分離 — 留意点5(差分配信の漏れ・二重カウント)の恒久対策。「前回配信時刻」などをポーラーごとに持ち、観測点が増えてもループ同士が干渉しません。

④ 負荷と障害分離をゲート化 — fake router を Yamaha 5台 + Cisco 5台・各1,000セッションで動かす自動試験を追加。同時実行は最大3、共通100通信の重複排除でユニーク9,100件、1台を意図的に落としても残り9台の9,000セッションを収集できることを CI で確認します。あわせて履歴を「SQLite に全件・メモリに新しい100,000件」の二層にし、実データ216,582件で常駐メモリを約995MB→約604MB(約39%減)にしました。

結果はテストの数字に出ています: ユニットテスト1,132件(うち Cisco 関連64件) + Playwright E2E。特に②により「実機がないと確かめられない」領域を対話の外周だけに追い込めたことが、品質面のいちばんの成果です。


それでも自動化できないもの

実機での接続確認(SSH の実挙動、実機の NAT 出力形式、ホスト鍵の初回受け入れ)だけは手動で残ります。用意できた実機は Cisco 1台と Yamaha 1台。補足試験として各実機を異なる routerId で2重登録して4個体として並列収集し、同じ Cisco 通信17件・Yamaha 通信178件が両 routerId から観測され、観測元を失わず重複排除できることを確認しました。ただしこれは SSH 並列処理・個体別状態・重複排除の確認であり、異なる物理機器2台の構成や HSRP/VRRP、実フェイルオーバーを検証したものではありません。

ここは対応機種を増やすほど重くなるので、「この機種・この IOS で動いた/動かなかった」という動作報告をもらえれば、と思います。


試し方

項目 内容
EgressView v1.3.5(GitHub Release)
Node.js 22 以上
ルーター側 SSH ログイン + show ip nat translations が実行できること
権限 privilege 15 のユーザー、または enable パスワード(設定画面に入力欄あり)
動作確認済み環境 Cisco C841M-4X-JSEC/K9 / IOS 15.5(3)M9

詳細手順は Cisco IOS — SSH 設定ガイドにあります。

git clone https://github.com/yo1t/egressview.git
cd egressview
npm install
npm start

ルーターが手元になくてもデモモードで UI を確認できます。

DEMO_MODE=true DEMO_ADMIN_TOKEN=my-token npm start

「この機種・この IOS バージョンで動いた/動かなかった」の報告だけでも大変ありがたいです。Issue・PR も歓迎です。


参考


おわりに

2機種目の対応は、1機種目で対応した抽象化の答え合わせのようなものでした。データの正規形と境界を先に決めてあったおかげで、Cisco 対応の中心は「CLI の出力差異を吸収するアダプタを書く」ことに収まり、専用コードは全体の5%弱となりました。

さらに、将来の対応機種追加と冗長構成を見据え、最大10台のルーターを共通のschedulerとregistryで扱えるようにしました。機種固有の差はアダプタの内側へ閉じ込め、保存・分析・可視化は共通化する。この境界を保てるかぎり、3機種目以降も同じ設計で追加していけると考えています。

免責

こちらの内容にもとづく環境の構築は、自己の責任でお願いします。各種サービス、AIクライアント、APIを利用する場合は、それぞれの利用条件に従ってください。また、ここに記載の全ての内容については個人の意見であり、所属する企業や団体とは関係ありません。


商標について

Cisco、Cisco IOS、Yamaha などの製品名は、各社の商標または登録商標です。EgressView はこれらの企業と提携・承認・後援関係にありません。

0
2
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
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?