├── .gitignore ├── examples ├── gosnmpgetnext.go ├── gosnmpget.go └── example.go ├── README.md ├── LICENSE ├── gosnmp_test.go ├── decode.go ├── helper.go ├── gosnmp.go └── packet.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | # Editor Files 25 | .*.sw* 26 | 27 | # Mac OS X crap 28 | .DS_Store -------------------------------------------------------------------------------- /examples/gosnmpgetnext.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Andreas Louca. All rights reserved. 2 | // Use of this source code is goverend by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "github.com/alouca/gosnmp" 11 | ) 12 | 13 | var ( 14 | cmdCommunity string 15 | cmdTarget string 16 | cmdOid string 17 | cmdDebug string 18 | cmdTimeout int64 19 | ) 20 | 21 | func init() { 22 | flag.StringVar(&cmdDebug, "debug", "", "Debug flag expects byte array of raw packet to test decoding") 23 | 24 | flag.StringVar(&cmdTarget, "target", "", "Target SNMP Agent") 25 | flag.StringVar(&cmdCommunity, "community", "public", "SNNP Community") 26 | flag.StringVar(&cmdOid, "oid", "", "OID") 27 | flag.Int64Var(&cmdTimeout, "timeout", 5, "Set the timeout in seconds") 28 | flag.Parse() 29 | } 30 | 31 | func main() { 32 | if cmdTarget == "" || cmdOid == "" { 33 | flag.PrintDefaults() 34 | return 35 | } 36 | 37 | s, err := gosnmp.NewGoSNMP(cmdTarget, cmdCommunity, gosnmp.Version2c, cmdTimeout) 38 | if cmdDebug == "yes" { 39 | s.SetDebug(true) 40 | s.SetVerbose(true) 41 | } 42 | if err != nil { 43 | fmt.Printf("Error creating SNMP instance: %s\n", err.Error()) 44 | return 45 | } 46 | 47 | s.SetTimeout(cmdTimeout) 48 | fmt.Printf("Getting %s\n", cmdOid) 49 | resp, err := s.GetNext(cmdOid) 50 | if err != nil { 51 | fmt.Printf("Error getting response: %s\n", err.Error()) 52 | } else { 53 | for _, v := range resp.Variables { 54 | fmt.Printf("%s -> ", v.Name) 55 | switch v.Type { 56 | case gosnmp.OctetString: 57 | if s, ok := v.Value.(string); ok { 58 | fmt.Printf("%s\n", s) 59 | } else { 60 | fmt.Printf("Response is not a string\n") 61 | } 62 | default: 63 | fmt.Printf("Type: %d - Value: %v\n", v.Type, v.Value) 64 | } 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /examples/gosnmpget.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Andreas Louca. All rights reserved. 2 | // Use of this source code is goverend by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "github.com/alouca/gosnmp" 11 | "strings" 12 | ) 13 | 14 | var ( 15 | cmdCommunity string 16 | cmdTarget string 17 | cmdOid string 18 | cmdDebug string 19 | cmdTimeout int64 20 | ) 21 | 22 | func init() { 23 | flag.StringVar(&cmdDebug, "debug", "", "Debug flag expects byte array of raw packet to test decoding") 24 | 25 | flag.StringVar(&cmdTarget, "target", "", "Target SNMP Agent") 26 | flag.StringVar(&cmdCommunity, "community", "public", "SNNP Community") 27 | flag.StringVar(&cmdOid, "oid", "", "The request OID. Multiple OIDs can be separated by a comma") 28 | flag.Int64Var(&cmdTimeout, "timeout", 5, "Set the timeout in seconds") 29 | flag.Parse() 30 | } 31 | 32 | func main() { 33 | if cmdTarget == "" || cmdOid == "" { 34 | flag.PrintDefaults() 35 | return 36 | } 37 | 38 | s, err := gosnmp.NewGoSNMP(cmdTarget, cmdCommunity, gosnmp.Version2c, cmdTimeout) 39 | if cmdDebug == "yes" { 40 | s.SetDebug(true) 41 | s.SetVerbose(true) 42 | } 43 | if err != nil { 44 | fmt.Printf("Error creating SNMP instance: %s\n", err.Error()) 45 | return 46 | } 47 | 48 | s.SetTimeout(cmdTimeout) 49 | fmt.Printf("Getting %s\n", cmdOid) 50 | oid := strings.Split(cmdOid, ",") 51 | resp, err := s.GetMulti(oid) 52 | if err != nil { 53 | fmt.Printf("Error getting response: %s\n", err.Error()) 54 | } else { 55 | for _, v := range resp.Variables { 56 | fmt.Printf("%s -> ", v.Name) 57 | switch v.Type { 58 | case gosnmp.OctetString: 59 | if s, ok := v.Value.(string); ok { 60 | fmt.Printf("%s\n", s) 61 | } else { 62 | fmt.Printf("Response is not a string\n") 63 | } 64 | default: 65 | fmt.Printf("Type: %d - Value: %v\n", v.Type, v.Value) 66 | } 67 | } 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /examples/example.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Andreas Louca. All rights reserved. 2 | // Use of this source code is goverend by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/hex" 9 | "flag" 10 | "fmt" 11 | "github.com/alouca/gosnmp" 12 | ) 13 | 14 | var ( 15 | cmdCommunity string 16 | cmdTarget string 17 | cmdOid string 18 | cmdDebug string 19 | cmdTimeout int64 20 | ) 21 | 22 | func init() { 23 | flag.StringVar(&cmdDebug, "debug", "", "Debug flag expects byte array of raw packet to test decoding") 24 | 25 | flag.StringVar(&cmdTarget, "target", "", "Target SNMP Agent") 26 | flag.StringVar(&cmdCommunity, "community", "public", "SNNP Community") 27 | flag.StringVar(&cmdOid, "oid", "", "OID") 28 | flag.Int64Var(&cmdTimeout, "timeout", 5, "Set the timeout in seconds") 29 | flag.Parse() 30 | } 31 | 32 | func main() { 33 | if cmdDebug != "" { 34 | fmt.Printf("Running in debug mode\n") 35 | s, err := gosnmp.NewGoSNMP("", "", gosnmp.Version2c, 5) 36 | s.SetDebug(true) 37 | s.SetVerbose(true) 38 | packet, err := hex.DecodeString(cmdDebug) 39 | if err != nil { 40 | fmt.Printf("Unable to decode raw packet: %s\n", err.Error()) 41 | return 42 | } 43 | 44 | pckt, err := s.Debug(packet) 45 | 46 | if err != nil { 47 | fmt.Printf("Error while debugging: %s\n", err.Error()) 48 | } else { 49 | for _, resp := range pckt.Variables { 50 | fmt.Printf("%s -> %v\n", resp.Name, resp.Value) 51 | } 52 | } 53 | 54 | return 55 | } 56 | 57 | if cmdTarget == "" || cmdOid == "" { 58 | flag.PrintDefaults() 59 | return 60 | } 61 | 62 | s, err := gosnmp.NewGoSNMP(cmdTarget, cmdCommunity, gosnmp.Version2c, cmdTimeout) 63 | s.SetDebug(false) 64 | s.SetVerbose(false) 65 | if err != nil { 66 | fmt.Printf("Error creating SNMP instance: %s\n", err.Error()) 67 | return 68 | } 69 | 70 | s.SetTimeout(cmdTimeout) 71 | fmt.Printf("Getting %s\n", cmdOid) 72 | resp, err := s.Get(cmdOid) 73 | if err != nil { 74 | fmt.Printf("Error getting response: %s\n", err.Error()) 75 | } else { 76 | for _, v := range resp.Variables { 77 | fmt.Printf("%s -> ", v.Name) 78 | switch v.Type { 79 | case gosnmp.OctetString: 80 | if s, ok := v.Value.(string); ok { 81 | fmt.Printf("%s\n", s) 82 | } else { 83 | fmt.Printf("Response is not a string\n") 84 | } 85 | default: 86 | fmt.Printf("Type: %d - Value: %v\n", v.Type, v.Value) 87 | } 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gosnmp 2 | ====== 3 | 4 | GoSNMP is a simple SNMP client library, written fully in Go. Currently it supports only GetRequest (with the rest GetNextRequest, SetRequest in the pipe line). Support for traps is also in the plans. 5 | 6 | 7 | Install 8 | ------- 9 | 10 | The easiest way to install is via go get: 11 | 12 | go get github.com/alouca/gosnmp 13 | 14 | License 15 | ------- 16 | 17 | Some parts of the code are borrowed by the Golang project (specifically some functions for unmarshaling BER responses), which are under the same terms and conditions as the Go language, which are marked appropriately in the source code. The rest of the code is under the BSD license. 18 | 19 | See the LICENSE file for more details. 20 | 21 | Usage 22 | ----- 23 | The library usage is pretty simple: 24 | 25 | ```go 26 | // Connect to 192.168.0.1 with timeout of 5 seconds 27 | 28 | import ( 29 | "github.com/alouca/gosnmp" 30 | "log" 31 | ) 32 | 33 | s, err := gosnmp.NewGoSNMP("61.147.69.87", "public", gosnmp.Version2c, 5) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | resp, err := s.Get(".1.3.6.1.2.1.1.1.0") 38 | if err == nil { 39 | for _, v := range resp.Variables { 40 | switch v.Type { 41 | case gosnmp.OctetString: 42 | log.Printf("Response: %s : %s : %s \n", v.Name, v.Value.(string), v.Type.String()) 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | The response value is always given as an interface{} depending on the PDU response from the SNMP server. For an example checkout examples/example.go. 49 | 50 | Responses are a struct of the following format: 51 | 52 | ```go 53 | type Variable struct { 54 | Name asn1.ObjectIdentifier 55 | Type Asn1BER 56 | Value interface{} 57 | } 58 | ``` 59 | 60 | Where Name is the OID encoded as an object identifier, Type is the encoding type of the response and Value is an interface{} type, with the response appropriately decoded. 61 | 62 | SNMP BER Types can be one of the following: 63 | 64 | ```go 65 | type Asn1BER byte 66 | 67 | const ( 68 | Integer Asn1BER = 0x02 69 | BitString = 0x03 70 | OctetString = 0x04 71 | Null = 0x05 72 | ObjectIdentifier = 0x06 73 | Counter32 = 0x41 74 | Gauge32 = 0x42 75 | TimeTicks = 0x43 76 | Opaque = 0x44 77 | NsapAddress = 0x45 78 | Counter64 = 0x46 79 | Uinteger32 = 0x47 80 | ) 81 | ``` 82 | 83 | GoSNMP supports most of the above values, subsequent releases will support all of them. 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013, Andreas Louca 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | Parts of the gosnmp code are from GoLang ASN.1 Library 27 | (as marked in the source code). 28 | For those part of code the following license applies: 29 | 30 | Copyright (c) 2009 The Go Authors. All rights reserved. 31 | 32 | Redistribution and use in source and binary forms, with or without 33 | modification, are permitted provided that the following conditions are 34 | met: 35 | 36 | * Redistributions of source code must retain the above copyright 37 | notice, this list of conditions and the following disclaimer. 38 | * Redistributions in binary form must reproduce the above 39 | copyright notice, this list of conditions and the following disclaimer 40 | in the documentation and/or other materials provided with the 41 | distribution. 42 | * Neither the name of Google Inc. nor the names of its 43 | contributors may be used to endorse or promote products derived from 44 | this software without specific prior written permission. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 47 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 48 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 49 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 50 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 51 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 52 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 53 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 54 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 55 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 56 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /gosnmp_test.go: -------------------------------------------------------------------------------- 1 | package gosnmp 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | var ( 10 | TestPackets []string = []string{ 11 | "3082003202010104067075626c6963a28200230201000201000201003082001630820012060a2b060102010202010a02410409b3fe85", 12 | // "30820132020101040977767370645f345f5fa28201200204264a86e6020100020100308201103082010c06082b060102010101000481ff436973636f20494f5320536f6674776172652c20633736303072737037323034335f727020536f6674776172652028633736303072737037323034335f72702d414456495053455256494345534b392d4d292c2056657273696f6e2031352e3328312953312c2052454c4541534520534f4654574152452028666331290d0a546563686e6963616c20537570706f72743a20687474703a2f2f7777772e636973636f2e636f6d2f74656368737570706f72740d0a436f707972696768742028632920313938362d3230313320627920436973636f2053797374656d732c20496e632e0d0a436f6d70696c6564205468752030372d4665622d31332030363a32", 13 | // "307b020101040977767370645f345f5fa26b020431b6dfa5020100020100305d305b06082b06010201010100044f4c696e757820646e733120322e362e33322d34352d73657276657220233130342d5562756e747520534d5020547565204665622031392032313a33353a3031205554432032303133207838365f3634", 14 | // "3036020101040977767370645f345f5fa2260204662322fa02010002010030183016060c2b060102011f0101010a8206460619ed7896f6e0", 15 | // "3081ce02010104067075626c6963a281c0020408659d0c0201000201003081b13012060a2b060102010202010a02410400f34d353012060a2b060102010202010a03410401119cc3300f060a2b060102010202010a04410100300f060a2b060102010202010a05410100300f060a2b060102010202010a06410100300f060a2b060102010202010a07410100300f060a2b060102010202010a08410100300f060a2b060102010202010a09410100300f060a2b060102010202010a0a4101003010060a2b060102010202010a1041020a59", 16 | } 17 | ) 18 | 19 | func BenchmarkUnmarshal(t *testing.B) { 20 | t.Log("Running Decode Benchmark\n") 21 | packet, _ := hex.DecodeString(TestPackets[0]) 22 | s, _ := NewGoSNMP("", "", Version2c, 5) 23 | for i := 0; i < t.N; i++ { 24 | s.Debug(packet) 25 | } 26 | } 27 | 28 | func TestDecode(t *testing.T) { 29 | t.Log("Running Decode Test\n") 30 | s, _ := NewGoSNMP("", "", Version2c, 5) 31 | s.SetDebug(true) 32 | s.SetVerbose(true) 33 | 34 | for _, p := range TestPackets { 35 | packet, err := hex.DecodeString(p) 36 | if err != nil { 37 | t.Errorf("Unable to decode raw packet: %s\n", err.Error()) 38 | return 39 | } 40 | 41 | pckt, err := s.Debug(packet) 42 | 43 | if err != nil { 44 | fmt.Printf("Error while debugging: %s\n", err.Error()) 45 | } else { 46 | for _, resp := range pckt.Variables { 47 | fmt.Printf("%s -> %v\n", resp.Name, resp.Value) 48 | } 49 | } 50 | 51 | } 52 | } 53 | 54 | func TestWalk(t *testing.T) { 55 | t.Log("Running walk test") 56 | s, _ := NewGoSNMP("sample", "demo", Version2c, 5) 57 | s.SetDebug(true) 58 | s.SetVerbose(true) 59 | res, err := s.Walk(".1.3.6.1.2.1.2") 60 | 61 | if err != nil { 62 | t.Fatalf("Unable to perform walk: %s\n", err.Error()) 63 | } else { 64 | for i, r := range res { 65 | t.Logf("%d: %s -> %v", i, r.Name, r.Value) 66 | } 67 | } 68 | 69 | } 70 | 71 | // Test SNMP connections with different ports 72 | func TestConnect(t *testing.T) { 73 | t.Log("Running connection tests") 74 | targets := []string{"localhost", "localhost:161"} 75 | 76 | for _, target := range targets { 77 | _, err := NewGoSNMP(target, "public", Version2c, 5) 78 | 79 | if err != nil { 80 | t.Fatalf("Unable to connect to %s: %s\n", target, err) 81 | } 82 | } 83 | } 84 | 85 | // Test Data Type stringer 86 | func TestDataTypeStrings(t *testing.T) { 87 | var dataType Asn1BER 88 | 89 | // Defined data type 90 | dataType = Integer 91 | 92 | if dataType.String() != "Integer" { 93 | t.Errorf("Data Type strings:\n\twant: %q\n\tgot : %q", "Integer", dataType) 94 | } 95 | 96 | // Undefined data type 97 | dataType = 0x00 98 | 99 | if dataType.String() != "Unknown" { 100 | t.Errorf("Data Type strings:\n\twant: %q\n\tgot : %q", "Integer", dataType) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Andreas Louca. All rights reserved. 2 | // Use of this source code is goverend by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gosnmp 6 | 7 | import ( 8 | "fmt" 9 | "net" 10 | ) 11 | 12 | type Asn1BER byte 13 | 14 | // SNMP Data Types 15 | const ( 16 | Integer Asn1BER = 0x02 17 | BitString = 0x03 18 | OctetString = 0x04 19 | Null = 0x05 20 | ObjectIdentifier = 0x06 21 | Sequence = 0x30 22 | IpAddress = 0x40 23 | Counter32 = 0x41 24 | Gauge32 = 0x42 25 | TimeTicks = 0x43 26 | Opaque = 0x44 27 | NsapAddress = 0x45 28 | Counter64 = 0x46 29 | Uinteger32 = 0x47 30 | NoSuchObject = 0x80 31 | NoSuchInstance = 0x81 32 | GetRequest = 0xa0 33 | GetNextRequest = 0xa1 34 | GetResponse = 0xa2 35 | SetRequest = 0xa3 36 | Trap = 0xa4 37 | GetBulkRequest = 0xa5 38 | EndOfMibView = 0x82 39 | ) 40 | 41 | // String representations of each SNMP Data Type 42 | var dataTypeStrings = map[Asn1BER]string{ 43 | Integer: "Integer", 44 | BitString: "BitString", 45 | OctetString: "OctetString", 46 | Null: "Null", 47 | ObjectIdentifier: "ObjectIdentifier", 48 | IpAddress: "IpAddress", 49 | Sequence: "Sequence", 50 | Counter32: "Counter32", 51 | Gauge32: "Gauge32", 52 | TimeTicks: "TimeTicks", 53 | Opaque: "Opaque", 54 | NsapAddress: "NsapAddress", 55 | Counter64: "Counter64", 56 | Uinteger32: "Uinteger32", 57 | NoSuchObject: "NoSuchObject", 58 | NoSuchInstance: "NoSuchInstance", 59 | GetRequest: "GetRequest", 60 | GetNextRequest: "GetNextRequest", 61 | GetResponse: "GetResponse", 62 | SetRequest: "SetRequest", 63 | Trap: "Trap", 64 | GetBulkRequest: "GetBulkRequest", 65 | EndOfMibView: "endOfMib", 66 | } 67 | 68 | func (dataType Asn1BER) String() string { 69 | str, ok := dataTypeStrings[dataType] 70 | 71 | if !ok { 72 | str = "Unknown" 73 | } 74 | 75 | return str 76 | } 77 | 78 | type Variable struct { 79 | Name []int 80 | Type Asn1BER 81 | Size uint64 82 | Value interface{} 83 | } 84 | 85 | func decodeValue(valueType Asn1BER, data []byte) (retVal *Variable, err error) { 86 | retVal = new(Variable) 87 | retVal.Size = uint64(len(data)) 88 | 89 | switch Asn1BER(valueType) { 90 | 91 | // Integer 92 | case Integer: 93 | ret, err := parseInt(data) 94 | if err != nil { 95 | break 96 | } 97 | retVal.Type = Integer 98 | retVal.Value = ret 99 | // Octet 100 | case OctetString: 101 | retVal.Type = OctetString 102 | retVal.Value = string(data) 103 | case ObjectIdentifier: 104 | retVal.Type = ObjectIdentifier 105 | retVal.Value, _ = parseObjectIdentifier(data) 106 | // IpAddress 107 | case IpAddress: 108 | retVal.Type = IpAddress 109 | retVal.Value = net.IP{data[0], data[1], data[2], data[3]} 110 | // Counter32 111 | case Counter32: 112 | ret := Uvarint(data) 113 | retVal.Type = Counter32 114 | retVal.Value = ret 115 | case TimeTicks: 116 | ret, err := parseInt(data) 117 | if err != nil { 118 | break 119 | } 120 | retVal.Type = TimeTicks 121 | retVal.Value = ret 122 | // Gauge32 123 | case Gauge32: 124 | ret := Uvarint(data) 125 | retVal.Type = Gauge32 126 | retVal.Value = ret 127 | case Counter64: 128 | ret := Uvarint(data) 129 | retVal.Type = Counter64 130 | retVal.Value = ret 131 | case Null: 132 | retVal.Value = nil 133 | case Sequence: 134 | // NOOP 135 | retVal.Value = data 136 | case GetResponse: 137 | // NOOP 138 | retVal.Value = data 139 | case GetRequest: 140 | // NOOP 141 | retVal.Value = data 142 | case EndOfMibView: 143 | retVal.Type = EndOfMibView 144 | retVal.Value = "endOfMib" 145 | case GetBulkRequest: 146 | // NOOP 147 | retVal.Value = data 148 | case NoSuchInstance: 149 | return nil, fmt.Errorf("No such instance") 150 | case NoSuchObject: 151 | return nil, fmt.Errorf("No such object") 152 | default: 153 | err = fmt.Errorf("Unable to decode %s %#v - not implemented", valueType, valueType) 154 | } 155 | 156 | return retVal, err 157 | } 158 | 159 | // Parses UINT16 160 | func ParseUint16(content []byte) int { 161 | number := uint8(content[1]) | uint8(content[0])<<8 162 | //fmt.Printf("\t%d\n", number) 163 | 164 | return int(number) 165 | } 166 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gosnmp 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | ) 12 | 13 | func marshalObjectIdentifier(oid []int) (ret []byte, err error) { 14 | out := bytes.NewBuffer(make([]byte, 0, 128)) 15 | if len(oid) < 2 || oid[0] > 6 || oid[1] >= 40 { 16 | return nil, errors.New("invalid object identifier") 17 | } 18 | 19 | err = out.WriteByte(byte(oid[0]*40 + oid[1])) 20 | if err != nil { 21 | return 22 | } 23 | for i := 2; i < len(oid); i++ { 24 | err = marshalBase128Int(out, int64(oid[i])) 25 | if err != nil { 26 | return 27 | } 28 | } 29 | 30 | ret = out.Bytes() 31 | 32 | return 33 | } 34 | 35 | // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and 36 | // returns it. An object identifier is a sequence of variable length integers 37 | // that are assigned in a hierarchy. 38 | func parseObjectIdentifier(bytes []byte) (s []int, err error) { 39 | if len(bytes) == 0 { 40 | err = fmt.Errorf("zero length OBJECT IDENTIFIER") 41 | return 42 | } 43 | 44 | // In the worst case, we get two elements from the first byte (which is 45 | // encoded differently) and then every varint is a single byte long. 46 | s = make([]int, len(bytes)+1) 47 | 48 | // The first byte is 40*value1 + value2: 49 | s[0] = int(bytes[0]) / 40 50 | s[1] = int(bytes[0]) % 40 51 | i := 2 52 | for offset := 1; offset < len(bytes); i++ { 53 | var v int 54 | v, offset, err = parseBase128Int(bytes, offset) 55 | if err != nil { 56 | return 57 | } 58 | s[i] = v 59 | } 60 | s = s[0:i] 61 | return 62 | } 63 | 64 | // parseBase128Int parses a base-128 encoded int from the given offset in the 65 | // given byte slice. It returns the value and the new offset. 66 | func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) { 67 | offset = initOffset 68 | for shifted := 0; offset < len(bytes); shifted++ { 69 | if shifted > 4 { 70 | err = fmt.Errorf("Structural Error: base 128 integer too large") 71 | return 72 | } 73 | ret <<= 7 74 | b := bytes[offset] 75 | ret |= int(b & 0x7f) 76 | offset++ 77 | if b&0x80 == 0 { 78 | return 79 | } 80 | } 81 | err = fmt.Errorf("Syntax Error: truncated base 128 integer") 82 | return 83 | } 84 | 85 | func marshalBase128Int(out *bytes.Buffer, n int64) (err error) { 86 | if n == 0 { 87 | err = out.WriteByte(0) 88 | return 89 | } 90 | 91 | l := 0 92 | for i := n; i > 0; i >>= 7 { 93 | l++ 94 | } 95 | 96 | for i := l - 1; i >= 0; i-- { 97 | o := byte(n >> uint(i*7)) 98 | o &= 0x7f 99 | if i != 0 { 100 | o |= 0x80 101 | } 102 | err = out.WriteByte(o) 103 | if err != nil { 104 | return 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | 111 | // parseInt64 treats the given bytes as a big-endian, signed integer and 112 | // returns the result. 113 | func parseInt64(bytes []byte) (ret int64, err error) { 114 | if len(bytes) > 8 { 115 | // We'll overflow an int64 in this case. 116 | err = errors.New("integer too large") 117 | return 118 | } 119 | for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { 120 | ret <<= 8 121 | ret |= int64(bytes[bytesRead]) 122 | } 123 | 124 | // Shift up and down in order to sign extend the result. 125 | ret <<= 64 - uint8(len(bytes))*8 126 | ret >>= 64 - uint8(len(bytes))*8 127 | return 128 | } 129 | 130 | // parseInt treats the given bytes as a big-endian, signed integer and returns 131 | // the result. 132 | func parseInt(bytes []byte) (int, error) { 133 | ret64, err := parseInt64(bytes) 134 | if err != nil { 135 | return 0, err 136 | } 137 | if ret64 != int64(int(ret64)) { 138 | return 0, errors.New("integer too large") 139 | } 140 | return int(ret64), nil 141 | } 142 | 143 | func Uvarint(buf []byte) (x uint64) { 144 | for i, b := range buf { 145 | x = x<<8 + uint64(b) 146 | if i == 7 { 147 | return 148 | } 149 | } 150 | return 151 | } 152 | 153 | // BIT STRING 154 | 155 | // BitStringValue is the structure to use when you want an ASN.1 BIT STRING type. A 156 | // bit string is padded up to the nearest byte in memory and the number of 157 | // valid bits is recorded. Padding bits will be zero. 158 | type BitStringValue struct { 159 | Bytes []byte // bits packed into bytes. 160 | BitLength int // length in bits. 161 | } 162 | 163 | // At returns the bit at the given index. If the index is out of range it 164 | // returns false. 165 | func (b BitStringValue) At(i int) int { 166 | if i < 0 || i >= b.BitLength { 167 | return 0 168 | } 169 | x := i / 8 170 | y := 7 - uint(i%8) 171 | return int(b.Bytes[x]>>y) & 1 172 | } 173 | 174 | // RightAlign returns a slice where the padding bits are at the beginning. The 175 | // slice may share memory with the BitString. 176 | func (b BitStringValue) RightAlign() []byte { 177 | shift := uint(8 - (b.BitLength % 8)) 178 | if shift == 8 || len(b.Bytes) == 0 { 179 | return b.Bytes 180 | } 181 | 182 | a := make([]byte, len(b.Bytes)) 183 | a[0] = b.Bytes[0] >> shift 184 | for i := 1; i < len(b.Bytes); i++ { 185 | a[i] = b.Bytes[i-1] << (8 - shift) 186 | a[i] |= b.Bytes[i] >> shift 187 | } 188 | 189 | return a 190 | } 191 | 192 | // parseBitString parses an ASN.1 bit string from the given byte slice and returns it. 193 | func parseBitString(bytes []byte) (ret BitStringValue, err error) { 194 | if len(bytes) == 0 { 195 | err = errors.New("zero length BIT STRING") 196 | return 197 | } 198 | paddingBits := int(bytes[0]) 199 | if paddingBits > 7 || 200 | len(bytes) == 1 && paddingBits > 0 || 201 | bytes[len(bytes)-1]&((1< 0 { 86 | if strings.Index(res.Variables[0].Name, requestOid) > -1 { 87 | if res.Variables[0].Value == "endOfMib" { 88 | break 89 | } 90 | c <- res.Variables[0] 91 | // Set to the next 92 | oid = res.Variables[0].Name 93 | x.Log.Debug("Moving to %s\n", oid) 94 | } else { 95 | x.Log.Debug("Root OID mismatch, stopping walk\n") 96 | break 97 | } 98 | } else { 99 | break 100 | } 101 | } else { 102 | break 103 | } 104 | 105 | } 106 | close(c) 107 | return nil 108 | } 109 | 110 | // BulkWalk sends an walks the target using SNMP BULK-GET requests. This returns 111 | // a Variable with the response and the error condition 112 | func (x *GoSNMP) BulkWalk(maxRepetitions uint8, oid string) (results []SnmpPDU, err error) { 113 | if oid == "" { 114 | return nil, fmt.Errorf("No OID given\n") 115 | } 116 | return x._bulkWalk(maxRepetitions, oid, oid) 117 | } 118 | func (x *GoSNMP) _bulkWalk(maxRepetitions uint8, searchingOid string, rootOid string) (results []SnmpPDU, err error) { 119 | response, err := x.GetBulk(0, maxRepetitions, searchingOid) 120 | if err != nil { 121 | return 122 | } 123 | for i, v := range response.Variables { 124 | if v.Value == "endOfMib" { 125 | return 126 | } 127 | // is this variable still in the requested oid range 128 | if strings.HasPrefix(v.Name, rootOid) { 129 | results = append(results, v) 130 | // is the last oid received still in the requested range 131 | if i == len(response.Variables)-1 { 132 | var subResults []SnmpPDU 133 | subResults, err = x._bulkWalk(maxRepetitions, v.Name, rootOid) 134 | if err != nil { 135 | return 136 | } 137 | results = append(results, subResults...) 138 | } 139 | } 140 | } 141 | return 142 | } 143 | 144 | // Walk will SNMP walk the target, blocking until the process is complete 145 | func (x *GoSNMP) Walk(oid string) (results []SnmpPDU, err error) { 146 | if oid == "" { 147 | return nil, fmt.Errorf("No OID given\n") 148 | } 149 | results = make([]SnmpPDU, 0) 150 | requestOid := oid 151 | 152 | for { 153 | res, err := x.GetNext(oid) 154 | if err != nil { 155 | return results, err 156 | } 157 | if res != nil { 158 | if len(res.Variables) > 0 { 159 | if strings.Index(res.Variables[0].Name, requestOid) > -1 { 160 | results = append(results, res.Variables[0]) 161 | // Set to the next 162 | oid = res.Variables[0].Name 163 | x.Log.Debug("Moving to %s\n", oid) 164 | } else { 165 | x.Log.Debug("Root OID mismatch, stopping walk\n") 166 | break 167 | } 168 | } else { 169 | break 170 | } 171 | } else { 172 | break 173 | } 174 | 175 | } 176 | return 177 | } 178 | 179 | // sendPacket marshals & send an SNMP request. Unmarshals the response and 180 | // returns back the parsed SNMP packet 181 | func (x *GoSNMP) sendPacket(packet *SnmpPacket) (*SnmpPacket, error) { 182 | // Set timeouts on the connection 183 | deadline := time.Now() 184 | x.conn.SetDeadline(deadline.Add(x.Timeout)) 185 | 186 | // Create random Request-ID 187 | packet.RequestID = rand.Uint32() 188 | 189 | // Marshal it 190 | fBuf, err := packet.marshal() 191 | 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | // Send the packet! 197 | _, err = x.conn.Write(fBuf) 198 | if err != nil { 199 | return nil, fmt.Errorf("Error writing to socket: %s\n", err.Error()) 200 | } 201 | // Try to read the response 202 | resp := make([]byte, 8192, 8192) 203 | n, err := x.conn.Read(resp) 204 | 205 | if err != nil { 206 | return nil, fmt.Errorf("Error reading from UDP: %s\n", err.Error()) 207 | } 208 | 209 | // Unmarshal the read bytes 210 | pdu, err := Unmarshal(resp[:n]) 211 | 212 | if err != nil { 213 | return nil, fmt.Errorf("Unable to decode packet: %s\n", err.Error()) 214 | } 215 | 216 | if len(pdu.Variables) < 1 { 217 | return nil, fmt.Errorf("No responses received.") 218 | } 219 | 220 | // check Request-ID 221 | if pdu.RequestID != packet.RequestID { 222 | return nil, fmt.Errorf("Request ID mismatch") 223 | } 224 | 225 | return pdu, nil 226 | } 227 | 228 | // GetNext sends an SNMP Get Next Request to the target. Returns the next 229 | // variable response from the OID given or an error 230 | func (x *GoSNMP) GetNext(oid string) (*SnmpPacket, error) { 231 | return x.request(GetNextRequest, oid) 232 | } 233 | 234 | // Debug function. Unmarshals raw bytes and returns the result without the network part 235 | func (x *GoSNMP) Debug(data []byte) (*SnmpPacket, error) { 236 | packet, err := Unmarshal(data) 237 | 238 | if err != nil { 239 | return nil, fmt.Errorf("Unable to decode packet: %s\n", err.Error()) 240 | } 241 | return packet, nil 242 | } 243 | 244 | // GetBulk sends an SNMP BULK-GET request to the target. Returns a Variable with 245 | // the response or an error 246 | func (x *GoSNMP) GetBulk(nonRepeaters, maxRepetitions uint8, oids ...string) (*SnmpPacket, error) { 247 | // Create and send the packet 248 | return x.sendPacket(&SnmpPacket{ 249 | Version: x.Version, 250 | Community: x.Community, 251 | RequestType: GetBulkRequest, 252 | NonRepeaters: nonRepeaters, 253 | MaxRepetitions: maxRepetitions, 254 | Variables: oidsToPbus(oids...), 255 | }) 256 | } 257 | 258 | // Get sends an SNMP GET request to the target. Returns a Variable with the 259 | // response or an error 260 | func (x *GoSNMP) Get(oid string) (*SnmpPacket, error) { 261 | return x.request(GetRequest, oid) 262 | } 263 | 264 | // GetMulti sends an SNMP GET request to the target. Returns a Variable with the 265 | // response or an error 266 | func (x *GoSNMP) GetMulti(oids []string) (*SnmpPacket, error) { 267 | return x.request(GetRequest, oids...) 268 | } 269 | 270 | func (x *GoSNMP) request(requestType Asn1BER, oids ...string) (*SnmpPacket, error) { 271 | // Create and send the packet 272 | return x.sendPacket(&SnmpPacket{ 273 | Version: x.Version, 274 | Community: x.Community, 275 | RequestType: requestType, 276 | Variables: oidsToPbus(oids...), 277 | }) 278 | } 279 | 280 | func oidsToPbus(oids ...string) []SnmpPDU { 281 | pdus := make([]SnmpPDU, len(oids)) 282 | for i, oid := range oids { 283 | pdus[i] = SnmpPDU{Name: oid, Type: Null} 284 | } 285 | return pdus 286 | } 287 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package gosnmp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strconv" 9 | "strings" 10 | 11 | l "github.com/alouca/gologger" 12 | ) 13 | 14 | type SnmpVersion uint8 15 | 16 | const ( 17 | Version1 SnmpVersion = 0x0 18 | Version2c SnmpVersion = 0x1 19 | ) 20 | 21 | func (s SnmpVersion) String() string { 22 | if s == Version1 { 23 | return "1" 24 | } else if s == Version2c { 25 | return "2c" 26 | } 27 | return "U" 28 | } 29 | 30 | type SnmpPacket struct { 31 | Version SnmpVersion 32 | Community string 33 | RequestType Asn1BER 34 | RequestID uint32 35 | Error uint8 36 | ErrorIndex uint8 37 | NonRepeaters uint8 38 | MaxRepetitions uint8 39 | Variables []SnmpPDU 40 | } 41 | 42 | type SnmpPDU struct { 43 | Name string 44 | Type Asn1BER 45 | Value interface{} 46 | } 47 | 48 | func Unmarshal(packet []byte) (*SnmpPacket, error) { 49 | log := l.GetDefaultLogger() 50 | 51 | log.Debug("Begin SNMP Packet unmarshal\n") 52 | 53 | //var err error 54 | response := new(SnmpPacket) 55 | response.Variables = make([]SnmpPDU, 0, 5) 56 | 57 | // Start parsing the packet 58 | var cursor uint64 = 0 59 | 60 | // First bytes should be 0x30 61 | if Asn1BER(packet[0]) == Sequence { 62 | // Parse packet length 63 | ber, err := parseField(packet) 64 | 65 | if err != nil { 66 | log.Error("Unable to parse packet header: %s\n", err.Error()) 67 | return nil, err 68 | } 69 | 70 | log.Debug("Packet sanity verified, we got all the bytes (%d)\n", ber.DataLength) 71 | 72 | cursor += ber.HeaderLength 73 | // Parse SNMP Version 74 | rawVersion, err := parseField(packet[cursor:]) 75 | 76 | if err != nil { 77 | return nil, fmt.Errorf("Error parsing SNMP packet version: %s", err.Error()) 78 | } 79 | 80 | cursor += rawVersion.DataLength + rawVersion.HeaderLength 81 | if version, ok := rawVersion.BERVariable.Value.(int); ok { 82 | response.Version = SnmpVersion(version) 83 | log.Debug("Parsed Version %d\n", version) 84 | } 85 | 86 | // Parse community 87 | rawCommunity, err := parseField(packet[cursor:]) 88 | if err != nil { 89 | log.Debug("Unable to parse Community Field: %s\n", err) 90 | } 91 | cursor += rawCommunity.DataLength + rawCommunity.HeaderLength 92 | 93 | if community, ok := rawCommunity.BERVariable.Value.(string); ok { 94 | response.Community = community 95 | log.Debug("Parsed community %s\n", community) 96 | } 97 | 98 | rawPDU, err := parseField(packet[cursor:]) 99 | 100 | if err != nil { 101 | log.Debug("Unable to parse SNMP PDU: %s\n", err.Error()) 102 | } 103 | response.RequestType = rawPDU.Type 104 | 105 | switch rawPDU.Type { 106 | default: 107 | log.Debug("Unsupported SNMP Packet Type %s\n", rawPDU.Type.String()) 108 | log.Debug("PDU Size is %d\n", rawPDU.DataLength) 109 | case GetRequest, GetResponse, GetBulkRequest: 110 | log.Debug("SNMP Packet is %s\n", rawPDU.Type.String()) 111 | log.Debug("PDU Size is %d\n", rawPDU.DataLength) 112 | cursor += rawPDU.HeaderLength 113 | 114 | // Parse Request ID 115 | rawRequestId, err := parseField(packet[cursor:]) 116 | 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | cursor += rawRequestId.DataLength + rawRequestId.HeaderLength 122 | if requestid, ok := rawRequestId.BERVariable.Value.(int); ok { 123 | response.RequestID = uint32(requestid) 124 | log.Debug("Parsed Request ID: %d\n", requestid) 125 | } 126 | 127 | // Parse Error 128 | rawError, err := parseField(packet[cursor:]) 129 | 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | cursor += rawError.DataLength + rawError.HeaderLength 135 | if errorNo, ok := rawError.BERVariable.Value.(int); ok { 136 | response.Error = uint8(errorNo) 137 | } 138 | 139 | // Parse Error Index 140 | rawErrorIndex, err := parseField(packet[cursor:]) 141 | 142 | if err != nil { 143 | return nil, err 144 | } 145 | 146 | cursor += rawErrorIndex.DataLength + rawErrorIndex.HeaderLength 147 | 148 | if errorindex, ok := rawErrorIndex.BERVariable.Value.(int); ok { 149 | response.ErrorIndex = uint8(errorindex) 150 | } 151 | 152 | log.Debug("Request ID: %d Error: %d Error Index: %d\n", response.RequestID, response.Error, response.ErrorIndex) 153 | rawResp, err := parseField(packet[cursor:]) 154 | 155 | if err != nil { 156 | return nil, err 157 | } 158 | 159 | cursor += rawResp.HeaderLength 160 | // Loop & parse Varbinds 161 | for cursor < uint64(len(packet)) { 162 | log.Debug("Parsing var bind response (Cursor at %d/%d)", cursor, len(packet)) 163 | 164 | rawVarbind, err := parseField(packet[cursor:]) 165 | 166 | if err != nil { 167 | return nil, err 168 | } 169 | 170 | cursor += rawVarbind.HeaderLength 171 | log.Debug("Varbind length: %d/%d\n", rawVarbind.HeaderLength, rawVarbind.DataLength) 172 | 173 | log.Debug("Parsing OID (Cursor at %d)\n", cursor) 174 | // Parse OID 175 | rawOid, err := parseField(packet[cursor:]) 176 | 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | cursor += rawOid.HeaderLength + rawOid.DataLength 182 | 183 | log.Debug("OID (%v) Field was %d bytes\n", rawOid, rawOid.DataLength) 184 | 185 | rawValue, err := parseField(packet[cursor:]) 186 | 187 | if err != nil { 188 | return nil, err 189 | } 190 | cursor += rawValue.HeaderLength + rawValue.DataLength 191 | 192 | log.Debug("Value field was %d bytes\n", rawValue.DataLength) 193 | 194 | if oid, ok := rawOid.BERVariable.Value.([]int); ok { 195 | log.Debug("Varbind decoding success\n") 196 | response.Variables = append(response.Variables, SnmpPDU{oidToString(oid), rawValue.Type, rawValue.BERVariable.Value}) 197 | } 198 | } 199 | 200 | } 201 | } else { 202 | return nil, fmt.Errorf("Invalid packet header\n") 203 | } 204 | 205 | return response, nil 206 | } 207 | 208 | type RawBER struct { 209 | Type Asn1BER 210 | HeaderLength uint64 211 | DataLength uint64 212 | Data []byte 213 | BERVariable *Variable 214 | } 215 | 216 | // Parses a given field, return the ASN.1 BER Type, its header length and the data 217 | func parseField(data []byte) (*RawBER, error) { 218 | log := l.GetDefaultLogger() 219 | var err error 220 | 221 | if len(data) == 0 { 222 | return nil, fmt.Errorf("Unable to parse BER: Data length 0") 223 | } 224 | 225 | ber := new(RawBER) 226 | 227 | ber.Type = Asn1BER(data[0]) 228 | 229 | // Parse Length 230 | length := data[1] 231 | 232 | // Check if this is padded or not 233 | if length > 0x80 { 234 | length = length - 0x80 235 | log.Debug("Field length is padded to %d bytes\n", length) 236 | ber.DataLength = Uvarint(data[2 : 2+length]) 237 | log.Debug("Decoded final length: %d\n", ber.DataLength) 238 | 239 | ber.HeaderLength = 2 + uint64(length) 240 | 241 | } else { 242 | ber.HeaderLength = 2 243 | ber.DataLength = uint64(length) 244 | } 245 | 246 | // Do sanity checks 247 | if ber.DataLength > uint64(len(data)) { 248 | return nil, fmt.Errorf("Unable to parse BER: provided data length is longer than actual data (%d vs %d)", ber.DataLength, len(data)) 249 | } 250 | 251 | ber.Data = data[ber.HeaderLength : ber.HeaderLength+ber.DataLength] 252 | 253 | ber.BERVariable, err = decodeValue(ber.Type, ber.Data) 254 | 255 | if err != nil { 256 | return nil, fmt.Errorf("Unable to decode value: %s\n", err.Error()) 257 | } 258 | 259 | return ber, nil 260 | } 261 | 262 | func (packet *SnmpPacket) marshal() ([]byte, error) { 263 | // Marshal the SNMP PDU 264 | snmpPduBuffer := make([]byte, 0, 1024) 265 | snmpPduBuf := bytes.NewBuffer(snmpPduBuffer) 266 | 267 | requestIDBytes := make([]byte, 4) 268 | binary.BigEndian.PutUint32(requestIDBytes, packet.RequestID) 269 | snmpPduBuf.Write(append([]byte{byte(packet.RequestType), 0, 2, 4}, requestIDBytes...)) 270 | 271 | switch packet.RequestType { 272 | case GetBulkRequest: 273 | snmpPduBuf.Write([]byte{ 274 | 2, 1, packet.NonRepeaters, 275 | 2, 1, packet.MaxRepetitions, 276 | }) 277 | default: 278 | snmpPduBuf.Write([]byte{ 279 | 2, 1, packet.Error, 280 | 2, 1, packet.ErrorIndex, 281 | }) 282 | } 283 | 284 | snmpPduBuf.Write([]byte{byte(Sequence), 0}) 285 | 286 | pduLength := 0 287 | for _, varlist := range packet.Variables { 288 | pdu, err := marshalPDU(&varlist) 289 | 290 | if err != nil { 291 | return nil, err 292 | } 293 | pduLength += len(pdu) 294 | snmpPduBuf.Write(pdu) 295 | } 296 | 297 | pduBytes := snmpPduBuf.Bytes() 298 | // Varbind list length 299 | pduBytes[15] = byte(pduLength) 300 | // SNMP PDU length (PDU header + varbind list length) 301 | pduBytes[1] = byte(pduLength + 14) 302 | 303 | // Prepare the buffer to send 304 | buffer := make([]byte, 0, 1024) 305 | buf := bytes.NewBuffer(buffer) 306 | 307 | // Write the message type 0x30 308 | buf.Write([]byte{byte(Sequence)}) 309 | 310 | // Find the size of the whole data 311 | // The extra 5 bytes are snmp verion (3 bytes) + community string type and 312 | // community string length 313 | dataLength := len(pduBytes) + len(packet.Community) + 5 314 | 315 | // If the data is 128 bytes or larger then we need to use 2 or more bytes 316 | // to represent the length 317 | if dataLength >= 128 { 318 | // Work out how many bytes we require 319 | bytesNeeded := int(math.Ceil(math.Log2(float64(dataLength)) / 8)) 320 | // Set the most significant bit to 1 to show we are using the long for, 321 | // then the 7 least significant bits to show how many bytes will be used 322 | // to represent the length 323 | lengthIdentifier := 128 + bytesNeeded 324 | buf.Write([]byte{uint8(lengthIdentifier)}) 325 | lengthBytes := make([]byte, bytesNeeded) 326 | for i := bytesNeeded; i >= 1; i-- { 327 | lengthBytes[i-1] = uint8(dataLength % 256) 328 | dataLength = dataLength >> 8 329 | } 330 | buf.Write(lengthBytes) 331 | } else { 332 | buf.Write([]byte{uint8(dataLength)}) 333 | } 334 | 335 | // Write the Version 336 | buf.Write([]byte{2, 1, byte(packet.Version)}) 337 | 338 | // Write Community 339 | buf.Write([]byte{4, uint8(len(packet.Community))}) 340 | buf.WriteString(packet.Community) 341 | 342 | // Write the PDU 343 | buf.Write(pduBytes) 344 | 345 | // Write the 346 | //buf.Write([]byte{packet.RequestType, uint8(17 + len(mOid)), 2, 1, 1, 2, 1, 0, 2, 1, 0, 0x30, uint8(6 + len(mOid)), 0x30, uint8(4 + len(mOid)), 6, uint8(len(mOid))}) 347 | //buf.Write(mOid) 348 | //buf.Write([]byte{5, 0}) 349 | 350 | return buf.Bytes(), nil 351 | } 352 | 353 | func marshalPDU(pdu *SnmpPDU) ([]byte, error) { 354 | oid, err := marshalOID(pdu.Name) 355 | if err != nil { 356 | return nil, err 357 | } 358 | 359 | pduBuffer := make([]byte, 0, 1024) 360 | pduBuf := bytes.NewBuffer(pduBuffer) 361 | 362 | // Mashal the PDU type into the appropriate BER 363 | switch pdu.Type { 364 | case Null: 365 | pduBuf.Write([]byte{byte(Sequence), byte(len(oid) + 4)}) 366 | pduBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) 367 | pduBuf.Write(oid) 368 | pduBuf.Write([]byte{Null, 0x00}) 369 | default: 370 | return nil, fmt.Errorf("Unable to marshal PDU: unknown BER type %d", pdu.Type) 371 | } 372 | 373 | return pduBuf.Bytes(), nil 374 | } 375 | 376 | func oidToString(oid []int) (ret string) { 377 | values := make([]interface{}, len(oid)) 378 | for i, v := range oid { 379 | values[i] = v 380 | } 381 | return fmt.Sprintf(strings.Repeat(".%d", len(oid)), values...) 382 | } 383 | 384 | func marshalOID(oid string) ([]byte, error) { 385 | var err error 386 | 387 | // Encode the oid 388 | oid = strings.Trim(oid, ".") 389 | oidParts := strings.Split(oid, ".") 390 | oidBytes := make([]int, len(oidParts)) 391 | 392 | // Convert the string OID to an array of integers 393 | for i := 0; i < len(oidParts); i++ { 394 | oidBytes[i], err = strconv.Atoi(oidParts[i]) 395 | if err != nil { 396 | return nil, fmt.Errorf("Unable to parse OID: %s\n", err.Error()) 397 | } 398 | } 399 | 400 | mOid, err := marshalObjectIdentifier(oidBytes) 401 | 402 | if err != nil { 403 | return nil, fmt.Errorf("Unable to marshal OID: %s\n", err.Error()) 404 | } 405 | 406 | return mOid, err 407 | } 408 | --------------------------------------------------------------------------------