Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
yseto committed Feb 20, 2023
0 parents commit af5619d
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 0 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# practice-go-snmptrap

## references

- https://github.com/sleepinggenius2/gosmi/
- https://github.com/gosnmp/gosnmp
23 changes: 23 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

type MIB struct {
Directory []string `yaml:"directory"`
LoadModules []string `yaml:"modules"`
}

type TrapServer struct {
Address string `yaml:"addr"`
Port string `yaml:"port"`
}

type Trap struct {
Ident string `yaml:"ident"`
Format string `yaml:"format"`
}

type Config struct {
MIB *MIB `yaml:"mib"`
TrapServer *TrapServer `yaml:"snmp"`
Trap []*Trap `yaml:"trap"`
Debug bool `yaml:"debug"`
}
25 changes: 25 additions & 0 deletions config.yaml.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
snmp:
addr: 0.0.0.0
port: 9162
mib:
directory:
- "/var/lib/snmp/mibs/ietf/"
modules:
- SNMPv2-MIB
- IF-MIB
# - CISCO-CONFIG-MAN-MIB
# - CISCO-GENERAL-TRAPS
# - CISCO-SYSLOG-MIB
debug: false
trap:
# - ident: .1.3.6.1.4.1.9.9.41.2.0.1
# format: '[{{ addr }}] {{ read "CISCO-SYSLOG-MIB::clogHistFacility" }} is msg:{{ read "CISCO-SYSLOG-MIB::clogHistMsgName" }} text:{{ read "CISCO-SYSLOG-MIB::clogHistMsgText" }}'
- ident: .1.3.6.1.6.3.1.1.5.1
format: '{{ addr }} is cold started'
- ident: .1.3.6.1.6.3.1.1.5.2
format: '{{ addr }} is warm started'
- ident: .1.3.6.1.6.3.1.1.5.3
format: '{{ addr }} {{ read "IF-MIB::ifDescr" }} is linkdown'
- ident: .1.3.6.1.6.3.1.1.5.4
format: '{{ addr }} {{ read "IF-MIB::ifDescr" }} is linkup'

11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/yseto/practice-go-snmptrap

go 1.19

require (
github.com/gosnmp/gosnmp v1.35.0
github.com/sleepinggenius2/gosmi v0.4.4
gopkg.in/yaml.v3 v3.0.1
)

require github.com/alecthomas/participle v0.4.1 // indirect
29 changes: 29 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
github.com/alecthomas/go-thrift v0.0.0-20170109061633-7914173639b2/go.mod h1:CxCgO+NdpMdi9SsTlGbc0W+/UNxO3I0AabOEJZ3w61w=
github.com/alecthomas/kong v0.2.1/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
github.com/alecthomas/participle v0.4.1 h1:P2PJWzwrSpuCWXKnzqvw0b0phSfH1kJo4p2HvLynVsI=
github.com/alecthomas/participle v0.4.1/go.mod h1:T8u4bQOSMwrkTWOSyt8/jSFPEnRtd0FKFMjVfYBlqPs=
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
github.com/alecthomas/repr v0.0.0-20210301060118-828286944d6a/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gosnmp/gosnmp v1.35.0 h1:EuWWNPxTCdAUx2/NbQcSa3WdNxjzpy4Phv57b4MWpJM=
github.com/gosnmp/gosnmp v1.35.0/go.mod h1:2AvKZ3n9aEl5TJEo/fFmf/FGO4Nj4cVeEc5yuk88CYc=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sleepinggenius2/gosmi v0.4.4 h1:xgu+Mt7CptuB10IPt3SVXBAA9tARToT4B9xGzjjxQX8=
github.com/sleepinggenius2/gosmi v0.4.4/go.mod h1:l8OniPmd3bJzw0MXP2/qh7AhP/e+bTY2CNivIhsnDT0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
72 changes: 72 additions & 0 deletions smi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package main

import (
"fmt"
"strings"

"github.com/sleepinggenius2/gosmi"
"github.com/sleepinggenius2/gosmi/types"
)

type SMI struct {
Modules []string
Paths []string
}

func (s *SMI) Init() error {
gosmi.Init()

for _, path := range s.Paths {
gosmi.AppendPath(path)
}
for i, module := range s.Modules {
moduleName, err := gosmi.LoadModule(module)
if err != nil {
return err
}
s.Modules[i] = moduleName
}
return nil
}

func (s *SMI) Close() {
gosmi.Exit()
}

