Juniper/go-netconfをざっと触る
Junos の自動化に Python ライブラリの PyEZ があることはご存知(ネットワークエンジニアの世界)の方が多いと思います。最近は Go 言語を触る機会が多いので、ふと調べたらJuniper/go-netconfが出てきたのでざっと触ってみました。
試した際のログはsourjp/lab-networkに置きました。
go-netonf?
This library is a simple NETCONF client based on RFC6241 and RFC6242 (although not fully compliant yet). Note: this is currently pre-alpha release. API and features may and probably will change. Suggestions and pull requests are welcome.
Juniper 公式パッケージで、netconf 接続を go 言語で実装されています。簡単に言えば PyEZ の Go 言語版に位置しますが、実装は比べると少なく、記載の通り pre-alpha です。
基本的な使い方
基本的な使い方は PyEZ と同じ感覚です。
- Connection を作成
- XML メッセージを投げる
とりあえずサンプルを書きました。
package main import ( "fmt" "log" "github.com/Juniper/go-netconf/netconf" "golang.org/x/crypto/ssh" ) func main() { sshConfig := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ssh.Password("Juniper")}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } s, err := netconf.DialSSH("localhost:2830", sshConfig) if err != nil { log.Fatal(err) } defer s.Close() r, err := s.Exec(netconf.RawMethod("<get-chassis-inventory/>")) if err != nil { log.Fatal(err) } fmt.Printf("Reply: %+v", r) } """ $ go run main.go [] 0 Reply: &{XMLName:{Space:urn:ietf:params:xml:ns:netconf:base:1.0 Local:rpc-reply} Errors:[] Data: <chassis-inventory xmlns="http://xml.juniper.net/junos/12.1X47/junos-chassis"> <chassis junos:style="inventory"> <name>Chassis</name> ... """
まず、netconf.DialSSH()
でコネクションを作成します。
作成したコネクションのExec()
により RPC を通して XML メッセージを送信します。
このExec()
で受け取る型はRPCMethod
で、実体はstring
です。
そしてこの型を作成する方法は次の 4 種類です。
// RawMethod defines how a raw text request will be responded to type RawMethod string // MethodLock files a NETCONF lock target request with the remote host func MethodLock(target string) RawMethod { return RawMethod(fmt.Sprintf("<lock><target><%s/></target></lock>", target)) } // MethodUnlock files a NETCONF unlock target request with the remote host func MethodUnlock(target string) RawMethod { return RawMethod(fmt.Sprintf("<unlock><target><%s/></target></unlock>", target)) } // MethodGetConfig files a NETCONF get-config source request with the remote host func MethodGetConfig(source string) RawMethod { return RawMethod(fmt.Sprintf("<get-config><source><%s/></source></get-config>", source)) }
このことから次の使い分けをして情報を取得するようです。
メソッド | 用途 |
---|---|
netconf.RawMethod() | show コマンドなど情報を取得したい場合 |
netconf.MethodLock() | 設定を変える場合 |
netconf.MethodUnlock() | MethodLock()をしたい場合は忘れずに |
netconf.MethodGetConfig() | config を取得する場合 |
RPC の調べ方
先ほどのサンプルのように情報を取得したい場合は、特定のコマンドが Junos XML API のどれに当たるのかを調べる必要があります。
Junos XML API Explorerで、コマンドで検索すれば一発です。(昔からありましたっけ?)
ただ古い OS を利用しているとマトリクスに存在しないので、(きっと新しい ver と同じでしょうが)調べる方法があります。次のように CMD | display xml rpc
とつけることで、何を使っているかがわかります。
root@vsrx1> show interfaces terse | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X47/junos"> <rpc> <get-interface-information> <terse/> </get-interface-information> </rpc> <cli> <banner></banner> </cli> </rpc-reply>
上記の場合であれば、次のようにすることで show interface terse
の結果が XML で得ることができます。
r, err := s.Exec(netconf.RawMethod("<get-interface-information><terse/></get-interface-information>"))
応用的な使い方
レポジトリにもいくつかサンプルがあります。
その中で面白かったのが CLI の作成で、ちょっとしたサンプルを書いてみました。
引数で接続先(port 番号)を与えて、取りたい情報を-chassis
や-if
で指定しています。
$ ./main -chassis -port=2830 Chassis : 340603e5d1fa $ ./main -chassis -port=2831 Chassis : 8c7c5874f721 $ ./main -if -port=2830 ge-0/0/0 : up ge-0/0/1 : up ge-0/0/2 : up
長いので chassis の serial number 取得する部分だけ抜粋します。
func main() { target := flag.String("target", "localhost", "target") port := flag.String("port", "2830", "port") chassisCmd := flag.Bool("chassis", false, "get chassis serial number from 'show chassis hardware'") ifCmd := flag.Bool("if", false, "get if admin from 'show interface terse'") flag.Parse() c, err := NewConn(*target, *port) if err != nil { log.Fatalln(err) } defer c.conn.Close() if *chassisCmd { if err := c.GetChassisSN(); err != nil { log.Fatal(err) } } if *ifCmd { if err := c.GetIfAdmin(); err != nil { log.Fatal(err) } } } // Conn netconf.Session type Conn struct { conn *netconf.Session } // NewConn Entrypoint of netconf.Session func NewConn(target, port string) (Conn, error) { conf := &ssh.ClientConfig{ User: "root", Auth: []ssh.AuthMethod{ssh.Password("Juniper")}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } d := fmt.Sprintf("%v:%v", target, port) s, err := netconf.DialSSH(d, conf) if err != nil { return Conn{}, err } return Conn{conn: s}, nil } // GetChassisSN get chassis serial number from "show chassis hardware" func (c *Conn) GetChassisSN() error { out, err := c.conn.Exec(netconf.RawMethod("<get-chassis-inventory/>")) if err != nil { return err } var ci ChassisInventory if err = xml.Unmarshal([]byte(out.Data), &ci); err != nil { return err } chassisName := strings.ReplaceAll(ci.Chassis.Name, "\n", "") chassisSN := strings.ReplaceAll(ci.Chassis.SerialNumber, "\n", "") fmt.Printf("%v : %v\n", chassisName, chassisSN) return nil } // ChassisInventory struct for "show chassis hardware" type ChassisInventory struct { XMLName xml.Name `xml:"chassis-inventory"` Text string `xml:",chardata"` Xmlns string `xml:"xmlns,attr"` Chassis struct { Text string `xml:",chardata"` Style string `xml:"style,attr"` Name string `xml:"name"` SerialNumber string `xml:"serial-number"` Description string `xml:"description"` ChassisModule []struct { Text string `xml:",chardata"` Name string `xml:"name"` Description string `xml:"description"` ChassisSubModule struct { Text string `xml:",chardata"` Name string `xml:"name"` Description string `xml:"description"` } `xml:"chassis-sub-module"` } `xml:"chassis-module"` } `xml:"chassis"` }
このように Go であれば簡単に CLI ツールを作成できますし、Go 言語の特徴としてクロスコンパイルができるので、作成した運用コマンド集を CLI として配布して、Windows も MAC ユーザーも同じように叩けるっていうのは一つかも、、しれません。 あとは API の作成も簡単ですし、xml の構造体を json に変換してもいいかもしれません。
一番手間がかかるのは Go で構造体を用意することですが、Junos から XML のレスポンスを受けて、XML to Go structに貼り付ければ簡単に作れます。
まとめ
いかがでしょうか?シンプルな実装で使いやすいですね。ただ commit 状況やその他ベンダのサポートも考えると Python ライブラリによる実装が今もメインかもしれません。
しかしながら他の言語で使えるようになると選択肢が増えるのでとてもいいことですね。gobgp
もありますし、個人的には Go の方が好きだったりします。
接続が失敗する場合
次のように接続できない場合についてです。
$ go run main.go 2020/12/17 15:27:55 dial tcp [::1]:830: connect: connection refused exit status 1
PyEZ でもそうですが接続方法が netconf 接続であるため、port 830
を利用します。
Junos には次の設定を忘れずにしましょう。
set system services netconf ssh
そして Vagrantfile を利用している方は次の設定を行ってください。
vsrx.vm.network :forwarded_port, id: "ssh", guest: 22, host: 2201 # for SSH vsrx.vm.network :forwarded_port, id: "netconf", guest: 830, host: 2830 # for NETCONF