├── .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 | // Connect to 192.168.0.1 with timeout of 5 seconds 26 | 27 | import ( 28 | "github.com/alouca/gosnmp" 29 | "log" 30 | ) 31 | 32 | s, err := gosnmp.NewGoSNMP("61.147.69.87", "public", gosnmp.Version2c, 5) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | resp, err := s.Get(".1.3.6.1.2.1.1.1.0") 37 | if err == nil { 38 | for _, v := range resp.Variables { 39 | switch v.Type { 40 | case gosnmp.OctetString: 41 | log.Printf("Response: %s : %s : %s \n", v.Name, v.Value.(string), v.Type.String()) 42 | } 43 | } 44 | } 45 | 46 | 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. 47 | 48 | Responses are a struct of the following format: 49 | 50 | type Variable struct { 51 | Name asn1.ObjectIdentifier 52 | Type Asn1BER 53 | Value interface{} 54 | } 55 | 56 | 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. 57 | 58 | SNMP BER Types can be one of the following: 59 | 60 | type Asn1BER byte 61 | 62 | const ( 63 | Integer Asn1BER = 0x02 64 | BitString = 0x03 65 | OctetString = 0x04 66 | Null = 0x05 67 | ObjectIdentifier = 0x06 68 | Counter32 = 0x41 69 | Gauge32 = 0x42 70 | TimeTicks = 0x43 71 | Opaque = 0x44 72 | NsapAddress = 0x45 73 | Counter64 = 0x46 74 | Uinteger32 = 0x47 75 | ) 76 | 77 | GoSNMP supports most of the above values, subsequent releases will support all of them. 78 | -------------------------------------------------------------------------------- /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, err := parseInt(data) 113 | if err != nil { 114 | break 115 | } 116 | retVal.Type = Counter32 117 | retVal.Value = ret 118 | case TimeTicks: 119 | ret, err := parseInt(data) 120 | if err != nil { 121 | break 122 | } 123 | retVal.Type = TimeTicks 124 | retVal.Value = ret 125 | // Gauge32 126 | case Gauge32: 127 | ret, err := parseInt(data) 128 | if err != nil { 129 | break 130 | } 131 | retVal.Type = Gauge32 132 | retVal.Value = ret 133 | case Counter64: 134 | ret, err := parseInt64(data) 135 | 136 | // Decode it 137 | if err != nil { 138 | break 139 | } 140 | 141 | retVal.Type = Counter64 142 | retVal.Value = ret 143 | case Null: 144 | retVal.Value = nil 145 | case Sequence: 146 | // NOOP 147 | retVal.Value = data 148 | case GetResponse: 149 | // NOOP 150 | retVal.Value = data 151 | case GetRequest: 152 | // NOOP 153 | retVal.Value = data 154 | case EndOfMibView: 155 | retVal.Type = EndOfMibView 156 | retVal.Value = "endOfMib" 157 | case GetBulkRequest: 158 | // NOOP 159 | retVal.Value = data 160 | case NoSuchInstance: 161 | return nil, fmt.Errorf("No such instance") 162 | case NoSuchObject: 163 | return nil, fmt.Errorf("No such object") 164 | default: 165 | err = fmt.Errorf("Unable to decode %s %#v - not implemented", valueType, valueType) 166 | } 167 | 168 | return retVal, err 169 | } 170 | 171 | // Parses UINT16 172 | func ParseUint16(content []byte) int { 173 | number := uint8(content[1]) | uint8(content[0])<<8 174 | //fmt.Printf("\t%d\n", number) 175 | 176 | return int(number) 177 | } 178 | -------------------------------------------------------------------------------- /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 uint64, 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 |= uint64(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 != uint64(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 { 117 | if strings.Index(res.Variables[0].Name, requestOid) > -1 { 118 | results = append(results, res.Variables[0]) 119 | // Set to the next 120 | oid = res.Variables[0].Name 121 | x.Log.Debug("Moving to %s\n", oid) 122 | } else { 123 | x.Log.Debug("Root OID mismatch, stopping walk\n") 124 | break 125 | } 126 | } else { 127 | break 128 | } 129 | } else { 130 | break 131 | } 132 | 133 | } 134 | return 135 | } 136 | 137 | // Marshals & send an SNMP request. Unmarshals the response and returns back the parsed 138 | // SNMP packet 139 | func (x *GoSNMP) sendPacket(packet *SnmpPacket) (*SnmpPacket, error) { 140 | // Set timeouts on the connection 141 | deadline := time.Now() 142 | x.conn.SetDeadline(deadline.Add(x.Timeout)) 143 | 144 | // Marshal it 145 | fBuf, err := packet.marshal() 146 | 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | // Send the packet! 152 | _, err = x.conn.Write(fBuf) 153 | if err != nil { 154 | return nil, fmt.Errorf("Error writing to socket: %s\n", err.Error()) 155 | } 156 | // Try to read the response 157 | resp := make([]byte, 8192, 8192) 158 | n, err := x.conn.Read(resp) 159 | 160 | if err != nil { 161 | return nil, fmt.Errorf("Error reading from UDP: %s\n", err.Error()) 162 | } 163 | 164 | // Unmarshal the read bytes 165 | pdu, err := Unmarshal(resp[:n]) 166 | 167 | if err != nil { 168 | return nil, fmt.Errorf("Unable to decode packet: %s\n", err.Error()) 169 | } else { 170 | if len(pdu.Variables) < 1 { 171 | return nil, fmt.Errorf("No responses received.") 172 | } else { 173 | return pdu, nil 174 | } 175 | } 176 | 177 | return nil, nil 178 | } 179 | 180 | // Sends an SNMP Get Next Request to the target. Returns the next variable response from the OID given or an error 181 | func (x *GoSNMP) GetNext(oid string) (ret *SnmpPacket, err error) { 182 | defer func() { 183 | if e := recover(); e != nil { 184 | err = fmt.Errorf("%v", e) 185 | } 186 | }() 187 | 188 | // Create the packet 189 | packet := new(SnmpPacket) 190 | 191 | packet.Community = x.Community 192 | packet.Error = 0 193 | packet.ErrorIndex = 0 194 | packet.RequestType = GetNextRequest 195 | packet.Version = 1 // version 2 196 | packet.Variables = []SnmpPDU{SnmpPDU{Name: oid, Type: Null}} 197 | 198 | return x.sendPacket(packet) 199 | } 200 | 201 | // Debug function. Unmarshals raw bytes and returns the result without the network part 202 | func (x *GoSNMP) Debug(data []byte) (*SnmpPacket, error) { 203 | packet, err := Unmarshal(data) 204 | 205 | if err != nil { 206 | return nil, fmt.Errorf("Unable to decode packet: %s\n", err.Error()) 207 | } 208 | return packet, nil 209 | } 210 | 211 | // Sends an SNMP BULK-GET request to the target. Returns a Variable with the response or an error 212 | func (x *GoSNMP) GetBulk(non_repeaters, max_repetitions uint8, oids ...string) (ret *SnmpPacket, err error) { 213 | defer func() { 214 | if e := recover(); e != nil { 215 | err = fmt.Errorf("%v", e) 216 | } 217 | }() 218 | 219 | // Create the packet 220 | packet := new(SnmpPacket) 221 | 222 | packet.Community = x.Community 223 | packet.NonRepeaters = non_repeaters 224 | packet.MaxRepetitions = max_repetitions 225 | packet.RequestType = GetBulkRequest 226 | packet.Version = 1 // version 2 227 | packet.Variables = make([]SnmpPDU, len(oids)) 228 | 229 | for i, oid := range oids { 230 | packet.Variables[i] = SnmpPDU{Name: oid, Type: Null} 231 | } 232 | 233 | return x.sendPacket(packet) 234 | } 235 | 236 | // Sends an SNMP GET request to the target. Returns a Variable with the response or an error 237 | func (x *GoSNMP) Get(oid string) (ret *SnmpPacket, err error) { 238 | defer func() { 239 | if e := recover(); e != nil { 240 | err = fmt.Errorf("%v", e) 241 | } 242 | }() 243 | 244 | // Create the packet 245 | packet := new(SnmpPacket) 246 | 247 | packet.Community = x.Community 248 | packet.Error = 0 249 | packet.ErrorIndex = 0 250 | packet.RequestType = GetRequest 251 | packet.Version = 1 // version 2 252 | packet.Variables = []SnmpPDU{SnmpPDU{Name: oid, Type: Null}} 253 | 254 | return x.sendPacket(packet) 255 | } 256 | 257 | // Sends an SNMP GET request to the target. Returns a Variable with the response or an error 258 | func (x *GoSNMP) GetMulti(oids []string) (ret *SnmpPacket, err error) { 259 | defer func() { 260 | if e := recover(); e != nil { 261 | err = fmt.Errorf("%v", e) 262 | } 263 | }() 264 | 265 | // Create the packet 266 | packet := new(SnmpPacket) 267 | 268 | packet.Community = x.Community 269 | packet.Error = 0 270 | packet.ErrorIndex = 0 271 | packet.RequestType = GetRequest 272 | packet.Version = 1 // version 2 273 | packet.Variables = make([]SnmpPDU, len(oids)) 274 | 275 | for i, oid := range oids { 276 | packet.Variables[i] = SnmpPDU{Name: oid, Type: Null} 277 | } 278 | 279 | return x.sendPacket(packet) 280 | } 281 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package gosnmp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | l "github.com/alouca/gologger" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type SnmpVersion uint8 12 | 13 | const ( 14 | Version1 SnmpVersion = 0x0 15 | Version2c SnmpVersion = 0x1 16 | ) 17 | 18 | func (s SnmpVersion) String() string { 19 | if s == Version1 { 20 | return "1" 21 | } else if s == Version2c { 22 | return "2c" 23 | } 24 | return "U" 25 | } 26 | 27 | type SnmpPacket struct { 28 | Version SnmpVersion 29 | Community string 30 | RequestType Asn1BER 31 | RequestID uint8 32 | Error uint8 33 | ErrorIndex uint8 34 | NonRepeaters uint8 35 | MaxRepetitions uint8 36 | Variables []SnmpPDU 37 | } 38 | 39 | type SnmpPDU struct { 40 | Name string 41 | Type Asn1BER 42 | Value interface{} 43 | } 44 | 45 | func Unmarshal(packet []byte) (*SnmpPacket, error) { 46 | log := l.GetDefaultLogger() 47 | 48 | log.Debug("Begin SNMP Packet unmarshal\n") 49 | 50 | //var err error 51 | response := new(SnmpPacket) 52 | response.Variables = make([]SnmpPDU, 0, 5) 53 | 54 | // Start parsing the packet 55 | var cursor uint64 = 0 56 | 57 | // First bytes should be 0x30 58 | if Asn1BER(packet[0]) == Sequence { 59 | // Parse packet length 60 | ber, err := parseField(packet) 61 | 62 | if err != nil { 63 | log.Error("Unable to parse packet header: %s\n", err.Error()) 64 | return nil, err 65 | } 66 | 67 | log.Debug("Packet sanity verified, we got all the bytes (%d)\n", ber.DataLength) 68 | 69 | cursor += ber.HeaderLength 70 | // Parse SNMP Version 71 | rawVersion, err := parseField(packet[cursor:]) 72 | 73 | if err != nil { 74 | return nil, fmt.Errorf("Error parsing SNMP packet version: %s", err.Error()) 75 | } 76 | 77 | cursor += rawVersion.DataLength + rawVersion.HeaderLength 78 | if version, ok := rawVersion.BERVariable.Value.(int); ok { 79 | response.Version = SnmpVersion(version) 80 | log.Debug("Parsed Version %d\n", version) 81 | } 82 | 83 | // Parse community 84 | rawCommunity, err := parseField(packet[cursor:]) 85 | if err != nil { 86 | log.Debug("Unable to parse Community Field: %s\n", err) 87 | } 88 | cursor += rawCommunity.DataLength + rawCommunity.HeaderLength 89 | 90 | if community, ok := rawCommunity.BERVariable.Value.(string); ok { 91 | response.Community = community 92 | log.Debug("Parsed community %s\n", community) 93 | } 94 | 95 | rawPDU, err := parseField(packet[cursor:]) 96 | 97 | if err != nil { 98 | log.Debug("Unable to parse SNMP PDU: %s\n", err.Error()) 99 | } 100 | response.RequestType = rawPDU.Type 101 | 102 | switch rawPDU.Type { 103 | default: 104 | log.Debug("Unsupported SNMP Packet Type %s\n", rawPDU.Type.String()) 105 | log.Debug("PDU Size is %d\n", rawPDU.DataLength) 106 | case GetRequest, GetResponse, GetBulkRequest: 107 | log.Debug("SNMP Packet is %s\n", rawPDU.Type.String()) 108 | log.Debug("PDU Size is %d\n", rawPDU.DataLength) 109 | cursor += rawPDU.HeaderLength 110 | 111 | // Parse Request ID 112 | rawRequestId, err := parseField(packet[cursor:]) 113 | 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | cursor += rawRequestId.DataLength + rawRequestId.HeaderLength 119 | if requestid, ok := rawRequestId.BERVariable.Value.(int); ok { 120 | response.RequestID = uint8(requestid) 121 | log.Debug("Parsed Request ID: %d\n", requestid) 122 | } 123 | 124 | // Parse Error 125 | rawError, err := parseField(packet[cursor:]) 126 | 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | cursor += rawError.DataLength + rawError.HeaderLength 132 | if errorNo, ok := rawError.BERVariable.Value.(int); ok { 133 | response.Error = uint8(errorNo) 134 | } 135 | 136 | // Parse Error Index 137 | rawErrorIndex, err := parseField(packet[cursor:]) 138 | 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | cursor += rawErrorIndex.DataLength + rawErrorIndex.HeaderLength 144 | 145 | if errorindex, ok := rawErrorIndex.BERVariable.Value.(int); ok { 146 | response.ErrorIndex = uint8(errorindex) 147 | } 148 | 149 | log.Debug("Request ID: %d Error: %d Error Index: %d\n", response.RequestID, response.Error, response.ErrorIndex) 150 | rawResp, err := parseField(packet[cursor:]) 151 | 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | cursor += rawResp.HeaderLength 157 | // Loop & parse Varbinds 158 | for cursor < uint64(len(packet)) { 159 | log.Debug("Parsing var bind response (Cursor at %d/%d)", cursor, len(packet)) 160 | 161 | rawVarbind, err := parseField(packet[cursor:]) 162 | 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | cursor += rawVarbind.HeaderLength 168 | log.Debug("Varbind length: %d/%d\n", rawVarbind.HeaderLength, rawVarbind.DataLength) 169 | 170 | log.Debug("Parsing OID (Cursor at %d)\n", cursor) 171 | // Parse OID 172 | rawOid, err := parseField(packet[cursor:]) 173 | 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | cursor += rawOid.HeaderLength + rawOid.DataLength 179 | 180 | log.Debug("OID (%v) Field was %d bytes\n", rawOid, rawOid.DataLength) 181 | 182 | rawValue, err := parseField(packet[cursor:]) 183 | 184 | if err != nil { 185 | return nil, err 186 | } 187 | cursor += rawValue.HeaderLength + rawValue.DataLength 188 | 189 | log.Debug("Value field was %d bytes\n", rawValue.DataLength) 190 | 191 | if oid, ok := rawOid.BERVariable.Value.([]int); ok { 192 | log.Debug("Varbind decoding success\n") 193 | response.Variables = append(response.Variables, SnmpPDU{oidToString(oid), rawValue.Type, rawValue.BERVariable.Value}) 194 | } 195 | } 196 | 197 | } 198 | } else { 199 | return nil, fmt.Errorf("Invalid packet header\n") 200 | } 201 | 202 | return response, nil 203 | } 204 | 205 | type RawBER struct { 206 | Type Asn1BER 207 | HeaderLength uint64 208 | DataLength uint64 209 | Data []byte 210 | BERVariable *Variable 211 | } 212 | 213 | // Parses a given field, return the ASN.1 BER Type, its header length and the data 214 | func parseField(data []byte) (*RawBER, error) { 215 | log := l.GetDefaultLogger() 216 | var err error 217 | 218 | if len(data) == 0 { 219 | return nil, fmt.Errorf("Unable to parse BER: Data length 0") 220 | } 221 | 222 | ber := new(RawBER) 223 | 224 | ber.Type = Asn1BER(data[0]) 225 | 226 | // Parse Length 227 | length := data[1] 228 | 229 | // Check if this is padded or not 230 | if length > 0x80 { 231 | length = length - 0x80 232 | log.Debug("Field length is padded to %d bytes\n", length) 233 | ber.DataLength = Uvarint(data[2 : 2+length]) 234 | log.Debug("Decoded final length: %d\n", ber.DataLength) 235 | 236 | ber.HeaderLength = 2 + uint64(length) 237 | 238 | } else { 239 | ber.HeaderLength = 2 240 | ber.DataLength = uint64(length) 241 | } 242 | 243 | // Do sanity checks 244 | if ber.DataLength > uint64(len(data)) { 245 | return nil, fmt.Errorf("Unable to parse BER: provided data length is longer than actual data (%d vs %d)", ber.DataLength, len(data)) 246 | } 247 | 248 | ber.Data = data[ber.HeaderLength : ber.HeaderLength+ber.DataLength] 249 | 250 | ber.BERVariable, err = decodeValue(ber.Type, ber.Data) 251 | 252 | if err != nil { 253 | return nil, fmt.Errorf("Unable to decode value: %s\n", err.Error()) 254 | } 255 | 256 | return ber, nil 257 | } 258 | 259 | func (packet *SnmpPacket) marshal() ([]byte, error) { 260 | // Prepare the buffer to send 261 | buffer := make([]byte, 0, 1024) 262 | buf := bytes.NewBuffer(buffer) 263 | 264 | // Write the packet header (Message type 0x30) & Version = 2 265 | buf.Write([]byte{byte(Sequence), 0, 2, 1, byte(packet.Version)}) 266 | 267 | // Write Community 268 | buf.Write([]byte{4, uint8(len(packet.Community))}) 269 | buf.WriteString(packet.Community) 270 | 271 | // Marshal the SNMP PDU 272 | snmpPduBuffer := make([]byte, 0, 1024) 273 | snmpPduBuf := bytes.NewBuffer(snmpPduBuffer) 274 | 275 | snmpPduBuf.Write([]byte{byte(packet.RequestType), 0, 2, 1, packet.RequestID}) 276 | 277 | switch packet.RequestType { 278 | case GetBulkRequest: 279 | snmpPduBuf.Write([]byte{ 280 | 2, 1, packet.NonRepeaters, 281 | 2, 1, packet.MaxRepetitions, 282 | }) 283 | default: 284 | snmpPduBuf.Write([]byte{ 285 | 2, 1, packet.Error, 286 | 2, 1, packet.ErrorIndex, 287 | }) 288 | } 289 | 290 | snmpPduBuf.Write([]byte{byte(Sequence), 0}) 291 | 292 | pduLength := 0 293 | for _, varlist := range packet.Variables { 294 | pdu, err := marshalPDU(&varlist) 295 | 296 | if err != nil { 297 | return nil, err 298 | } 299 | pduLength += len(pdu) 300 | snmpPduBuf.Write(pdu) 301 | } 302 | 303 | pduBytes := snmpPduBuf.Bytes() 304 | // Varbind list length 305 | pduBytes[12] = byte(pduLength) 306 | // SNMP PDU length (PDU header + varbind list length) 307 | pduBytes[1] = byte(pduLength + 11) 308 | 309 | buf.Write(pduBytes) 310 | 311 | // Write the 312 | //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))}) 313 | //buf.Write(mOid) 314 | //buf.Write([]byte{5, 0}) 315 | 316 | ret := buf.Bytes() 317 | 318 | // Set the packet size 319 | ret[1] = uint8(len(ret) - 2) 320 | 321 | return ret, nil 322 | } 323 | 324 | func marshalPDU(pdu *SnmpPDU) ([]byte, error) { 325 | oid, err := marshalOID(pdu.Name) 326 | if err != nil { 327 | return nil, err 328 | } 329 | 330 | pduBuffer := make([]byte, 0, 1024) 331 | pduBuf := bytes.NewBuffer(pduBuffer) 332 | 333 | // Mashal the PDU type into the appropriate BER 334 | switch pdu.Type { 335 | case Null: 336 | pduBuf.Write([]byte{byte(Sequence), byte(len(oid) + 4)}) 337 | pduBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) 338 | pduBuf.Write(oid) 339 | pduBuf.Write([]byte{Null, 0x00}) 340 | default: 341 | return nil, fmt.Errorf("Unable to marshal PDU: unknown BER type %d", pdu.Type) 342 | } 343 | 344 | return pduBuf.Bytes(), nil 345 | } 346 | 347 | func oidToString(oid []int) (ret string) { 348 | values := make([]interface{}, len(oid)) 349 | for i, v := range oid { 350 | values[i] = v 351 | } 352 | return fmt.Sprintf(strings.Repeat(".%d", len(oid)), values...) 353 | } 354 | 355 | func marshalOID(oid string) ([]byte, error) { 356 | var err error 357 | 358 | // Encode the oid 359 | oid = strings.Trim(oid, ".") 360 | oidParts := strings.Split(oid, ".") 361 | oidBytes := make([]int, len(oidParts)) 362 | 363 | // Convert the string OID to an array of integers 364 | for i := 0; i < len(oidParts); i++ { 365 | oidBytes[i], err = strconv.Atoi(oidParts[i]) 366 | if err != nil { 367 | return nil, fmt.Errorf("Unable to parse OID: %s\n", err.Error()) 368 | } 369 | } 370 | 371 | mOid, err := marshalObjectIdentifier(oidBytes) 372 | 373 | if err != nil { 374 | return nil, fmt.Errorf("Unable to marshal OID: %s\n", err.Error()) 375 | } 376 | 377 | return mOid, err 378 | } 379 | --------------------------------------------------------------------------------