type Node struct {
Readable string
Node gosmi.SmiNode
}

func (s *SMI) FromOID(oid string) (*Node, error) {
var node gosmi.SmiNode
var err error
if (oid[0] >= '0' && oid[0] <= '9') || oid[0] == '.' {
node, err = gosmi.GetNodeByOID(types.OidMustFromString(oid))
} else {
node, err = gosmi.GetNode(oid)
}
if err != nil {
return nil, err
}

subtree := node.GetSubtree()

if len(subtree) != 1 {
return nil, fmt.Errorf("mismatch oid : %q, len : %d", oid, len(subtree))
}

if !subtree[0].Oid.ParentOf(types.OidMustFromString(oid)) {
return nil, fmt.Errorf("mismatch oid. : %q", oid)
}

// readable
readable := strings.Replace(
types.OidMustFromString(oid).String(),
subtree[0].RenderNumeric(),
subtree[0].RenderQualified(),
1,
)

return &Node{Readable: readable, Node: subtree[0]}, nil
}
69 changes: 69 additions & 0 deletions trap-send/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"log"

g "github.com/gosnmp/gosnmp"
)

func main() {
g.Default.Target = "127.0.0.1"
g.Default.Port = 9162
g.Default.Version = g.Version2c
g.Default.Community = "public"
// g.Default.Logger = g.NewLogger(log.New(os.Stdout, "", 0))
err := g.Default.Connect()
if err != nil {
log.Fatalf("Connect() err: %v", err)
}
defer g.Default.Conn.Close()

trap := g.SnmpTrap{
Variables: []g.SnmpPDU{
{Value: ".1.3.6.1.6.3.1.1.5.3", Name: ".1.3.6.1.6.3.1.1.4.1.0", Type: g.ObjectIdentifier},
{Value: 9, Name: ".1.3.6.1.2.1.2.2.1.1.9", Type: g.Integer},
{Value: []byte("dum0"), Name: ".1.3.6.1.2.1.2.2.1.2.9", Type: g.OctetString},
{Value: 6, Name: ".1.3.6.1.2.1.2.2.1.3.9", Type: g.Integer},
{Value: 2, Name: ".1.3.6.1.2.1.2.2.1.7.9", Type: g.Integer},
{Value: 2, Name: ".1.3.6.1.2.1.2.2.1.8.9", Type: g.Integer},
{Value: ".1.3.6.1.4.1.8072.3.2.10", Name: ".1.3.6.1.6.3.1.1.4.3.0", Type: g.ObjectIdentifier},
},
}
/*
trap := g.SnmpTrap{
Variables: []g.SnmpPDU{
// {Name: ".1.3.6.1.6.3.1.1.4.1.0", Type: g.ObjectIdentifier, Value: ".1.3.6.1.6.3.1.1.5.1"},
{Name: ".1.3.6.1.6.3.1.1.4.1.0", Type: g.ObjectIdentifier, Value: ".1.3.6.1.6.3.1.1.5.2"},
},
}
*/

/*
trap := g.SnmpTrap{
Variables: []g.SnmpPDU{
{Value: ".1.3.6.1.4.1.9.9.43.2.0.1", Name: ".1.3.6.1.6.3.1.1.4.1.0", Type: g.ObjectIdentifier},
{Value: 1, Name: ".1.3.6.1.4.1.9.9.43.1.1.6.1.3.10", Type: g.Integer},
{Value: 2, Name: ".1.3.6.1.4.1.9.9.43.1.1.6.1.4.10", Type: g.Integer},
{Value: 3, Name: ".1.3.6.1.4.1.9.9.43.1.1.6.1.5.10", Type: g.Integer},
},
}
*/

/*
trap := g.SnmpTrap{
Variables: []g.SnmpPDU{
{Value: ".1.3.6.1.4.1.9.9.41.2.0.1", Name: ".1.3.6.1.6.3.1.1.4.1.0", Type: g.ObjectIdentifier},
{Value: []byte("DOT11"), Name: ".1.3.6.1.4.1.9.9.41.1.2.3.1.2.1414", Type: g.OctetString},
{Value: 5, Name: ".1.3.6.1.4.1.9.9.41.1.2.3.1.3.1414", Type: g.Integer},
{Value: []byte("MAXRETRIES"), Name: ".1.3.6.1.4.1.9.9.41.1.2.3.1.4.1414", Type: g.OctetString},
{Value: []byte("Packet to client e28c.c759.c887 reached max retries, removing the client"), Name: ".1.3.6.1.4.1.9.9.41.1.2.3.1.5.1414", Type: g.OctetString},
{Value: uint32(79758314), Name: ".1.3.6.1.4.1.9.9.41.1.2.3.1.6.1414", Type: g.TimeTicks},
},
}
*/

_, err = g.Default.SendTrap(trap)
if err != nil {
log.Fatalf("SendTrap() err: %v", err)
}
}
150 changes: 150 additions & 0 deletions trap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package main

