ntc-templateにほしいパーサーがなかったので自作入門してみました。
やりたかったこと
show ip nat translationsを実行したときに想定通りのStaticNATのエントリーが見たい
準備
環境
python
- Python 3.9.6
ライブラリ
- textfsm==1.1.2
- ntc-templates==2.1.0
公式ドキュメントを参考に準備を開始 github.com
自作のテンプレートを格納するディレクトを作成
/templates
テンプレートの名前は下記の規則で作ってねと説明ありました。
{{ vendor_os }}_{{ command_with_underscores }}.textfsm
今回はiosのshow ip nat translationsの結果をパースするテンプレートのため、
ほかのテンプレートを参考にcisco_ios_show_ip_nat_translations.textfsm
としました。
実装
テンプレートの作成を始めます。
今回想定しているshow ip nat translationsの結果が以下になります。
StaticNATのInside globalとInside locaが想定通りであるか?の確認をするためにパースします。
StaticNATのエントリとしては最後の行が該当します。
Pro Inside global Inside local Outside local Outside global tcp 10.9.0.0:51776 10.1.0.2:51776 10.2.0.2:21 10.2.0.2:21 tcp 10.9.0.0:51778 10.1.0.2:51778 10.2.0.2:21 10.2.0.2:21 tcp 10.9.0.0:56384 10.1.0.2:56384 10.2.0.2:22 10.2.0.2:22 icmp 10.9.0.0:56111 10.1.0.2:56384 10.2.0.2:23 10.2.0.2:23 --- 10.9.0.0 10.1.0.2 --- ---
ntc-template(textfsm)で対象の文字列を取得するのは正規表現でマッチさせる必要があります。
正規表現の書き方を確認するには下記サイトなどを利用しています。(機微な情報は載せないように気を付けてください)
regex101.com
正規表現やほかのテンプレートを参考に下記テンプレートを作成しました
Value PROTOCOL (tcp|udp|icmp|---) Value INSIDE_GLOBAL (\S+) Value INSIDE_LOCAL (\S+) Value OUTSIDE_LOCAL (\S+) Value OUTSIDE_GLOBAL (\S+) Start ^${PROTOCOL}\s+${INSIDE_GLOBAL}\s+${INSIDE_LOCAL}\s+${OUTSIDE_LOCAL}\s+${OUTSIDE_GLOBAL} -> Record
Valueにはパース後のキーを定義します。
key名と(ヒットさせる条件)の間にはスペースが必要です。
Value key名 (ヒットさせる条件)
テキストの解析処理はStart
から始まります。
今回は^${PROTOCOL}\s・・・をキーに解析を始めます。
PROTOCOLはtcpかudpかicmpか---が入る想定なのでカラムを除いたステータスの部分にマッチします。
そのほかのINSIDE_GLOBALなどは(\S+)のルールで空白以外の文字列をマッチさせています。
Pro Inside global Inside local Outside local Outside global
のカラムもマッチしていますがパース対象の文字列として含まれないように調整しています。
本来であれば、PATしている情報(IP:Portなどを値)も扱いやすいようにIPとPort分けて保存したほうがいいかもしれませんが、StaticNATの確認はこのテンプレートで十分できるのでここで終了します。
indexに今回追加したテンプレートを呼び出すルールを記載します。
これが必要なことに気づかず時間を結構食ってしまいました。。
templates/index
Template, Hostname, Platform, Command cisco_ios_show_ip_nat_translations.textfsm, .*, cisco_ios, sh[[ow]] ip nat translations
確認
作成したテンプレートを動かすPythonのスクリプトを作成します。
import os import pprint from ntc_templates.parse import parse_output # (1) os.environ["NTC_TEMPLATES_DIR"] = "./templates" # (2) output1 = ( "Pro Inside global Inside local Outside local Outside global\n" "tcp 10.9.0.0:51776 10.1.0.2:51776 10.2.0.2:21 10.2.0.2:21\n" "tcp 10.9.0.0:51778 10.1.0.2:51778 10.2.0.2:21 10.2.0.2:21\n" "tcp 10.9.0.0:56384 10.1.0.2:56384 10.2.0.2:22 10.2.0.2:22\n" "icmp 10.9.0.0:56111 10.1.0.2:56384 10.2.0.2:23 10.2.0.2:23\n" "--- 10.9.0.0 10.1.0.2 --- ---\n" ) # (3) parsed = parse_output(platform="cisco_ios", command="show ip nat translations", data=output1) # (4) pprint.pprint(parsed) # (5)
1) ntc_templates.parseにあるparse_outputを読み込みます。
2) テンプレートを格納しているディレクトを指定します。
3) 想定のアウトプットを格納します。
4) パース処理を実行します、platform,command,dataを定義します。
5) 実行結果を表示します。
スクリプトを実行します。
sandbox# python nat_parser.py [{'inside_global': '10.9.0.0:51776', 'inside_local': '10.1.0.2:51776', 'outside_global': '10.2.0.2:21', 'outside_local': '10.2.0.2:21', 'protocol': 'tcp'}, {'inside_global': '10.9.0.0:51778', 'inside_local': '10.1.0.2:51778', 'outside_global': '10.2.0.2:21', 'outside_local': '10.2.0.2:21', 'protocol': 'tcp'}, {'inside_global': '10.9.0.0:56384', 'inside_local': '10.1.0.2:56384', 'outside_global': '10.2.0.2:22', 'outside_local': '10.2.0.2:22', 'protocol': 'tcp'}, {'inside_global': '10.9.0.0:56111', 'inside_local': '10.1.0.2:56384', 'outside_global': '10.2.0.2:23', 'outside_local': '10.2.0.2:23', 'protocol': 'icmp'}, {'inside_global': '10.9.0.0', 'inside_local': '10.1.0.2', 'outside_global': '---', 'outside_local': '---', 'protocol': '---'}]
無事にパースができました。
StaticNATのエントリはprotocl/outside_global/outside_localが---
になるので下記の要素を確認します。
{'inside_global': '10.9.0.0', 'inside_local': '10.1.0.2', 'outside_global': '---', 'outside_local': '---', 'protocol': '---'}]
パースがうまくできているので後はassertなどで結果の判断が簡単にできます。
まとめ
簡単なテンプレートでしたが、ntc-templateを自作することができました。
今回やったことは本当に入門レベルでntc-templateは複雑なテキストをパースすることも可能です。
自作したntc-templateをansibleに読み込ませてわちゃわちゃしたいと思います。
また、TTPパーサーも自作をしたのでいつか記事にします。