Building a relevant query
We need a yangsuite
https://github.com/CiscoDevNet/yangsuite
-
As a
root
( use a Virtual machine) runcd /yangsuite/docker && ./start_yang_suite.sh
and answer the question -
Open web interface at https://localhost:8443/
-
Load your yang models via 'Setup' menu
-
Setup 'my_device' in Device profiles
-
Then click 'protocols', 'netconf'
-
Select your 'YANG set', select "Modules" of interest
-
"Netconf Operation" select 'get', device 'my_devise' from the previous steps
-
Click thorough items of your interest and click 'Build RPC', you should get something similar to the pictured below.
Picture above in the result from the Drivenets YANG model.
Get a sample reply from a network device.
In many cases one can not perform queries from the yangsute
directly to a device, therefore let’s do a query from a go(lang) code.
package main import ( "encoding/xml" "flag" "fmt" "log" "git.dev.as6453.net/gtac-systems/if-stats-snmp/internal/cfg" "github.com/Juniper/go-netconf/netconf" "golang.org/x/crypto/ssh" ) func GetInterfaceData(s *netconf.Session, routerName string) error { request := `<?xml version="1.0"?> <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"> <get> <filter> <drivenets-top xmlns="http://drivenets.com/ns/yang/dn-top"> <interfaces xmlns="http://drivenets.com/ns/yang/dn-interfaces"> <interface> <name/> <oper-items> <name/> <description/> <enabled/> <type/> <oper-status/> <interface-speed/> <if-index/> <counters> <ethernet-counters> <rx-octets/> <tx-octets/> </ethernet-counters> <fec-counters> <fec-uncorrectable-words/> </fec-counters> </counters> </oper-items> </interface> </interfaces> </drivenets-top> </filter> </get> </rpc>` s.Transport.Send([]byte(request)) r, err := s.Transport.Receive() log.Printf("Answer is: %+v\n", string(r)) return err } func main() { fname := flag.String("c", "/path/to/config.json", "path to config") flag.Parse() confParams := cfg.LoadConfiguration(*fname) fmt.Printf("%+v\n", confParams) var routerNames []string routerNames = flag.Args() netconfPort := confParams.RouterCredentials.NetconfPort sshConfig := &ssh.ClientConfig{ User: confParams.RouterCredentials.User, Auth: []ssh.AuthMethod{ssh.Password(confParams.RouterCredentials.Password)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } for _, routerName := range routerNames { connectStr := routerName + ":" + netconfPort s, err := netconf.DialSSH(connectStr, sshConfig) log.Printf("Server Capabiliies: %v\n", s.ServerCapabilities) log.Printf("SessionID: %v\n", s.SessionID) defer s.Close() err = GetInterfaceData(s, routerName) if err != nil { log.Printf("Error %v\n", err) } //log.Printf("%+v\n", interfaceData) //fmt.Println(s.ServerCapabilities) //fmt.Println(s.SessionID) //reply, err := s.Exec(netconf.MethodGetConfig("running")) } }
SSH login/password is stored in the json
file.
Once router quiried, there should be an XML answer back, something like:
<?xml version="1.0"?> <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"> <data> <drivenets-top xmlns="http://drivenets.com/ns/yang/dn-top"> <interfaces xmlns="http://drivenets.com/ns/yang/dn-interfaces"> <interface> <name>ge400-0/0/12</name> <oper-items> <name>ge400-0/0/12</name> <description>SOME_CUSTOMER_LINK</description> <enabled>true</enabled> <oper-status>up</oper-status> <interface-speed>400000</interface-speed> <if-index>1</if-index> <type xmlns:iana-if-type="urn:ietf:params:xml:ns:yang:iana-if-type">iana-if-type:ethernetCsmacd</type> <counters> <ethernet-counters> <rx-octets>82498375161495453</rx-octets> <tx-octets>25852003909943900</tx-octets> </ethernet-counters> <fec-counters> <fec-uncorrectable-words>19</fec-uncorrectable-words> </fec-counters> </counters> </oper-items> </interface> ---- the rest is omited ----
Generate a go(lang) type definitions from the reply
Once reply is saved as a file, chidley
https://github.com/gnewton/chidley
can be used to generate go(lang) type definition.
~/chidley/chidley -X ./i-f.xml
where i-f.xml
in a file with the reply results.
One should get the file below (make sure to add package / import):
package main import ( "encoding/xml" ) type Crpc_dash_reply__nc struct { XMLName xml.Name `xml:"rpc-reply,omitempty" json:"rpc-reply,omitempty"` Attrmessage_dash_id string `xml:"message-id,attr" json:",omitempty"` AttrXmlnsnc string `xml:"xmlns nc,attr" json:",omitempty"` Cdata *Cdata `xml:"data,omitempty" json:"data,omitempty"` } type Cdata struct { XMLName xml.Name `xml:"data,omitempty" json:"data,omitempty"` Cdrivenets_dash_top *Cdrivenets_dash_top `xml:"http://drivenets.com/ns/yang/dn-top drivenets-top,omitempty" json:"drivenets-top,omitempty"` } type Cdrivenets_dash_top struct { XMLName xml.Name `xml:"drivenets-top,omitempty" json:"drivenets-top,omitempty"` Attrxmlns string `xml:"xmlns,attr" json:",omitempty"` Cinterfaces *Cinterfaces `xml:"http://drivenets.com/ns/yang/dn-interfaces interfaces,omitempty" json:"interfaces,omitempty"` } type Cinterfaces struct { XMLName xml.Name `xml:"interfaces,omitempty" json:"interfaces,omitempty"` Attrxmlns string `xml:"xmlns,attr" json:",omitempty"` Cinterface []*Cinterface `xml:"http://drivenets.com/ns/yang/dn-interfaces interface,omitempty" json:"interface,omitempty"` } type Cinterface struct { XMLName xml.Name `xml:"interface,omitempty" json:"interface,omitempty"` Cname *Cname `xml:"http://drivenets.com/ns/yang/dn-interfaces name,omitempty" json:"name,omitempty"` Coper_dash_items *Coper_dash_items `xml:"http://drivenets.com/ns/yang/dn-interfaces oper-items,omitempty" json:"oper-items,omitempty"` } type Cname struct { XMLName xml.Name `xml:"name,omitempty" json:"name,omitempty"` Name string `xml:",chardata" json:",omitempty"` } type Coper_dash_items struct { XMLName xml.Name `xml:"oper-items,omitempty" json:"oper-items,omitempty"` Ccounters *Ccounters `xml:"http://drivenets.com/ns/yang/dn-interfaces counters,omitempty" json:"counters,omitempty"` Cdescription *Cdescription `xml:"http://drivenets.com/ns/yang/dn-interfaces description,omitempty" json:"description,omitempty"` Cenabled *Cenabled `xml:"http://drivenets.com/ns/yang/dn-interfaces enabled,omitempty" json:"enabled,omitempty"` Cif_dash_index *Cif_dash_index `xml:"http://drivenets.com/ns/yang/dn-interfaces if-index,omitempty" json:"if-index,omitempty"` Coper_dash_status *Coper_dash_status `xml:"http://drivenets.com/ns/yang/dn-interfaces oper-status,omitempty" json:"oper-status,omitempty"` Cparent_dash_interface *Cparent_dash_interface `xml:"http://drivenets.com/ns/yang/dn-interfaces parent-interface,omitempty" json:"parent-interface,omitempty"` } type Cdescription struct { XMLName xml.Name `xml:"description,omitempty" json:"description,omitempty"` Description string `xml:",chardata" json:",omitempty"` } type Cenabled struct { XMLName xml.Name `xml:"enabled,omitempty" json:"enabled,omitempty"` Enabled string `xml:",chardata" json:",omitempty"` } type Cparent_dash_interface struct { XMLName xml.Name `xml:"parent-interface,omitempty" json:"parent-interface,omitempty"` ParentInterface string `xml:",chardata" json:",omitempty"` } type Coper_dash_status struct { XMLName xml.Name `xml:"oper-status,omitempty" json:"oper-status,omitempty"` OperStatus string `xml:",chardata" json:",omitempty"` } type Cif_dash_index struct { XMLName xml.Name `xml:"if-index,omitempty" json:"if-index,omitempty"` IfIndex string `xml:",chardata" json:",omitempty"` } type Ccounters struct { XMLName xml.Name `xml:"counters,omitempty" json:"counters,omitempty"` Cethernet_dash_counters *Cethernet_dash_counters `xml:"http://drivenets.com/ns/yang/dn-interfaces ethernet-counters,omitempty" json:"ethernet-counters,omitempty"` Cethernet_dash_drop_dash_counters *Cethernet_dash_drop_dash_counters `xml:"http://drivenets.com/ns/yang/dn-interfaces ethernet-drop-counters,omitempty" json:"ethernet-drop-counters,omitempty"` Cfec_dash_counters *Cfec_dash_counters `xml:"http://drivenets.com/ns/yang/dn-interfaces fec-counters,omitempty" json:"fec-counters,omitempty"` } type Cethernet_dash_counters struct { XMLName xml.Name `xml:"ethernet-counters,omitempty" json:"ethernet-counters,omitempty"` Crx_dash_octets *Crx_dash_octets `xml:"http://drivenets.com/ns/yang/dn-interfaces rx-octets,omitempty" json:"rx-octets,omitempty"` Ctx_dash_octets *Ctx_dash_octets `xml:"http://drivenets.com/ns/yang/dn-interfaces tx-octets,omitempty" json:"tx-octets,omitempty"` } type Crx_dash_octets struct { XMLName xml.Name `xml:"rx-octets,omitempty" json:"rx-octets,omitempty"` RxOctets string `xml:",chardata" json:",omitempty"` } type Ctx_dash_octets struct { XMLName xml.Name `xml:"tx-octets,omitempty" json:"tx-octets,omitempty"` TxOctets string `xml:",chardata" json:",omitempty"` } type Cfec_dash_counters struct { XMLName xml.Name `xml:"fec-counters,omitempty" json:"fec-counters,omitempty"` Cfec_dash_uncorrectable_dash_words *Cfec_dash_uncorrectable_dash_words `xml:"http://drivenets.com/ns/yang/dn-interfaces fec-uncorrectable-words,omitempty" json:"fec-uncorrectable-words,omitempty"` } type Cfec_dash_uncorrectable_dash_words struct { XMLName xml.Name `xml:"fec-uncorrectable-words,omitempty" json:"fec-uncorrectable-words,omitempty"` FecUncorrectableWords string `xml:",chardata" json:",omitempty"` } type Cethernet_dash_drop_dash_counters struct { XMLName xml.Name `xml:"ethernet-drop-counters,omitempty" json:"ethernet-drop-counters,omitempty"` Crx_dash_errors *Crx_dash_errors `xml:"http://drivenets.com/ns/yang/dn-interfaces rx-errors,omitempty" json:"rx-errors,omitempty"` Crx_dash_fcs_dash_errors *Crx_dash_fcs_dash_errors `xml:"http://drivenets.com/ns/yang/dn-interfaces rx-fcs-errors,omitempty" json:"rx-fcs-errors,omitempty"` Ctx_dash_errors *Ctx_dash_errors `xml:"http://drivenets.com/ns/yang/dn-interfaces tx-errors,omitempty" json:"tx-errors,omitempty"` } type Crx_dash_errors struct { XMLName xml.Name `xml:"rx-errors,omitempty" json:"rx-errors,omitempty"` RxErrors string `xml:",chardata" json:",omitempty"` } type Crx_dash_fcs_dash_errors struct { XMLName xml.Name `xml:"rx-fcs-errors,omitempty" json:"rx-fcs-errors,omitempty"` RxFcsErrors string `xml:",chardata" json:",omitempty"` } type Ctx_dash_errors struct { XMLName xml.Name `xml:"tx-errors,omitempty" json:"tx-errors,omitempty"` TxErrors string `xml:",chardata" json:",omitempty"` }
save all above as interfaces-structs.go
Finalize the program
package main import ( "encoding/xml" "flag" "fmt" "log" "git.dev.as6453.net/gtac-systems/if-stats-snmp/internal/cfg" "github.com/Juniper/go-netconf/netconf" "golang.org/x/crypto/ssh" ) func GetInterfaceData(s *netconf.Session, routerName string) error { //var allInterfaces AllInterfaces var rpcReply Crpc_dash_reply__nc request := `<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"> <get> <filter> <drivenets-top xmlns="http://drivenets.com/ns/yang/dn-top"> <interfaces xmlns="http://drivenets.com/ns/yang/dn-interfaces"> <interface> <name/> <oper-items> <description/> <enabled/> <parent-interface/> <oper-status/> <if-index/> <counters> <ethernet-counters> <rx-octets/> <tx-octets/> </ethernet-counters> <fec-counters> <fec-uncorrectable-words/> </fec-counters> <ethernet-drop-counters> <rx-errors/> <rx-fcs-errors/> <tx-errors/> </ethernet-drop-counters> </counters> </oper-items> </interface> </interfaces> </drivenets-top> </filter> </get> </rpc>` s.Transport.Send([]byte(request)) r, err := s.Transport.Receive() log.Printf("Answer is: %+v\n", string(r)) err = xml.Unmarshal([]byte(r), &rpcReply) //log.Printf("rpcReply unmarshaled: %+v, error: %v\n", rpcReply, err) log.Printf("rpcReply Cdata: %+v", rpcReply.Cdata.Cdrivenets_dash_top.Cinterfaces.Cinterface) for k, v := range rpcReply.Cdata.Cdrivenets_dash_top.Cinterfaces.Cinterface { log.Printf("interface data for %#v is %#v\n", k, v) log.Printf("interface Name for %#v is %#v\n", k, v.Cname.Name) log.Printf("IfIndex for %#v is %#v\n", k, v.Coper_dash_items.Cif_dash_index.IfIndex) if v.Coper_dash_items.Coper_dash_status != nil { log.Printf("OperStatus for %#v is %#v\n", k, v.Coper_dash_items.Coper_dash_status.OperStatus) } if v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters != nil { if v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters.Crx_dash_errors != nil { log.Printf("RxErrors for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters.Crx_dash_errors.RxErrors) log.Printf("TxErrors for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters.Ctx_dash_errors.TxErrors) } if v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters.Crx_dash_fcs_dash_errors != nil { log.Printf("TxErrors for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cethernet_dash_drop_dash_counters.Crx_dash_fcs_dash_errors.RxFcsErrors) } } if v.Coper_dash_items.Ccounters.Cethernet_dash_counters != nil { if v.Coper_dash_items.Ccounters.Cethernet_dash_counters.Crx_dash_octets != nil { log.Printf("RxOctets for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cethernet_dash_counters.Crx_dash_octets.RxOctets) log.Printf("TxOctets for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cethernet_dash_counters.Ctx_dash_octets.TxOctets) } } if v.Coper_dash_items.Ccounters.Cfec_dash_counters.Cfec_dash_uncorrectable_dash_words != nil { log.Printf("FecUncorrectableWords for %#v is %#v\n", k, v.Coper_dash_items.Ccounters.Cfec_dash_counters.Cfec_dash_uncorrectable_dash_words.FecUncorrectableWords) } log.Printf("interface data for %#v is %#v\n", k, v.Coper_dash_items.Cparent_dash_interface) } return err } func main() { fname := flag.String("c", "/path/to/config.json", "path to config") flag.Parse() confParams := cfg.LoadConfiguration(*fname) fmt.Printf("%+v\n", confParams) var routerNames []string routerNames = flag.Args() netconfPort := confParams.RouterCredentials.NetconfPort sshConfig := &ssh.ClientConfig{ User: confParams.RouterCredentials.User, Auth: []ssh.AuthMethod{ssh.Password(confParams.RouterCredentials.Password)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } for _, routerName := range routerNames { connectStr := routerName + ":" + netconfPort s, err := netconf.DialSSH(connectStr, sshConfig) log.Printf("Server Capabiliies: %v\n", s.ServerCapabilities) log.Printf("SessionID: %v\n", s.SessionID) defer s.Close() err = GetInterfaceData(s, routerName) if err != nil { log.Printf("Error %v\n", err) } //log.Printf("%+v\n", interfaceData) //fmt.Println(s.ServerCapabilities) //fmt.Println(s.SessionID) //reply, err := s.Exec(netconf.MethodGetConfig("running")) } }
Results
Run ./netconf-if-stats -c ./route.json 192.168.7.19
Results for the first three interfaces.
2024/09/24 08:23:58 interface Name for 0 is "ge400-0/0/12" 2024/09/24 08:23:58 IfIndex for 0 is "1" 2024/09/24 08:23:58 OperStatus for 0 is "up" 2024/09/24 08:23:58 RxErrors for 0 is "0" 2024/09/24 08:23:58 TxErrors for 0 is "0" 2024/09/24 08:23:58 TxErrors for 0 is "0" 2024/09/24 08:23:58 RxOctets for 0 is "85490659663335152" 2024/09/24 08:23:58 TxOctets for 0 is "27402847406025900" 2024/09/24 08:23:58 FecUncorrectableWords for 0 is "19" 2024/09/24 08:23:58 interface Name for 1 is "ge400-0/0/23" 2024/09/24 08:23:58 IfIndex for 1 is "2" 2024/09/24 08:23:58 OperStatus for 1 is "up" 2024/09/24 08:23:58 RxErrors for 1 is "25384" 2024/09/24 08:23:58 TxErrors for 1 is "0" 2024/09/24 08:23:58 TxErrors for 1 is "25368" 2024/09/24 08:23:58 RxOctets for 1 is "39374827552939581" 2024/09/24 08:23:58 TxOctets for 1 is "63651355058334264" 2024/09/24 08:23:58 interface Name for 2 is "ge400-0/0/16" 2024/09/24 08:23:58 IfIndex for 2 is "3" 2024/09/24 08:23:58 OperStatus for 2 is "up" 2024/09/24 08:23:58 RxErrors for 2 is "917348" 2024/09/24 08:23:58 TxErrors for 2 is "0" 2024/09/24 08:23:58 TxErrors for 2 is "917342" 2024/09/24 08:23:58 RxOctets for 2 is "60103917409819006" 2024/09/24 08:23:58 TxOctets for 2 is "42222945991248034" 2024/09/24 08:23:58 interface Name for 3 is "ge400-0/0/32" 2024/09/24 08:23:58 IfIndex for 3 is "4" 2024/09/24 08:23:58 OperStatus for 3 is "down" 2024/09/24 08:23:58 RxErrors for 3 is "0" 2024/09/24 08:23:58 TxErrors for 3 is "0" 2024/09/24 08:23:58 TxErrors for 3 is "0" 2024/09/24 08:23:58 RxOctets for 3 is "0" 2024/09/24 08:23:58 TxOctets for 3 is "0"