import (
"fmt"
"log"
"net"
"os"
"strings"
"text/template"

g "github.com/gosnmp/gosnmp"
"github.com/sleepinggenius2/gosmi/types"
"gopkg.in/yaml.v3"
)

const SnmpTrapOIDPrefix = ".1.3.6.1.6.3.1.1.4.1"

var mibParser SMI
var c Config

func main() {
defer func() {
mibParser.Close()
}()

// TODO args.
f, err := os.ReadFile("config.yaml")

err = yaml.Unmarshal(f, &c)
if err != nil {
log.Fatalf("error: %v", err)
}

mibParser.Modules = c.MIB.LoadModules
mibParser.Paths = c.MIB.Directory
mibParser.Init()

// template tests.
funcmap := template.FuncMap{
"read": func(key string) string {
return "dummy"
},
"addr": func() string {
return "dummy"
},
}

var tmpl = template.New("").Funcs(funcmap)

for i := range c.Trap {
if _, err := tmpl.Parse(c.Trap[i].Format); err != nil {
log.Fatalln(err)
}
}

trapListner := g.NewTrapListener()
trapListner.OnNewTrap = trapHandler
trapListner.Params = g.Default
if c.Debug {
trapListner.Params.Logger = g.NewLogger(log.New(os.Stdout, "<GOSNMP DEBUG LOGGER>", 0))
}

err = trapListner.Listen(net.JoinHostPort(c.TrapServer.Address, c.TrapServer.Port))
if err != nil {
log.Fatalf("error in listen: %s", err)
}
}

func trapHandler(packet *g.SnmpPacket, addr *net.UDPAddr) {
// log.Printf("got trapdata from %s\n", addr.IP)

var pad = make(map[string]string)
var specificTrapFormat string

for _, v := range packet.Variables {
if strings.HasPrefix(v.Name, SnmpTrapOIDPrefix) {
for i := range c.Trap {
if strings.HasPrefix(v.Value.(string), c.Trap[i].Ident) {
specificTrapFormat = c.Trap[i].Format
}
}
}

var padKey, padValue string
padKey = v.Name
node, err := mibParser.FromOID(v.Name)
if err != nil {
fmt.Printf("%+v\n", err)
} else {
if node != nil {
padKey = node.Node.RenderQualified()
}

if node.Node.Type != nil && node.Node.Type.BaseType == types.BaseTypeEnum {
i, ok := v.Value.(int)
if ok {
padValue = node.Node.Type.Enum.Name(int64(i))
}
}
}
if padValue == "" {
switch v.Type {
case g.OctetString:
b := v.Value.([]byte)
padValue = string(b)
// fmt.Printf("OID: %s, string: %s\n", v.Name, string(b))
case g.ObjectIdentifier:
valNode, err := mibParser.FromOID(v.Value.(string))
if err != nil {
fmt.Printf("%+v\n", err)
padValue = v.Value.(string)
} else {
padValue = valNode.Node.Name
}
// fmt.Printf("OID: %s, value: %s ObjectIdentifier: %s\n", v.Name, v.Value.(string), valNode.Node.Name)
default:
// fmt.Printf("trap: %+v\n", v)
padValue = fmt.Sprintf("%v", v.Value)
}
}

if padKey != "" && padValue != "" {
pad[padKey] = padValue
}
}

if specificTrapFormat == "" {
if c.Debug {
log.Printf("skip because nothing template : %+v\n", pad)
}
return
}

funcmap := template.FuncMap{
"read": func(key string) string {
return fmt.Sprintf("%s", pad[key])
},
"addr": func() string {
return addr.IP.String()
},
}

// fmt.Printf("%+v\n", pad)

var tpl = template.New("").Funcs(funcmap)

if err := template.Must(tpl.Parse(specificTrapFormat)).Execute(os.Stdout, pad); err != nil {
log.Println(err)
}
}

0 comments on commit af5619d

Please sign in to comment.