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.shand 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"