pax_global_header00006660000000000000000000000064136240450400014510gustar00rootroot0000000000000052 comment=e3a0d759eeff664255695ef2574b3b5f74097b46 gosnmp-1.24.0/000077500000000000000000000000001362404504000130775ustar00rootroot00000000000000gosnmp-1.24.0/.gitignore000066400000000000000000000021131362404504000150640ustar00rootroot00000000000000# Created by https://www.gitignore.io/api/go,osx,vim ### Go ### # Binaries for programs and plugins *.exe *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ ### OSX ### *.DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Vim ### # swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-v][a-z] [._]sw[a-p] # session Session.vim # temporary .netrwhist *~ # auto-generated tag files tags # End of https://www.gitignore.io/api/go,osx,vim # gogland .idea/ # git rebase files *.orig # test coverage outputs coverage.json gosnmp.html # profiling outputs cpu.out mem.out gosnmp.test gosnmp-1.24.0/.travis.yml000066400000000000000000000015051362404504000152110ustar00rootroot00000000000000language: go go: - "1.11" # $TRAVIS_GOARCH hack - see https://github.com/travis-ci/travis-ci/issues/6126 env: global: - GOSNMP_TARGET=127.0.0.1 - GOSNMP_PORT=161 - GOSNMP_TARGET_IPV4=127.0.0.1 - GOSNMP_PORT_IPV4=161 - GOSNMP_TARGET_IPV6='::1' - GOSNMP_PORT_IPV6=161 matrix: - TRAVIS_GOARCH=amd64 - TRAVIS_GOARCH=386 before_install: - sudo apt-get update -qq - sudo apt-get install snmpd - export GOARCH=$TRAVIS_GOARCH install: - go get github.com/soniah/gosnmp - go get github.com/stretchr/testify/assert - go get -u golang.org/x/tools/cmd/goimports - sudo ./snmp_users.sh - sudo /etc/init.d/snmpd restart script: - ./linters.sh - go test -v -tags helper - go test -v -tags marshal - go test -v -tags misc - go test -v -tags api - go test -v -tags end2end - go test -v -tags trap gosnmp-1.24.0/AUTHORS.md000066400000000000000000000052501362404504000145500ustar00rootroot00000000000000# GoSNMP authors `git log --pretty=format:"* %an %ae" df49b4fc0b10ed2cab253cecc8c3d86b72cec41d..HEAD | sort -f | uniq >> AUTHORS.md` `TODO: something clever with sed, etc to autogenerate this` * 10074432 liu.xuefeng1@zte.com.cn * Andreas Louca andreas@louca.org * Andrew Filonov aef@bks.tv * Andris Raugulis moo@arthepsy.eu * Balogh Ákos akos@rubin.hu * Benjamin benjamin.guy.thomas@gmail.com * Benjamin Thomas benjamin.guy.thomas@gmail.com * benthor github@benthor.name * Brian Brazil brian.brazil@robustperception.io * Bryan Hill bryan.d.hill@gmail.com * Bryan Hill bryan.hill@ontario.ca * Chris chris.dance@papercut.com * codedance dance.chris@gmail.com * Daniel Swarbrick daniel.swarbrick@gmail.com * davidbj david_bj@126.com * dramirez dramirez@rackspace.com * Eamon Bauman eamon@eamonbauman.com * Eduardo Ferro Aldama eduardo.ferro.aldama@gmail.com * Eduardo Ferro eduardo.ferro.aldama@gmail.com * Eli Yukelzon reflog@gmail.com * Felix Maurer felix@felix-maurer.de * frozenbubbleboy github@wildtongue.net * Guillem Jover gjover@sipwise.com * HD Moore x@hdm.io * Igor Novgorodov igor@novg.net * Ivan Radakovic iradakovic13@gmail.com * Jacob Dubinsky dubinskyjm@gmail.com * jacob dubinsky dubinskyjm@gmail.com * Jaime Gil de Sagredo Luna jgil@alea-soluciones.com * Jan Kodera koderja2@fit.cvut.cz * Jared Housh j.housh@f5.com * jclc jclc@protonmail.com * Joe Cracchiolo jjc@simplybits.com * Jon Auer jda@coldshore.com * Jon Auer jda@tapodi.net * Joshua Green joshua.green@mail.com * JP Kekkonen karatepekka@gmail.com * krkini16 krkini16@users.noreply.github.com * lilinzhe slayercat.registiononly@gmail.com * lilinzhe slayercat.subscription@gmail.com * Marc Arndt marc@marcarndt.com * Marc Arndt marcarndt@Marcs-MacBook-Pro.local * Martin Lindhe martinlindhe@users.noreply.github.com * Marty Schoch marty.schoch@gmail.com * Mattias Folke mattias.folke@gmail.com * Mattias Folke mattias.folke@tre.se * Mehdi Pourfar mehdipourfar@gmail.com * Michał Derkacz michal@Lnet.pl * Miroslav Genov mgenov@gmail.com * Nathan Owens nathan_owens@cable.comcast.com * Nathan Owens virtuallynathan@gmail.com * NewHooker yaocanwu@gmail.com * nikandfor nikandfor@gmail.com * Patrick Hemmer patrick.hemmer@gmail.com * Patryk Najda ptrknjd@gmail.com * Peter Vypov peter.vypov@gmail.com * Rene Fragoso ctrlrsf@gmail.com * rjammalamadaka rajanikanth.jammalamadaka@mandiant.com * Ross Wilson ross.wilson@iomart.com * Sonia Hamilton sonia@snowfrog.net * Tara taramerin@gmail.com * The Binary binary4bytes@gmail.com * toni-moreno toni.moreno@gmail.com * Vallimamod Abdullah vma@users.noreply.github.com * WangShouLin wang.shoulin1@zte.com.cn * Whitham D. Reeve II thetawaves@gmail.com * Whitham D. Reeve II wreeve@gci.com * x1unix ascii@live.ru gosnmp-1.24.0/CHANGELOG.md000066400000000000000000000027651362404504000147220ustar00rootroot00000000000000## v1.24.0 * doco, fix AUTHORS, fix copyright * decode more packet types * TCP trap listening ## v1.23.1 * add support for contexts * fix panic conditions by checking for out-of-bounds reads ## v1.23.0 * BREAKING CHANGE: The mocks have been moved to `github.com/soniah/gosnmp/mocks`. If you use them, you will need to adjust your imports. * bug fix: issue 170: No results when performing a walk starting on a leaf OID * bug fix: issue 210: Set function fails if value is an Integer * doco: loggingEnabled, MIB parser * linting ## v1.22.0 * travis now failing build when goimports needs running * gometalinter * shell script for running local tests * SNMPv3 - avoid crash when missing SecurityParameters * add support for Walk and Get over TCP - RFC 3430 * SNMPv3 - allow input of private key instead of passphrase ## v1.21.0 * add netsnmp functionality "not check returned OIDs are increasing" ## v1.20.0 * convert all tags to correct semantic versioning, and remove old tags * SNMPv1 trap IDs should be marshalInt32() not single byte * use packetSecParams not sp secretKey in v3 isAuthentic() * fix IPAddress marshalling in Set() ## v1.19.0 * bug fix: handle uninitialized v3 SecurityParameters in SnmpDecodePacket() * SNMPError, Asn1BER - stringers; types on constants ## v1.18.0 * bug fix: use format flags - logPrintf() not logPrint() * bug fix: parseObjectIdentifier() now returns []byte{0} rather than error when it receive zero length input * use gomock * start using go modules * start a changelog gosnmp-1.24.0/LICENSE000066400000000000000000000057531362404504000141160ustar00rootroot00000000000000Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Parts of the gosnmp code are from GoLang ASN.1 Library (as marked in the source code). For those part of code the following license applies: Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. gosnmp-1.24.0/README.md000066400000000000000000000166501362404504000143660ustar00rootroot00000000000000gosnmp ====== [![Build Status](https://travis-ci.org/soniah/gosnmp.svg?branch=master)](https://travis-ci.org/soniah/gosnmp) [![GoDoc](https://godoc.org/github.com/soniah/gosnmp?status.png)](http://godoc.org/github.com/soniah/gosnmp) https://github.com/soniah/gosnmp GoSNMP is an SNMP client library fully written in Go. It provides Get, GetNext, GetBulk, Walk, BulkWalk, Set and Traps. It supports IPv4 and IPv6, using __SNMPv2c__ or __SNMPv3__. Builds are tested against linux/amd64 and linux/386. About ----- **soniah/gosnmp** was originally based on **alouca/gosnmp**, but has been completely rewritten. Many thanks to Andreas Louca, other contributors (AUTHORS.md) and these project collaborators: * Whitham Reeve ([@wdreeveii](https://github.com/wdreeveii/)) Sonia Hamilton, sonia@snowfrog.net Overview -------- GoSNMP has the following SNMP functions: * **Get** (single or multiple OIDs) * **GetNext** * **GetBulk** * **Walk** - retrieves a subtree of values using GETNEXT. * **BulkWalk** - retrieves a subtree of values using GETBULK. * **Set** - supports Integers and OctetStrings. * **SendTrap** - send SNMP TRAPs. * **Listen** - act as an NMS for receiving TRAPs. GoSNMP has the following **helper** functions: * **ToBigInt** - treat returned values as `*big.Int` * **Partition** - facilitates dividing up large slices of OIDs **soniah/gosnmp** has completely diverged from **alouca/gosnmp**, your code will require modification in these (and other) locations: * the **Get** function has a different method signature * the **NewGoSNMP** function has been removed, use **Connect** instead (see Usage below). `Connect` uses the `GoSNMP` struct; `gosnmp.Default` is provided for you to build on. * GoSNMP no longer relies on **alouca/gologger** - you can use your logger if it conforms to the `gosnmp.Logger` interface; otherwise debugging will be discarded (/dev/null). ```go type Logger interface { Print(v ...interface{}) Printf(format string, v ...interface{}) } ``` Installation ------------ ```shell go get github.com/soniah/gosnmp ``` Documentation ------------- http://godoc.org/github.com/soniah/gosnmp Usage ----- Here is `examples/example.go`, demonstrating how to use GoSNMP: ```go // Default is a pointer to a GoSNMP struct that contains sensible defaults // eg port 161, community public, etc g.Default.Target = "192.168.1.10" err := g.Default.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer g.Default.Conn.Close() oids := []string{"1.3.6.1.2.1.1.4.0", "1.3.6.1.2.1.1.7.0"} result, err2 := g.Default.Get(oids) // Get() accepts up to g.MAX_OIDS if err2 != nil { log.Fatalf("Get() err: %v", err2) } for i, variable := range result.Variables { fmt.Printf("%d: oid: %s ", i, variable.Name) // the Value of each variable returned by Get() implements // interface{}. You could do a type switch... switch variable.Type { case g.OctetString: bytes := variable.Value.([]byte) fmt.Printf("string: %s\n", string(bytes)) default: // ... or often you're just interested in numeric values. // ToBigInt() will return the Value as a BigInt, for plugging // into your calculations. fmt.Printf("number: %d\n", g.ToBigInt(variable.Value)) } } ``` Running this example gives the following output (from my printer): ```shell % go run example.go 0: oid: 1.3.6.1.2.1.1.4.0 string: Administrator 1: oid: 1.3.6.1.2.1.1.7.0 number: 104 ``` * `examples/example2.go` is similar to `example.go`, however it uses a custom `&GoSNMP` rather than `g.Default` * `examples/walkexample.go` demonstrates using `BulkWalk` * `examples/example3.go` demonstrates `SNMPv3` * `examples/trapserver.go` demonstrates writing an SNMP v2c trap server MIB Parser ---------- I don't have any plans to write a mib parser. Others have suggested https://github.com/sleepinggenius2/gosmi Contributions ------------- Contributions are welcome, especially ones that have packet captures (see below). If you've never contributed to a Go project before, here is an example workflow. 1. [fork this repo on the GitHub webpage](https://github.com/soniah/gosnmp/fork) 1. `go get github.com/soniah/gosnmp` 1. `cd $GOPATH/src/github.com/soniah/gosnmp` 1. `git remote rename origin upstream` 1. `git remote add origin git@github.com:/gosnmp.git` 1. `git checkout -b development` 1. `git push -u origin development` (setup where you push to, check it works) Packet Captures --------------- Create your packet captures in the following way: Expected output, obtained via an **snmp** command. For example: ```shell % snmpget -On -v2c -c public 203.50.251.17 1.3.6.1.2.1.1.7.0 \ 1.3.6.1.2.1.2.2.1.2.6 1.3.6.1.2.1.2.2.1.5.3 .1.3.6.1.2.1.1.7.0 = INTEGER: 78 .1.3.6.1.2.1.2.2.1.2.6 = STRING: GigabitEthernet0 .1.3.6.1.2.1.2.2.1.5.3 = Gauge32: 4294967295 ``` A packet capture, obtained while running the snmpget. For example: ```shell sudo tcpdump -s 0 -i eth0 -w foo.pcap host 203.50.251.17 and port 161 ``` Bugs ---- Rane's document [SNMP: Simple? Network Management Protocol](http://www.rane.com/note161.html) was useful when learning the SNMP protocol. Please create an [issue](https://github.com/soniah/gosnmp/issues) on Github with packet captures (upload capture to Google Drive, Dropbox, or similar) containing samples of missing BER types, or of any other bugs you find. If possible, please include 2 or 3 examples of the missing/faulty BER type. The following BER types have been implemented: * 0x00 UnknownType * 0x01 Boolean * 0x02 Integer * 0x03 BitString * 0x04 OctetString * 0x05 Null * 0x06 ObjectIdentifier * 0x07 ObjectDescription * 0x40 IPAddress (IPv4 & IPv6) * 0x41 Counter32 * 0x42 Gauge32 * 0x43 TimeTicks * 0x44 Opaque (Float & Double) * 0x45 NsapAddress * 0x46 Counter64 * 0x47 Uinteger32 * 0x78 OpaqueFloat * 0x79 OpaqueDouble * 0x80 NoSuchObject * 0x81 NoSuchInstance * 0x82 EndOfMibView Running the Tests ----------------- ```shell export GOSNMP_TARGET=1.2.3.4 export GOSNMP_PORT=161 export GOSNMP_TARGET_IPV4=1.2.3.4 export GOSNMP_PORT_IPV4=161 export GOSNMP_TARGET_IPV6='0:0:0:0:0:ffff:102:304' export GOSNMP_PORT_IPV6=161 go test -v -tags all # for example go test -v -tags helper # for example ``` Tests are grouped as follows: * Unit tests (validating data packing and marshalling): * `marshal_test.go` * `misc_test.go` * Public API consistency tests: * `gosnmp_api_test.go` * End-to-end integration tests: * `generic_e2e_test.go` The generic end-to-end integration test `generic_e2e_test.go` should work against any SNMP MIB-2 compliant host (e.g. a router, NAS box, printer). Mocks were generated using: `mockgen -source=interface.go -destination=mocks/gosnmp_mock.go -package=mocks` To profile cpu usage: ```shell go test -cpuprofile cpu.out go test -c go tool pprof gosnmp.test cpu.out ``` To profile memory usage: ```shell go test -memprofile mem.out go test -c go tool pprof gosnmp.test mem.out ``` To check test coverage: ```shell go get github.com/axw/gocov/gocov go get github.com/matm/gocov-html gocov test github.com/soniah/gosnmp | gocov-html > gosnmp.html && firefox gosnmp.html & ``` License ------- Parts of the code are taken from the Golang project (specifically some functions for unmarshaling BER responses), which are under the same terms and conditions as the Go language. The rest of the code is under a BSD license. See the LICENSE file for more details. The remaining code is Copyright 2012-2020 the GoSNMP Authors - see AUTHORS.md for a list of authors. gosnmp-1.24.0/asn1ber_string.go000066400000000000000000000022171362404504000163510ustar00rootroot00000000000000// Code generated by "stringer -type Asn1BER"; DO NOT EDIT. package gosnmp import "strconv" const ( _Asn1BER_name_0 = "EndOfContentsBooleanIntegerBitStringOctetStringNullObjectIdentifierObjectDescription" _Asn1BER_name_1 = "IPAddressCounter32Gauge32TimeTicksOpaqueNsapAddressCounter64Uinteger32" _Asn1BER_name_2 = "OpaqueFloatOpaqueDouble" _Asn1BER_name_3 = "NoSuchObjectNoSuchInstanceEndOfMibView" ) var ( _Asn1BER_index_0 = [...]uint8{0, 13, 20, 27, 36, 47, 51, 67, 84} _Asn1BER_index_1 = [...]uint8{0, 9, 18, 25, 34, 40, 51, 60, 70} _Asn1BER_index_2 = [...]uint8{0, 11, 23} _Asn1BER_index_3 = [...]uint8{0, 12, 26, 38} ) func (i Asn1BER) String() string { switch { case 0 <= i && i <= 7: return _Asn1BER_name_0[_Asn1BER_index_0[i]:_Asn1BER_index_0[i+1]] case 64 <= i && i <= 71: i -= 64 return _Asn1BER_name_1[_Asn1BER_index_1[i]:_Asn1BER_index_1[i+1]] case 120 <= i && i <= 121: i -= 120 return _Asn1BER_name_2[_Asn1BER_index_2[i]:_Asn1BER_index_2[i+1]] case 128 <= i && i <= 130: i -= 128 return _Asn1BER_name_3[_Asn1BER_index_3[i]:_Asn1BER_index_3[i+1]] default: return "Asn1BER(" + strconv.FormatInt(int64(i), 10) + ")" } } gosnmp-1.24.0/examples/000077500000000000000000000000001362404504000147155ustar00rootroot00000000000000gosnmp-1.24.0/examples/example.go000066400000000000000000000023531362404504000167020ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package main import ( "fmt" "log" g "github.com/soniah/gosnmp" ) func main() { // Default is a pointer to a GoSNMP struct that contains sensible defaults // eg port 161, community public, etc g.Default.Target = "192.168.1.10" err := g.Default.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer g.Default.Conn.Close() oids := []string{"1.3.6.1.2.1.1.4.0", "1.3.6.1.2.1.1.7.0"} result, err2 := g.Default.Get(oids) // Get() accepts up to g.MAX_OIDS if err2 != nil { log.Fatalf("Get() err: %v", err2) } for i, variable := range result.Variables { fmt.Printf("%d: oid: %s ", i, variable.Name) // the Value of each variable returned by Get() implements // interface{}. You could do a type switch... switch variable.Type { case g.OctetString: fmt.Printf("string: %s\n", string(variable.Value.([]byte))) default: // ... or often you're just interested in numeric values. // ToBigInt() will return the Value as a BigInt, for plugging // into your calculations. fmt.Printf("number: %d\n", g.ToBigInt(variable.Value)) } } } gosnmp-1.24.0/examples/example2.go000066400000000000000000000033551362404504000167670ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package main import ( "fmt" "log" "os" "strconv" "time" g "github.com/soniah/gosnmp" ) func main() { // get Target and Port from environment envTarget := os.Getenv("GOSNMP_TARGET") envPort := os.Getenv("GOSNMP_PORT") if len(envTarget) <= 0 { log.Fatalf("environment variable not set: GOSNMP_TARGET") } if len(envPort) <= 0 { log.Fatalf("environment variable not set: GOSNMP_PORT") } port, _ := strconv.ParseUint(envPort, 10, 16) // Build our own GoSNMP struct, rather than using g.Default. // Do verbose logging of packets. params := &g.GoSNMP{ Target: envTarget, Port: uint16(port), Community: "public", Version: g.Version2c, Timeout: time.Duration(2) * time.Second, Logger: log.New(os.Stdout, "", 0), } err := params.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer params.Conn.Close() oids := []string{"1.3.6.1.2.1.1.4.0", "1.3.6.1.2.1.1.7.0"} result, err2 := params.Get(oids) // Get() accepts up to g.MAX_OIDS if err2 != nil { log.Fatalf("Get() err: %v", err2) } for i, variable := range result.Variables { fmt.Printf("%d: oid: %s ", i, variable.Name) // the Value of each variable returned by Get() implements // interface{}. You could do a type switch... switch variable.Type { case g.OctetString: fmt.Printf("string: %s\n", string(variable.Value.([]byte))) default: // ... or often you're just interested in numeric values. // ToBigInt() will return the Value as a BigInt, for plugging // into your calculations. fmt.Printf("number: %d\n", g.ToBigInt(variable.Value)) } } } gosnmp-1.24.0/examples/example3.go000066400000000000000000000031241362404504000167620ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package main import ( "fmt" "log" "time" g "github.com/soniah/gosnmp" ) func main() { // build our own GoSNMP struct, rather than using g.Default params := &g.GoSNMP{ Target: "192.168.91.20", Port: 161, Version: g.Version3, Timeout: time.Duration(30) * time.Second, SecurityModel: g.UserSecurityModel, MsgFlags: g.AuthPriv, SecurityParameters: &g.UsmSecurityParameters{UserName: "user", AuthenticationProtocol: g.SHA, AuthenticationPassphrase: "password", PrivacyProtocol: g.DES, PrivacyPassphrase: "password", }, } err := params.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer params.Conn.Close() oids := []string{"1.3.6.1.2.1.1.4.0", "1.3.6.1.2.1.1.7.0"} result, err2 := params.Get(oids) // Get() accepts up to g.MAX_OIDS if err2 != nil { log.Fatalf("Get() err: %v", err2) } for i, variable := range result.Variables { fmt.Printf("%d: oid: %s ", i, variable.Name) // the Value of each variable returned by Get() implements // interface{}. You could do a type switch... switch variable.Type { case g.OctetString: fmt.Printf("string: %s\n", string(variable.Value.([]byte))) default: // ... or often you're just interested in numeric values. // ToBigInt() will return the Value as a BigInt, for plugging // into your calculations. fmt.Printf("number: %d\n", g.ToBigInt(variable.Value)) } } } gosnmp-1.24.0/examples/tcp_trapserver.go000066400000000000000000000053261362404504000203150ustar00rootroot00000000000000package main import ( "fmt" "net" "time" log "github.com/sirupsen/logrus" gosnmp "github.com/soniah/gosnmp" ) func main() { log.SetLevel(log.DebugLevel) log.Infof("Starting") var port uint16 port = 2162 go Start(fmt.Sprintf("tcp://0.0.0.0:%d", port)) gosnmp.Default.Target = "127.0.0.1" gosnmp.Default.Transport = "tcp" gosnmp.Default.Port = port gosnmp.Default.Community = "public" gosnmp.Default.Version = gosnmp.Version1 time.Sleep(time.Duration(1) * time.Second) oid := gosnmp.SnmpPDU{ Name: "1.3.6.1.2.1.1.6", Type: gosnmp.ObjectIdentifier, Value: "1.3.6.1.2.1.1.6.10", } oid1 := gosnmp.SnmpPDU{ Name: "1.3.6.1.2.1.1.7", Type: gosnmp.OctetString, Value: "Testing TCP trap...", } oid2 := gosnmp.SnmpPDU{ Name: "1.3.6.1.2.1.1.8", Type: gosnmp.Integer, Value: 123, } cou := 5 for cou > 0 { time.Sleep(time.Duration(1) * time.Second) err := gosnmp.Default.Connect() if err != nil { log.Panicf(err.Error()) } defer gosnmp.Default.Conn.Close() //RebuildCron() log.Infof("Running (%d)", cou) trap := gosnmp.SnmpTrap{ Variables: []gosnmp.SnmpPDU{oid, oid1, oid2}, Enterprise: ".1.3.6.1.6.3.1.1.5.1", AgentAddress: "127.0.0.1", GenericTrap: 0, SpecificTrap: 0, Timestamp: 300, } _, err = gosnmp.Default.SendTrap(trap) if err != nil { log.Fatalf("SendTrap() err: %v", err) } cou-- } //time.Sleep(time.Duration(10) * time.Second) log.Infof("Stop...") } // Start SNMP server func Start(address string) { log.Infof("Starting SNMP TRAP Server on: %s", address) tl := gosnmp.NewTrapListener() tl.OnNewTrap = myTrapHandler tl.Params = gosnmp.Default //tl.Params.Logger = log.New() err := tl.Listen(address) if err != nil { time.Sleep(1) log.Fatalf("Error in TRAP listen: %s", err) } } type valueAndTypeType struct { Value string Type string } func myTrapHandler(packet *gosnmp.SnmpPacket, addr *net.UDPAddr) { log.Infof("SNMP trap received from: %s:%d. Community:%s, SnmpVersion:%s ", addr.IP, addr.Port, packet.Community, packet.Version) for i, variable := range packet.Variables { var val string switch variable.Type { case gosnmp.OctetString: val = string(variable.Value.([]byte)) case gosnmp.ObjectIdentifier: val = fmt.Sprintf("%s", variable.Value) case gosnmp.TimeTicks: a := gosnmp.ToBigInt(variable.Value) val = fmt.Sprintf("%d", (*a).Int64()) case gosnmp.Null: val = "" default: // ... or often you're just interested in numeric values. // ToBigInt() will return the Value as a BigInt, for plugging // into your calculations. a := gosnmp.ToBigInt(variable.Value) val = fmt.Sprintf("%d", (*a).Int64()) } log.Debugf("- oid[%d]: %s (%s) = %v ", i, variable.Name, variable.Type, val) } } gosnmp-1.24.0/examples/trapsend_v1.go000066400000000000000000000016701362404504000174760ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package main import ( "log" g "github.com/soniah/gosnmp" ) func main() { // Default is a pointer to a GoSNMP struct that contains sensible defaults // eg port 161, community public, etc g.Default.Target = "127.0.0.1" g.Default.Port = 162 g.Default.Version = g.Version1 err := g.Default.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer g.Default.Conn.Close() pdu := g.SnmpPDU{ Name: "1.3.6.1.2.1.1.6", Type: g.OctetString, Value: "Oval Office", } trap := g.SnmpTrap{ Variables: []g.SnmpPDU{pdu}, Enterprise: ".1.3.6.1.6.3.1.1.5.1", AgentAddress: "127.0.0.1", GenericTrap: 0, SpecificTrap: 0, Timestamp: 300, } _, err = g.Default.SendTrap(trap) if err != nil { log.Fatalf("SendTrap() err: %v", err) } } gosnmp-1.24.0/examples/trapsend_v2plus.go000066400000000000000000000016371362404504000204060ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package main import ( "log" "os" g "github.com/soniah/gosnmp" ) func main() { // Default is a pointer to a GoSNMP struct that contains sensible defaults // eg port 161, community public, etc g.Default.Target = "127.0.0.1" g.Default.Port = 162 g.Default.Version = g.Version2c g.Default.Community = "public" g.Default.Logger = log.New(os.Stdout, "", 0) err := g.Default.Connect() if err != nil { log.Fatalf("Connect() err: %v", err) } defer g.Default.Conn.Close() pdu := 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", } trap := g.SnmpTrap{ Variables: []g.SnmpPDU{pdu}, } _, err = g.Default.SendTrap(trap) if err != nil { log.Fatalf("SendTrap() err: %v", err) } } gosnmp-1.24.0/examples/trapserver.go000066400000000000000000000022201362404504000174350ustar00rootroot00000000000000// Copyright 2012-2016 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. /* The developer of the trapserver code (https://github.com/jda) says "I'm working on the best level of abstraction but I'm able to receive traps from a Cisco switch and Net-SNMP". Pull requests welcome. */ package main import ( "flag" "fmt" "log" "net" "os" "path/filepath" g "github.com/soniah/gosnmp" ) func main() { flag.Usage = func() { fmt.Printf("Usage:\n") fmt.Printf(" %s\n", filepath.Base(os.Args[0])) flag.PrintDefaults() } tl := g.NewTrapListener() tl.OnNewTrap = myTrapHandler tl.Params = g.Default tl.Params.Logger = log.New(os.Stdout, "", 0) err := tl.Listen("0.0.0.0:9162") if err != nil { log.Panicf("error in listen: %s", err) } } func myTrapHandler(packet *g.SnmpPacket, addr *net.UDPAddr) { log.Printf("got trapdata from %s\n", addr.IP) for _, v := range packet.Variables { switch v.Type { case g.OctetString: b := v.Value.([]byte) fmt.Printf("OID: %s, string: %x\n", v.Name, b) default: log.Printf("trap: %+v\n", v) } } } gosnmp-1.24.0/examples/walkexample.go000066400000000000000000000031101362404504000175510ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // This program demonstrates BulkWalk. package main import ( "flag" "fmt" "os" "path/filepath" "time" "github.com/soniah/gosnmp" ) func main() { flag.Usage = func() { fmt.Printf("Usage:\n") fmt.Printf(" %s [-community=] host [oid]\n", filepath.Base(os.Args[0])) fmt.Printf(" host - the host to walk/scan\n") fmt.Printf(" oid - the MIB/Oid defining a subtree of values\n\n") flag.PrintDefaults() } var community string flag.StringVar(&community, "community", "public", "the community string for device") flag.Parse() if len(flag.Args()) < 1 { flag.Usage() os.Exit(1) } target := flag.Args()[0] var oid string if len(flag.Args()) > 1 { oid = flag.Args()[1] } gosnmp.Default.Target = target gosnmp.Default.Community = community gosnmp.Default.Timeout = time.Duration(10 * time.Second) // Timeout better suited to walking err := gosnmp.Default.Connect() if err != nil { fmt.Printf("Connect err: %v\n", err) os.Exit(1) } defer gosnmp.Default.Conn.Close() err = gosnmp.Default.BulkWalk(oid, printValue) if err != nil { fmt.Printf("Walk Error: %v\n", err) os.Exit(1) } } func printValue(pdu gosnmp.SnmpPDU) error { fmt.Printf("%s = ", pdu.Name) switch pdu.Type { case gosnmp.OctetString: b := pdu.Value.([]byte) fmt.Printf("STRING: %s\n", string(b)) default: fmt.Printf("TYPE %d: %d\n", pdu.Type, gosnmp.ToBigInt(pdu.Value)) } return nil } gosnmp-1.24.0/examples/walktcpexample.go000066400000000000000000000035741362404504000202760ustar00rootroot00000000000000// Copyright 2012-2014 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // This program demonstrates BulkWalk. package main import ( "flag" "fmt" "log" "os" "path/filepath" "time" "github.com/soniah/gosnmp" ) func main() { flag.Usage = func() { fmt.Printf("Usage:\n") fmt.Printf(" %s [-community=] host [oid]\n", filepath.Base(os.Args[0])) fmt.Printf(" host - the host to walk/scan\n") fmt.Printf(" oid - the MIB/Oid defining a subtree of values\n\n") flag.PrintDefaults() } var community string flag.StringVar(&community, "community", "public", "the community string for device") flag.Parse() if len(flag.Args()) < 1 { flag.Usage() os.Exit(1) } target := flag.Args()[0] var oid string if len(flag.Args()) > 1 { oid = flag.Args()[1] } gosnmp.Default.Target = target gosnmp.Default.Transport = "tcp" gosnmp.Default.Community = community gosnmp.Default.Timeout = time.Duration(10 * time.Second) // Timeout better suited to walking gosnmp.Default.Logger = log.New(os.Stdout, "", 0) err := gosnmp.Default.Connect() if err != nil { fmt.Printf("Connect err: %v\n", err) os.Exit(1) } defer gosnmp.Default.Conn.Close() err = gosnmp.Default.BulkWalk(oid, printValue) if err != nil { fmt.Printf("Walk Error: %v\n", err) os.Exit(1) } // This may lead to the remote server closing the TCP connection time.Sleep(15 * time.Second) err = gosnmp.Default.BulkWalk(oid, printValue) if err != nil { fmt.Printf("Walk Error: %v\n", err) os.Exit(1) } } func printValue(pdu gosnmp.SnmpPDU) error { fmt.Printf("%s = ", pdu.Name) switch pdu.Type { case gosnmp.OctetString: b := pdu.Value.([]byte) fmt.Printf("STRING: %s\n", string(b)) default: fmt.Printf("TYPE %d: %d\n", pdu.Type, gosnmp.ToBigInt(pdu.Value)) } return nil } gosnmp-1.24.0/generic_e2e_test.go000066400000000000000000000356651362404504000166530ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // This set of end-to-end integration tests execute gosnmp against a real // SNMP MIB-2 host. Potential test systems could include a router, NAS box, printer, // or a linux box running snmpd, snmpsimd.py, etc. // // Ensure "gosnmp-test-host" is defined in your hosts file, and points to your // generic test system. // +build all end2end package gosnmp import ( "fmt" "os" "strconv" "strings" "testing" "time" ) func setupConnection(t *testing.T) { envTarget := os.Getenv("GOSNMP_TARGET") envPort := os.Getenv("GOSNMP_PORT") if len(envTarget) <= 0 { t.Skip("environment variable not set: GOSNMP_TARGET") } Default.Target = envTarget if len(envPort) <= 0 { t.Skip("environment variable not set: GOSNMP_PORT") } port, _ := strconv.ParseUint(envPort, 10, 16) Default.Port = uint16(port) err := Default.Connect() if err != nil { if len(envTarget) > 0 { t.Fatalf("Connection failed. Is snmpd reachable on %s:%s?\n(err: %v)", envTarget, envPort, err) } } } func setupConnectionIPv4(t *testing.T) { envTarget := os.Getenv("GOSNMP_TARGET_IPV4") envPort := os.Getenv("GOSNMP_PORT_IPV4") if len(envTarget) <= 0 { t.Skip("environment variable not set: GOSNMP_TARGET_IPV4") } Default.Target = envTarget if len(envPort) <= 0 { t.Skip("environment variable not set: GOSNMP_PORT_IPV4") } port, _ := strconv.ParseUint(envPort, 10, 16) Default.Port = uint16(port) err := Default.ConnectIPv4() if err != nil { if len(envTarget) > 0 { t.Fatalf("Connection failed. Is snmpd reachable on %s:%s?\n(err: %v)", envTarget, envPort, err) } } } /* TODO work out ipv6 networking, etc func setupConnectionIPv6(t *testing.T) { envTarget := os.Getenv("GOSNMP_TARGET_IPV6") envPort := os.Getenv("GOSNMP_PORT_IPV6") if len(envTarget) <= 0 { t.Error("environment variable not set: GOSNMP_TARGET_IPV6") } Default.Target = envTarget if len(envPort) <= 0 { t.Error("environment variable not set: GOSNMP_PORT_IPV6") } port, _ := strconv.ParseUint(envPort, 10, 16) Default.Port = uint16(port) err := Default.ConnectIPv6() if err != nil { if len(envTarget) > 0 { t.Fatalf("Connection failed. Is snmpd reachable on %s:%s?\n(err: %v)", envTarget, envPort, err) } } } */ func TestGenericBasicGet(t *testing.T) { setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestGenericBasicGetIPv4Only(t *testing.T) { setupConnectionIPv4(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } /* func TestGenericBasicGetIPv6Only(t *testing.T) { setupConnectionIPv6(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } */ func TestGenericMultiGet(t *testing.T) { setupConnection(t) defer Default.Conn.Close() oids := []string{ ".1.3.6.1.2.1.1.1.0", // SNMP MIB-2 sysDescr ".1.3.6.1.2.1.1.5.0", // SNMP MIB-2 sysName } result, err := Default.Get(oids) if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 2 { t.Fatalf("Expected result of size 2") } for _, v := range result.Variables { if v.Type != OctetString { t.Fatalf("Expected OctetString") } } } func TestGenericGetNext(t *testing.T) { setupConnection(t) defer Default.Conn.Close() sysDescrOid := ".1.3.6.1.2.1.1.1.0" // SNMP MIB-2 sysDescr result, err := Default.GetNext([]string{sysDescrOid}) if err != nil { t.Fatalf("GetNext() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Name == sysDescrOid { t.Fatalf("Expected next OID") } } func TestGenericWalk(t *testing.T) { setupConnection(t) defer Default.Conn.Close() result, err := Default.WalkAll("") if err != nil { t.Fatalf("WalkAll() Failed with error => %v", err) } if len(result) <= 1 { t.Fatalf("Expected multiple values, got %d", len(result)) } } func TestGenericBulkWalk(t *testing.T) { setupConnection(t) defer Default.Conn.Close() result, err := Default.BulkWalkAll("") if err != nil { t.Fatalf("BulkWalkAll() Failed with error => %v", err) } if len(result) <= 1 { t.Fatalf("Expected multiple values, got %d", len(result)) } } // Standard exception/error tests func TestMaxOids(t *testing.T) { setupConnection(t) defer Default.Conn.Close() Default.MaxOids = 1 var err error oids := []string{".1.3.6.1.2.1.1.7.0", ".1.3.6.1.2.1.2.2.1.10.1"} // 2 arbitrary Oids errString := "oid count (2) is greater than MaxOids (1)" _, err = Default.Get(oids) if err == nil { t.Fatalf("Expected too many oids failure. Got nil") } else if err.Error() != errString { t.Fatalf("Expected too many oids failure. Got => %v", err) } _, err = Default.GetNext(oids) if err == nil { t.Fatalf("Expected too many oids failure. Got nil") } else if err.Error() != errString { t.Fatalf("Expected too many oids failure. Got => %v", err) } _, err = Default.GetBulk(oids, 0, 0) if err == nil { t.Fatalf("Expected too many oids failure. Got nil") } else if err.Error() != errString { t.Fatalf("Expected too many oids failure. Got => %v", err) } } func TestGenericFailureUnknownHost(t *testing.T) { unknownHost := fmt.Sprintf("gosnmp-test-unknown-host-%d", time.Now().UTC().UnixNano()) Default.Target = unknownHost err := Default.Connect() if err == nil { t.Fatalf("Expected connection failure due to unknown host") } if !strings.Contains(strings.ToLower(err.Error()), "no such host") { t.Fatalf("Expected connection error of type 'no such host'! Got => %v", err) } _, err = Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err == nil { t.Fatalf("Expected get to fail due to missing connection") } } func TestGenericFailureConnectionTimeout(t *testing.T) { envTarget := os.Getenv("GOSNMP_TARGET") if len(envTarget) <= 0 { t.Skip("local testing - skipping this slow one") // TODO test tag, or something } Default.Target = "198.51.100.1" // Black hole err := Default.Connect() if err != nil { t.Fatalf("Did not expect connection error with IP address") } _, err = Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err == nil { t.Fatalf("Expected Get() to fail due to invalid IP") } if !strings.Contains(err.Error(), "timeout") { t.Fatalf("Expected timeout error. Got => %v", err) } } func TestGenericFailureConnectionRefused(t *testing.T) { Default.Target = "127.0.0.1" Default.Port = 1 // Don't expect SNMP to be running here! err := Default.Connect() if err != nil { t.Fatalf("Did not expect connection error with IP address") } _, err = Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err == nil { t.Fatalf("Expected Get() to fail due to invalid port") } if !(strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "forcibly closed")) { t.Fatalf("Expected connection refused error. Got => %v", err) } } func TestSnmpV3NoAuthNoPrivBasicGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = NoAuthNoPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "noAuthNoPrivUser"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthNoPrivMD5Get(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthNoPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authMD5OnlyUser", AuthenticationProtocol: MD5, AuthenticationPassphrase: "testingpass0123456789"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthNoPrivSHAGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthNoPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authSHAOnlyUser", AuthenticationProtocol: SHA, AuthenticationPassphrase: "testingpass9876543210"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthMD5PrivDESGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authMD5PrivDESUser", AuthenticationProtocol: MD5, AuthenticationPassphrase: "testingpass9876543210", PrivacyProtocol: DES, PrivacyPassphrase: "testingpass9876543210"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthSHAPrivDESGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authSHAPrivDESUser", AuthenticationProtocol: SHA, AuthenticationPassphrase: "testingpassabc6543210", PrivacyProtocol: DES, PrivacyPassphrase: "testingpassabc6543210"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthMD5PrivAESGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authMD5PrivAESUser", AuthenticationProtocol: MD5, AuthenticationPassphrase: "AEStestingpass9876543210", PrivacyProtocol: AES, PrivacyPassphrase: "AEStestingpass9876543210"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3AuthSHAPrivAESGet(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authSHAPrivAESUser", AuthenticationProtocol: SHA, AuthenticationPassphrase: "AEStestingpassabc6543210", PrivacyProtocol: AES, PrivacyPassphrase: "AEStestingpassabc6543210"} setupConnection(t) defer Default.Conn.Close() result, err := Default.Get([]string{".1.3.6.1.2.1.1.1.0"}) // SNMP MIB-2 sysDescr if err != nil { t.Fatalf("Get() failed with error => %v", err) } if len(result.Variables) != 1 { t.Fatalf("Expected result of size 1") } if result.Variables[0].Type != OctetString { t.Fatalf("Expected sysDescr to be OctetString") } sysDescr := result.Variables[0].Value.([]byte) if len(sysDescr) == 0 { t.Fatalf("Got a zero length sysDescr") } } func TestSnmpV3PrivEmptyPrivatePassword(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authSHAPrivAESUser", AuthenticationProtocol: SHA, AuthenticationPassphrase: "AEStestingpassabc6543210", PrivacyProtocol: AES, PrivacyPassphrase: ""} err := Default.Connect() if err == nil { t.Fatalf("Expected validation error for empty PrivacyPassphrase") } } func TestSnmpV3AuthNoPrivEmptyPrivatePassword(t *testing.T) { Default.Version = Version3 Default.MsgFlags = AuthNoPriv Default.SecurityModel = UserSecurityModel Default.SecurityParameters = &UsmSecurityParameters{UserName: "authSHAOnlyUser", AuthenticationProtocol: SHA, AuthenticationPassphrase: "testingpass9876543210", PrivacyProtocol: AES, PrivacyPassphrase: ""} err := Default.Connect() if err == nil { t.Fatalf("Expected validation error for empty PrivacyPassphrase") } } gosnmp-1.24.0/go.mod000066400000000000000000000001601362404504000142020ustar00rootroot00000000000000module github.com/soniah/gosnmp require ( github.com/golang/mock v1.2.0 github.com/stretchr/testify v1.3.0 ) gosnmp-1.24.0/go.sum000066400000000000000000000014031362404504000142300ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= gosnmp-1.24.0/gosnmp.go000066400000000000000000000454561362404504000147470ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gosnmp import ( "context" "fmt" "io/ioutil" "log" "math/big" "math/rand" "net" "strconv" "sync/atomic" "time" ) const ( // MaxOids is the maximum number of OIDs permitted in a single call, // otherwise error. MaxOids too high can cause remote devices to fail // strangely. 60 seems to be a common value that works, but you will want // to change this in the GoSNMP struct MaxOids = 60 // Base OID for MIB-2 defined SNMP variables baseOid = ".1.3.6.1.2.1" // Java SNMP uses 50, snmp-net uses 10 defaultMaxRepetitions = 50 ) // GoSNMP represents GoSNMP library state type GoSNMP struct { // Conn is net connection to use, typically established using GoSNMP.Connect() Conn net.Conn // Target is an ipv4 address Target string // Port is a port Port uint16 // Transport is the transport protocol to use ("udp" or "tcp"); if unset "udp" will be used. Transport string // Community is an SNMP Community string Community string // Version is an SNMP Version Version SnmpVersion // Context allows for overall deadlines and cancellation Context context.Context // Timeout is the timeout for one SNMP request/response Timeout time.Duration // Set the number of retries to attempt within timeout Retries int // Double timeout in each retry ExponentialTimeout bool // Logger is the GoSNMP.Logger to use for debugging. If nil, debugging // output will be discarded (/dev/null). For verbose logging to stdout: // x.Logger = log.New(os.Stdout, "", 0) Logger Logger // loggingEnabled is set if the Logger isn't nil, otherwise any logging calls // are ignored via shortcircuit loggingEnabled bool // MaxOids is the maximum number of oids allowed in a Get() // (default: MaxOids) MaxOids int // MaxRepetitions sets the GETBULK max-repetitions used by BulkWalk* // Unless MaxRepetitions is specified it will use defaultMaxRepetitions (50) // This may cause issues with some devices, if so set MaxRepetitions lower. // See comments in https://github.com/soniah/gosnmp/issues/100 MaxRepetitions uint8 // NonRepeaters sets the GETBULK max-repeaters used by BulkWalk* // (default: 0 as per RFC 1905) NonRepeaters int // netsnmp has '-C APPOPTS - set various application specific behaviours' // // - 'c: do not check returned OIDs are increasing' - use AppOpts = map[string]interface{"c":true} with // Walk() or BulkWalk(). The library user needs to implement their own policy for terminating walks. // - 'p,i,I,t,E' -> pull requests welcome AppOpts map[string]interface{} // Internal - used to sync requests to responses requestID uint32 random *rand.Rand rxBuf *[rxBufSize]byte // has to be pointer due to https://github.com/golang/go/issues/11728 // MsgFlags is an SNMPV3 MsgFlags MsgFlags SnmpV3MsgFlags // SecurityModel is an SNMPV3 Security Model SecurityModel SnmpV3SecurityModel // SecurityParameters is an SNMPV3 Security Model parameters struct SecurityParameters SnmpV3SecurityParameters // ContextEngineID is SNMPV3 ContextEngineID in ScopedPDU ContextEngineID string // ContextName is SNMPV3 ContextName in ScopedPDU ContextName string // Internal - used to sync requests to responses - snmpv3 msgID uint32 } // Default connection settings var Default = &GoSNMP{ Port: 161, Transport: "udp", Community: "public", Version: Version2c, Timeout: time.Duration(2) * time.Second, Retries: 3, ExponentialTimeout: true, MaxOids: MaxOids, } // SnmpPDU will be used when doing SNMP Set's type SnmpPDU struct { // Name is an oid in string format eg ".1.3.6.1.4.9.27" Name string // The type of the value eg Integer Type Asn1BER // The value to be set by the SNMP set, or the value when // sending a trap Value interface{} // Logger implements the Logger interface Logger Logger } // AsnExtensionID mask to identify types > 30 in subsequent byte const AsnExtensionID = 0x1F //go:generate stringer -type Asn1BER // Asn1BER is the type of the SNMP PDU type Asn1BER byte // Asn1BER's - http://www.ietf.org/rfc/rfc1442.txt const ( EndOfContents Asn1BER = 0x00 UnknownType Asn1BER = 0x00 Boolean Asn1BER = 0x01 Integer Asn1BER = 0x02 BitString Asn1BER = 0x03 OctetString Asn1BER = 0x04 Null Asn1BER = 0x05 ObjectIdentifier Asn1BER = 0x06 ObjectDescription Asn1BER = 0x07 IPAddress Asn1BER = 0x40 Counter32 Asn1BER = 0x41 Gauge32 Asn1BER = 0x42 TimeTicks Asn1BER = 0x43 Opaque Asn1BER = 0x44 NsapAddress Asn1BER = 0x45 Counter64 Asn1BER = 0x46 Uinteger32 Asn1BER = 0x47 OpaqueFloat Asn1BER = 0x78 OpaqueDouble Asn1BER = 0x79 NoSuchObject Asn1BER = 0x80 NoSuchInstance Asn1BER = 0x81 EndOfMibView Asn1BER = 0x82 ) //go:generate stringer -type SNMPError // SNMPError is the type for standard SNMP errors. type SNMPError uint8 // SNMP Errors const ( NoError SNMPError = iota // No error occurred. This code is also used in all request PDUs, since they have no error status to report. TooBig // The size of the Response-PDU would be too large to transport. NoSuchName // The name of a requested object was not found. BadValue // A value in the request didn't match the structure that the recipient of the request had for the object. For example, an object in the request was specified with an incorrect length or type. ReadOnly // An attempt was made to set a variable that has an Access value indicating that it is read-only. GenErr // An error occurred other than one indicated by a more specific error code in this table. NoAccess // Access was denied to the object for security reasons. WrongType // The object type in a variable binding is incorrect for the object. WrongLength // A variable binding specifies a length incorrect for the object. WrongEncoding // A variable binding specifies an encoding incorrect for the object. WrongValue // The value given in a variable binding is not possible for the object. NoCreation // A specified variable does not exist and cannot be created. InconsistentValue // A variable binding specifies a value that could be held by the variable but cannot be assigned to it at this time. ResourceUnavailable // An attempt to set a variable required a resource that is not available. CommitFailed // An attempt to set a particular variable failed. UndoFailed // An attempt to set a particular variable as part of a group of variables failed, and the attempt to then undo the setting of other variables was not successful. AuthorizationError // A problem occurred in authorization. NotWritable // The variable cannot be written or created. InconsistentName // The name in a variable binding specifies a variable that does not exist. ) // // Public Functions (main interface) // // Connect creates and opens a socket. Because UDP is a connectionless // protocol, you won't know if the remote host is responding until you send // packets. Neither will you know if the host is regularly disappearing and reappearing. // // For historical reasons (ie this is part of the public API), the method won't // be renamed to Dial(). func (x *GoSNMP) Connect() error { return x.connect("") } // ConnectIPv4 forces an IPv4-only connection func (x *GoSNMP) ConnectIPv4() error { return x.connect("4") } // ConnectIPv6 forces an IPv6-only connection func (x *GoSNMP) ConnectIPv6() error { return x.connect("6") } // connect to address addr on the given network // // https://golang.org/pkg/net/#Dial gives acceptable network values as: // "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp", "udp4" (IPv4-only),"udp6" (IPv6-only), "ip", // "ip4" (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket" func (x *GoSNMP) connect(networkSuffix string) error { err := x.validateParameters() if err != nil { return err } x.Transport = x.Transport + networkSuffix err = x.netConnect() if err != nil { return fmt.Errorf("error establishing connection to host: %s", err.Error()) } if x.random == nil { x.random = rand.New(rand.NewSource(time.Now().UTC().UnixNano())) } // http://tools.ietf.org/html/rfc3412#section-6 - msgID only // uses the first 31 bits // msgID INTEGER (0..2147483647) x.msgID = uint32(x.random.Int31()) // RequestID is Integer32 from SNMPV2-SMI and uses all 32 bits x.requestID = x.random.Uint32() x.rxBuf = new([rxBufSize]byte) return nil } // Performs the real socket opening network operation. This can be used to do a // reconnect (needed for TCP) func (x *GoSNMP) netConnect() error { var err error addr := net.JoinHostPort(x.Target, strconv.Itoa(int(x.Port))) dialer := net.Dialer{Timeout: x.Timeout} x.Conn, err = dialer.DialContext(x.Context, x.Transport, addr) return err } func (x *GoSNMP) validateParameters() error { if x.Logger == nil { x.Logger = log.New(ioutil.Discard, "", 0) } else { x.loggingEnabled = true } if x.Transport == "" { x.Transport = "udp" } if x.MaxOids == 0 { x.MaxOids = MaxOids } else if x.MaxOids < 0 { return fmt.Errorf("MaxOids cannot be less than 0") } if x.Version == Version3 { x.MsgFlags |= Reportable // tell the snmp server that a report PDU MUST be sent err := x.validateParametersV3() if err != nil { return err } err = x.SecurityParameters.init(x.Logger) if err != nil { return err } } if x.Context == nil { x.Context = context.Background() } return nil } func (x *GoSNMP) mkSnmpPacket(pdutype PDUType, pdus []SnmpPDU, nonRepeaters uint8, maxRepetitions uint8) *SnmpPacket { var newSecParams SnmpV3SecurityParameters if x.SecurityParameters != nil { newSecParams = x.SecurityParameters.Copy() } return &SnmpPacket{ Version: x.Version, Community: x.Community, MsgFlags: x.MsgFlags, SecurityModel: x.SecurityModel, SecurityParameters: newSecParams, ContextEngineID: x.ContextEngineID, ContextName: x.ContextName, Error: 0, ErrorIndex: 0, PDUType: pdutype, NonRepeaters: nonRepeaters, MaxRepetitions: maxRepetitions, Variables: pdus, } } // Get sends an SNMP GET request func (x *GoSNMP) Get(oids []string) (result *SnmpPacket, err error) { oidCount := len(oids) if oidCount > x.MaxOids { return nil, fmt.Errorf("oid count (%d) is greater than MaxOids (%d)", oidCount, x.MaxOids) } // convert oids slice to pdu slice var pdus []SnmpPDU for _, oid := range oids { pdus = append(pdus, SnmpPDU{oid, Null, nil, x.Logger}) } // build up SnmpPacket packetOut := x.mkSnmpPacket(GetRequest, pdus, 0, 0) return x.send(packetOut, true) } // Set sends an SNMP SET request func (x *GoSNMP) Set(pdus []SnmpPDU) (result *SnmpPacket, err error) { var packetOut *SnmpPacket switch pdus[0].Type { // TODO test Gauge32 case Integer, OctetString, Gauge32, IPAddress: packetOut = x.mkSnmpPacket(SetRequest, pdus, 0, 0) default: return nil, fmt.Errorf("ERR:gosnmp currently only supports SNMP SETs for Integers, IPAddress and OctetStrings") } return x.send(packetOut, true) } // GetNext sends an SNMP GETNEXT request func (x *GoSNMP) GetNext(oids []string) (result *SnmpPacket, err error) { oidCount := len(oids) if oidCount > x.MaxOids { return nil, fmt.Errorf("oid count (%d) is greater than MaxOids (%d)", oidCount, x.MaxOids) } // convert oids slice to pdu slice var pdus []SnmpPDU for _, oid := range oids { pdus = append(pdus, SnmpPDU{oid, Null, nil, x.Logger}) } // Marshal and send the packet packetOut := x.mkSnmpPacket(GetNextRequest, pdus, 0, 0) return x.send(packetOut, true) } // GetBulk sends an SNMP GETBULK request // // For maxRepetitions greater than 255, use BulkWalk() or BulkWalkAll() func (x *GoSNMP) GetBulk(oids []string, nonRepeaters uint8, maxRepetitions uint8) (result *SnmpPacket, err error) { oidCount := len(oids) if oidCount > x.MaxOids { return nil, fmt.Errorf("oid count (%d) is greater than MaxOids (%d)", oidCount, x.MaxOids) } // convert oids slice to pdu slice var pdus []SnmpPDU for _, oid := range oids { pdus = append(pdus, SnmpPDU{oid, Null, nil, x.Logger}) } // Marshal and send the packet packetOut := x.mkSnmpPacket(GetBulkRequest, pdus, nonRepeaters, maxRepetitions) return x.send(packetOut, true) } // SnmpEncodePacket exposes SNMP packet generation to external callers. // This is useful for generating traffic for use over separate transport // stacks and creating traffic samples for test purposes. func (x *GoSNMP) SnmpEncodePacket(pdutype PDUType, pdus []SnmpPDU, nonRepeaters uint8, maxRepetitions uint8) ([]byte, error) { err := x.validateParameters() if err != nil { return []byte{}, err } pkt := x.mkSnmpPacket(pdutype, pdus, nonRepeaters, maxRepetitions) // Request ID is an atomic counter (started at a random value) reqID := atomic.AddUint32(&(x.requestID), 1) // TODO: fix overflows pkt.RequestID = reqID if x.Version == Version3 { msgID := atomic.AddUint32(&(x.msgID), 1) // TODO: fix overflows pkt.MsgID = msgID err = x.initPacket(pkt) if err != nil { return []byte{}, err } } var out []byte out, err = pkt.marshalMsg() if err != nil { return []byte{}, err } return out, nil } // SnmpDecodePacket exposes SNMP packet parsing to external callers. // This is useful for processing traffic from other sources and // building test harnesses. func (x *GoSNMP) SnmpDecodePacket(resp []byte) (*SnmpPacket, error) { var err error result := new(SnmpPacket) err = x.validateParameters() if err != nil { return result, err } result.Logger = x.Logger if x.SecurityParameters != nil { result.SecurityParameters = x.SecurityParameters.Copy() } var cursor int cursor, err = x.unmarshalHeader(resp, result) if err != nil { err = fmt.Errorf("Unable to decode packet header: %s", err.Error()) return result, err } if result.Version == Version3 { resp, cursor, err = x.decryptPacket(resp, cursor, result) if err != nil { return result, err } } err = x.unmarshalPayload(resp, cursor, result) if err != nil { err = fmt.Errorf("Unable to decode packet body: %s", err.Error()) return result, err } if result == nil { err = fmt.Errorf("Unable to decode packet: no variables") return result, err } return result, nil } // SetRequestID sets the base ID value for future requests func (x *GoSNMP) SetRequestID(reqID uint32) { x.requestID = reqID } // SetMsgID sets the base ID value for future messages func (x *GoSNMP) SetMsgID(msgID uint32) { x.msgID = msgID & 0x7fffffff } // // SNMP Walk functions - Analogous to net-snmp's snmpwalk commands // // WalkFunc is the type of the function called for each data unit visited // by the Walk function. If an error is returned processing stops. type WalkFunc func(dataUnit SnmpPDU) error // BulkWalk retrieves a subtree of values using GETBULK. As the tree is // walked walkFn is called for each new value. The function immediately returns // an error if either there is an underlaying SNMP error (e.g. GetBulk fails), // or if walkFn returns an error. func (x *GoSNMP) BulkWalk(rootOid string, walkFn WalkFunc) error { return x.walk(GetBulkRequest, rootOid, walkFn) } // BulkWalkAll is similar to BulkWalk but returns a filled array of all values // rather than using a callback function to stream results. Caution: if you // have set x.AppOpts to 'c', BulkWalkAll may loop indefinitely and cause an // Out Of Memory - use BulkWalk instead. func (x *GoSNMP) BulkWalkAll(rootOid string) (results []SnmpPDU, err error) { return x.walkAll(GetBulkRequest, rootOid) } // Walk retrieves a subtree of values using GETNEXT - a request is made for each // value, unlike BulkWalk which does this operation in batches. As the tree is // walked walkFn is called for each new value. The function immediately returns // an error if either there is an underlaying SNMP error (e.g. GetNext fails), // or if walkFn returns an error. func (x *GoSNMP) Walk(rootOid string, walkFn WalkFunc) error { return x.walk(GetNextRequest, rootOid, walkFn) } // WalkAll is similar to Walk but returns a filled array of all values rather // than using a callback function to stream results. Caution: if you have set // x.AppOpts to 'c', WalkAll may loop indefinitely and cause an Out Of Memory - // use Walk instead. func (x *GoSNMP) WalkAll(rootOid string) (results []SnmpPDU, err error) { return x.walkAll(GetNextRequest, rootOid) } // // Public Functions (helpers) - in alphabetical order // // Partition - returns true when dividing a slice into // partitionSize lengths, including last partition which may be smaller // than partitionSize. This is useful when you have a large array of OIDs // to run Get() on. See the tests for example usage. // // For example for a slice of 8 items to be broken into partitions of // length 3, Partition returns true for the currentPosition having // the following values: // // 0 1 2 3 4 5 6 7 // T T T // func Partition(currentPosition, partitionSize, sliceLength int) bool { if currentPosition < 0 || currentPosition >= sliceLength { return false } if partitionSize == 1 { // redundant, but an obvious optimisation return true } if currentPosition%partitionSize == partitionSize-1 { return true } if currentPosition == sliceLength-1 { return true } return false } // ToBigInt converts SnmpPDU.Value to big.Int, or returns a zero big.Int for // non int-like types (eg strings). // // This is a convenience function to make working with SnmpPDU's easier - it // reduces the need for type assertions. A big.Int is convenient, as SNMP can // return int32, uint32, and uint64. func ToBigInt(value interface{}) *big.Int { var val int64 switch value := value.(type) { // shadow case int: val = int64(value) case int8: val = int64(value) case int16: val = int64(value) case int32: val = int64(value) case int64: val = int64(value) case uint: val = int64(value) case uint8: val = int64(value) case uint16: val = int64(value) case uint32: val = int64(value) case uint64: return (uint64ToBigInt(value)) case string: // for testing and other apps - numbers may appear as strings var err error if val, err = strconv.ParseInt(value, 10, 64); err != nil { return new(big.Int) } default: return new(big.Int) } return big.NewInt(val) } gosnmp-1.24.0/gosnmp_api_test.go000066400000000000000000000041361362404504000166250ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // The purpose of these tests is to validate gosnmp's public APIs. // // IMPORTANT: If you're modifying _any_ existing code in this file, you // should be asking yourself about API compatibility! // +build all api package gosnmp_test // force external view import ( "io/ioutil" "log" "net" "testing" "time" "github.com/soniah/gosnmp" ) func TestAPIConfigTypes(t *testing.T) { g := &gosnmp.GoSNMP{} g.Target = "" g.Port = 0 g.Community = "" g.Version = gosnmp.Version1 g.Version = gosnmp.Version2c g.Timeout = time.Duration(0) g.Retries = 0 g.Logger = log.New(ioutil.Discard, "", 0) g.MaxOids = 0 g.MaxRepetitions = 0 g.NonRepeaters = 0 var c net.Conn c = g.Conn _ = c } func TestAPIDefault(t *testing.T) { var g *gosnmp.GoSNMP g = gosnmp.Default _ = g } func TestAPIConnectMethodSignature(t *testing.T) { var f func() error f = gosnmp.Default.Connect _ = f } func TestAPIGetMethodSignature(t *testing.T) { var f func([]string) (*gosnmp.SnmpPacket, error) f = gosnmp.Default.Get _ = f } func TestAPISetMethodSignature(t *testing.T) { var f func([]gosnmp.SnmpPDU) (*gosnmp.SnmpPacket, error) f = gosnmp.Default.Set _ = f } func TestAPIGetNextMethodSignature(t *testing.T) { var f func([]string) (*gosnmp.SnmpPacket, error) f = gosnmp.Default.GetNext _ = f } func TestAPIBulkWalkMethodSignature(t *testing.T) { var f func(string, gosnmp.WalkFunc) error f = gosnmp.Default.BulkWalk _ = f } func TestAPIBulkWalkAllMethodSignature(t *testing.T) { var f func(string) ([]gosnmp.SnmpPDU, error) f = gosnmp.Default.BulkWalkAll _ = f } func TestAPIWalkMethodSignature(t *testing.T) { var f func(string, gosnmp.WalkFunc) error f = gosnmp.Default.Walk _ = f } func TestAPIWalkAllMethodSignature(t *testing.T) { var f func(string) ([]gosnmp.SnmpPDU, error) f = gosnmp.Default.WalkAll _ = f } func TestAPIWalkFuncSignature(t *testing.T) { var f gosnmp.WalkFunc f = func(du gosnmp.SnmpPDU) (err error) { return } _ = f } gosnmp-1.24.0/helper.go000066400000000000000000000513001362404504000147040ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gosnmp import ( // "bytes" "bytes" "encoding/binary" "errors" "fmt" "log" "math" "math/big" "net" "os" "strconv" "strings" ) // variable struct is used by decodeValue(), which is used for debugging type variable struct { Name []int Type Asn1BER Value interface{} } // -- helper functions (mostly) in alphabetical order -------------------------- // Check makes checking errors easy, so they actually get a minimal check func (x *GoSNMP) Check(err error) { if err != nil { x.Logger.Printf("Check: %v\n", err) os.Exit(1) } } // Check makes checking errors easy, so they actually get a minimal check func (p *SnmpPacket) Check(err error) { if err != nil { p.Logger.Printf("Check: %v\n", err) os.Exit(1) } } // Check makes checking errors easy, so they actually get a minimal check func (p *SnmpPDU) Check(err error) { if err != nil { p.Logger.Printf("Check: %v\n", err) os.Exit(1) } } // Check makes checking errors easy, so they actually get a minimal check func Check(err error) { if err != nil { log.Fatalf("Check: %v\n", err) } } func (x *GoSNMP) decodeValue(data []byte, msg string) (retVal *variable, err error) { retVal = new(variable) if len(data) == 0 { return retVal, fmt.Errorf("err: zero byte buffer") } // values matching this mask have the type in subsequent byte if data[0]&AsnExtensionID == AsnExtensionID { if len(data) < 2 { return retVal, fmt.Errorf("bytes: % x err: truncated (data %d length %d)", data, len(data), 2) } data = data[1:] } switch Asn1BER(data[0]) { case Integer: // 0x02. signed x.logPrint("decodeValue: type is Integer") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("bytes: % x err: truncated (data %d length %d)", data, len(data), length) } var ret int var err error if ret, err = parseInt(data[cursor:length]); err != nil { x.logPrintf("%v:", err) return retVal, fmt.Errorf("bytes: % x err: %v", data, err) } retVal.Type = Integer retVal.Value = ret case OctetString: // 0x04 x.logPrint("decodeValue: type is OctetString") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("bytes: % x err: truncated (data %d length %d)", data, len(data), length) } retVal.Type = OctetString retVal.Value = []byte(data[cursor:length]) case Null: // 0x05 x.logPrint("decodeValue: type is Null") retVal.Type = Null retVal.Value = nil case ObjectIdentifier: // 0x06 x.logPrint("decodeValue: type is ObjectIdentifier") rawOid, _, err := parseRawField(data, "OID") if err != nil { return nil, fmt.Errorf("Error parsing OID Value: %s", err.Error()) } var oid []int var ok bool if oid, ok = rawOid.([]int); !ok { return nil, fmt.Errorf("unable to type assert rawOid |%v| to []int", rawOid) } retVal.Type = ObjectIdentifier retVal.Value = oidToString(oid) case IPAddress: // 0x40 x.logPrint("decodeValue: type is IPAddress") retVal.Type = IPAddress if len(data) < 2 { return retVal, fmt.Errorf("not enough data for ipv4 address: %x", data) } switch data[1] { case 0: // real life, buggy devices returning bad data retVal.Value = nil return retVal, nil case 4: // IPv4 if len(data) < 6 { return nil, fmt.Errorf("not enough data for ipv4 address: %x", data) } retVal.Value = net.IPv4(data[2], data[3], data[4], data[5]).String() case 16: // IPv6 if len(data) < 18 { return nil, fmt.Errorf("not enough data for ipv6 address: %x", data) } d := make(net.IP, 16) copy(d, data[2:17]) retVal.Value = d.String() default: return nil, fmt.Errorf("got ipaddress len %d, expected 4 or 16", data[1]) } case Counter32: // 0x41. unsigned x.logPrint("decodeValue: type is Counter32") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for Counter32 %x (data %d length %d)", data, len(data), length) } ret, err := parseUint(data[cursor:length]) if err != nil { x.logPrintf("decodeValue: err is %v", err) break } retVal.Type = Counter32 retVal.Value = ret case Gauge32: // 0x42. unsigned x.logPrint("decodeValue: type is Gauge32") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for Gauge32 %x (data %d length %d)", data, len(data), length) } ret, err := parseUint(data[cursor:length]) if err != nil { x.logPrintf("decodeValue: err is %v", err) break } retVal.Type = Gauge32 retVal.Value = ret case TimeTicks: // 0x43 x.logPrint("decodeValue: type is TimeTicks") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for TimeTicks %x (data %d length %d)", data, len(data), length) } ret, err := parseUint32(data[cursor:length]) if err != nil { x.logPrintf("decodeValue: err is %v", err) break } retVal.Type = TimeTicks retVal.Value = ret case Opaque: // 0x44 x.logPrint("decodeValue: type is Opaque") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for Opaque %x (data %d length %d)", data, len(data), length) } opaqueData := data[cursor:length] // recursively decode opaque data return x.decodeValue(opaqueData, msg) case Counter64: // 0x46 x.logPrint("decodeValue: type is Counter64") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for Counter64 %x (data %d length %d)", data, len(data), length) } ret, err := parseUint64(data[cursor:length]) if err != nil { x.logPrintf("decodeValue: err is %v", err) break } retVal.Type = Counter64 retVal.Value = ret case OpaqueFloat: // 0x78 x.logPrint("decodeValue: type is OpaqueFloat") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for OpaqueFloat %x (data %d length %d)", data, len(data), length) } retVal.Type = OpaqueFloat retVal.Value, err = parseFloat32(data[cursor:length]) case OpaqueDouble: // 0x79 x.logPrint("decodeValue: type is OpaqueDouble") length, cursor := parseLength(data) if length > len(data) { return retVal, fmt.Errorf("not enough data for OpaqueDouble %x (data %d length %d)", data, len(data), length) } retVal.Type = OpaqueDouble retVal.Value, err = parseFloat64(data[cursor:length]) case NoSuchObject: // 0x80 x.logPrint("decodeValue: type is NoSuchObject") retVal.Type = NoSuchObject retVal.Value = nil case NoSuchInstance: // 0x81 x.logPrint("decodeValue: type is NoSuchInstance") retVal.Type = NoSuchInstance retVal.Value = nil case EndOfMibView: // 0x82 x.logPrint("decodeValue: type is EndOfMibView") retVal.Type = EndOfMibView retVal.Value = nil default: x.logPrintf("decodeValue: type %x isn't implemented", data[0]) retVal.Type = UnknownType retVal.Value = nil } x.logPrintf("decodeValue: value is %#v", retVal.Value) return } func marshalUvarInt(x uint32) []byte { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, x) i := 0 for ; i < 3; i++ { if buf[i] != 0 { break } } buf = buf[i:] // if the highest bit in buf is set and x is not negative - prepend a byte to make it positive if len(buf) > 0 && buf[0]&0x80 > 0 { buf = append([]byte{0}, buf...) } return buf } func marshalBase128Int(out *bytes.Buffer, n int64) (err error) { if n == 0 { err = out.WriteByte(0) return } l := 0 for i := n; i > 0; i >>= 7 { l++ } for i := l - 1; i >= 0; i-- { o := byte(n >> uint(i*7)) o &= 0x7f if i != 0 { o |= 0x80 } err = out.WriteByte(o) if err != nil { return } } return nil } /* snmp Integer32 and INTEGER: -2^31 and 2^31-1 inclusive (-2147483648 to 2147483647 decimal) (FYI https://groups.google.com/forum/#!topic/comp.protocols.snmp/1xaAMzCe_hE) versus: snmp Counter32, Gauge32, TimeTicks, Unsigned32: (below) non-negative integer, maximum value of 2^32-1 (4294967295 decimal) */ // marshalInt32 builds a byte representation of a signed 32 bit int in BigEndian form // ie -2^31 and 2^31-1 inclusive (-2147483648 to 2147483647 decimal) func marshalInt32(value int) (rs []byte, err error) { rs = make([]byte, 4) if 0 <= value && value <= 2147483647 { binary.BigEndian.PutUint32(rs, uint32(value)) if value < 0x80 { return rs[3:], nil } if value < 0x8000 { return rs[2:], nil } if value < 0x800000 { return rs[1:], nil } return rs, nil } if -2147483648 <= value && value < 0 { value = ^value binary.BigEndian.PutUint32(rs, uint32(value)) for k, v := range rs { rs[k] = ^v } return rs, nil } return nil, fmt.Errorf("unable to marshal %d", value) } func marshalUint64(v interface{}) ([]byte, error) { bs := make([]byte, 8) source := v.(uint64) binary.BigEndian.PutUint64(bs, source) // will panic on failure // truncate leading zeros. Cleaner technique? return bytes.TrimLeft(bs, "\x00"), nil //return bs, nil } // Counter32, Gauge32, TimeTicks, Unsigned32 func marshalUint32(v interface{}) ([]byte, error) { bs := make([]byte, 4) source := v.(uint32) binary.BigEndian.PutUint32(bs, source) // will panic on failure // truncate leading zeros. Cleaner technique? if source < 0x80 { return bs[3:], nil } if source < 0x8000 { return bs[2:], nil } if source < 0x800000 { return bs[1:], nil } return bs, nil } func marshalFloat32(v interface{}) ([]byte, error) { //func Float64bits(f float64) uint64 source := v.(float32) i32 := math.Float32bits(source) return marshalUint32(i32) } func marshalFloat64(v interface{}) ([]byte, error) { //func Float64bits(f float64) uint64 source := v.(float64) i64 := math.Float64bits(source) return marshalUint64(i64) } // marshalLength builds a byte representation of length // // http://luca.ntop.org/Teaching/Appunti/asn1.html // // Length octets. There are two forms: short (for lengths between 0 and 127), // and long definite (for lengths between 0 and 2^1008 -1). // // * Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length. // * Long form. Two to 127 octets. Bit 8 of first octet has value "1" and bits // 7-1 give the number of additional length octets. Second and following // octets give the length, base 256, most significant digit first. func marshalLength(length int) ([]byte, error) { // more convenient to pass length as int than uint64. Therefore check < 0 if length < 0 { return nil, fmt.Errorf("length must be greater than zero") } else if length < 127 { return []byte{byte(length)}, nil } buf := new(bytes.Buffer) err := binary.Write(buf, binary.BigEndian, uint64(length)) if err != nil { return nil, err } bufBytes := buf.Bytes() // strip leading zeros for idx, octect := range bufBytes { if octect != 00 { bufBytes = bufBytes[idx:] break } } header := []byte{byte(128 | len(bufBytes))} return append(header, bufBytes...), nil } func marshalObjectIdentifier(oid []int) (ret []byte, err error) { out := new(bytes.Buffer) if len(oid) < 2 || oid[0] > 6 || oid[1] >= 40 { return nil, errors.New("invalid object identifier") } err = out.WriteByte(byte(oid[0]*40 + oid[1])) if err != nil { return } for i := 2; i < len(oid); i++ { err = marshalBase128Int(out, int64(oid[i])) if err != nil { return } } ret = out.Bytes() return } func marshalOID(oid string) ([]byte, error) { var err error // Encode the oid oid = strings.Trim(oid, ".") oidParts := strings.Split(oid, ".") oidBytes := make([]int, len(oidParts)) // Convert the string OID to an array of integers for i := 0; i < len(oidParts); i++ { oidBytes[i], err = strconv.Atoi(oidParts[i]) if err != nil { return nil, fmt.Errorf("unable to parse OID: %s", err.Error()) } } mOid, err := marshalObjectIdentifier(oidBytes) if err != nil { return nil, fmt.Errorf("unable to marshal OID: %s", err.Error()) } return mOid, err } func oidToString(oid []int) (ret string) { oidAsString := make([]string, len(oid)+1) // used for appending of the first dot oidAsString[0] = "" for i := range oid { oidAsString[i+1] = strconv.Itoa(oid[i]) } return strings.Join(oidAsString, ".") } // TODO no tests func ipv4toBytes(ip net.IP) []byte { return []byte(ip)[12:] } // parseBase128Int parses a base-128 encoded int from the given offset in the // given byte slice. It returns the value and the new offset. func parseBase128Int(bytes []byte, initOffset int) (ret, offset int, err error) { offset = initOffset for shifted := 0; offset < len(bytes); shifted++ { if shifted > 4 { err = fmt.Errorf("Structural Error: base 128 integer too large") return } ret <<= 7 b := bytes[offset] ret |= int(b & 0x7f) offset++ if b&0x80 == 0 { return } } err = fmt.Errorf("Syntax Error: truncated base 128 integer") return } // parseInt64 treats the given bytes as a big-endian, signed integer and // returns the result. func parseInt64(bytes []byte) (ret int64, err error) { if len(bytes) > 8 { // We'll overflow an int64 in this case. err = errors.New("integer too large") return } for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { ret <<= 8 ret |= int64(bytes[bytesRead]) } // Shift up and down in order to sign extend the result. ret <<= 64 - uint8(len(bytes))*8 ret >>= 64 - uint8(len(bytes))*8 return } // parseInt treats the given bytes as a big-endian, signed integer and returns // the result. func parseInt(bytes []byte) (int, error) { ret64, err := parseInt64(bytes) if err != nil { return 0, err } if ret64 != int64(int(ret64)) { return 0, errors.New("integer too large") } return int(ret64), nil } // parseLength parses and calculates an snmp packet length // // http://luca.ntop.org/Teaching/Appunti/asn1.html // // Length octets. There are two forms: short (for lengths between 0 and 127), // and long definite (for lengths between 0 and 2^1008 -1). // // * Short form. One octet. Bit 8 has value "0" and bits 7-1 give the length. // * Long form. Two to 127 octets. Bit 8 of first octet has value "1" and bits // 7-1 give the number of additional length octets. Second and following // octets give the length, base 256, most significant digit first. func parseLength(bytes []byte) (length int, cursor int) { if len(bytes) <= 2 { // handle null octet strings ie "0x04 0x00" cursor = len(bytes) length = len(bytes) } else if int(bytes[1]) <= 127 { length = int(bytes[1]) length += 2 cursor += 2 } else { numOctets := int(bytes[1]) & 127 for i := 0; i < numOctets; i++ { length <<= 8 length += int(bytes[2+i]) } length += 2 + numOctets cursor += 2 + numOctets } return length, cursor } // parseObjectIdentifier parses an OBJECT IDENTIFIER from the given bytes and // returns it. An object identifier is a sequence of variable length integers // that are assigned in a hierarchy. func parseObjectIdentifier(bytes []byte) (s []int, err error) { if len(bytes) == 0 { return []int{0}, nil } // In the worst case, we get two elements from the first byte (which is // encoded differently) and then every varint is a single byte long. s = make([]int, len(bytes)+1) // The first byte is 40*value1 + value2: s[0] = int(bytes[0]) / 40 s[1] = int(bytes[0]) % 40 i := 2 for offset := 1; offset < len(bytes); i++ { var v int v, offset, err = parseBase128Int(bytes, offset) if err != nil { return } s[i] = v } s = s[0:i] return } func parseRawField(data []byte, msg string) (interface{}, int, error) { if len(data) == 0 { return nil, 0, fmt.Errorf("empty data passed to parseRawField") } switch Asn1BER(data[0]) { case Integer: length, cursor := parseLength(data) if length > len(data) { return nil, 0, fmt.Errorf("not enough data for Integer (%d vs %d): %x", length, len(data), data) } i, err := parseInt(data[cursor:length]) if err != nil { return nil, 0, fmt.Errorf("Unable to parse raw INTEGER: %x err: %v", data, err) } return i, length, nil case OctetString: length, cursor := parseLength(data) if length > len(data) { return nil, 0, fmt.Errorf("not enough data for OctetString (%d vs %d): %x", length, len(data), data) } return string(data[cursor:length]), length, nil case ObjectIdentifier: length, cursor := parseLength(data) if length > len(data) { return nil, 0, fmt.Errorf("not enough data for OID (%d vs %d): %x", length, len(data), data) } oid, err := parseObjectIdentifier(data[cursor:length]) return oid, length, err case IPAddress: length, _ := parseLength(data) if len(data) < 2 { return nil, 0, fmt.Errorf("not enough data for ipv4 address: %x", data) } switch data[1] { case 0: // real life, buggy devices returning bad data return nil, length, nil case 4: // IPv4 if len(data) < 6 { return nil, 0, fmt.Errorf("not enough data for ipv4 address: %x", data) } return net.IPv4(data[2], data[3], data[4], data[5]).String(), length, nil default: return nil, 0, fmt.Errorf("got ipaddress len %d, expected 4", data[1]) } case TimeTicks: length, cursor := parseLength(data) if length > len(data) { return nil, 0, fmt.Errorf("not enough data for TimeTicks (%d vs %d): %x", length, len(data), data) } ret, err := parseUint(data[cursor:length]) if err != nil { return nil, 0, fmt.Errorf("Error in parseUint: %s", err) } return ret, length, nil } return nil, 0, fmt.Errorf("unknown field type: %x", data[0]) } // parseUint64 treats the given bytes as a big-endian, unsigned integer and returns // the result. func parseUint64(bytes []byte) (ret uint64, err error) { if len(bytes) > 9 || (len(bytes) > 8 && bytes[0] != 0x0) { // We'll overflow a uint64 in this case. err = errors.New("integer too large") return } for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { ret <<= 8 ret |= uint64(bytes[bytesRead]) } return } // parseUint32 treats the given bytes as a big-endian, signed integer and returns // the result. func parseUint32(bytes []byte) (uint32, error) { ret, err := parseUint(bytes) if err != nil { return 0, err } return uint32(ret), nil } // parseUint treats the given bytes as a big-endian, signed integer and returns // the result. func parseUint(bytes []byte) (uint, error) { ret64, err := parseUint64(bytes) if err != nil { return 0, err } if ret64 != uint64(uint(ret64)) { return 0, errors.New("integer too large") } return uint(ret64), nil } func parseFloat32(bytes []byte) (ret float32, err error) { if len(bytes) > 4 { // We'll overflow a uint64 in this case. err = errors.New("float too large") return } ret = math.Float32frombits(binary.BigEndian.Uint32(bytes)) return } func parseFloat64(bytes []byte) (ret float64, err error) { if len(bytes) > 8 { // We'll overflow a uint64 in this case. err = errors.New("float too large") return } ret = math.Float64frombits(binary.BigEndian.Uint64(bytes)) return } // Issue 4389: math/big: add SetUint64 and Uint64 functions to *Int // // uint64ToBigInt copied from: http://github.com/cznic/mathutil/blob/master/mathutil.go#L341 // // replace with Uint64ToBigInt or equivalent when using Go 1.1 var uint64ToBigIntDelta big.Int func init() { uint64ToBigIntDelta.SetBit(&uint64ToBigIntDelta, 63, 1) } func uint64ToBigInt(n uint64) *big.Int { if n <= math.MaxInt64 { return big.NewInt(int64(n)) } y := big.NewInt(int64(n - uint64(math.MaxInt64) - 1)) return y.Add(y, &uint64ToBigIntDelta) } // -- Bit String --------------------------------------------------------------- // BitStringValue is the structure to use when you want an ASN.1 BIT STRING type. A // bit string is padded up to the nearest byte in memory and the number of // valid bits is recorded. Padding bits will be zero. type BitStringValue struct { Bytes []byte // bits packed into bytes. BitLength int // length in bits. } // At returns the bit at the given index. If the index is out of range it // returns false. func (b BitStringValue) At(i int) int { if i < 0 || i >= b.BitLength { return 0 } x := i / 8 y := 7 - uint(i%8) return int(b.Bytes[x]>>y) & 1 } // RightAlign returns a slice where the padding bits are at the beginning. The // slice may share memory with the BitString. func (b BitStringValue) RightAlign() []byte { shift := uint(8 - (b.BitLength % 8)) if shift == 8 || len(b.Bytes) == 0 { return b.Bytes } a := make([]byte, len(b.Bytes)) a[0] = b.Bytes[0] >> shift for i := 1; i < len(b.Bytes); i++ { a[i] = b.Bytes[i-1] << (8 - shift) a[i] |= b.Bytes[i] >> shift } return a } // -- SnmpVersion -------------------------------------------------------------- func (s SnmpVersion) String() string { if s == Version1 { return "1" } else if s == Version2c { return "2c" } return "3" } gosnmp-1.24.0/helper_test.go000066400000000000000000000143251362404504000157510ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // +build all helper package gosnmp import ( "encoding/base64" "testing" "github.com/stretchr/testify/assert" ) // https://www.scadacore.com/tools/programming-calculators/online-hex-converter/ is useful func TestOidToString(t *testing.T) { oid := []int{1, 2, 3, 4, 5} expected := ".1.2.3.4.5" result := oidToString(oid) if result != expected { t.Errorf("oidToString(%v) = %s, want %s", oid, result, expected) } } func TestWithAnotherOid(t *testing.T) { oid := []int{4, 3, 2, 1, 3} expected := ".4.3.2.1.3" result := oidToString(oid) if result != expected { t.Errorf("oidToString(%v) = %s, want %s", oid, result, expected) } } func BenchmarkOidToString(b *testing.B) { oid := []int{1, 2, 3, 4, 5} for i := 0; i < b.N; i++ { oidToString(oid) } } type testsMarshalUint32T struct { value uint32 goodBytes []byte } var testsMarshalUint32 = []testsMarshalUint32T{ {0, []byte{0x00}}, {2, []byte{0x02}}, // 2 {128, []byte{0x00, 0x80}}, {257, []byte{0x01, 0x01}}, // FF + 2 {65537, []byte{0x01, 0x00, 0x01}}, // FFFF + 2 {16777217, []byte{0x01, 0x00, 0x00, 0x01}}, // FFFFFF + 2 {18542501, []byte{0x01, 0x1a, 0xef, 0xa5}}, } func TestMarshalUint32(t *testing.T) { for i, test := range testsMarshalUint32 { result, err := marshalUint32(test.value) if err != nil { t.Errorf("%d: expected %0x got err %v", i, test.goodBytes, err) } if !checkByteEquality2(test.goodBytes, result) { t.Errorf("%d: expected %0x got %0x", i, test.goodBytes, result) } } } var testsMarshalInt32 = []struct { value int goodBytes []byte }{ {0, []byte{0x00}}, {2, []byte{0x02}}, // 2 {128, []byte{0x00, 0x80}}, {257, []byte{0x01, 0x01}}, // FF + 2 {65537, []byte{0x01, 0x00, 0x01}}, // FFFF + 2 {16777217, []byte{0x01, 0x00, 0x00, 0x01}}, // FFFFFF + 2 {2147483647, []byte{0x7f, 0xff, 0xff, 0xff}}, {-2147483648, []byte{0x80, 0x00, 0x00, 0x00}}, {-16777217, []byte{0xfe, 0xff, 0xff, 0xff}}, {-16777216, []byte{0xff, 0x00, 0x00, 0x00}}, {-65537, []byte{0xff, 0xfe, 0xff, 0xff}}, {-65536, []byte{0xff, 0xff, 0x00, 0x00}}, {-257, []byte{0xff, 0xff, 0xfe, 0xff}}, {-256, []byte{0xff, 0xff, 0xff, 0x00}}, {-2, []byte{0xff, 0xff, 0xff, 0xfe}}, {-1, []byte{0xff, 0xff, 0xff, 0xff}}, } func TestMarshalInt32(t *testing.T) { for _, aTest := range testsMarshalInt32 { result, err := marshalInt32(aTest.value) assert.NoErrorf(t, err, "value %d", aTest.value) assert.EqualValues(t, aTest.goodBytes, result, "bad marshalInt32()") } } func TestParseUint64(t *testing.T) { tests := []struct { data []byte n uint64 }{ {[]byte{}, 0}, {[]byte{0x00}, 0}, {[]byte{0x01}, 1}, {[]byte{0x01, 0x01}, 257}, {[]byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e, 0xb3, 0xbf}, 18446744073694786495}, } for _, test := range tests { if ret, err := parseUint64(test.data); err != nil || ret != test.n { t.Errorf("parseUint64(%v) = %d, %v want %d, ", test.data, ret, err, test.n) } } } var testsInvalidSNMPResponses = []string{ "MIIHIQIBAQQHcHJpdmF0ZaKCBxECBGwvRyoCAQACAQAwggcBMBgGCCsGAQIBAQIABgwrBgEEAZJRAwE/AQYwEAYIKwYBAgEBAwBDBBU2aN0wDAYIKwYBAgEBBAAEADAMBggrBgECAQEFAAQAMAwGCCsGAQIBAQYABAAwDQYIKwYBAgEBBwACAUgwDQYIKwYBAgECAQACAQAwDwYKKwYBAgECAgEBAQIBATAPBgorBgECAQICAQcBAgEBMA8GCisGAQIBAgIBBwECAQEwDwYKKwYBAgECAgEHAQIBATAPBgorBgECAQICAQcBAgEBMA8GCisGAQIBAgIBBwECAQEwDwYKKwYBAgECAgEHAQIBATAPBgorBgECAQICAQcBAgEBMBAGCCsGAQIBAQMAQwQVNmjdMAwGCCsGAQIBAQQABAAwDAYIKwYBAgEBBQAEADAMBggrBgECAQEGAAQAMA0GCCsGAQIBAQcAAgFIMA0GCCsGAQIBAgEAAgEAMA8GCisGAQIBAgIBAQECAQEwFgYKKwYBAgECAgECAQQIRXRoZXJuZXQwDwYKKwYBAgECAgEIAQIBATAPBgorBgECAQICAQgBAgEBMA8GCisGAQIBAgIBCAECAQEwDwYKKwYBAgECAgEIAQIBATAPBgorBgECAQICAQgBAgEBMA8GCisGAQIBAgIBCAECAQEwDwYKKwYBAgECAgEIAQIBATAMBggrBgECAQEEAAQAMAwGCCsGAQIBAQUABAAwDAYIKwYBAgEBBgAEADANBggrBgECAQEHAAIBSDANBggrBgECAQIBAAIBADAPBgorBgECAQICAQEBAgEBMBYGCisGAQIBAgIBAgEECEV0aGVybmV0MA8GCisGAQIBAgIBAwECAQYwDwYKKwYBAgECAgEJAUMBADAPBgorBgECAQICAQkBQwEAMA8GCisGAQIBAgIBCQFDAQAwDwYKKwYBAgECAgEJAUMBADAPBgorBgECAQICAQkBQwEAMA8GCisGAQIBAgIBCQFDAQAwDwYKKwYBAgECAgEJAUMBADAMBggrBgECAQEFAAQAMAwGCCsGAQIBAQYABAAwDQYIKwYBAgEBBwACAUgwDQYIKwYBAgECAQACAQAwDwYKKwYBAgECAgEBAQIBATAWBgorBgECAQICAQIBBAhFdGhlcm5ldDAPBgorBgECAQICAQMBAgEGMBAGCisGAQIBAgIBBAECAgXqMBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MBIGCisGAQIBAgIBCgFBBQCUMR+2MAwGCCsGAQIBAQYABAAwDQYIKwYBAgEBBwACAUgwDQYIKwYBAgECAQACAQAwDwYKKwYBAgECAgEBAQIBATAWBgorBgECAQICAQIBBAhFdGhlcm5ldDAPBgorBgECAQICAQMBAgEGMBAGCisGAQIBAgIBBAECAgXqMBIGCisGAQIBAgIBBQFCBDuaygAwEQYKKwYBAgECAgELAUEDDQDJMBEGCisGAQIBAgIBCwFBAw0AyTARBgorBgECAQICAQsBQQMNAMkwEQYKKwYBAgECAgELAUEDDQDJMBEGCisGAQIBAgIBCwFBAw0AyTARBgorBgECAQICAQsBQQMNAMkwEQYKKwYBAgECAgELAUEDDQDJMA0GCCsGAQIBAQcAAgFIMA0GCCsGAQIBAgEAAgEAMA8GCisGAQIBAgIBAQECAQEwFgYKKwYBAgECAgECAQQIRXRoZXJuZXQwDwYKKwYBAgECAgEDAQIBBjAQBgorBgECAQICAQQBAgIF6jASBgorBgECAQICAQUBQgQ7msoAMBQGCisGAQIBAgIBBgEEBryxgWZeBTASBgorBgECAQICAQwBQQQAu5coMBIGCisGAQIBAgIBDAFBBAC7lygwEgYKKwYBAgECAgEMAUEEALuXKDASBgorBgECAQICAQwBQQQAu5coMBIGCisGAQIBAgIBDAFBBAC7lygwEgYKKwYBAgECAgEMAUEEALuXKDASBgorBgECAQICAQwBQQQAu5coMA0GCCsGAQIBAgEAAgEAMA8GCisGAQIBAgIBAQECAQEwFgYKKwYBAgECAgECAQQIRXRoZXJuZXQwDwYKKwYBAgECAgEDAQIBBjAQBgorBgECAQICAQQBAgIF6jASBgorBgECAQICAQUBQgQ7msoAMBQGCisGAQIBAgIBBgEEBryxgWZeBTAPBgorBgECAQICAQcBAgEBMBEGCisGAQIBAgIBDQFBAwdNFzARBgorBgECAQICAQ0BQQMHTRcwEQYKKwYBAgECAgE=", "MBoCAQEEB3ByaXZhdGWiDAIESESkywIBBQIBAA==", "MEo=", } func TestInvalidSNMPResponses(t *testing.T) { g := &GoSNMP{ Target: "127.0.0.1", Port: 161, Community: "public", Version: Version2c, } for i, test := range testsInvalidSNMPResponses { testBytes, _ := base64.StdEncoding.DecodeString(test) result, err := g.SnmpDecodePacket(testBytes) if err == nil { t.Errorf("#%d, failed to error %v", i, result) } } } func checkByteEquality2(a, b []byte) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } gosnmp-1.24.0/interface.go000066400000000000000000000212701362404504000153700ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gosnmp import ( "time" ) //go:generate mockgen --destination gosnmp_mock.go --package=gosnmp --source interface.go // Handler is a GoSNMP interface // // Handler is provided to assist with testing using mocks type Handler interface { // Connect creates and opens a socket. Because UDP is a connectionless // protocol, you won't know if the remote host is responding until you send // packets. And if the host is regularly disappearing and reappearing, you won't // know if you've only done a Connect(). // // For historical reasons (ie this is part of the public API), the method won't // be renamed. Connect() error // ConnectIPv4 connects using IPv4 ConnectIPv4() error // ConnectIPv6 connects using IPv6 ConnectIPv6() error // Get sends an SNMP GET request Get(oids []string) (result *SnmpPacket, err error) // GetBulk sends an SNMP GETBULK request // // For maxRepetitions greater than 255, use BulkWalk() or BulkWalkAll() GetBulk(oids []string, nonRepeaters uint8, maxRepetitions uint8) (result *SnmpPacket, err error) // GetNext sends an SNMP GETNEXT request GetNext(oids []string) (result *SnmpPacket, err error) // Walk retrieves a subtree of values using GETNEXT - a request is made for each // value, unlike BulkWalk which does this operation in batches. As the tree is // walked walkFn is called for each new value. The function immediately returns // an error if either there is an underlaying SNMP error (e.g. GetNext fails), // or if walkFn returns an error. Walk(rootOid string, walkFn WalkFunc) error // WalkAll is similar to Walk but returns a filled array of all values rather // than using a callback function to stream results. WalkAll(rootOid string) (results []SnmpPDU, err error) // BulkWalk retrieves a subtree of values using GETBULK. As the tree is // walked walkFn is called for each new value. The function immediately returns // an error if either there is an underlaying SNMP error (e.g. GetBulk fails), // or if walkFn returns an error. BulkWalk(rootOid string, walkFn WalkFunc) error // BulkWalkAll is similar to BulkWalk but returns a filled array of all values // rather than using a callback function to stream results. BulkWalkAll(rootOid string) (results []SnmpPDU, err error) // SendTrap sends a SNMP Trap (v2c/v3 only) // // pdus[0] can a pdu of Type TimeTicks (with the desired uint32 epoch // time). Otherwise a TimeTicks pdu will be prepended, with time set to // now. This mirrors the behaviour of the Net-SNMP command-line tools. // // SendTrap doesn't wait for a return packet from the NMS (Network // Management Station). // // See also Listen() and examples for creating an NMS. SendTrap(trap SnmpTrap) (result *SnmpPacket, err error) // UnmarshalTrap unpacks the SNMP Trap. UnmarshalTrap(trap []byte) (result *SnmpPacket) // Set sends an SNMP SET request Set(pdus []SnmpPDU) (result *SnmpPacket, err error) // Check makes checking errors easy, so they actually get a minimal check Check(err error) // Close closes the connection Close() error // Target gets the Target Target() string // SetTarget sets the Target SetTarget(target string) // Port gets the Port Port() uint16 // SetPort sets the Port SetPort(port uint16) // Community gets the Community Community() string // SetCommunity sets the Community SetCommunity(community string) // Version gets the Version Version() SnmpVersion // SetVersion sets the Version SetVersion(version SnmpVersion) // Timeout gets the Timeout Timeout() time.Duration // SetTimeout sets the Timeout SetTimeout(timeout time.Duration) // Retries gets the Retries Retries() int // SetRetries sets the Retries SetRetries(retries int) // GetExponentialTimeout gets the ExponentialTimeout GetExponentialTimeout() bool // SetExponentialTimeout sets the ExponentialTimeout SetExponentialTimeout(value bool) // Logger gets the Logger Logger() Logger // SetLogger sets the Logger SetLogger(logger Logger) // MaxOids gets the MaxOids MaxOids() int // SetMaxOids sets the MaxOids SetMaxOids(maxOids int) // MaxRepetitions gets the maxRepetitions MaxRepetitions() uint8 // SetMaxRepetitions sets the maxRepetitions SetMaxRepetitions(maxRepetitions uint8) // NonRepeaters gets the nonRepeaters NonRepeaters() int // SetNonRepeaters sets the nonRepeaters SetNonRepeaters(nonRepeaters int) // MsgFlags gets the MsgFlags MsgFlags() SnmpV3MsgFlags // SetMsgFlags sets the MsgFlags SetMsgFlags(msgFlags SnmpV3MsgFlags) // SecurityModel gets the SecurityModel SecurityModel() SnmpV3SecurityModel // SetSecurityModel sets the SecurityModel SetSecurityModel(securityModel SnmpV3SecurityModel) // SecurityParameters gets the SecurityParameters SecurityParameters() SnmpV3SecurityParameters // SetSecurityParameters sets the SecurityParameters SetSecurityParameters(securityParameters SnmpV3SecurityParameters) // ContextEngineID gets the ContextEngineID ContextEngineID() string // SetContextEngineID sets the ContextEngineID SetContextEngineID(contextEngineID string) // ContextName gets the ContextName ContextName() string // SetContextName sets the ContextName SetContextName(contextName string) } // snmpHandler is a wrapper around gosnmp type snmpHandler struct { GoSNMP } // NewHandler creates a new Handler using gosnmp func NewHandler() Handler { return &snmpHandler{ GoSNMP{ Port: Default.Port, Community: Default.Community, Version: Default.Version, Timeout: Default.Timeout, Retries: Default.Retries, MaxOids: Default.MaxOids, }, } } func (x *snmpHandler) Target() string { // not x.Target because it would reference function Target return x.GoSNMP.Target } func (x *snmpHandler) SetTarget(target string) { x.GoSNMP.Target = target } func (x *snmpHandler) Port() uint16 { return x.GoSNMP.Port } func (x *snmpHandler) SetPort(port uint16) { x.GoSNMP.Port = port } func (x *snmpHandler) Community() string { return x.GoSNMP.Community } func (x *snmpHandler) SetCommunity(community string) { x.GoSNMP.Community = community } func (x *snmpHandler) Version() SnmpVersion { return x.GoSNMP.Version } func (x *snmpHandler) SetVersion(version SnmpVersion) { x.GoSNMP.Version = version } func (x *snmpHandler) Timeout() time.Duration { return x.GoSNMP.Timeout } func (x *snmpHandler) SetTimeout(timeout time.Duration) { x.GoSNMP.Timeout = timeout } func (x *snmpHandler) Retries() int { return x.GoSNMP.Retries } func (x *snmpHandler) SetRetries(retries int) { x.GoSNMP.Retries = retries } func (x *snmpHandler) GetExponentialTimeout() bool { return x.GoSNMP.ExponentialTimeout } func (x *snmpHandler) SetExponentialTimeout(value bool) { x.GoSNMP.ExponentialTimeout = value } func (x *snmpHandler) Logger() Logger { return x.GoSNMP.Logger } func (x *snmpHandler) SetLogger(logger Logger) { x.GoSNMP.Logger = logger } func (x *snmpHandler) MaxOids() int { return x.GoSNMP.MaxOids } func (x *snmpHandler) SetMaxOids(maxOids int) { x.GoSNMP.MaxOids = maxOids } func (x *snmpHandler) MaxRepetitions() uint8 { return x.GoSNMP.MaxRepetitions } func (x *snmpHandler) SetMaxRepetitions(maxRepetitions uint8) { x.GoSNMP.MaxRepetitions = maxRepetitions } func (x *snmpHandler) NonRepeaters() int { return x.GoSNMP.NonRepeaters } func (x *snmpHandler) SetNonRepeaters(nonRepeaters int) { x.GoSNMP.NonRepeaters = nonRepeaters } func (x *snmpHandler) MsgFlags() SnmpV3MsgFlags { return x.GoSNMP.MsgFlags } func (x *snmpHandler) SetMsgFlags(msgFlags SnmpV3MsgFlags) { x.GoSNMP.MsgFlags = msgFlags } func (x *snmpHandler) SecurityModel() SnmpV3SecurityModel { return x.GoSNMP.SecurityModel } func (x *snmpHandler) SetSecurityModel(securityModel SnmpV3SecurityModel) { x.GoSNMP.SecurityModel = securityModel } func (x *snmpHandler) SecurityParameters() SnmpV3SecurityParameters { return x.GoSNMP.SecurityParameters } func (x *snmpHandler) SetSecurityParameters(securityParameters SnmpV3SecurityParameters) { x.GoSNMP.SecurityParameters = securityParameters } func (x *snmpHandler) ContextEngineID() string { return x.GoSNMP.ContextEngineID } func (x *snmpHandler) SetContextEngineID(contextEngineID string) { x.GoSNMP.ContextEngineID = contextEngineID } func (x *snmpHandler) ContextName() string { return x.GoSNMP.ContextName } func (x *snmpHandler) SetContextName(contextName string) { x.GoSNMP.ContextName = contextName } func (x *snmpHandler) Close() error { // not x.Conn for consistency return x.GoSNMP.Conn.Close() } gosnmp-1.24.0/linters.sh000077500000000000000000000006141362404504000151170ustar00rootroot00000000000000#!/bin/bash # TODO pillage more from # - https://gitlab.allegorithmic.com/open-source/go-storage/blob/9fa15deb79fdcb87dff55af662b8ebd51cef4f89/vendor/google.golang.org/grpc/vet.sh fail_on_output() { tee /dev/stderr | (! read) } if [ "$TRAVIS_GOARCH" = "amd64" ] ; then # travis doesn't install goimports on 386 - WTF? echo "=== GOIMPORTS ===" goimports -d . 2>&1 | fail_on_output fi gosnmp-1.24.0/local_tests.sh000077500000000000000000000001771362404504000157570ustar00rootroot00000000000000#!/bin/bash go test -v -tags helper go test -v -tags marshal go test -v -tags misc go test -v -tags api go test -v -tags trap gosnmp-1.24.0/marshal.go000066400000000000000000000756051362404504000150720ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package gosnmp import ( "bytes" "context" "encoding/asn1" "encoding/binary" "fmt" "io" "net" "reflect" "runtime" "strings" "sync/atomic" ) // // Remaining globals and definitions located here. // See http://www.rane.com/note161.html for a succint description of the SNMP // protocol. // // SnmpVersion 1, 2c and 3 implemented type SnmpVersion uint8 // SnmpVersion 1, 2c and 3 implemented const ( Version1 SnmpVersion = 0x0 Version2c SnmpVersion = 0x1 Version3 SnmpVersion = 0x3 ) // SnmpPacket struct represents the entire SNMP Message or Sequence at the // application layer. type SnmpPacket struct { Version SnmpVersion MsgFlags SnmpV3MsgFlags SecurityModel SnmpV3SecurityModel SecurityParameters SnmpV3SecurityParameters // interface ContextEngineID string ContextName string Community string PDUType PDUType MsgID uint32 RequestID uint32 MsgMaxSize uint32 Error SNMPError ErrorIndex uint8 NonRepeaters uint8 MaxRepetitions uint8 Variables []SnmpPDU Logger Logger // interface // v1 traps have a very different format from v2c and v3 traps. // // These fields are set via the SnmpTrap parameter to SendTrap(). SnmpTrap } // SnmpTrap is used to define a SNMP trap, and is passed into SendTrap type SnmpTrap struct { Variables []SnmpPDU // These fields are required for SNMPV1 Trap Headers Enterprise string AgentAddress string GenericTrap int SpecificTrap int Timestamp uint } // VarBind struct represents an SNMP Varbind. type VarBind struct { Name asn1.ObjectIdentifier Value asn1.RawValue } // PDUType describes which SNMP Protocol Data Unit is being sent. type PDUType byte // The currently supported PDUType's const ( Sequence PDUType = 0x30 GetRequest PDUType = 0xa0 GetNextRequest PDUType = 0xa1 GetResponse PDUType = 0xa2 SetRequest PDUType = 0xa3 Trap PDUType = 0xa4 // v1 GetBulkRequest PDUType = 0xa5 InformRequest PDUType = 0xa6 SNMPv2Trap PDUType = 0xa7 // v2c, v3 Report PDUType = 0xa8 ) const rxBufSize = 65535 // max size of IPv4 & IPv6 packet // Logger is an interface used for debugging. Both Print and // Printf have the same interfaces as Package Log in the std library. The // Logger interface is small to give you flexibility in how you do // your debugging. // // For verbose logging to stdout: // // gosnmp_logger = log.New(os.Stdout, "", 0) type Logger interface { Print(v ...interface{}) Printf(format string, v ...interface{}) } func (x *GoSNMP) logPrint(v ...interface{}) { if x.loggingEnabled { x.Logger.Print(v...) } } func (x *GoSNMP) logPrintf(format string, v ...interface{}) { if x.loggingEnabled { x.Logger.Printf(format, v...) } } // send/receive one snmp request func (x *GoSNMP) sendOneRequest(packetOut *SnmpPacket, wait bool) (result *SnmpPacket, err error) { allReqIDs := make([]uint32, 0, x.Retries+1) // allMsgIDs := make([]uint32, 0, x.Retries+1) // unused timeout := x.Timeout for retries := 0; ; retries++ { if retries > 0 { x.logPrintf("Retry number %d. Last error was: %v", retries, err) if x.ExponentialTimeout { // https://www.webnms.com/snmp/help/snmpapi/snmpv3/v1/timeout.html timeout *= 2 } if retries > x.Retries { if strings.Contains(err.Error(), "timeout") { err = fmt.Errorf("Request timeout (after %d retries)", retries-1) } break } } err = nil if x.Context.Err() != nil { return nil, x.Context.Err() } ctx, cancel := context.WithTimeout(x.Context, timeout) defer cancel() deadline, _ := ctx.Deadline() err = x.Conn.SetDeadline(deadline) if err != nil { return nil, err } // Request ID is an atomic counter (started at a random value) reqID := atomic.AddUint32(&(x.requestID), 1) // TODO: fix overflows allReqIDs = append(allReqIDs, reqID) packetOut.RequestID = reqID if x.Version == Version3 { msgID := atomic.AddUint32(&(x.msgID), 1) // TODO: fix overflows // allMsgIDs = append(allMsgIDs, msgID) // unused packetOut.MsgID = msgID err = x.initPacket(packetOut) if err != nil { break } } if x.loggingEnabled && x.Version == Version3 { packetOut.SecurityParameters.Log() } var outBuf []byte outBuf, err = packetOut.marshalMsg() if err != nil { // Don't retry - not going to get any better! err = fmt.Errorf("marshal: %v", err) break } x.logPrintf("SENDING PACKET: %#+v", *packetOut) _, err = x.Conn.Write(outBuf) if err != nil { continue } // all sends wait for the return packet, except for SNMPv2Trap if !wait { return &SnmpPacket{}, nil } waitingResponse: for { x.logPrint("WAITING RESPONSE...") // Receive response and try receiving again on any decoding error. // Let the deadline abort us if we don't receive a valid response. var resp []byte resp, err = x.receive() if err == io.EOF && strings.HasPrefix(x.Transport, "tcp") { // EOF on TCP: reconnect and retry. Do not count // as retry as socket was broken x.logPrintf("ERROR: EOF. Performing reconnect") err = x.netConnect() if err != nil { return nil, err } retries-- break } else if err != nil { // receive error. retrying won't help. abort break } x.logPrintf("GET RESPONSE OK: %+v", resp) result = new(SnmpPacket) result.Logger = x.Logger result.MsgFlags = packetOut.MsgFlags if packetOut.SecurityParameters != nil { result.SecurityParameters = packetOut.SecurityParameters.Copy() } var cursor int cursor, err = x.unmarshalHeader(resp, result) if err != nil { x.logPrintf("ERROR on unmarshall header: %s", err) continue } if x.Version == Version3 { err = x.testAuthentication(resp, result) if err != nil { x.logPrintf("ERROR on Test Authentication on v3: %s", err) break } resp, cursor, _ = x.decryptPacket(resp, cursor, result) } err = x.unmarshalPayload(resp, cursor, result) if err != nil { x.logPrintf("ERROR on UnmarshalPayload on v3: %s", err) continue } if len(result.Variables) < 1 { x.logPrintf("ERROR on UnmarshalPayload on v3: %s", err) continue } // Detect usmStats report PDUs and go out of this function with all data // (usmStatsNotInTimeWindows [1.3.6.1.6.3.15.1.1.2.0] will be handled by the calling // function, and retransmitted. All others need to be handled by user code) if result.Version == Version3 && len(result.Variables) == 1 && result.PDUType == Report { switch result.Variables[0].Name { case ".1.3.6.1.6.3.15.1.1.1.0", ".1.3.6.1.6.3.15.1.1.2.0", ".1.3.6.1.6.3.15.1.1.3.0", ".1.3.6.1.6.3.15.1.1.4.0", ".1.3.6.1.6.3.15.1.1.5.0", ".1.3.6.1.6.3.15.1.1.6.0": break waitingResponse } } validID := false for _, id := range allReqIDs { if id == result.RequestID { validID = true } } if result.RequestID == 0 { validID = true } if !validID { x.logPrint("ERROR out of order") continue } break } if err != nil { continue } // Success! return result, nil } // Return last error return nil, err } // generic "sender" that negotiate any version of snmp request // // all sends wait for the return packet, except for SNMPv2Trap func (x *GoSNMP) send(packetOut *SnmpPacket, wait bool) (result *SnmpPacket, err error) { defer func() { if e := recover(); e != nil { var buf = make([]byte, 8192) runtime.Stack(buf, true) err = fmt.Errorf("recover: %v\nStack:%v\n", e, string(buf)) } }() if x.Conn == nil { return nil, fmt.Errorf("&GoSNMP.Conn is missing. Provide a connection or use Connect()") } if x.Retries < 0 { x.Retries = 0 } x.logPrint("SEND INIT") if packetOut.Version == Version3 { x.logPrint("SEND INIT NEGOTIATE SECURITY PARAMS") if err = x.negotiateInitialSecurityParameters(packetOut, wait); err != nil { return &SnmpPacket{}, err } x.logPrint("SEND END NEGOTIATE SECURITY PARAMS") } // perform request result, err = x.sendOneRequest(packetOut, wait) if err != nil { x.logPrintf("SEND Error on the first Request Error: %s", err) return result, err } if result.Version == Version3 { x.logPrintf("SEND STORE SECURITY PARAMS from result: %+v", result) err = x.storeSecurityParameters(result) // detect out-of-time-window error and retransmit with updated auth engine parameters if len(result.Variables) == 1 && result.Variables[0].Name == ".1.3.6.1.6.3.15.1.1.2.0" { x.logPrint("WARNING detected out-of-time-window ERROR") err = x.updatePktSecurityParameters(packetOut) if err != nil { x.logPrintf("ERROR updatePktSecurityParameters error: %s", err) return nil, err } result, err = x.sendOneRequest(packetOut, wait) } } // detect unknown engine id error and retransmit with updated engine id if len(result.Variables) == 1 && result.Variables[0].Name == ".1.3.6.1.6.3.15.1.1.4.0" { x.logPrint("WARNING detected unknown enginer id ERROR") err = x.updatePktSecurityParameters(packetOut) if err != nil { x.logPrintf("ERROR updatePktSecurityParameters error: %s", err) return nil, err } result, err = x.sendOneRequest(packetOut, wait) } return result, err } func (packet *SnmpPacket) logPrintf(format string, v ...interface{}) { if packet.Logger != nil { packet.Logger.Printf(format, v...) } } // -- Marshalling Logic -------------------------------------------------------- // MarshalMsg marshalls a snmp packet, ready for sending across the wire func (packet *SnmpPacket) MarshalMsg() ([]byte, error) { return packet.marshalMsg() } // marshal an SNMP message func (packet *SnmpPacket) marshalMsg() ([]byte, error) { var err error buf := new(bytes.Buffer) // version buf.Write([]byte{2, 1, byte(packet.Version)}) if packet.Version == Version3 { buf, err = packet.marshalV3(buf) if err != nil { return nil, err } } else { // community buf.Write([]byte{4, uint8(len(packet.Community))}) buf.WriteString(packet.Community) // pdu pdu, err := packet.marshalPDU() if err != nil { return nil, err } buf.Write(pdu) } // build up resulting msg - sequence, length then the tail (buf) msg := new(bytes.Buffer) msg.WriteByte(byte(Sequence)) bufLengthBytes, err2 := marshalLength(buf.Len()) if err2 != nil { return nil, err2 } msg.Write(bufLengthBytes) _, err = buf.WriteTo(msg) if err != nil { return nil, err } authenticatedMessage, err := packet.authenticate(msg.Bytes()) if err != nil { return nil, err } return authenticatedMessage, nil } func (packet *SnmpPacket) marshalSNMPV1TrapHeader() ([]byte, error) { buf := new(bytes.Buffer) // marshal OID oidBytes, err := marshalOID(packet.Enterprise) if err != nil { return nil, fmt.Errorf("unable to marshal OID: %s", err.Error()) } buf.Write([]byte{byte(ObjectIdentifier), byte(len(oidBytes))}) buf.Write(oidBytes) // marshal AgentAddress (ip address) ip := net.ParseIP(packet.AgentAddress) ipAddressBytes := ipv4toBytes(ip) buf.Write([]byte{byte(IPAddress), byte(len(ipAddressBytes))}) buf.Write(ipAddressBytes) // marshal GenericTrap. Could just cast GenericTrap to a single byte as IDs greater than 6 are unknown, // but do it properly. See issue 182. var genericTrapBytes []byte genericTrapBytes, err = marshalInt32(packet.GenericTrap) if err != nil { return nil, fmt.Errorf("Unable to marshal SNMPv1 GenericTrap: %s", err.Error()) } buf.Write([]byte{byte(Integer), byte(len(genericTrapBytes))}) buf.Write(genericTrapBytes) // marshal SpecificTrap var specificTrapBytes []byte specificTrapBytes, err = marshalInt32(packet.SpecificTrap) if err != nil { return nil, fmt.Errorf("Unable to marshal SNMPv1 SpecificTrap: %s", err.Error()) } buf.Write([]byte{byte(Integer), byte(len(specificTrapBytes))}) buf.Write(specificTrapBytes) // marshal timeTicks timeTickBytes, e := marshalUint32(uint32(packet.Timestamp)) if e != nil { return nil, fmt.Errorf("unable to Timestamp: %s", e.Error()) } buf.Write([]byte{byte(TimeTicks), byte(len(timeTickBytes))}) buf.Write(timeTickBytes) return buf.Bytes(), nil } // marshal a PDU func (packet *SnmpPacket) marshalPDU() ([]byte, error) { buf := new(bytes.Buffer) switch packet.PDUType { case GetBulkRequest: // requestid buf.Write([]byte{2, 4}) err := binary.Write(buf, binary.BigEndian, packet.RequestID) if err != nil { return nil, err } // non repeaters buf.Write([]byte{2, 1, packet.NonRepeaters}) // max repetitions buf.Write([]byte{2, 1, packet.MaxRepetitions}) case Trap: // write SNMP V1 Trap Header fields snmpV1TrapHeader, err := packet.marshalSNMPV1TrapHeader() if err != nil { return nil, err } buf.Write(snmpV1TrapHeader) default: // requestid buf.Write([]byte{2, 4}) err := binary.Write(buf, binary.BigEndian, packet.RequestID) if err != nil { return nil, fmt.Errorf("unable to marshal OID: %s", err.Error()) } // error buf.Write([]byte{2, 1, byte(packet.Error)}) // error index buf.Write([]byte{2, 1, byte(packet.ErrorIndex)}) } // varbind list vbl, err := packet.marshalVBL() if err != nil { return nil, err } buf.Write(vbl) // build up resulting pdu - request type, length, then the tail (buf) pdu := new(bytes.Buffer) pdu.WriteByte(byte(packet.PDUType)) bufLengthBytes, err2 := marshalLength(buf.Len()) if err2 != nil { return nil, err2 } pdu.Write(bufLengthBytes) _, err = buf.WriteTo(pdu) if err != nil { return nil, err } return pdu.Bytes(), nil } // marshal a varbind list func (packet *SnmpPacket) marshalVBL() ([]byte, error) { vblBuf := new(bytes.Buffer) for _, pdu := range packet.Variables { vb, err := marshalVarbind(&pdu) if err != nil { return nil, err } vblBuf.Write(vb) } vblBytes := vblBuf.Bytes() vblLengthBytes, err := marshalLength(len(vblBytes)) if err != nil { return nil, err } // FIX does bytes.Buffer give better performance than byte slices? result := []byte{byte(Sequence)} result = append(result, vblLengthBytes...) result = append(result, vblBytes...) return result, nil } // marshal a varbind func marshalVarbind(pdu *SnmpPDU) ([]byte, error) { oid, err := marshalOID(pdu.Name) if err != nil { return nil, err } pduBuf := new(bytes.Buffer) tmpBuf := new(bytes.Buffer) // Marshal the PDU type into the appropriate BER switch pdu.Type { case Null: ltmp, err := marshalLength(len(oid)) if err != nil { return nil, err } tmpBuf.Write([]byte{byte(ObjectIdentifier)}) tmpBuf.Write(ltmp) tmpBuf.Write(oid) tmpBuf.Write([]byte{byte(Null), byte(EndOfContents)}) ltmp, err = marshalLength(tmpBuf.Len()) if err != nil { return nil, err } pduBuf.Write([]byte{byte(Sequence)}) pduBuf.Write(ltmp) _, err = tmpBuf.WriteTo(pduBuf) if err != nil { return nil, err } case Integer: // Oid tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) // Number var intBytes []byte switch value := pdu.Value.(type) { case byte: intBytes = []byte{byte(pdu.Value.(int))} case int: intBytes, err = marshalInt32(value) pdu.Check(err) default: return nil, fmt.Errorf("unable to marshal PDU Integer; not byte or int") } tmpBuf.Write([]byte{byte(Integer), byte(len(intBytes))}) tmpBuf.Write(intBytes) // Sequence, length of oid + integer, then oid/integer data pduBuf.WriteByte(byte(Sequence)) pduBuf.WriteByte(byte(len(oid) + len(intBytes) + 4)) pduBuf.Write(tmpBuf.Bytes()) case Counter32, Gauge32, TimeTicks, Uinteger32: // Oid tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) // Number var intBytes []byte switch value := pdu.Value.(type) { case uint32: intBytes, err = marshalUint32(value) pdu.Check(err) case uint: intBytes, err = marshalUint32(uint32(value)) pdu.Check(err) default: return nil, fmt.Errorf("Unable to marshal pdu.Type %v; unknown pdu.Value %v[type=%v]", pdu.Type, pdu.Value, reflect.TypeOf(pdu.Value)) } tmpBuf.Write([]byte{byte(pdu.Type), byte(len(intBytes))}) tmpBuf.Write(intBytes) // Sequence, length of oid + integer, then oid/integer data pduBuf.WriteByte(byte(Sequence)) pduBuf.WriteByte(byte(len(oid) + len(intBytes) + 4)) pduBuf.Write(tmpBuf.Bytes()) case OctetString, BitString: //Oid tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) //OctetString var octetStringBytes []byte switch value := pdu.Value.(type) { case []byte: octetStringBytes = value case string: octetStringBytes = []byte(value) default: return nil, fmt.Errorf("unable to marshal PDU OctetString; not []byte or String") } var length []byte length, err = marshalLength(len(octetStringBytes)) if err != nil { return nil, err } tmpBuf.WriteByte(byte(pdu.Type)) tmpBuf.Write(length) tmpBuf.Write(octetStringBytes) tmpBytes := tmpBuf.Bytes() length, err = marshalLength(len(tmpBytes)) if err != nil { return nil, err } // Sequence, length of oid + octetstring, then oid/octetstring data pduBuf.WriteByte(byte(Sequence)) pduBuf.Write(length) pduBuf.Write(tmpBytes) case ObjectIdentifier: //Oid tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) value := pdu.Value.(string) oidBytes, err := marshalOID(value) pdu.Check(err) //Oid data var length []byte length, err = marshalLength(len(oidBytes)) if err != nil { return nil, err } tmpBuf.WriteByte(byte(pdu.Type)) tmpBuf.Write(length) tmpBuf.Write(oidBytes) tmpBytes := tmpBuf.Bytes() length, err = marshalLength(len(tmpBytes)) if err != nil { return nil, err } // Sequence, length of oid + oid, then oid/oid data pduBuf.WriteByte(byte(Sequence)) pduBuf.Write(length) pduBuf.Write(tmpBytes) case IPAddress: //Oid tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) //OctetString var ipAddressBytes []byte switch value := pdu.Value.(type) { case []byte: ipAddressBytes = value case string: ip := net.ParseIP(value) ipAddressBytes = ipv4toBytes(ip) default: return nil, fmt.Errorf("unable to marshal PDU IPAddress; not []byte or String") } tmpBuf.Write([]byte{byte(IPAddress), byte(len(ipAddressBytes))}) tmpBuf.Write(ipAddressBytes) // Sequence, length of oid + octetstring, then oid/octetstring data pduBuf.WriteByte(byte(Sequence)) pduBuf.WriteByte(byte(len(oid) + len(ipAddressBytes) + 4)) pduBuf.Write(tmpBuf.Bytes()) case Counter64, OpaqueFloat, OpaqueDouble: converters := map[Asn1BER]func(interface{}) ([]byte, error){ Counter64: marshalUint64, OpaqueFloat: marshalFloat32, OpaqueDouble: marshalFloat64, } tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) tmpBuf.WriteByte(byte(pdu.Type)) intBytes, err := converters[pdu.Type](pdu.Value) pdu.Check(err) tmpBuf.WriteByte(byte(len(intBytes))) tmpBuf.Write(intBytes) tmpBytes := tmpBuf.Bytes() length, err := marshalLength(len(tmpBytes)) if err != nil { return nil, err } // Sequence, length of oid + oid, then oid/oid data pduBuf.WriteByte(byte(Sequence)) pduBuf.Write(length) pduBuf.Write(tmpBytes) case NoSuchInstance, NoSuchObject, EndOfMibView: tmpBuf.Write([]byte{byte(ObjectIdentifier), byte(len(oid))}) tmpBuf.Write(oid) tmpBuf.WriteByte(byte(pdu.Type)) tmpBuf.WriteByte(byte(EndOfContents)) tmpBytes := tmpBuf.Bytes() length, err := marshalLength(len(tmpBytes)) if err != nil { return nil, err } // Sequence, length of oid + oid, then oid/oid data pduBuf.WriteByte(byte(Sequence)) pduBuf.Write(length) pduBuf.Write(tmpBytes) default: return nil, fmt.Errorf("Unable to marshal PDU: unknown BER type %q", pdu.Type) } return pduBuf.Bytes(), nil } // -- Unmarshalling Logic ------------------------------------------------------ func (x *GoSNMP) unmarshalHeader(packet []byte, response *SnmpPacket) (int, error) { if len(packet) < 2 { return 0, fmt.Errorf("Cannot unmarshal empty packet") } if response == nil { return 0, fmt.Errorf("Cannot unmarshal response into nil packet reference") } response.Variables = make([]SnmpPDU, 0, 5) // Start parsing the packet cursor := 0 // First bytes should be 0x30 if PDUType(packet[0]) != Sequence { return 0, fmt.Errorf("invalid packet header") } length, cursor := parseLength(packet) if len(packet) != length { return 0, fmt.Errorf("error verifying packet sanity: Got %d Expected: %d", len(packet), length) } x.logPrintf("Packet sanity verified, we got all the bytes (%d)", length) // Parse SNMP Version rawVersion, count, err := parseRawField(packet[cursor:], "version") if err != nil { return 0, fmt.Errorf("Error parsing SNMP packet version: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if version, ok := rawVersion.(int); ok { response.Version = SnmpVersion(version) x.logPrintf("Parsed version %d", version) } if response.Version == Version3 { oldcursor := cursor cursor, err = x.unmarshalV3Header(packet, cursor, response) if err != nil { return 0, err } x.logPrintf("UnmarshalV3Header done. [with SecurityParameters]. Header Size %d. Last 4 Bytes=[%v]", cursor-oldcursor, packet[cursor-4:cursor]) } else { // Parse community rawCommunity, count, err := parseRawField(packet[cursor:], "community") if err != nil { return 0, fmt.Errorf("Error parsing community string: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if community, ok := rawCommunity.(string); ok { response.Community = community x.logPrintf("Parsed community %s", community) } } return cursor, nil } func (x *GoSNMP) unmarshalPayload(packet []byte, cursor int, response *SnmpPacket) error { var err error // Parse SNMP packet type requestType := PDUType(packet[cursor]) x.logPrintf("UnmarshalPayload Meet PDUType %#x. Offset %v", requestType, cursor) switch requestType { // known, supported types case GetResponse, GetNextRequest, GetBulkRequest, Report, SNMPv2Trap, GetRequest, SetRequest, InformRequest: response.PDUType = requestType err = x.unmarshalResponse(packet[cursor:], response) if err != nil { return fmt.Errorf("Error in unmarshalResponse: %s", err.Error()) } case Trap: response.PDUType = requestType err = x.unmarshalTrapV1(packet[cursor:], response) if err != nil { return fmt.Errorf("Error in unmarshalTrapV1: %s", err.Error()) } default: x.logPrintf("UnmarshalPayload Meet Unknown PDUType %#x. Offset %v", requestType, cursor) return fmt.Errorf("Unknown PDUType %#x", requestType) } return nil } func (x *GoSNMP) unmarshalResponse(packet []byte, response *SnmpPacket) error { cursor := 0 getResponseLength, cursor := parseLength(packet) if len(packet) != getResponseLength { return fmt.Errorf("error verifying Response sanity: Got %d Expected: %d", len(packet), getResponseLength) } x.logPrintf("getResponseLength: %d", getResponseLength) // Parse Request-ID rawRequestID, count, err := parseRawField(packet[cursor:], "request id") if err != nil { return fmt.Errorf("Error parsing SNMP packet request ID: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if requestid, ok := rawRequestID.(int); ok { response.RequestID = uint32(requestid) x.logPrintf("requestID: %d", response.RequestID) } if response.PDUType == GetBulkRequest { // Parse Non Repeaters rawNonRepeaters, count, err := parseRawField(packet[cursor:], "non repeaters") if err != nil { return fmt.Errorf("Error parsing SNMP packet non repeaters: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if nonRepeaters, ok := rawNonRepeaters.(int); ok { response.NonRepeaters = uint8(nonRepeaters) } // Parse Max Repetitions rawMaxRepetitions, count, err := parseRawField(packet[cursor:], "max repetitions") if err != nil { return fmt.Errorf("Error parsing SNMP packet max repetitions: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if maxRepetitions, ok := rawMaxRepetitions.(int); ok { response.MaxRepetitions = uint8(maxRepetitions) } } else { // Parse Error-Status rawError, count, err := parseRawField(packet[cursor:], "error-status") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if errorStatus, ok := rawError.(int); ok { response.Error = SNMPError(errorStatus) x.logPrintf("errorStatus: %d", uint8(errorStatus)) } // Parse Error-Index rawErrorIndex, count, err := parseRawField(packet[cursor:], "error index") if err != nil { return fmt.Errorf("Error parsing SNMP packet error index: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if errorindex, ok := rawErrorIndex.(int); ok { response.ErrorIndex = uint8(errorindex) x.logPrintf("error-index: %d", uint8(errorindex)) } } return x.unmarshalVBL(packet[cursor:], response) } func (x *GoSNMP) unmarshalTrapV1(packet []byte, response *SnmpPacket) error { cursor := 0 getResponseLength, cursor := parseLength(packet) if len(packet) != getResponseLength { return fmt.Errorf("error verifying Response sanity: Got %d Expected: %d", len(packet), getResponseLength) } x.logPrintf("getResponseLength: %d", getResponseLength) // Parse Enterprise rawEnterprise, count, err := parseRawField(packet[cursor:], "enterprise") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if Enterprise, ok := rawEnterprise.([]int); ok { response.Enterprise = oidToString(Enterprise) x.logPrintf("Enterprise: %+v", Enterprise) } // Parse AgentAddress rawAgentAddress, count, err := parseRawField(packet[cursor:], "agent-address") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if AgentAddress, ok := rawAgentAddress.(string); ok { response.AgentAddress = AgentAddress x.logPrintf("AgentAddress: %s", AgentAddress) } // Parse GenericTrap rawGenericTrap, count, err := parseRawField(packet[cursor:], "generic-trap") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if GenericTrap, ok := rawGenericTrap.(int); ok { response.GenericTrap = GenericTrap x.logPrintf("GenericTrap: %d", GenericTrap) } // Parse SpecificTrap rawSpecificTrap, count, err := parseRawField(packet[cursor:], "specific-trap") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if SpecificTrap, ok := rawSpecificTrap.(int); ok { response.SpecificTrap = SpecificTrap x.logPrintf("SpecificTrap: %d", SpecificTrap) } // Parse TimeStamp rawTimestamp, count, err := parseRawField(packet[cursor:], "time-stamp") if err != nil { return fmt.Errorf("Error parsing SNMP packet error: %s", err.Error()) } cursor += count if cursor > len(packet) { return fmt.Errorf("Error parsing SNMP packet, packet length %d cursor %d", len(packet), cursor) } if Timestamp, ok := rawTimestamp.(uint); ok { response.Timestamp = Timestamp x.logPrintf("Timestamp: %d", Timestamp) } return x.unmarshalVBL(packet[cursor:], response) } // unmarshal a Varbind list func (x *GoSNMP) unmarshalVBL(packet []byte, response *SnmpPacket) error { var cursor, cursorInc int var vblLength int if len(packet) == 0 || cursor > len(packet) { return fmt.Errorf("Truncated packet when unmarshalling a VBL, got length %d cursor %d", len(packet), cursor) } if packet[cursor] != 0x30 { return fmt.Errorf("Expected a sequence when unmarshalling a VBL, got %x", packet[cursor]) } vblLength, cursor = parseLength(packet) if vblLength == 0 || vblLength > len(packet) { return fmt.Errorf("Truncated packet when unmarshalling a VBL, packet length %d cursor %d", len(packet), cursor) } if len(packet) != vblLength { return fmt.Errorf("error verifying: packet length %d vbl length %d", len(packet), vblLength) } x.logPrintf("vblLength: %d", vblLength) // check for an empty response if vblLength == 2 && packet[1] == 0x00 { return nil } // Loop & parse Varbinds for cursor < vblLength { if packet[cursor] != 0x30 { return fmt.Errorf("Expected a sequence when unmarshalling a VB, got %x", packet[cursor]) } _, cursorInc = parseLength(packet[cursor:]) cursor += cursorInc if cursor > len(packet) { return fmt.Errorf("Error parsing OID Value: packet %d cursor %d", len(packet), cursor) } // Parse OID rawOid, oidLength, err := parseRawField(packet[cursor:], "OID") if err != nil { return fmt.Errorf("Error parsing OID Value: %s", err.Error()) } cursor += oidLength if cursor > len(packet) { return fmt.Errorf("Error parsing OID Value: truncated, packet length %d cursor %d", len(packet), cursor) } var oid []int var ok bool if oid, ok = rawOid.([]int); !ok { return fmt.Errorf("unable to type assert rawOid |%v| to []int", rawOid) } oidStr := oidToString(oid) x.logPrintf("OID: %s", oidStr) // Parse Value v, err := x.decodeValue(packet[cursor:], "value") if err != nil { return fmt.Errorf("Error decoding value: %v", err) } valueLength, _ := parseLength(packet[cursor:]) cursor += valueLength if cursor > len(packet) { return fmt.Errorf("Error decoding OID Value: truncated, packet length %d cursor %d", len(packet), cursor) } response.Variables = append(response.Variables, SnmpPDU{oidStr, v.Type, v.Value, x.Logger}) } return nil } // receive response from network and read into a byte array func (x *GoSNMP) receive() ([]byte, error) { n, err := x.Conn.Read(x.rxBuf[:]) if err == io.EOF { return nil, err } else if err != nil { return nil, fmt.Errorf("Error reading from socket: %s", err.Error()) } if n == rxBufSize { // This should never happen unless we're using something like a unix domain socket. return nil, fmt.Errorf("response buffer too small") } resp := make([]byte, n) copy(resp, x.rxBuf[:n]) return resp, nil } gosnmp-1.24.0/marshal_test.go000066400000000000000000001471631362404504000161300ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // +build all marshal package gosnmp import ( "bytes" "encoding/hex" "fmt" "io/ioutil" "log" "net" "reflect" "runtime" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" ) // Tests in alphabetical order of function being tested // -- Enmarshal ---------------------------------------------------------------- // "Enmarshal" not "Marshal" - easier to select tests via a regex type testsEnmarshalVarbindPosition struct { oid string /* start and finish position of bytes are calculated with application layer starting at byte 0. There are two ways to understand Wireshark dumps, switch between them: 1) the standard decode of the full packet - easier to understand what's actually happening 2) for counting byte positions: select "Simple Network Management Protocal" line in Wiresharks middle pane, then right click and choose "Export Packet Bytes..." (as .raw). Open the capture in wireshark, it will decode as "BER Encoded File". Click on each varbind and the "packet bytes" window will highlight the corresponding bytes, then the start and end positions can be found. */ /* go-bindata has changed output format. Old style is needed: go get -u github.com/jteeuwen/go-bindata/... git co 79847ab rm ~/go/bin/go-bindata # belts and braces go install ~/go/bin/go-bindata -uncompressed *.pcap */ start int finish int pduType Asn1BER pduValue interface{} } type testsEnmarshalT struct { version SnmpVersion community string requestType PDUType requestid uint32 msgid uint32 // function and function name returning bytes from tcpdump goodBytes func() []byte funcName string // could do this via reflection // start position of the pdu pduStart int // start position of the vbl vblStart int // finish position of pdu, vbl and message - all the same finish int // a slice of positions containing start and finish of each varbind vbPositions []testsEnmarshalVarbindPosition } var testsEnmarshal = []testsEnmarshalT{ { Version2c, "public", GetRequest, 1871507044, 0, kyoceraRequestBytes, "kyocera_request", 0x0e, // pdu start 0x1d, // vbl start 0xa0, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.2.1.1.7.0", 0x20, 0x2d, Null, nil}, {".1.3.6.1.2.1.2.2.1.10.1", 0x2e, 0x3d, Null, nil}, {".1.3.6.1.2.1.2.2.1.5.1", 0x3e, 0x4d, Null, nil}, {".1.3.6.1.2.1.1.4.0", 0x4e, 0x5b, Null, nil}, {".1.3.6.1.2.1.43.5.1.1.15.1", 0x5c, 0x6c, Null, nil}, {".1.3.6.1.2.1.4.21.1.1.127.0.0.1", 0x6d, 0x7f, Null, nil}, {".1.3.6.1.4.1.23.2.5.1.1.1.4.2", 0x80, 0x92, Null, nil}, {".1.3.6.1.2.1.1.3.0", 0x93, 0xa0, Null, nil}, }, }, { Version1, "privatelab", SetRequest, 526895288, 0, portOnOutgoing1, "portOnOutgoing1", 0x11, // pdu start 0x1f, // vbl start 0x36, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.4.1.318.1.1.4.4.2.1.3.5", 0x21, 0x36, Integer, 1}, }, }, { Version1, "privatelab", SetRequest, 1826072803, 0, portOffOutgoing1, "portOffOutgoing1", 0x11, // pdu start 0x1f, // vbl start 0x36, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.4.1.318.1.1.4.4.2.1.3.5", 0x21, 0x36, Integer, 2}, }, }, // MrSpock Set stuff { Version2c, "private", SetRequest, 756726019, 0, setOctet1, "setOctet1", 0x0e, // pdu start 0x1c, // vbl start 0x32, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.4.1.2863.205.1.1.75.1.0", 0x1e, 0x32, OctetString, []byte{0x80}}, }, }, { Version2c, "private", SetRequest, 1000552357, 0, setOctet2, "setOctet2", 0x0e, // pdu start 0x1c, // vbl start 0x37, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.4.1.2863.205.1.1.75.2.0", 0x1e, 0x36, OctetString, "telnet"}, }, }, // MrSpock Set stuff { Version2c, "private", SetRequest, 1664317637, 0, setInteger1, "setInteger1", 0x0e, // pdu start 0x1c, // vbl start 0x7f, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2.2", 0x1e, 0x36, Integer, 5001}, {".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3.2", 0x37, 0x4f, Integer, 5001}, {".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4.2", 0x50, 0x67, Integer, 2}, {".1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5.2", 0x68, 0x7f, Integer, 1}, }, }, // Issue 35, empty responses. { Version2c, "public", GetRequest, 1883298028, 0, emptyErrRequest, "emptyErrRequest", 0x0d, // pdu start 0x1b, // vbl start 0x1c, // finish []testsEnmarshalVarbindPosition{}, }, // trap - TimeTicks // snmptrap different with timetick 2, integer 5 // trap1 - capture is from frame - less work, decode easier // varbinds - because Wireshark is decoding as BER's, need to subtract 2 // from start of varbinds { Version2c, "public", SNMPv2Trap, 1918693186, 0, trap1, "trap1", 0x0e, // pdu start 0x1c, // vbl start 0x82, // finish []testsEnmarshalVarbindPosition{ {".1.3.6.1.2.1.1.3.0", 0x1e, 0x2f, TimeTicks, uint32(18542501)}, {".1.3.6.1.6.3.1.1.4.1.0", 0x30, 0x45, ObjectIdentifier, ".1.3.6.1.2.1.1"}, {".1.3.6.1.2.1.1.1.0", 0x46, 0x59, OctetString, "red laptop"}, {".1.3.6.1.2.1.1.7.0", 0x5e, 0x6c, Integer, 5}, {".1.3.6.1.2.1.1.2", 0x6d, 0x82, ObjectIdentifier, ".1.3.6.1.4.1.2.3.4.5"}, }, }, } // helpers for Enmarshal tests // vbPosPdus returns a slice of oids in the given test func vbPosPdus(test testsEnmarshalT) (pdus []SnmpPDU) { for _, vbp := range test.vbPositions { pdu := SnmpPDU{vbp.oid, vbp.pduType, vbp.pduValue, nil} pdus = append(pdus, pdu) } return } // checkByteEquality walks the bytes in testBytes, and compares them to goodBytes func checkByteEquality(t *testing.T, test testsEnmarshalT, testBytes []byte, start int, finish int) { testBytesLen := len(testBytes) goodBytes := test.goodBytes() goodBytes = goodBytes[start : finish+1] for cursor := range goodBytes { if testBytesLen < cursor { t.Errorf("%s: testBytesLen (%d) < cursor (%d)", test.funcName, testBytesLen, cursor) break } if testBytes[cursor] != goodBytes[cursor] { t.Errorf("%s: cursor %d: testBytes != goodBytes:\n%s\n%s", test.funcName, cursor, dumpBytes2("good", goodBytes, cursor), dumpBytes2("test", testBytes, cursor)) break } } } // Enmarshal tests in order that should be used for troubleshooting // ie check each varbind is working, then the varbind list, etc func TestEnmarshalVarbind(t *testing.T) { Default.Logger = log.New(ioutil.Discard, "", 0) for _, test := range testsEnmarshal { for j, test2 := range test.vbPositions { snmppdu := &SnmpPDU{test2.oid, test2.pduType, test2.pduValue, nil} testBytes, err := marshalVarbind(snmppdu) if err != nil { t.Errorf("#%s:%d:%s err returned: %v", test.funcName, j, test2.oid, err) } checkByteEquality(t, test, testBytes, test2.start, test2.finish) } } } func TestEnmarshalVBL(t *testing.T) { Default.Logger = log.New(ioutil.Discard, "", 0) for _, test := range testsEnmarshal { x := &SnmpPacket{ Community: test.community, Version: test.version, RequestID: test.requestid, Variables: vbPosPdus(test), } testBytes, err := x.marshalVBL() if err != nil { t.Errorf("#%s: marshalVBL() err returned: %v", test.funcName, err) } checkByteEquality(t, test, testBytes, test.vblStart, test.finish) } } func TestEnmarshalPDU(t *testing.T) { Default.Logger = log.New(ioutil.Discard, "", 0) for _, test := range testsEnmarshal { x := &SnmpPacket{ Community: test.community, Version: test.version, PDUType: test.requestType, RequestID: test.requestid, Variables: vbPosPdus(test), } testBytes, err := x.marshalPDU() if err != nil { t.Errorf("#%s: marshalPDU() err returned: %v", test.funcName, err) } checkByteEquality(t, test, testBytes, test.pduStart, test.finish) } } func TestEnmarshalMsg(t *testing.T) { Default.Logger = log.New(ioutil.Discard, "", 0) for _, test := range testsEnmarshal { x := &SnmpPacket{ Community: test.community, Version: test.version, PDUType: test.requestType, RequestID: test.requestid, MsgID: test.msgid, Variables: vbPosPdus(test), } testBytes, err := x.marshalMsg() if err != nil { t.Errorf("#%s: marshal() err returned: %v", test.funcName, err) } checkByteEquality(t, test, testBytes, 0, test.finish) t.Run(fmt.Sprintf("TestEnmarshalMsgUnmarshal/PDU[%v]/RequestID[%v]", test.requestType, test.requestid), func(t *testing.T) { vhandle := GoSNMP{} vhandle.Logger = Default.Logger result, err := vhandle.SnmpDecodePacket(testBytes) if err != nil { t.Errorf("#%s: SnmpDecodePacket() err returned: %v", test.funcName, err) } newResultTestBytes, err := result.marshalMsg() if err != nil { t.Errorf("#%s: marshal() err returned: %v", test.funcName, err) } if len(newResultTestBytes) == 0 { t.Errorf("#%s: marshal() length of result is 0 : %v", test.funcName, (newResultTestBytes)) return } checkByteEquality(t, test, newResultTestBytes, 0, test.finish) }) } } // -- Unmarshal ----------------------------------------------------------------- var testsUnmarshal = []struct { in func() []byte out *SnmpPacket }{ {kyoceraResponseBytes, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 1066889284, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.2.1.1.7.0", Type: Integer, Value: 104, }, { Name: ".1.3.6.1.2.1.2.2.1.10.1", Type: Counter32, Value: 271070065, }, { Name: ".1.3.6.1.2.1.2.2.1.5.1", Type: Gauge32, Value: 100000000, }, { Name: ".1.3.6.1.2.1.1.4.0", Type: OctetString, Value: []byte("Administrator"), }, { Name: ".1.3.6.1.2.1.43.5.1.1.15.1", Type: Null, Value: nil, }, { Name: ".1.3.6.1.2.1.4.21.1.1.127.0.0.1", Type: IPAddress, Value: "127.0.0.1", }, { Name: ".1.3.6.1.4.1.23.2.5.1.1.1.4.2", Type: OctetString, Value: []byte{0x00, 0x15, 0x99, 0x37, 0x76, 0x2b}, }, { Name: ".1.3.6.1.2.1.1.3.0", Type: TimeTicks, Value: 318870100, }, }, }, }, {ciscoResponseBytes, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 4876669, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.2.1.1.7.0", Type: Integer, Value: 78, }, { Name: ".1.3.6.1.2.1.2.2.1.2.6", Type: OctetString, Value: []byte("GigabitEthernet0"), }, { Name: ".1.3.6.1.2.1.2.2.1.5.3", Type: Gauge32, Value: uint(4294967295), }, { Name: ".1.3.6.1.2.1.2.2.1.7.2", Type: NoSuchInstance, Value: nil, }, { Name: ".1.3.6.1.2.1.2.2.1.9.3", Type: TimeTicks, Value: 2970, }, { Name: ".1.3.6.1.2.1.3.1.1.2.10.1.10.11.0.17", Type: OctetString, Value: []byte{0x00, 0x07, 0x7d, 0x4d, 0x09, 0x00}, }, { Name: ".1.3.6.1.2.1.3.1.1.3.10.1.10.11.0.2", Type: IPAddress, Value: "10.11.0.2", }, { Name: ".1.3.6.1.2.1.4.20.1.1.110.143.197.1", Type: IPAddress, Value: "110.143.197.1", }, { Name: ".1.3.6.1.66.1", Type: NoSuchObject, Value: nil, }, { Name: ".1.3.6.1.2.1.1.2.0", Type: ObjectIdentifier, Value: ".1.3.6.1.4.1.9.1.1166", }, }, }, }, {portOnIncoming1, &SnmpPacket{ Version: Version1, Community: "privatelab", PDUType: GetResponse, RequestID: 526895288, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.4.1.318.1.1.4.4.2.1.3.5", Type: Integer, Value: 1, }, }, }, }, {portOffIncoming1, &SnmpPacket{ Version: Version1, Community: "privatelab", PDUType: GetResponse, RequestID: 1826072803, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.4.1.318.1.1.4.4.2.1.3.5", Type: Integer, Value: 2, }, }, }, }, {ciscoGetnextResponseBytes, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 1528674030, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.2.1.3.1.1.3.2.1.192.168.104.2", Type: IPAddress, Value: "192.168.104.2", }, { Name: ".1.3.6.1.2.1.92.1.2.1.0", Type: Counter32, Value: 0, }, { Name: ".1.3.6.1.2.1.1.9.1.3.3", Type: OctetString, Value: []byte("The MIB module for managing IP and ICMP implementations"), }, { Name: ".1.3.6.1.2.1.1.9.1.4.2", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.2.1.0", Type: Integer, Value: 3, }, { Name: ".1.3.6.1.2.1.1.2.0", Type: ObjectIdentifier, Value: ".1.3.6.1.4.1.8072.3.2.10", }, }, }, }, {ciscoGetbulkResponseBytes, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 250000266, NonRepeaters: 0, MaxRepetitions: 10, Variables: []SnmpPDU{ { Name: ".1.3.6.1.2.1.1.9.1.4.1", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.1.9.1.4.2", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.1.9.1.4.3", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.1.9.1.4.4", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.1.9.1.4.5", Type: TimeTicks, Value: 21, }, { Name: ".1.3.6.1.2.1.1.9.1.4.6", Type: TimeTicks, Value: 23, }, { Name: ".1.3.6.1.2.1.1.9.1.4.7", Type: TimeTicks, Value: 23, }, { Name: ".1.3.6.1.2.1.1.9.1.4.8", Type: TimeTicks, Value: 23, }, { Name: ".1.3.6.1.2.1.2.1.0", Type: Integer, Value: 3, }, { Name: ".1.3.6.1.2.1.2.2.1.1.1", Type: Integer, Value: 1, }, }, }, }, {emptyErrResponse, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 1883298028, Error: 0, Variables: []SnmpPDU{}, }, }, {counter64Response, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 190378322, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.2.1.31.1.1.1.10.1", Type: Counter64, Value: 1527943, }, }, }, }, {opaqueFloatResponse, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 601216773, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.4.1.6574.4.2.12.1.0", Type: OpaqueFloat, Value: float32(10.0), }, }, }, }, {opaqueDoubleResponse, &SnmpPacket{ Version: Version2c, Community: "public", PDUType: GetResponse, RequestID: 601216773, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.4.1.6574.4.2.12.1.0", Type: OpaqueDouble, Value: float64(10.0), }, }, }, }, {snmpv3HelloRequest, &SnmpPacket{ Version: Version3, PDUType: GetRequest, MsgID: 91040642, RequestID: 1157240545, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{}, }, }, {snmpv3HelloResponse, &SnmpPacket{ Version: Version3, PDUType: Report, MsgID: 91040642, RequestID: 1157240545, Error: 0, ErrorIndex: 0, Variables: []SnmpPDU{ { Name: ".1.3.6.1.6.3.15.1.1.4.0", Type: Counter32, Value: 21, }, }, }, }, } func TestUnmarshal(t *testing.T) { Default.Logger = log.New(ioutil.Discard, "", 0) for i, test := range testsUnmarshal { funcName := runtime.FuncForPC(reflect.ValueOf(test.in).Pointer()).Name() splitedFuncName := strings.Split(funcName, ".") funcName = splitedFuncName[len(splitedFuncName)-1] t.Run(fmt.Sprintf("%v-%v", i, funcName), func(t *testing.T) { vhandle := GoSNMP{} vhandle.Logger = Default.Logger testBytes := test.in() res, err := vhandle.SnmpDecodePacket(testBytes) if err != nil { t.Errorf("#%s: SnmpDecodePacket() err returned: %v", funcName, err) } t.Run("unmarshal", func(t *testing.T) { // test "header" fields if res.Version != test.out.Version { t.Errorf("#%d Version result: %v, test: %v", i, res.Version, test.out.Version) } if res.Community != test.out.Community { t.Errorf("#%d Community result: %v, test: %v", i, res.Community, test.out.Community) } if res.PDUType != test.out.PDUType { t.Errorf("#%d PDUType result: %v, test: %v", i, res.PDUType, test.out.PDUType) } if res.RequestID != test.out.RequestID { t.Errorf("#%d RequestID result: %v, test: %v", i, res.RequestID, test.out.RequestID) } if res.Error != test.out.Error { t.Errorf("#%d Error result: %v, test: %v", i, res.Error, test.out.Error) } if res.ErrorIndex != test.out.ErrorIndex { t.Errorf("#%d ErrorIndex result: %v, test: %v", i, res.ErrorIndex, test.out.ErrorIndex) } // test varbind values for n, vb := range test.out.Variables { if len(res.Variables) < n { t.Errorf("#%d:%d ran out of varbind results", i, n) return } vbr := res.Variables[n] if vbr.Name != vb.Name { t.Errorf("#%d:%d Name result: %v, test: %v", i, n, vbr.Name, vb.Name) } if vbr.Type != vb.Type { t.Errorf("#%d:%d Type result: %v, test: %v", i, n, vbr.Type, vb.Type) } switch vb.Type { case Integer, Gauge32, Counter32, TimeTicks, Counter64: vbval := ToBigInt(vb.Value) vbrval := ToBigInt(vbr.Value) if vbval.Cmp(vbrval) != 0 { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } case OctetString: if !bytes.Equal(vb.Value.([]byte), vbr.Value.([]byte)) { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } case IPAddress, ObjectIdentifier: if vb.Value != vbr.Value { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } case Null, NoSuchObject, NoSuchInstance: if (vb.Value != nil) || (vbr.Value != nil) { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } case OpaqueFloat: if vb.Value.(float32) != vbr.Value.(float32) { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } case OpaqueDouble: if vb.Value.(float64) != vbr.Value.(float64) { t.Errorf("#%d:%d Value result: %v, test: %v", i, n, vbr.Value, vb.Value) } default: t.Errorf("#%d:%d Unhandled case result: %v, test: %v", i, n, vbr.Value, vb.Value) } } }) t.Run("remarshal", func(t *testing.T) { result, err := res.marshalMsg() if err != nil { t.Fatalf("#%s: marshalMsg() err returned: %v", funcName, err) } resNew, err := vhandle.SnmpDecodePacket(result) if err != nil { t.Fatalf("#%s: SnmpDecodePacket() err returned: %v", funcName, err) } assert.EqualValues(t, res, resNew) }) }) } } // ----------------------------------------------------------------------------- /* * byte dumps generated using tcpdump and github.com/jteeuwen/go-bindata eg `sudo tcpdump -s 0 -i eth0 -w cisco.pcap host 203.50.251.17 and port 161` * Frame, Ethernet II, IP and UDP layers removed from generated bytes */ /* kyoceraResponseBytes corresponds to the response section of this snmpget Simple Network Management Protocol version: v2c (1) community: public data: get-response (2) get-response request-id: 1066889284 error-status: noError (0) error-index: 0 variable-bindings: 8 items 1.3.6.1.2.1.1.7.0: 104 1.3.6.1.2.1.2.2.1.10.1: 271070065 1.3.6.1.2.1.2.2.1.5.1: 100000000 1.3.6.1.2.1.1.4.0: 41646d696e6973747261746f72 1.3.6.1.2.1.43.5.1.1.15.1: Value (Null) 1.3.6.1.2.1.4.21.1.1.127.0.0.1: 127.0.0.1 (127.0.0.1) 1.3.6.1.4.1.23.2.5.1.1.1.4.2: 00159937762b 1.3.6.1.2.1.1.3.0: 318870100 */ func kyoceraResponseBytes() []byte { return []byte{ 0x30, 0x81, 0xc2, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x81, 0xb4, 0x02, 0x04, 0x3f, 0x97, 0x70, 0x44, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x81, 0xa5, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x07, 0x00, 0x02, 0x01, 0x68, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x0a, 0x01, 0x41, 0x04, 0x10, 0x28, 0x33, 0x71, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x05, 0x01, 0x42, 0x04, 0x05, 0xf5, 0xe1, 0x00, 0x30, 0x19, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x04, 0x00, 0x04, 0x0d, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x30, 0x0f, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x2b, 0x05, 0x01, 0x01, 0x0f, 0x01, 0x05, 0x00, 0x30, 0x15, 0x06, 0x0d, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x04, 0x15, 0x01, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x40, 0x04, 0x7f, 0x00, 0x00, 0x01, 0x30, 0x17, 0x06, 0x0d, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x17, 0x02, 0x05, 0x01, 0x01, 0x01, 0x04, 0x02, 0x04, 0x06, 0x00, 0x15, 0x99, 0x37, 0x76, 0x2b, 0x30, 0x10, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x43, 0x04, 0x13, 0x01, 0x92, 0x54, } } /* ciscoResponseBytes corresponds to the response section of this snmpget: % snmpget -On -v2c -c public 203.50.251.17 1.3.6.1.2.1.1.7.0 1.3.6.1.2.1.2.2.1.2.6 1.3.6.1.2.1.2.2.1.5.3 1.3.6.1.2.1.2.2.1.7.2 1.3.6.1.2.1.2.2.1.9.3 1.3.6.1.2.1.3.1.1.2.10.1.10.11.0.17 1.3.6.1.2.1.3.1.1.3.10.1.10.11.0.2 1.3.6.1.2.1.4.20.1.1.110.143.197.1 1.3.6.1.66.1 1.3.6.1.2.1.1.2.0 .1.3.6.1.2.1.1.7.0 = INTEGER: 78 .1.3.6.1.2.1.2.2.1.2.6 = STRING: GigabitEthernet0 .1.3.6.1.2.1.2.2.1.5.3 = Gauge32: 4294967295 .1.3.6.1.2.1.2.2.1.7.2 = No Such Instance currently exists at this OID .1.3.6.1.2.1.2.2.1.9.3 = Timeticks: (2970) 0:00:29.70 .1.3.6.1.2.1.3.1.1.2.10.1.10.11.0.17 = Hex-STRING: 00 07 7D 4D 09 00 .1.3.6.1.2.1.3.1.1.3.10.1.10.11.0.2 = Network Address: 0A:0B:00:02 .1.3.6.1.2.1.4.20.1.1.110.143.197.1 = IPAddress: 110.143.197.1 .1.3.6.1.66.1 = No Such Object available on this agent at this OID .1.3.6.1.2.1.1.2.0 = OID: .1.3.6.1.4.1.9.1.1166 */ func ciscoResponseBytes() []byte { return []byte{ 0x30, 0x81, 0xf1, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x81, 0xe3, 0x02, 0x03, 0x4a, 0x69, 0x7d, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x81, 0xd5, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x07, 0x00, 0x02, 0x01, 0x4e, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x02, 0x06, 0x04, 0x10, 0x47, 0x69, 0x67, 0x61, 0x62, 0x69, 0x74, 0x45, 0x74, 0x68, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x30, 0x30, 0x13, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x05, 0x03, 0x42, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x07, 0x02, 0x81, 0x00, 0x30, 0x10, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x09, 0x03, 0x43, 0x02, 0x0b, 0x9a, 0x30, 0x19, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x03, 0x01, 0x01, 0x02, 0x0a, 0x01, 0x0a, 0x0b, 0x00, 0x11, 0x04, 0x06, 0x00, 0x07, 0x7d, 0x4d, 0x09, 0x00, 0x30, 0x17, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x03, 0x01, 0x01, 0x03, 0x0a, 0x01, 0x0a, 0x0b, 0x00, 0x02, 0x40, 0x04, 0x0a, 0x0b, 0x00, 0x02, 0x30, 0x17, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x04, 0x14, 0x01, 0x01, 0x6e, 0x81, 0x0f, 0x81, 0x45, 0x01, 0x40, 0x04, 0x6e, 0x8f, 0xc5, 0x01, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x06, 0x01, 0x42, 0x01, 0x80, 0x00, 0x30, 0x15, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x02, 0x00, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x09, 0x01, 0x89, 0x0e, } } /* kyoceraRequestBytes corresponds to the request section of this snmpget: snmpget -On -v2c -c public 192.168.1.10 1.3.6.1.2.1.1.7.0 1.3.6.1.2.1.2.2.1.10.1 1.3.6.1.2.1.2.2.1.5.1 1.3.6.1.2.1.1.4.0 1.3.6.1.2.1.43.5.1.1.15.1 1.3.6.1.2.1.4.21.1.1.127.0.0.1 1.3.6.1.4.1.23.2.5.1.1.1.4.2 1.3.6.1.2.1.1.3.0 .1.3.6.1.2.1.1.7.0 = INTEGER: 104 .1.3.6.1.2.1.2.2.1.10.1 = Counter32: 144058856 .1.3.6.1.2.1.2.2.1.5.1 = Gauge32: 100000000 .1.3.6.1.2.1.1.4.0 = STRING: "Administrator" .1.3.6.1.2.1.43.5.1.1.15.1 = NULL .1.3.6.1.2.1.4.21.1.1.127.0.0.1 = IPAddress: 127.0.0.1 .1.3.6.1.4.1.23.2.5.1.1.1.4.2 = Hex-STRING: 00 15 99 37 76 2B .1.3.6.1.2.1.1.3.0 = Timeticks: (120394900) 13 days, 22:25:49.00 */ func kyoceraRequestBytes() []byte { return []byte{ 0x30, 0x81, 0x9e, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x81, 0x90, 0x02, 0x04, 0x6f, 0x8c, 0xee, 0x64, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x81, 0x81, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x07, 0x00, 0x05, 0x00, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x0a, 0x01, 0x05, 0x00, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x05, 0x01, 0x05, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x04, 0x00, 0x05, 0x00, 0x30, 0x0f, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x2b, 0x05, 0x01, 0x01, 0x0f, 0x01, 0x05, 0x00, 0x30, 0x11, 0x06, 0x0d, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x04, 0x15, 0x01, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x05, 0x00, 0x30, 0x11, 0x06, 0x0d, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x17, 0x02, 0x05, 0x01, 0x01, 0x01, 0x04, 0x02, 0x05, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x05, 0x00, } } // === snmpset dumps === /* port_on_*1() correspond to this snmpset and response: snmpset -v 1 -c privatelab 192.168.100.124 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 i 1 Simple Network Management Protocol version: version-1 (0) community: privatelab data: set-request (3) set-request request-id: 526895288 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5: Object Name: 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 (iso.3.6.1.4.1.318.1.1.4.4.2.1.3.5) Value (Integer32): 1 Simple Network Management Protocol version: version-1 (0) community: privatelab data: get-response (2) get-response request-id: 526895288 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5: Object Name: 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 (iso.3.6.1.4.1.318.1.1.4.4.2.1.3.5) Value (Integer32): 1 */ func portOnOutgoing1() []byte { return []byte{ 0x30, 0x35, 0x02, 0x01, 0x00, 0x04, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x6c, 0x61, 0x62, 0xa3, 0x24, 0x02, 0x04, 0x1f, 0x67, 0xc8, 0xb8, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x16, 0x30, 0x14, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x3e, 0x01, 0x01, 0x04, 0x04, 0x02, 0x01, 0x03, 0x05, 0x02, 0x01, 0x01, } } func portOnIncoming1() []byte { return []byte{ 0x30, 0x82, 0x00, 0x35, 0x02, 0x01, 0x00, 0x04, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x6c, 0x61, 0x62, 0xa2, 0x24, 0x02, 0x04, 0x1f, 0x67, 0xc8, 0xb8, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x16, 0x30, 0x14, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x3e, 0x01, 0x01, 0x04, 0x04, 0x02, 0x01, 0x03, 0x05, 0x02, 0x01, 0x01, } } /* port_off_*1() correspond to this snmpset and response: snmpset -v 1 -c privatelab 192.168.100.124 .1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 i 2 Simple Network Management Protocol version: version-1 (0) community: privatelab data: set-request (3) set-request request-id: 1826072803 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5: Object Name: 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 (iso.3.6.1.4.1.318.1.1.4.4.2.1.3.5) Value (Integer32): 2 Simple Network Management Protocol version: version-1 (0) community: privatelab data: get-response (2) get-response request-id: 1826072803 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5: Object Name: 1.3.6.1.4.1.318.1.1.4.4.2.1.3.5 (iso.3.6.1.4.1.318.1.1.4.4.2.1.3.5) Value (Integer32): 2 */ func portOffOutgoing1() []byte { return []byte{ 0x30, 0x35, 0x02, 0x01, 0x00, 0x04, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x6c, 0x61, 0x62, 0xa3, 0x24, 0x02, 0x04, 0x6c, 0xd7, 0xa8, 0xe3, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x16, 0x30, 0x14, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x3e, 0x01, 0x01, 0x04, 0x04, 0x02, 0x01, 0x03, 0x05, 0x02, 0x01, 0x02, } } func portOffIncoming1() []byte { return []byte{ 0x30, 0x82, 0x00, 0x35, 0x02, 0x01, 0x00, 0x04, 0x0a, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x6c, 0x61, 0x62, 0xa2, 0x24, 0x02, 0x04, 0x6c, 0xd7, 0xa8, 0xe3, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x16, 0x30, 0x14, 0x06, 0x0f, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x3e, 0x01, 0x01, 0x04, 0x04, 0x02, 0x01, 0x03, 0x05, 0x02, 0x01, 0x02, } } // MrSpock START /* setOctet1: Simple Network Management Protocol version: v2c (1) community: private data: set-request (3) set-request request-id: 756726019 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.2863.205.1.1.75.1.0: 80 Object Name: 1.3.6.1.4.1.2863.205.1.1.75.1.0 (iso.3.6.1.4.1.2863.205.1.1.75.1.0) Value (OctetString): 80 setOctet2: Simple Network Management Protocol version: v2c (1) community: private data: set-request (3) set-request request-id: 1000552357 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.4.1.2863.205.1.1.75.2.0: 74656c6e6574 Object Name: 1.3.6.1.4.1.2863.205.1.1.75.2.0 (iso.3.6.1.4.1.2863.205.1.1.75.2.0) Value (OctetString): 74656c6e6574 ("telnet") */ func setOctet1() []byte { return []byte{ 0x30, 0x31, 0x02, 0x01, 0x01, 0x04, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0xa3, 0x23, 0x02, 0x04, 0x2d, 0x1a, 0xb9, 0x03, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x15, 0x30, 0x13, 0x06, 0x0e, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x01, 0x01, 0x4b, 0x01, 0x00, 0x04, 0x01, 0x80, } } func setOctet2() []byte { return []byte{ 0x30, 0x36, 0x02, 0x01, 0x01, 0x04, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0xa3, 0x28, 0x02, 0x04, 0x3b, 0xa3, 0x37, 0xa5, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x1a, 0x30, 0x18, 0x06, 0x0e, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x01, 0x01, 0x4b, 0x02, 0x00, 0x04, 0x06, 0x74, 0x65, 0x6c, 0x6e, 0x65, 0x74, } } /* setInteger1: snmpset -c private -v2c 10.80.0.14 \ .1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2.2 i 5001 \ .1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3.2 i 5001 \ .1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4.2 i 2 \ .1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5.2 i 1 Simple Network Management Protocol version: v2c (1) community: private data: set-request (3) set-request request-id: 1664317637 error-status: noError (0) error-index: 0 variable-bindings: 4 items 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2.2: Object Name: 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.2.2 (iso.3.6.1.4.1.2863.205.10.1.33.2.5.1.2.2) Value (Integer32): 5001 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3.2: Object Name: 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.3.2 (iso.3.6.1.4.1.2863.205.10.1.33.2.5.1.3.2) Value (Integer32): 5001 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4.2: Object Name: 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.4.2 (iso.3.6.1.4.1.2863.205.10.1.33.2.5.1.4.2) Value (Integer32): 2 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5.2: Object Name: 1.3.6.1.4.1.2863.205.10.1.33.2.5.1.5.2 (iso.3.6.1.4.1.2863.205.10.1.33.2.5.1.5.2) Value (Integer32): 1 */ func setInteger1() []byte { return []byte{ 0x30, 0x7e, 0x02, 0x01, 0x01, 0x04, 0x07, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0xa3, 0x70, 0x02, 0x04, 0x63, 0x33, 0x78, 0xc5, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x62, 0x30, 0x17, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x0a, 0x01, 0x21, 0x02, 0x05, 0x01, 0x02, 0x02, 0x02, 0x02, 0x13, 0x89, 0x30, 0x17, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x0a, 0x01, 0x21, 0x02, 0x05, 0x01, 0x03, 0x02, 0x02, 0x02, 0x13, 0x89, 0x30, 0x16, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x0a, 0x01, 0x21, 0x02, 0x05, 0x01, 0x04, 0x02, 0x02, 0x01, 0x02, 0x30, 0x16, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x96, 0x2f, 0x81, 0x4d, 0x0a, 0x01, 0x21, 0x02, 0x05, 0x01, 0x05, 0x02, 0x02, 0x01, 0x01, } } // MrSpock FINISH func ciscoGetnextResponseBytes() []byte { return []byte{ 0x30, 0x81, 0xc8, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x81, 0xba, 0x02, 0x04, 0x5b, 0x1d, 0xb6, 0xee, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x81, 0xab, 0x30, 0x19, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x03, 0x01, 0x01, 0x03, 0x02, 0x01, 0x81, 0x40, 0x81, 0x28, 0x68, 0x02, 0x40, 0x04, 0xc0, 0xa8, 0x68, 0x02, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x5c, 0x01, 0x02, 0x01, 0x00, 0x41, 0x01, 0x00, 0x30, 0x45, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x03, 0x03, 0x04, 0x37, 0x54, 0x68, 0x65, 0x20, 0x4d, 0x49, 0x42, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x20, 0x49, 0x50, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x49, 0x43, 0x4d, 0x50, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x02, 0x43, 0x01, 0x15, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x03, 0x30, 0x16, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x02, 0x00, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xbf, 0x08, 0x03, 0x02, 0x0a, } } func ciscoGetnextRequestBytes() []byte { return []byte{ 0x30, 0x7e, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa1, 0x71, 0x02, 0x04, 0x5b, 0x1d, 0xb6, 0xee, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x63, 0x30, 0x15, 0x06, 0x11, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x03, 0x01, 0x01, 0x03, 0x02, 0x01, 0x81, 0x40, 0x81, 0x28, 0x68, 0x01, 0x05, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x5c, 0x01, 0x02, 0x05, 0x00, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x03, 0x02, 0x05, 0x00, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x01, 0x05, 0x00, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x08, 0x05, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x05, 0x00, } } /* cisco getbulk bytes corresponds to this snmpbulkget command: $ snmpbulkget -v2c -cpublic 127.0.0.1:161 1.3.6.1.2.1.1.9.1.3.52 iso.3.6.1.2.1.1.9.1.4.1 = Timeticks: (21) 0:00:00.21 iso.3.6.1.2.1.1.9.1.4.2 = Timeticks: (21) 0:00:00.21 iso.3.6.1.2.1.1.9.1.4.3 = Timeticks: (21) 0:00:00.21 iso.3.6.1.2.1.1.9.1.4.4 = Timeticks: (21) 0:00:00.21 iso.3.6.1.2.1.1.9.1.4.5 = Timeticks: (21) 0:00:00.21 iso.3.6.1.2.1.1.9.1.4.6 = Timeticks: (23) 0:00:00.23 iso.3.6.1.2.1.1.9.1.4.7 = Timeticks: (23) 0:00:00.23 iso.3.6.1.2.1.1.9.1.4.8 = Timeticks: (23) 0:00:00.23 iso.3.6.1.2.1.2.1.0 = INTEGER: 3 iso.3.6.1.2.1.2.2.1.1.1 = INTEGER: 1 */ func ciscoGetbulkRequestBytes() []byte { return []byte{ 0x30, 0x2b, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa5, 0x1e, 0x02, 0x04, 0x7d, 0x89, 0x68, 0xda, 0x02, 0x01, 0x00, 0x02, 0x01, 0x0a, 0x30, 0x10, 0x30, 0x0e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x03, 0x34, 0x05, 0x00, 0x00, } } func ciscoGetbulkResponseBytes() []byte { return []byte{ 0x30, 0x81, 0xc5, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x81, 0xb7, 0x02, 0x04, 0x0e, 0xe6, 0xb3, 0x8a, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x81, 0xa8, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x01, 0x43, 0x01, 0x15, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x02, 0x43, 0x01, 0x15, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x03, 0x43, 0x01, 0x15, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x04, 0x43, 0x01, 0x15, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x05, 0x43, 0x01, 0x15, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x06, 0x43, 0x01, 0x17, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x07, 0x43, 0x01, 0x17, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x09, 0x01, 0x04, 0x08, 0x43, 0x01, 0x17, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x03, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, } } /* Issue 35, empty responses. Simple Network Management Protocol version: v2c (1) community: public data: get-request (0) get-request request-id: 1883298028 error-status: noError (0) error-index: 0 variable-bindings: 0 items */ func emptyErrRequest() []byte { return []byte{ 0x30, 0x1b, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa0, 0x0e, 0x02, 0x04, 0x70, 0x40, 0xd8, 0xec, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00, } } /* Issue 35, empty responses. Simple Network Management Protocol version: v2c (1) community: public data: get-response (2) get-response request-id: 1883298028 error-status: noError (0) error-index: 0 variable-bindings: 0 items */ func emptyErrResponse() []byte { return []byte{ 0x30, 0x1b, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x0e, 0x02, 0x04, 0x70, 0x40, 0xd8, 0xec, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00, } } /* Issue 15, test Counter64. Simple Network Management Protocol version: v2c (1) community: public data: get-response (2) get-response request-id: 190378322 error-status: noError (0) error-index: 0 variable-bindings: 1 item 1.3.6.1.2.1.31.1.1.1.10.1: 1527943 Object Name: 1.3.6.1.2.1.31.1.1.1.10.1 (iso.3.6.1.2.1.31.1.1.1.10.1) Value (Counter64): 1527943 */ func counter64Response() []byte { return []byte{ 0x30, 0x2f, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x22, 0x02, 0x04, 0x0b, 0x58, 0xf1, 0x52, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x14, 0x30, 0x12, 0x06, 0x0b, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x1f, 0x01, 0x01, 0x01, 0x0a, 0x01, 0x46, 0x03, 0x17, 0x50, 0x87, } } /* Opaque Float, observed from Synology NAS UPS MIB snmpget -v 2c -c public host 1.3.6.1.4.1.6574.4.2.12.1.0 */ func opaqueFloatResponse() []byte { return []byte{ 0x30, 0x34, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x27, 0x02, 0x04, 0x23, 0xd5, 0xd7, 0x05, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x19, 0x30, 0x17, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb3, 0x2e, 0x04, 0x02, 0x0c, 0x01, 0x00, 0x44, 0x07, 0x9f, 0x78, 0x04, 0x41, 0x20, 0x00, 0x00, } } /* Opaque Double, not observed, crafted based on description: https://tools.ietf.org/html/draft-perkins-float-00 */ func opaqueDoubleResponse() []byte { return []byte{ 0x30, 0x38, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa2, 0x2b, 0x02, 0x04, 0x23, 0xd5, 0xd7, 0x05, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x1d, 0x30, 0x17, 0x06, 0x0c, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xb3, 0x2e, 0x04, 0x02, 0x0c, 0x01, 0x00, 0x44, 0x0b, 0x9f, 0x79, 0x08, 0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } } func TestUnmarshalEmptyPanic(t *testing.T) { var in = []byte{} var res = new(SnmpPacket) _, err := Default.unmarshalHeader(in, res) if err == nil { t.Errorf("unmarshalHeader did not gracefully detect empty packet") } } func TestV3USMInitialPacket(t *testing.T) { logger := log.New(ioutil.Discard, "", 0) var emptyPdus []SnmpPDU blankPacket := &SnmpPacket{ Version: Version3, MsgFlags: Reportable | NoAuthNoPriv, SecurityModel: UserSecurityModel, SecurityParameters: &UsmSecurityParameters{Logger: logger}, PDUType: GetRequest, Logger: logger, Variables: emptyPdus, } iBytes, err := blankPacket.marshalMsg() if err != nil { t.Errorf("#TestV3USMInitialPacket: marshalMsg() err returned: %v", err) } engine := GoSNMP{Logger: logger} pktNew, errDecode := engine.SnmpDecodePacket(iBytes) if errDecode != nil { t.Logf("-->Bytes=%v", iBytes) t.Logf("-->Expect=%v", blankPacket) t.Logf("-->got=%v", pktNew) t.Errorf("#TestV3USMInitialPacket: SnmpDecodePacket() err returned: %v. ", errDecode) } } func TestSendOneRequest_dups(t *testing.T) { srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) defer srvr.Close() x := &GoSNMP{ Version: Version2c, Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), Timeout: time.Millisecond * 100, Retries: 2, } if err := x.Connect(); err != nil { t.Fatalf("Error connecting: %s", err) } go func() { buf := make([]byte, 256) for { n, addr, err := srvr.ReadFrom(buf) if err != nil { return } buf := buf[:n] var reqPkt SnmpPacket var cursor int cursor, err = x.unmarshalHeader(buf, &reqPkt) if err != nil { t.Errorf("Error: %s", err) } // if x.Version == Version3 { // buf, cursor, err = x.decryptPacket(buf, cursor, &reqPkt) // if err != nil { // t.Errorf("Error: %s", err) // } //} err = x.unmarshalPayload(buf, cursor, &reqPkt) if err != nil { t.Errorf("Error: %s", err) } rspPkt := x.mkSnmpPacket(GetResponse, []SnmpPDU{ { Name: ".1.2", Type: Integer, Value: 123, }, }, 0, 0) rspPkt.RequestID = reqPkt.RequestID outBuf, err := rspPkt.marshalMsg() if err != nil { t.Errorf("ERR: %s", err) } srvr.WriteTo(outBuf, addr) for i := 0; i <= x.Retries; i++ { srvr.WriteTo(outBuf, addr) } } }() pdus := []SnmpPDU{SnmpPDU{Name: ".1.2", Type: Null}} reqPkt := x.mkSnmpPacket(GetResponse, pdus, 0, 0) //not actually a GetResponse, but we need something our test server can unmarshal _, err = x.sendOneRequest(reqPkt, true) if err != nil { t.Errorf("Error: %s", err) return } _, err = x.sendOneRequest(reqPkt, true) if err != nil { t.Errorf("Error: %s", err) return } } func BenchmarkSendOneRequest(b *testing.B) { b.StopTimer() srvr, err := net.ListenUDP("udp4", &net.UDPAddr{}) defer srvr.Close() x := &GoSNMP{ Version: Version2c, Target: srvr.LocalAddr().(*net.UDPAddr).IP.String(), Port: uint16(srvr.LocalAddr().(*net.UDPAddr).Port), Timeout: time.Millisecond * 100, Retries: 2, } if err := x.Connect(); err != nil { b.Fatalf("Error connecting: %s", err) } go func() { buf := make([]byte, 256) outBuf := counter64Response() for { _, addr, err := srvr.ReadFrom(buf) if err != nil { return } copy(outBuf[17:21], buf[11:15]) // evil: copy request ID srvr.WriteTo(outBuf, addr) } }() pdus := []SnmpPDU{SnmpPDU{Name: ".1.3.6.1.2.1.31.1.1.1.10.1", Type: Null}} reqPkt := x.mkSnmpPacket(GetRequest, pdus, 0, 0) // make sure everything works before starting the test _, err = x.sendOneRequest(reqPkt, true) if err != nil { b.Fatalf("Precheck failed: %s", err) } b.StartTimer() for n := 0; n < b.N; n++ { _, err = x.sendOneRequest(reqPkt, true) if err != nil { b.Fatalf("Error: %s", err) return } } } /* $ snmptrap -v 2c -c public 192.168.1.10 '' SNMPv2-MIB::system SNMPv2-MIB::sysDescr.0 s "red laptop" SNMPv2-MIB::sysServices.0 i "5" Simple Network Management Protocol version: v2c (1) community: public data: snmpV2-trap (7) snmpV2-trap request-id: 1271509950 error-status: noError (0) error-index: 0 variable-bindings: 5 items 1.3.6.1.2.1.1.3.0: 1034156 Object Name: 1.3.6.1.2.1.1.3.0 (iso.3.6.1.2.1.1.3.0) Value (Timeticks): 1034156 1.3.6.1.6.3.1.1.4.1.0: 1.3.6.1.2.1.1 (iso.3.6.1.2.1.1) Object Name: 1.3.6.1.6.3.1.1.4.1.0 (iso.3.6.1.6.3.1.1.4.1.0) Value (OID): 1.3.6.1.2.1.1 (iso.3.6.1.2.1.1) 1.3.6.1.2.1.1.1.0: 726564206c6170746f70 Object Name: 1.3.6.1.2.1.1.1.0 (iso.3.6.1.2.1.1.1.0) Value (OctetString): 726564206c6170746f70 Variable-binding-string: red laptop 1.3.6.1.2.1.1.7.0: Object Name: 1.3.6.1.2.1.1.7.0 (iso.3.6.1.2.1.1.7.0) Value (Integer32): 5 1.3.6.1.2.1.1.2: 1.3.6.1.4.1.2.3.4.5 (iso.3.6.1.4.1.2.3.4.5) Object Name: 1.3.6.1.2.1.1.2 (iso.3.6.1.2.1.1.2) Value (OID): 1.3.6.1.4.1.2.3.4.5 (iso.3.6.1.4.1.2.3.4.5) */ func trap1() []byte { return []byte{ 0x30, 0x81, 0x80, 0x02, 0x01, 0x01, 0x04, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0xa7, 0x73, 0x02, 0x04, 0x72, 0x5c, 0xef, 0x42, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x65, 0x30, 0x10, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x43, 0x04, 0x01, 0x1a, 0xef, 0xa5, 0x30, 0x14, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x06, 0x03, 0x01, 0x01, 0x04, 0x01, 0x00, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x30, 0x16, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x04, 0x0a, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x07, 0x00, 0x02, 0x01, 0x05, 0x30, 0x14, 0x06, 0x07, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x02, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x3a, 0x05, 0x00, 0xa1, 0x27, 0x42, 0x0c, 0x46, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x10, 0x4a, 0x7d, 0x34, 0x3a, 0xa5, 0x74, 0xda, 0x38, 0x4d, 0x6c, 0x6c, 0x08, 0x00, 0x45, 0x00, 0x00, 0x38, 0xcc, 0xdb, 0x40, 0x00, 0xff, 0x01, 0x2b, 0x74, 0xc0, 0xa8, 0x01, 0x0a, 0xc0, 0xa8, 0x01, 0x1a, 0x03, 0x03, 0x11, 0x67, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x9f, 0xe6, 0x8f, 0x40, 0x00, 0x40, 0x11, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0x1a, 0xc0, 0xa8, 0x01, 0x0a, 0xaf, 0x78, 0x00, 0xa2, 0x00, 0x8b, 0x0b, 0x3a, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x3a, 0x05, 0x00, 0xca, 0x94, 0x67, 0x0c, 0x01, 0x00, 0x1c, 0x00, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x64, 0x75, 0x6d, 0x70, 0x63, 0x61, 0x70, 0x02, 0x00, 0x08, 0x00, 0x74, 0x3a, 0x05, 0x00, 0xdf, 0xba, 0x27, 0x0c, 0x03, 0x00, 0x08, 0x00, 0x74, 0x3a, 0x05, 0x00, 0x18, 0x94, 0x67, 0x0c, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00} } // Simple Network Management Protocol // msgVersion: snmpv3 (3) // msgGlobalData // msgID: 91040642 // msgMaxSize: 65507 // msgFlags: 04 // msgSecurityModel: USM (3) // msgAuthoritativeEngineID: // msgAuthoritativeEngineBoots: 0 // msgAuthoritativeEngineTime: 0 // msgUserName: // msgAuthenticationParameters: // msgPrivacyParameters: // msgData: plaintext (0) // plaintext func snmpv3HelloRequest() []byte { return []byte{0x30, 0x52, 0x02, 0x01, 0x03, 0x30, 0x11, 0x02, 0x04, 0x05, 0x6d, 0x2b, 0x82, 0x02, 0x03, 0x00, 0xff, 0xe3, 0x04, 0x01, 0x04, 0x02, 0x01, 0x03, 0x04, 0x10, 0x30, 0x0e, 0x04, 0x00, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x30, 0x28, 0x04, 0x00, 0x04, 0x14, 0x66, 0x6f, 0x72, 0x65, 0x69, 0x67, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0xa0, 0x0e, 0x02, 0x04, 0x44, 0xfa, 0x16, 0xe1, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x00} } // msgData: plaintext (0) // plaintext // contextEngineID: 80004fb8054445534b544f502d4a3732533245343ab63bc8 // 1... .... = Engine ID Conformance: RFC3411 (SNMPv3) // Engine Enterprise ID: pysnmp (20408) // Engine ID Format: Octets, administratively assigned (5) // Engine ID Data: 4445534b544f502d4a3732533245343ab63bc8 // contextName: foreignformats/linux // data: report (8) // report // request-id: 1157240545 // error-status: noError (0) // error-index: 0 // variable-bindings: 1 item // 1.3.6.1.6.3.15.1.1.4.0: 21 // Object Name: 1.3.6.1.6.3.15.1.1.4.0 (iso.3.6.1.6.3.15.1.1.4.0) // Value (Counter32): 21 func snmpv3HelloResponse() []byte { return []byte{ 0x30, 0x81, 0x95, 0x02, 0x01, 0x03, 0x30, 0x11, 0x02, 0x04, 0x05, 0x6d, 0x2b, 0x82, 0x02, 0x03, 0x00, 0xff, 0xe3, 0x04, 0x01, 0x00, 0x02, 0x01, 0x03, 0x04, 0x2a, 0x30, 0x28, 0x04, 0x18, 0x80, 0x00, 0x4f, 0xb8, 0x05, 0x44, 0x45, 0x53, 0x4b, 0x54, 0x4f, 0x50, 0x2d, 0x4a, 0x37, 0x32, 0x53, 0x32, 0x45, 0x34, 0x3a, 0xb6, 0x3b, 0xc8, 0x02, 0x01, 0x02, 0x02, 0x03, 0x00, 0xc4, 0x7a, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x30, 0x51, 0x04, 0x18, 0x80, 0x00, 0x4f, 0xb8, 0x05, 0x44, 0x45, 0x53, 0x4b, 0x54, 0x4f, 0x50, 0x2d, 0x4a, 0x37, 0x32, 0x53, 0x32, 0x45, 0x34, 0x3a, 0xb6, 0x3b, 0xc8, 0x04, 0x14, 0x66, 0x6f, 0x72, 0x65, 0x69, 0x67, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x2f, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0xa8, 0x1f, 0x02, 0x04, 0x44, 0xfa, 0x16, 0xe1, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x11, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x06, 0x03, 0x0f, 0x01, 0x01, 0x04, 0x00, 0x41, 0x01, 0x15, } } // dump bytes in a format similar to Wireshark func dumpBytes1(data []byte, msg string, maxlength int) { var buffer bytes.Buffer buffer.WriteString(msg) length := maxlength if len(data) < maxlength { length = len(data) } length *= 2 //One Byte Symobls Two Hex hexStr := hex.EncodeToString(data) for i := 0; length >= i+16; i += 16 { buffer.WriteString("\n") buffer.WriteString(strconv.Itoa(i / 2)) buffer.WriteString("\t") buffer.WriteString(hexStr[i : i+2]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+2 : i+4]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+4 : i+6]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+6 : i+8]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+8 : i+10]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+10 : i+12]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+12 : i+14]) buffer.WriteString(" ") buffer.WriteString(hexStr[i+14 : i+16]) } leftOver := length % 16 if leftOver != 0 { buffer.WriteString("\n") buffer.WriteString(strconv.Itoa((length - leftOver) / 2)) buffer.WriteString("\t") for i := 0; leftOver >= i+2; i += 2 { buffer.WriteString(hexStr[i : i+2]) buffer.WriteString(" ") } } buffer.WriteString("\n") } // dump bytes in one row, up to about screen width. Returns a string // rather than (dumpBytes1) writing to debugging log. func dumpBytes2(desc string, bb []byte, cursor int) string { cursor = cursor - 4 // give some context to dump if cursor < 0 { cursor = 0 } result := desc for i, b := range bb[cursor:] { if i > 30 { // about screen width... break } result += fmt.Sprintf(" %02x", b) } return result } gosnmp-1.24.0/misc_test.go000066400000000000000000000105601362404504000154220ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // +build all misc package gosnmp import ( "bytes" "errors" "reflect" "testing" "github.com/stretchr/testify/assert" ) // Tests in alphabetical order of function being tested // ----------------------------------------------------------------------------- var testsMarshalLength = []struct { length int expected []byte }{ {1, []byte{0x01}}, {129, []byte{0x81, 0x81}}, {256, []byte{0x82, 0x01, 0x00}}, {272, []byte{0x82, 0x01, 0x10}}, {435, []byte{0x82, 0x01, 0xb3}}, } func TestMarshalLength(t *testing.T) { for i, test := range testsMarshalLength { testBytes, err := marshalLength(test.length) if err != nil { t.Errorf("%d: length %d got err %v", i, test.length, err) } if !reflect.DeepEqual(testBytes, test.expected) { t.Errorf("%d: length %d got |%x| expected |%x|", i, test.length, testBytes, test.expected) } } } // ----------------------------------------------------------------------------- var testsPartition = []struct { currentPosition int partitionSize int sliceLength int ok bool }{ {-1, 3, 8, false}, // test out of range {8, 3, 8, false}, // test out of range {0, 3, 8, false}, // test 0-7/3 per doco {1, 3, 8, false}, {2, 3, 8, true}, {3, 3, 8, false}, {4, 3, 8, false}, {5, 3, 8, true}, {6, 3, 8, false}, {7, 3, 8, true}, {-1, 1, 3, false}, // partition size of one {0, 1, 3, true}, {1, 1, 3, true}, {2, 1, 3, true}, {3, 1, 3, false}, } func TestPartition(t *testing.T) { for i, test := range testsPartition { ok := Partition(test.currentPosition, test.partitionSize, test.sliceLength) if ok != test.ok { t.Errorf("#%d: Bad result: %v (expected %v)", i, ok, test.ok) } } } // --------------------------------------------------------------------- var testsSnmpVersionString = []struct { in SnmpVersion out string }{ {Version1, "1"}, {Version2c, "2c"}, {Version3, "3"}, } func TestSnmpVersionString(t *testing.T) { for i, test := range testsSnmpVersionString { result := test.in.String() if result != test.out { t.Errorf("#%d, got %v expected %v", i, result, test.out) } } } // --------------------------------------------------------------------- var testSnmpV3MD5HMAC = []struct { password string engineid string outKey []byte }{ {"maplesyrup", string([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}), []byte{0x52, 0x6f, 0x5e, 0xed, 0x9f, 0xcc, 0xe2, 0x6f, 0x89, 0x64, 0xc2, 0x93, 0x07, 0x87, 0xd8, 0x2b}}, } func TestMD5HMAC(t *testing.T) { for i, test := range testSnmpV3MD5HMAC { result, err := md5HMAC(test.password, test.engineid) assert.NoError(t, err) if !bytes.Equal(result, test.outKey) { t.Errorf("#%d, got %v expected %v", i, result, test.outKey) } } } var testSnmpV3SHAHMAC = []struct { password string engineid string outKey []byte }{ {"maplesyrup", string([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}), []byte{0x66, 0x95, 0xfe, 0xbc, 0x92, 0x88, 0xe3, 0x62, 0x82, 0x23, 0x5f, 0xc7, 0x15, 0x1f, 0x12, 0x84, 0x97, 0xb3, 0x8f, 0x3f}}, } func TestSHAHMAC(t *testing.T) { for i, test := range testSnmpV3SHAHMAC { result, _ := shaHMAC(test.password, test.engineid) if !bytes.Equal(result, test.outKey) { t.Errorf("#%d, got %v expected %v", i, result, test.outKey) } } } // --------------------------------------------------------------------- /* var testMarshalTimeticks = []struct { timeticks uint32 out []byte }{ {1034156, []byte{0x0f, 0xc7, 0xac}}, } func TestMarshalTimeticks(t *testing.T) { for i, test := range testMarshalTimeticks { result, err := marshalTimeticks(test.timeticks) if err != nil { t.Errorf("%d: expected %v got err %v", i, result, err) } if !bytes.Equal(result, test.out) { t.Errorf("#%d, got %v expected %v", i, result, test.out) } } } */ // parseBitString parses an ASN.1 bit string from the given byte slice and returns it. func parseBitString(bytes []byte) (ret BitStringValue, err error) { if len(bytes) == 0 { err = errors.New("zero length BIT STRING") return } paddingBits := int(bytes[0]) if paddingBits > 7 || len(bytes) == 1 && paddingBits > 0 || bytes[len(bytes)-1]&((1<> /etc/snmp/snmpd.conf createUser noAuthNoPrivUser createUser authMD5OnlyUser MD5 testingpass0123456789 createUser authSHAOnlyUser SHA testingpass9876543210 createUser authMD5PrivDESUser MD5 testingpass9876543210 DES createUser authSHAPrivDESUser SHA testingpassabc6543210 DES createUser authMD5PrivAESUser MD5 AEStestingpass9876543210 AES createUser authSHAPrivAESUser SHA AEStestingpassabc6543210 AES rouser noAuthNoPrivUser noauth rouser authMD5OnlyUser auth rouser authSHAOnlyUser auth rouser authMD5PrivDESUser authPriv rouser authSHAPrivDESUser authPriv rouser authMD5PrivAESUser authPriv rouser authSHAPrivAESUser authPriv EOF # enable ipv6 TODO restart fails - need to enable ipv6 on interface; spin up a Linux instance to check this # sed -i -e '/agentAddress/ s/^/#/' -e '/agentAddress/ s/^##//' /etc/snmp/snmpd.conf gosnmp-1.24.0/snmperror_string.go000066400000000000000000000012401362404504000170400ustar00rootroot00000000000000// Code generated by "stringer -type SNMPError"; DO NOT EDIT. package gosnmp import "strconv" const _SNMPError_name = "NoErrorTooBigNoSuchNameBadValueReadOnlyGenErrNoAccessWrongTypeWrongLengthWrongEncodingWrongValueNoCreationInconsistentValueResourceUnavailableCommitFailedUndoFailedAuthorizationErrorNotWritableInconsistentName" var _SNMPError_index = [...]uint8{0, 7, 13, 23, 31, 39, 45, 53, 62, 73, 86, 96, 106, 123, 142, 154, 164, 182, 193, 209} func (i SNMPError) String() string { if i >= SNMPError(len(_SNMPError_index)-1) { return "SNMPError(" + strconv.FormatInt(int64(i), 10) + ")" } return _SNMPError_name[_SNMPError_index[i]:_SNMPError_index[i+1]] } gosnmp-1.24.0/trap.go000066400000000000000000000167371362404504000144120ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package gosnmp import ( "fmt" "log" "net" "os" "strings" "sync" "sync/atomic" "time" ) // // Sending Traps ie GoSNMP acting as an Agent // // SendTrap sends a SNMP Trap (v2c/v3 only) // // pdus[0] can a pdu of Type TimeTicks (with the desired uint32 epoch // time). Otherwise a TimeTicks pdu will be prepended, with time set to // now. This mirrors the behaviour of the Net-SNMP command-line tools. // // SendTrap doesn't wait for a return packet from the NMS (Network // Management Station). // // See also Listen() and examples for creating an NMS. func (x *GoSNMP) SendTrap(trap SnmpTrap) (result *SnmpPacket, err error) { var pdutype PDUType if len(trap.Variables) == 0 { return nil, fmt.Errorf("SendTrap requires at least 1 PDU") } if trap.Variables[0].Type == TimeTicks { // check is uint32 if _, ok := trap.Variables[0].Value.(uint32); !ok { return nil, fmt.Errorf("SendTrap TimeTick must be uint32") } } switch x.Version { case Version2c, Version3: pdutype = SNMPv2Trap if trap.Variables[0].Type != TimeTicks { now := uint32(time.Now().Unix()) timetickPDU := SnmpPDU{"1.3.6.1.2.1.1.3.0", TimeTicks, now, x.Logger} // prepend timetickPDU trap.Variables = append([]SnmpPDU{timetickPDU}, trap.Variables...) } case Version1: pdutype = Trap if len(trap.Enterprise) == 0 { return nil, fmt.Errorf("SendTrap for SNMPV1 requires an Enterprise OID") } if len(trap.AgentAddress) == 0 { return nil, fmt.Errorf("SendTrap for SNMPV1 requires an Agent Address") } default: err = fmt.Errorf("SendTrap doesn't support %s", x.Version) return nil, err } packetOut := x.mkSnmpPacket(pdutype, trap.Variables, 0, 0) if x.Version == Version1 { packetOut.Enterprise = trap.Enterprise packetOut.AgentAddress = trap.AgentAddress packetOut.GenericTrap = trap.GenericTrap packetOut.SpecificTrap = trap.SpecificTrap packetOut.Timestamp = trap.Timestamp } // all sends wait for the return packet, except for SNMPv2Trap // -> wait is false return x.send(packetOut, false) } // // Receiving Traps ie GoSNMP acting as an NMS (Network Management // Station). // // GoSNMP.unmarshal() currently only handles SNMPv2Trap (ie v2c, v3) // // A TrapListener defines parameters for running a SNMP Trap receiver. // nil values will be replaced by default values. type TrapListener struct { sync.Mutex OnNewTrap func(s *SnmpPacket, u *net.UDPAddr) Params *GoSNMP // These unexported fields are for letting test cases // know we are ready. conn *net.UDPConn proto string finish int32 // Atomic flag; set to 1 when closing connection done chan bool listening chan bool } // NewTrapListener returns an initialized TrapListener. func NewTrapListener() *TrapListener { tl := &TrapListener{} tl.finish = 0 tl.done = make(chan bool) // Buffered because one doesn't have to block on it. tl.listening = make(chan bool, 1) return tl } // Listening returns a sentinel channel on which one can block // until the listener is ready to receive requests. func (t *TrapListener) Listening() <-chan bool { t.Lock() defer t.Unlock() return t.listening } // Close terminates the listening on TrapListener socket func (t *TrapListener) Close() { // Prevent concurrent calls to Close if atomic.CompareAndSwapInt32(&t.finish, 0, 1) { if t.conn.LocalAddr().Network() == "udp" { t.conn.Close() } <-t.done } } func (t *TrapListener) listenUDP(addr string) error { // udp udpAddr, err := net.ResolveUDPAddr(t.proto, addr) if err != nil { return err } t.conn, err = net.ListenUDP("udp", udpAddr) if err != nil { return err } defer t.conn.Close() // Mark that we are listening now. t.listening <- true for { switch { case atomic.LoadInt32(&t.finish) == 1: t.done <- true return nil default: var buf [4096]byte rlen, remote, err := t.conn.ReadFromUDP(buf[:]) if err != nil { if atomic.LoadInt32(&t.finish) == 1 { // err most likely comes from reading from a closed connection continue } t.Params.logPrintf("TrapListener: error in read %s\n", err) continue } msg := buf[:rlen] traps := t.Params.UnmarshalTrap(msg) if traps != nil { t.OnNewTrap(traps, remote) } } } } func (t *TrapListener) handleTCPRequest(conn net.Conn) { // Make a buffer to hold incoming data. buf := make([]byte, 4096) // Read the incoming connection into the buffer. reqLen, err := conn.Read(buf) if err != nil { t.Params.logPrintf("TrapListener: error in read %s\n", err) return } //fmt.Printf("TEST: handleTCPRequest:%s, %s", t.proto, conn.RemoteAddr()) msg := buf[:reqLen] traps := t.Params.UnmarshalTrap(msg) if traps != nil { // TODO: lieing for backward compatibility reason - create UDP Address ... not nice r, _ := net.ResolveUDPAddr("", conn.RemoteAddr().String()) t.OnNewTrap(traps, r) } // Close the connection when you're done with it. conn.Close() } func (t *TrapListener) listenTCP(addr string) error { // udp tcpAddr, err := net.ResolveTCPAddr(t.proto, addr) if err != nil { return err } l, err := net.ListenTCP("tcp", tcpAddr) if err != nil { return err } defer l.Close() // Mark that we are listening now. t.listening <- true for { switch { case atomic.LoadInt32(&t.finish) == 1: t.done <- true return nil default: // Listen for an incoming connection. conn, err := l.Accept() fmt.Printf("ACCEPT: %s", conn) if err != nil { fmt.Println("Error accepting: ", err.Error()) os.Exit(1) } // Handle connections in a new goroutine. go t.handleTCPRequest(conn) } } } // Listen listens on the UDP address addr and calls the OnNewTrap // function specified in *TrapListener for every trap received. func (t *TrapListener) Listen(addr string) error { if t.Params == nil { t.Params = Default } t.Params.validateParameters() /* TODO returning an error causes TestSendTrapBasic() (and others) to hang err := t.Params.validateParameters() if err != nil { return err } */ if t.OnNewTrap == nil { t.OnNewTrap = debugTrapHandler } splitted := strings.SplitN(addr, "://", 2) t.proto = "udp" if len(splitted) > 1 { t.proto = splitted[0] addr = splitted[1] } //fmt.Printf("TEST: Adress:%s, %s", t.proto, addr) if t.proto == "tcp" { return t.listenTCP(addr) } else if t.proto == "udp" { return t.listenUDP(addr) } return fmt.Errorf("Not implemented network protocol: %s [use: tcp/udp]", t.proto) } // Default trap handler func debugTrapHandler(s *SnmpPacket, u *net.UDPAddr) { log.Printf("got trapdata from %+v: %+v\n", u, s) } // UnmarshalTrap unpacks the SNMP Trap. func (x *GoSNMP) UnmarshalTrap(trap []byte) (result *SnmpPacket) { result = new(SnmpPacket) if x.SecurityParameters != nil { result.SecurityParameters = x.SecurityParameters.Copy() } cursor, err := x.unmarshalHeader(trap, result) if err != nil { x.logPrintf("UnmarshalTrap: %s\n", err) return nil } if result.Version == Version3 { if result.SecurityModel == UserSecurityModel { err = x.testAuthentication(trap, result) if err != nil { x.logPrintf("UnmarshalTrap v3 auth: %s\n", err) return nil } } trap, cursor, err = x.decryptPacket(trap, cursor, result) if err != nil { x.logPrintf("UnmarshalTrap v3 decrypt: %s\n", err) return nil } } err = x.unmarshalPayload(trap, cursor, result) if err != nil { x.logPrintf("UnmarshalTrap: %s\n", err) return nil } return result } gosnmp-1.24.0/trap.md000066400000000000000000000037611362404504000143760ustar00rootroot00000000000000# setup for working on traps ``` $ sudo aptitude -y install snmp-mibs-downloader snmp snmpd snmp-mibs-downloader ``` In the file `/etc/snmp/snmp.conf` ``` mibs +ALL ``` In the file `/etc/snmp/snmpd.conf` ``` comment out: agentAddress udp:127.0.0.1:161 uncomment: agentAddress udp:161,udp6:[::1]:161 comment out: rocommunity public default -V systemonly uncomment: rocommunity public 10.0.0.0/16 comment out: trapsink localhost public uncomment: trap2sink localhost public ``` Create the file `~/.snmp/snmp.conf` with the contents: ``` # ~ expansion fails persistentDir /home/sonia/.snmp_persist ``` ``` $ sudo /etc/init.d/snmpd restart ``` # test ``` snmptrap -v 2c -c public 192.168.1.10 '' SNMPv2-MIB::system SNMPv2-MIB::sysDescr.0 s "red laptop" SNMPv2-MIB::sysServices.0 i "5" SNMPv2-MIB::sysObjectID o "1.3.6.1.4.1.2.3.4.5" ``` # tshark, wireshark ``` sudo aptitude -y install wireshark tshark sudo dpkg-reconfigure wireshark-common # allow captures sudo usermod -a -G wireshark sonia sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/dumpcap sudo getcap /usr/bin/dumpcap # still 'Couldn't run /usr/bin/dumpcap in child process', so nuke it sudo chmod 777 /usr/bin/dumpcap ``` Logout, login to apply wireshark and tshark permissions In a second terminal, run: ``` tshark -i eth0 -f "port 161" -w trap.pcap ``` # snmptrap and MIBs ``` The TYPE is a single character, one of: i INTEGER INTEGER u UNSIGNED c COUNTER32 s STRING DisplayString x HEX STRING d DECIMAL STRING n NULLOBJ o OBJID OBJECT IDENTIFIER t TIMETICKS a IPADDRESS b BITS ``` # finding MIBs Look in the file `/usr/share/mibs/ietf/SNMPv2-MIB`. Here are some example lines: ``` line:77 sysDescr line:88 sysObjectID line:146 sysServices ``` For a gui MIB browser: https://l3net.wordpress.com/2013/05/12/installing-net-snmp-mibs-on-ubuntu-and-debian/ gosnmp-1.24.0/trap_test.go000066400000000000000000000223101362404504000154310ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // +build all trap package gosnmp import ( "log" "net" "os" //"io/ioutil" "testing" "time" ) const ( trapTestAddress = "127.0.0.1" // TODO this is bad. Listen and Connect expect different address formats // so we need an int version and a string version - they should be the same. trapTestPort = 9162 trapTestPortString = "9162" trapTestOid = ".1.2.1234.4.5" trapTestPayload = "TRAPTEST1234" trapTestEnterpriseOid = ".1.2.1234" trapTestAgentAddress = "127.0.0.1" trapTestGenericTrap = 6 trapTestSpecificTrap = 55 trapTestTimestamp = 300 ) var testsUnmarshalTrap = []struct { in func() []byte out *SnmpPacket }{ {genericV3Trap, &SnmpPacket{ Version: Version3, PDUType: SNMPv2Trap, RequestID: 190378322, MsgFlags: AuthNoPriv, SecurityParameters: &UsmSecurityParameters{ UserName: "myuser", AuthenticationProtocol: MD5, AuthenticationPassphrase: "mypassword", Logger: log.New(os.Stdout, "", 0), }, }, }, } /*func TestUnmarshalTrap(t *testing.T) { Default.Logger = log.New(os.Stdout, "", 0) SANITY: for i, test := range testsUnmarshalTrap { Default.SecurityParameters = test.out.SecurityParameters.Copy() var buf = test.in() var res = Default.unmarshalTrap(buf) if res == nil { t.Errorf("#%d, UnmarshalTrap returned nil", i) continue SANITY } // test enough fields fields to ensure unmarshalling was successful. // full unmarshal testing is performed in TestUnmarshal if res.Version != test.out.Version { t.Errorf("#%d Version result: %v, test: %v", i, res.Version, test.out.Version) } if res.RequestID != test.out.RequestID { t.Errorf("#%d RequestID result: %v, test: %v", i, res.RequestID, test.out.RequestID) } } } */ func genericV3Trap() []byte { return []byte{ 0x30, 0x81, 0xd7, 0x02, 0x01, 0x03, 0x30, 0x11, 0x02, 0x04, 0x62, 0xaf, 0x5a, 0x8e, 0x02, 0x03, 0x00, 0xff, 0xe3, 0x04, 0x01, 0x01, 0x02, 0x01, 0x03, 0x04, 0x33, 0x30, 0x31, 0x04, 0x11, 0x80, 0x00, 0x1f, 0x88, 0x80, 0x77, 0xdf, 0xe4, 0x4f, 0xaa, 0x70, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0f, 0x02, 0x01, 0x00, 0x04, 0x06, 0x6d, 0x79, 0x75, 0x73, 0x65, 0x72, 0x04, 0x0c, 0xd8, 0xb6, 0x9c, 0xb8, 0x22, 0x91, 0xfc, 0x65, 0xb6, 0x84, 0xcb, 0xfe, 0x04, 0x00, 0x30, 0x81, 0x89, 0x04, 0x11, 0x80, 0x00, 0x1f, 0x88, 0x80, 0x77, 0xdf, 0xe4, 0x4f, 0xaa, 0x70, 0x02, 0x58, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0xa7, 0x72, 0x02, 0x04, 0x39, 0x19, 0x9c, 0x61, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00, 0x30, 0x64, 0x30, 0x0f, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x03, 0x00, 0x43, 0x03, 0x15, 0x2f, 0xec, 0x30, 0x14, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x06, 0x03, 0x01, 0x01, 0x04, 0x01, 0x00, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x30, 0x16, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x01, 0x00, 0x04, 0x0a, 0x72, 0x65, 0x64, 0x20, 0x6c, 0x61, 0x70, 0x74, 0x6f, 0x70, 0x30, 0x0d, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x07, 0x00, 0x02, 0x01, 0x05, 0x30, 0x14, 0x06, 0x07, 0x2b, 0x06, 0x01, 0x02, 0x01, 0x01, 0x02, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x02, 0x03, 0x04, 0x05} } func makeTestTrapHandler(t *testing.T, done chan int, version SnmpVersion) func(*SnmpPacket, *net.UDPAddr) { return func(packet *SnmpPacket, addr *net.UDPAddr) { // log.Printf("got trapdata from %s\n", addr.IP) if version == Version1 { if packet.Enterprise != trapTestEnterpriseOid { t.Fatalf("incorrect trap Enterprise OID received, expected %s got %s", trapTestEnterpriseOid, packet.Enterprise) done <- 0 } if packet.AgentAddress != trapTestAgentAddress { t.Fatalf("incorrect trap Agent Address received, expected %s got %s", trapTestAgentAddress, packet.AgentAddress) done <- 0 } if packet.GenericTrap != trapTestGenericTrap { t.Fatalf("incorrect trap Generic Trap identifier received, expected %v got %v", trapTestGenericTrap, packet.GenericTrap) done <- 0 } if packet.SpecificTrap != trapTestSpecificTrap { t.Fatalf("incorrect trap Specific Trap identifier received, expected %v got %v", trapTestSpecificTrap, packet.SpecificTrap) done <- 0 } if packet.Timestamp != trapTestTimestamp { t.Fatalf("incorrect trap Timestamp received, expected %v got %v", trapTestTimestamp, packet.Timestamp) done <- 0 } } for _, v := range packet.Variables { switch v.Type { case OctetString: b := v.Value.([]byte) // log.Printf("OID: %s, string: %x\n", v.Name, b) // Only one OctetString in the payload, so it must be the expected one if v.Name != trapTestOid { t.Fatalf("incorrect trap OID received, expected %s got %s", trapTestOid, v.Name) done <- 0 } if string(b) != trapTestPayload { t.Fatalf("incorrect trap payload received, expected %s got %x", trapTestPayload, b) done <- 0 } default: // log.Printf("trap: %+v\n", v) } } done <- 0 } } // test sending a basic SNMP trap, using our own listener to receive func TestSendTrapBasic(t *testing.T) { done := make(chan int) tl := NewTrapListener() defer tl.Close() tl.OnNewTrap = makeTestTrapHandler(t, done, Version2c) tl.Params = Default // listener goroutine errch := make(chan error) go func() { err := tl.Listen(net.JoinHostPort(trapTestAddress, trapTestPortString)) if err != nil { errch <- err } }() // Wait until the listener is ready. select { case <-tl.Listening(): case err := <-errch: t.Fatalf("error in listen: %v", err) } ts := &GoSNMP{ Target: trapTestAddress, Port: trapTestPort, Community: "public", Version: Version2c, Timeout: time.Duration(2) * time.Second, Retries: 3, MaxOids: MaxOids, } err := ts.Connect() if err != nil { t.Fatalf("Connect() err: %v", err) } defer ts.Conn.Close() pdu := SnmpPDU{ Name: trapTestOid, Type: OctetString, Value: trapTestPayload, } trap := SnmpTrap{ Variables: []SnmpPDU{pdu}, } _, err = ts.SendTrap(trap) if err != nil { t.Fatalf("SendTrap() err: %v", err) } // wait for response from handler select { case <-done: case <-time.After(2 * time.Second): t.Fatal("timed out waiting for trap to be received") } } // test the listener is not blocked if Listening is not used func TestSendTrapWithoutWaitingOnListen(t *testing.T) { done := make(chan int) tl := NewTrapListener() defer tl.Close() tl.OnNewTrap = makeTestTrapHandler(t, done, Version2c) tl.Params = Default errch := make(chan error) listening := make(chan bool) go func() { // Reduce the chance of necessity for a restart. listening <- true err := tl.Listen(net.JoinHostPort(trapTestAddress, trapTestPortString)) if err != nil { errch <- err } }() select { case <-listening: case err := <-errch: t.Fatalf("error in listen: %v", err) } ts := &GoSNMP{ Target: trapTestAddress, Port: trapTestPort, Community: "public", Version: Version2c, Timeout: time.Duration(2) * time.Second, Retries: 3, MaxOids: MaxOids, } err := ts.Connect() if err != nil { t.Fatalf("Connect() err: %v", err) } defer ts.Conn.Close() pdu := SnmpPDU{ Name: trapTestOid, Type: OctetString, Value: trapTestPayload, } trap := SnmpTrap{ Variables: []SnmpPDU{pdu}, } _, err = ts.SendTrap(trap) if err != nil { t.Fatalf("SendTrap() err: %v", err) } // Wait for a response from the handler and restart the SendTrap // if the listener wasn't ready. select { case <-done: case <-time.After(2 * time.Second): _, err = ts.SendTrap(trap) if err != nil { t.Fatalf("restarted SendTrap() err: %v", err) } t.Log("restarted") select { case <-done: case <-time.After(2 * time.Second): t.Fatal("timed out waiting for trap to be received") } } } // test sending a basic SNMP trap, using our own listener to receive func TestSendV1Trap(t *testing.T) { done := make(chan int) tl := NewTrapListener() defer tl.Close() tl.OnNewTrap = makeTestTrapHandler(t, done, Version1) tl.Params = Default // listener goroutine errch := make(chan error) go func() { err := tl.Listen(net.JoinHostPort(trapTestAddress, trapTestPortString)) if err != nil { errch <- err } }() // Wait until the listener is ready. select { case <-tl.Listening(): case err := <-errch: t.Fatalf("error in listen: %v", err) } ts := &GoSNMP{ Target: trapTestAddress, Port: trapTestPort, //Community: "public", Version: Version1, Timeout: time.Duration(2) * time.Second, Retries: 3, MaxOids: MaxOids, } err := ts.Connect() if err != nil { t.Fatalf("Connect() err: %v", err) } defer ts.Conn.Close() pdu := SnmpPDU{ Name: trapTestOid, Type: OctetString, Value: trapTestPayload, } trap := SnmpTrap{ Variables: []SnmpPDU{pdu}, Enterprise: trapTestEnterpriseOid, AgentAddress: trapTestAgentAddress, GenericTrap: trapTestGenericTrap, SpecificTrap: trapTestSpecificTrap, Timestamp: trapTestTimestamp, } _, err = ts.SendTrap(trap) if err != nil { t.Fatalf("SendTrap() err: %v", err) } // wait for response from handler select { case <-done: case <-time.After(2 * time.Second): t.Fatal("timed out waiting for trap to be received") } } gosnmp-1.24.0/v3.go000066400000000000000000000333021362404504000137570ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gosnmp import ( "bytes" "encoding/binary" "fmt" "runtime" ) // SnmpV3MsgFlags contains various message flags to describe Authentication, Privacy, and whether a report PDU must be sent. type SnmpV3MsgFlags uint8 // Possible values of SnmpV3MsgFlags const ( NoAuthNoPriv SnmpV3MsgFlags = 0x0 // No authentication, and no privacy AuthNoPriv SnmpV3MsgFlags = 0x1 // Authentication and no privacy AuthPriv SnmpV3MsgFlags = 0x3 // Authentication and privacy Reportable SnmpV3MsgFlags = 0x4 // Report PDU must be sent. ) // SnmpV3SecurityModel describes the security model used by a SnmpV3 connection type SnmpV3SecurityModel uint8 // UserSecurityModel is the only SnmpV3SecurityModel currently implemented. const ( UserSecurityModel SnmpV3SecurityModel = 3 ) // SnmpV3SecurityParameters is a generic interface type to contain various implementations of SnmpV3SecurityParameters type SnmpV3SecurityParameters interface { Log() Copy() SnmpV3SecurityParameters validate(flags SnmpV3MsgFlags) error init(log Logger) error initPacket(packet *SnmpPacket) error discoveryRequired() *SnmpPacket getDefaultContextEngineID() string setSecurityParameters(in SnmpV3SecurityParameters) error marshal(flags SnmpV3MsgFlags) ([]byte, error) unmarshal(flags SnmpV3MsgFlags, packet []byte, cursor int) (int, error) authenticate(packet []byte) error isAuthentic(packetBytes []byte, packet *SnmpPacket) (bool, error) encryptPacket(scopedPdu []byte) ([]byte, error) decryptPacket(packet []byte, cursor int) ([]byte, error) } func (x *GoSNMP) validateParametersV3() error { // update following code if you implement a new security model if x.SecurityModel != UserSecurityModel { return fmt.Errorf("The SNMPV3 User Security Model is the only SNMPV3 security model currently implemented") } if x.SecurityParameters == nil { return fmt.Errorf("SNMPV3 SecurityParameters must be set") } return x.SecurityParameters.validate(x.MsgFlags) } // authenticate the marshalled result of a snmp version 3 packet func (packet *SnmpPacket) authenticate(msg []byte) ([]byte, error) { defer func() { if e := recover(); e != nil { var buf = make([]byte, 8192) runtime.Stack(buf, true) fmt.Printf("[v3::authenticate]recover: %v. Stack=%v\n", e, string(buf)) } }() if packet.Version != Version3 { return msg, nil } if packet.MsgFlags&AuthNoPriv > 0 { err := packet.SecurityParameters.authenticate(msg) if err != nil { return nil, err } } return msg, nil } func (x *GoSNMP) testAuthentication(packet []byte, result *SnmpPacket) error { if x.Version != Version3 { return fmt.Errorf("testAuthentication called with non Version3 connection") } if x.MsgFlags&AuthNoPriv > 0 { authentic, err := x.SecurityParameters.isAuthentic(packet, result) if err != nil { return err } if !authentic { return fmt.Errorf("Incoming packet is not authentic, discarding") } } return nil } func (x *GoSNMP) initPacket(packetOut *SnmpPacket) error { if x.MsgFlags&AuthPriv > AuthNoPriv { return x.SecurityParameters.initPacket(packetOut) } return nil } // http://tools.ietf.org/html/rfc2574#section-2.2.3 This code does not // check if the last message received was more than 150 seconds ago The // snmpds that this code was tested on emit an 'out of time window' // error with the new time and this code will retransmit when that is // received. func (x *GoSNMP) negotiateInitialSecurityParameters(packetOut *SnmpPacket, wait bool) error { if x.Version != Version3 || packetOut.Version != Version3 { return fmt.Errorf("negotiateInitialSecurityParameters called with non Version3 connection or packet") } if x.SecurityModel != packetOut.SecurityModel { return fmt.Errorf("connection security model does not match security model defined in packet") } if discoveryPacket := packetOut.SecurityParameters.discoveryRequired(); discoveryPacket != nil { result, err := x.sendOneRequest(discoveryPacket, wait) if err != nil { return err } err = x.storeSecurityParameters(result) if err != nil { return err } err = x.updatePktSecurityParameters(packetOut) if err != nil { return err } } return nil } // save the connection security parameters after a request/response func (x *GoSNMP) storeSecurityParameters(result *SnmpPacket) error { if x.Version != Version3 || result.Version != Version3 { return fmt.Errorf("storeParameters called with non Version3 connection or packet") } if x.SecurityModel != result.SecurityModel { return fmt.Errorf("connection security model does not match security model extracted from packet") } if x.ContextEngineID == "" { x.ContextEngineID = result.SecurityParameters.getDefaultContextEngineID() } return x.SecurityParameters.setSecurityParameters(result.SecurityParameters) } // update packet security parameters to match connection security parameters func (x *GoSNMP) updatePktSecurityParameters(packetOut *SnmpPacket) error { if x.Version != Version3 || packetOut.Version != Version3 { return fmt.Errorf("updatePktSecurityParameters called with non Version3 connection or packet") } if x.SecurityModel != packetOut.SecurityModel { return fmt.Errorf("connection security model does not match security model extracted from packet") } err := packetOut.SecurityParameters.setSecurityParameters(x.SecurityParameters) if err != nil { return err } if packetOut.ContextEngineID == "" { packetOut.ContextEngineID = x.ContextEngineID } return nil } func (packet *SnmpPacket) marshalV3(buf *bytes.Buffer) (*bytes.Buffer, error) { emptyBuffer := new(bytes.Buffer) // used when returning errors header, err := packet.marshalV3Header() if err != nil { return emptyBuffer, err } buf.Write([]byte{byte(Sequence), byte(len(header))}) packet.logPrintf("Marshal V3 Header len=%d. Eaten Last 4 Bytes=%v", len(header), header[len(header)-4:]) buf.Write(header) var securityParameters []byte securityParameters, err = packet.SecurityParameters.marshal(packet.MsgFlags) if err != nil { return emptyBuffer, err } packet.logPrintf("Marshal V3 SecurityParameters len=%d. Eaten Last 4 Bytes=%v", len(securityParameters), securityParameters[len(securityParameters)-4:]) buf.Write([]byte{byte(OctetString)}) secParamLen, err := marshalLength(len(securityParameters)) if err != nil { return emptyBuffer, err } buf.Write(secParamLen) buf.Write(securityParameters) scopedPdu, err := packet.marshalV3ScopedPDU() if err != nil { return emptyBuffer, err } buf.Write(scopedPdu) return buf, nil } // marshal a snmp version 3 packet header func (packet *SnmpPacket) marshalV3Header() ([]byte, error) { buf := new(bytes.Buffer) // msg id buf.Write([]byte{byte(Integer), 4}) err := binary.Write(buf, binary.BigEndian, packet.MsgID) if err != nil { return nil, err } oldLen := 0 packet.logPrintf("MarshalV3Header msgID len=%v", buf.Len()-oldLen) oldLen = buf.Len() // maximum response msg size var maxBufSize uint32 = rxBufSize if packet.MsgMaxSize != 0 { maxBufSize = packet.MsgMaxSize } maxmsgsize := marshalUvarInt(maxBufSize) buf.Write([]byte{byte(Integer), byte(len(maxmsgsize))}) buf.Write(maxmsgsize) packet.logPrintf("MarshalV3Header maxmsgsize len=%v", buf.Len()-oldLen) oldLen = buf.Len() // msg flags buf.Write([]byte{byte(OctetString), 1, byte(packet.MsgFlags)}) packet.logPrintf("MarshalV3Header msg flags len=%v", buf.Len()-oldLen) oldLen = buf.Len() // msg security model buf.Write([]byte{byte(Integer), 1, byte(packet.SecurityModel)}) packet.logPrintf("MarshalV3Header msg security model len=%v", buf.Len()-oldLen) oldLen = buf.Len() return buf.Bytes(), nil } // marshal and encrypt (if necessary) a snmp version 3 Scoped PDU func (packet *SnmpPacket) marshalV3ScopedPDU() ([]byte, error) { var b []byte scopedPdu, err := packet.prepareV3ScopedPDU() if err != nil { return nil, err } pduLen, err := marshalLength(len(scopedPdu)) if err != nil { return nil, err } b = append([]byte{byte(Sequence)}, pduLen...) scopedPdu = append(b, scopedPdu...) if packet.MsgFlags&AuthPriv > AuthNoPriv { scopedPdu, err = packet.SecurityParameters.encryptPacket(scopedPdu) if err != nil { return nil, err } } return scopedPdu, nil } // prepare the plain text of a snmp version 3 Scoped PDU func (packet *SnmpPacket) prepareV3ScopedPDU() ([]byte, error) { var buf bytes.Buffer //ContextEngineID idlen, err := marshalLength(len(packet.ContextEngineID)) if err != nil { return nil, err } buf.Write(append([]byte{byte(OctetString)}, idlen...)) buf.WriteString(packet.ContextEngineID) //ContextName namelen, err := marshalLength(len(packet.ContextName)) if err != nil { return nil, err } buf.Write(append([]byte{byte(OctetString)}, namelen...)) buf.WriteString(packet.ContextName) data, err := packet.marshalPDU() if err != nil { return nil, err } buf.Write(data) return buf.Bytes(), nil } func (x *GoSNMP) unmarshalV3Header(packet []byte, cursor int, response *SnmpPacket) (int, error) { if PDUType(packet[cursor]) != Sequence { return 0, fmt.Errorf("invalid SNMPV3 Header") } _, cursorTmp := parseLength(packet[cursor:]) cursor += cursorTmp if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } rawMsgID, count, err := parseRawField(packet[cursor:], "msgID") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } if MsgID, ok := rawMsgID.(int); ok { response.MsgID = uint32(MsgID) x.logPrintf("Parsed message ID %d", MsgID) } rawMsgMaxSize, count, err := parseRawField(packet[cursor:], "msgMaxSize") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 msgMaxSize: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } if MsgMaxSize, ok := rawMsgMaxSize.(int); ok { response.MsgMaxSize = uint32(MsgMaxSize) x.logPrintf("Parsed message max size %d", MsgMaxSize) } rawMsgFlags, count, err := parseRawField(packet[cursor:], "msgFlags") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 msgFlags: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } if MsgFlags, ok := rawMsgFlags.(string); ok { response.MsgFlags = SnmpV3MsgFlags(MsgFlags[0]) x.logPrintf("parsed msg flags %s", MsgFlags) } rawSecModel, count, err := parseRawField(packet[cursor:], "msgSecurityModel") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 msgSecModel: %s", err.Error()) } cursor += count if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } if SecModel, ok := rawSecModel.(int); ok { response.SecurityModel = SnmpV3SecurityModel(SecModel) x.logPrintf("Parsed security model %d", SecModel) } if PDUType(packet[cursor]) != PDUType(OctetString) { return 0, fmt.Errorf("invalid SNMPV3 Security Parameters") } _, cursorTmp = parseLength(packet[cursor:]) cursor += cursorTmp if cursor > len(packet) { return 0, fmt.Errorf("Error parsing SNMPV3 message ID: truncted packet") } if response.SecurityParameters == nil { response.SecurityParameters = &UsmSecurityParameters{Logger: x.Logger} } cursor, err = response.SecurityParameters.unmarshal(response.MsgFlags, packet, cursor) if err != nil { return 0, err } x.logPrintf("Parsed Security Parameters. now offset=%v,", cursor) return cursor, nil } func (x *GoSNMP) decryptPacket(packet []byte, cursor int, response *SnmpPacket) ([]byte, int, error) { var err error var decrypted = false if cursor > len(packet) { return nil, 0, fmt.Errorf("Error parsing SNMPV3: truncated packet") } switch PDUType(packet[cursor]) { case PDUType(OctetString): // pdu is encrypted packet, err = response.SecurityParameters.decryptPacket(packet, cursor) if err != nil { return nil, 0, err } decrypted = true fallthrough case Sequence: // pdu is plaintext or has been decrypted tlength, cursorTmp := parseLength(packet[cursor:]) if decrypted { // truncate padding that might have been included with // the encrypted PDU if cursor+tlength > len(packet) { return nil, 0, fmt.Errorf("Error parsing SNMPV3: truncated packet") } packet = packet[:cursor+tlength] } cursor += cursorTmp if cursor > len(packet) { return nil, 0, fmt.Errorf("Error parsing SNMPV3: truncated packet") } rawContextEngineID, count, err := parseRawField(packet[cursor:], "contextEngineID") if err != nil { return nil, 0, fmt.Errorf("Error parsing SNMPV3 contextEngineID: %s", err.Error()) } cursor += count if cursor > len(packet) { return nil, 0, fmt.Errorf("Error parsing SNMPV3: truncated packet") } if contextEngineID, ok := rawContextEngineID.(string); ok { response.ContextEngineID = contextEngineID x.logPrintf("Parsed contextEngineID %s", contextEngineID) } rawContextName, count, err := parseRawField(packet[cursor:], "contextName") if err != nil { return nil, 0, fmt.Errorf("Error parsing SNMPV3 contextName: %s", err.Error()) } cursor += count if cursor > len(packet) { return nil, 0, fmt.Errorf("Error parsing SNMPV3: truncated packet") } if contextName, ok := rawContextName.(string); ok { response.ContextName = contextName x.logPrintf("Parsed contextName %s", contextName) } default: return nil, 0, fmt.Errorf("error parsing SNMPV3 scoped PDU") } return packet, cursor, nil } gosnmp-1.24.0/v3_usm.go000066400000000000000000000517141362404504000146520ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package gosnmp import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/des" "crypto/md5" crand "crypto/rand" "crypto/sha1" "encoding/binary" "fmt" "hash" "sync" "sync/atomic" ) // SnmpV3AuthProtocol describes the authentication protocol in use by an authenticated SnmpV3 connection. type SnmpV3AuthProtocol uint8 // NoAuth, MD5, and SHA are implemented const ( NoAuth SnmpV3AuthProtocol = 1 MD5 SnmpV3AuthProtocol = 2 SHA SnmpV3AuthProtocol = 3 ) // SnmpV3PrivProtocol is the privacy protocol in use by an private SnmpV3 connection. type SnmpV3PrivProtocol uint8 // NoPriv, DES implemented, AES planned const ( NoPriv SnmpV3PrivProtocol = 1 DES SnmpV3PrivProtocol = 2 AES SnmpV3PrivProtocol = 3 ) // UsmSecurityParameters is an implementation of SnmpV3SecurityParameters for the UserSecurityModel type UsmSecurityParameters struct { // localAESSalt must be 64bit aligned to use with atomic operations. localAESSalt uint64 localDESSalt uint32 AuthoritativeEngineID string AuthoritativeEngineBoots uint32 AuthoritativeEngineTime uint32 UserName string AuthenticationParameters string PrivacyParameters []byte AuthenticationProtocol SnmpV3AuthProtocol PrivacyProtocol SnmpV3PrivProtocol AuthenticationPassphrase string PrivacyPassphrase string SecretKey []byte PrivacyKey []byte Logger Logger } // Log logs security paramater information to the provided GoSNMP Logger func (sp *UsmSecurityParameters) Log() { sp.Logger.Printf("SECURITY PARAMETERS:%+v", sp) } // Copy method for UsmSecurityParameters used to copy a SnmpV3SecurityParameters without knowing it's implementation func (sp *UsmSecurityParameters) Copy() SnmpV3SecurityParameters { return &UsmSecurityParameters{AuthoritativeEngineID: sp.AuthoritativeEngineID, AuthoritativeEngineBoots: sp.AuthoritativeEngineBoots, AuthoritativeEngineTime: sp.AuthoritativeEngineTime, UserName: sp.UserName, AuthenticationParameters: sp.AuthenticationParameters, PrivacyParameters: sp.PrivacyParameters, AuthenticationProtocol: sp.AuthenticationProtocol, PrivacyProtocol: sp.PrivacyProtocol, AuthenticationPassphrase: sp.AuthenticationPassphrase, PrivacyPassphrase: sp.PrivacyPassphrase, SecretKey: sp.SecretKey, PrivacyKey: sp.PrivacyKey, localDESSalt: sp.localDESSalt, localAESSalt: sp.localAESSalt, Logger: sp.Logger, } } func (sp *UsmSecurityParameters) getDefaultContextEngineID() string { return sp.AuthoritativeEngineID } func (sp *UsmSecurityParameters) setSecurityParameters(in SnmpV3SecurityParameters) error { var insp *UsmSecurityParameters var err error if insp, err = castUsmSecParams(in); err != nil { return err } if sp.AuthoritativeEngineID != insp.AuthoritativeEngineID { sp.AuthoritativeEngineID = insp.AuthoritativeEngineID if sp.AuthenticationProtocol > NoAuth && len(sp.SecretKey) == 0 { sp.SecretKey, err = genlocalkey(sp.AuthenticationProtocol, sp.AuthenticationPassphrase, sp.AuthoritativeEngineID) if err != nil { return err } } if sp.PrivacyProtocol > NoPriv && len(sp.PrivacyKey) == 0 { sp.PrivacyKey, err = genlocalkey(sp.AuthenticationProtocol, sp.PrivacyPassphrase, sp.AuthoritativeEngineID) if err != nil { return err } } } sp.AuthoritativeEngineBoots = insp.AuthoritativeEngineBoots sp.AuthoritativeEngineTime = insp.AuthoritativeEngineTime return nil } func (sp *UsmSecurityParameters) validate(flags SnmpV3MsgFlags) error { securityLevel := flags & AuthPriv // isolate flags that determine security level switch securityLevel { case AuthPriv: if sp.PrivacyProtocol <= NoPriv { return fmt.Errorf("SecurityParameters.PrivacyProtocol is required") } fallthrough case AuthNoPriv: if sp.AuthenticationProtocol <= NoAuth { return fmt.Errorf("SecurityParameters.AuthenticationProtocol is required") } fallthrough case NoAuthNoPriv: if sp.UserName == "" { return fmt.Errorf("SecurityParameters.UserName is required") } default: return fmt.Errorf("MsgFlags must be populated with an appropriate security level") } if sp.PrivacyProtocol > NoPriv && len(sp.PrivacyKey) == 0 { if sp.PrivacyPassphrase == "" { return fmt.Errorf("securityParameters.PrivacyPassphrase is required when a privacy protocol is specified") } } if sp.AuthenticationProtocol > NoAuth && len(sp.SecretKey) == 0 { if sp.AuthenticationPassphrase == "" { return fmt.Errorf("securityParameters.AuthenticationPassphrase is required when an authentication protocol is specified") } } return nil } func (sp *UsmSecurityParameters) init(log Logger) error { var err error sp.Logger = log switch sp.PrivacyProtocol { case AES: salt := make([]byte, 8) _, err = crand.Read(salt) if err != nil { return fmt.Errorf("error creating a cryptographically secure salt: %s", err.Error()) } sp.localAESSalt = binary.BigEndian.Uint64(salt) case DES: salt := make([]byte, 4) _, err = crand.Read(salt) if err != nil { return fmt.Errorf("error creating a cryptographically secure salt: %s", err.Error()) } sp.localDESSalt = binary.BigEndian.Uint32(salt) } return nil } func castUsmSecParams(secParams SnmpV3SecurityParameters) (*UsmSecurityParameters, error) { s, ok := secParams.(*UsmSecurityParameters) if !ok || s == nil { return nil, fmt.Errorf("SecurityParameters is not of type *UsmSecurityParameters") } return s, nil } var ( passwordKeyHashCache = make(map[string][]byte) passwordKeyHashMutex sync.RWMutex ) // Common passwordToKey algorithm, "caches" the result to avoid extra computation each reuse func cachedPasswordToKey(hash hash.Hash, hashType string, password string) ([]byte, error) { cacheKey := hashType + ":" + password passwordKeyHashMutex.RLock() value := passwordKeyHashCache[cacheKey] passwordKeyHashMutex.RUnlock() if value != nil { return value, nil } var pi int // password index for i := 0; i < 1048576; i += 64 { var chunk []byte for e := 0; e < 64; e++ { chunk = append(chunk, password[pi%len(password)]) pi++ } if _, err := hash.Write(chunk); err != nil { return []byte{}, err } } hashed := hash.Sum(nil) passwordKeyHashMutex.Lock() passwordKeyHashCache[cacheKey] = hashed passwordKeyHashMutex.Unlock() return hashed, nil } // MD5 HMAC key calculation algorithm func md5HMAC(password string, engineID string) ([]byte, error) { compressed, err := cachedPasswordToKey(md5.New(), "MD5", password) if err != nil { return []byte{}, nil } local := md5.New() _, err = local.Write(compressed) if err != nil { return []byte{}, err } _, err = local.Write([]byte(engineID)) if err != nil { return []byte{}, err } _, err = local.Write(compressed) if err != nil { return []byte{}, err } final := local.Sum(nil) return final, nil } // SHA HMAC key calculation algorithm func shaHMAC(password string, engineID string) ([]byte, error) { hashed, err := cachedPasswordToKey(sha1.New(), "SHA1", password) if err != nil { return []byte{}, nil } local := sha1.New() _, err = local.Write(hashed) if err != nil { return []byte{}, err } _, err = local.Write([]byte(engineID)) if err != nil { return []byte{}, err } _, err = local.Write(hashed) if err != nil { return []byte{}, err } final := local.Sum(nil) return final, nil } func genlocalkey(authProtocol SnmpV3AuthProtocol, passphrase string, engineID string) ([]byte, error) { var secretKey []byte var err error switch authProtocol { default: secretKey, err = md5HMAC(passphrase, engineID) if err != nil { return []byte{}, err } case SHA: secretKey, err = shaHMAC(passphrase, engineID) if err != nil { return []byte{}, err } } return secretKey, nil } // http://tools.ietf.org/html/rfc2574#section-8.1.1.1 // localDESSalt needs to be incremented on every packet. func (sp *UsmSecurityParameters) usmAllocateNewSalt() (interface{}, error) { var newSalt interface{} switch sp.PrivacyProtocol { case AES: newSalt = atomic.AddUint64(&(sp.localAESSalt), 1) default: newSalt = atomic.AddUint32(&(sp.localDESSalt), 1) } return newSalt, nil } func (sp *UsmSecurityParameters) usmSetSalt(newSalt interface{}) error { switch sp.PrivacyProtocol { case AES: aesSalt, ok := newSalt.(uint64) if !ok { return fmt.Errorf("salt provided to usmSetSalt is not the correct type for the AES privacy protocol") } var salt = make([]byte, 8) binary.BigEndian.PutUint64(salt, aesSalt) sp.PrivacyParameters = salt default: desSalt, ok := newSalt.(uint32) if !ok { return fmt.Errorf("salt provided to usmSetSalt is not the correct type for the DES privacy protocol") } var salt = make([]byte, 8) binary.BigEndian.PutUint32(salt, sp.AuthoritativeEngineBoots) binary.BigEndian.PutUint32(salt[4:], desSalt) sp.PrivacyParameters = salt } return nil } func (sp *UsmSecurityParameters) initPacket(packet *SnmpPacket) error { // http://tools.ietf.org/html/rfc2574#section-8.1.1.1 // localDESSalt needs to be incremented on every packet. newSalt, err := sp.usmAllocateNewSalt() if err != nil { return err } if packet.MsgFlags&AuthPriv > AuthNoPriv { var s *UsmSecurityParameters if s, err = castUsmSecParams(packet.SecurityParameters); err != nil { return err } return s.usmSetSalt(newSalt) } return nil } func (sp *UsmSecurityParameters) discoveryRequired() *SnmpPacket { if sp.AuthoritativeEngineID == "" { var emptyPdus []SnmpPDU // send blank packet to discover authoriative engine ID/boots/time blankPacket := &SnmpPacket{ Version: Version3, MsgFlags: Reportable | NoAuthNoPriv, SecurityModel: UserSecurityModel, SecurityParameters: &UsmSecurityParameters{Logger: sp.Logger}, PDUType: GetRequest, Logger: sp.Logger, Variables: emptyPdus, } return blankPacket } return nil } func usmFindAuthParamStart(packet []byte) (uint32, error) { idx := bytes.Index(packet, []byte{byte(OctetString), 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) if idx < 0 { return 0, fmt.Errorf("Unable to locate the position in packet to write authentication key") } return uint32(idx + 2), nil } func (sp *UsmSecurityParameters) authenticate(packet []byte) error { var extkey [64]byte var err error copy(extkey[:], sp.SecretKey) var k1, k2 [64]byte for i := 0; i < 64; i++ { k1[i] = extkey[i] ^ 0x36 k2[i] = extkey[i] ^ 0x5c } var h, h2 hash.Hash switch sp.AuthenticationProtocol { default: h = md5.New() h2 = md5.New() case SHA: h = sha1.New() h2 = sha1.New() } _, err = h.Write(k1[:]) if err != nil { return err } _, err = h.Write(packet) if err != nil { return err } d1 := h.Sum(nil) _, err = h2.Write(k2[:]) if err != nil { return err } _, err = h2.Write(d1) if err != nil { return err } authParamStart, err := usmFindAuthParamStart(packet) if err != nil { return nil } copy(packet[authParamStart:authParamStart+12], h2.Sum(nil)[:12]) return nil } // determine whether a message is authentic func (sp *UsmSecurityParameters) isAuthentic(packetBytes []byte, packet *SnmpPacket) (bool, error) { var packetSecParams *UsmSecurityParameters var err error if packetSecParams, err = castUsmSecParams(packet.SecurityParameters); err != nil { return false, err } // TODO: investigate call chain to determine if this is really the best spot for this var extkey [64]byte copy(extkey[:], packetSecParams.SecretKey) var k1, k2 [64]byte for i := 0; i < 64; i++ { k1[i] = extkey[i] ^ 0x36 k2[i] = extkey[i] ^ 0x5c } var h, h2 hash.Hash switch sp.AuthenticationProtocol { default: h = md5.New() h2 = md5.New() case SHA: h = sha1.New() h2 = sha1.New() } _, err = h.Write(k1[:]) if err != nil { return false, err } _, err = h.Write(packetBytes) if err != nil { return false, err } d1 := h.Sum(nil) _, err = h2.Write(k2[:]) if err != nil { return false, err } _, err = h2.Write(d1) if err != nil { return false, err } result := h2.Sum(nil)[:12] for k, v := range []byte(packetSecParams.AuthenticationParameters) { if result[k] != v { return false, nil } } return true, nil } func (sp *UsmSecurityParameters) encryptPacket(scopedPdu []byte) ([]byte, error) { var b []byte switch sp.PrivacyProtocol { case AES: var iv [16]byte binary.BigEndian.PutUint32(iv[:], sp.AuthoritativeEngineBoots) binary.BigEndian.PutUint32(iv[4:], sp.AuthoritativeEngineTime) copy(iv[8:], sp.PrivacyParameters) block, err := aes.NewCipher(sp.PrivacyKey[:16]) if err != nil { return nil, err } stream := cipher.NewCFBEncrypter(block, iv[:]) ciphertext := make([]byte, len(scopedPdu)) stream.XORKeyStream(ciphertext, scopedPdu) pduLen, err := marshalLength(len(ciphertext)) if err != nil { return nil, err } b = append([]byte{byte(OctetString)}, pduLen...) scopedPdu = append(b, ciphertext...) default: preiv := sp.PrivacyKey[8:] var iv [8]byte for i := 0; i < len(iv); i++ { iv[i] = preiv[i] ^ sp.PrivacyParameters[i] } block, err := des.NewCipher(sp.PrivacyKey[:8]) if err != nil { return nil, err } mode := cipher.NewCBCEncrypter(block, iv[:]) pad := make([]byte, des.BlockSize-len(scopedPdu)%des.BlockSize) scopedPdu = append(scopedPdu, pad...) ciphertext := make([]byte, len(scopedPdu)) mode.CryptBlocks(ciphertext, scopedPdu) pduLen, err := marshalLength(len(ciphertext)) if err != nil { return nil, err } b = append([]byte{byte(OctetString)}, pduLen...) scopedPdu = append(b, ciphertext...) } return scopedPdu, nil } func (sp *UsmSecurityParameters) decryptPacket(packet []byte, cursor int) ([]byte, error) { _, cursorTmp := parseLength(packet[cursor:]) cursorTmp += cursor if cursorTmp > len(packet) { return nil, fmt.Errorf("error decrypting ScopedPDU: truncated packet") } switch sp.PrivacyProtocol { case AES: var iv [16]byte binary.BigEndian.PutUint32(iv[:], sp.AuthoritativeEngineBoots) binary.BigEndian.PutUint32(iv[4:], sp.AuthoritativeEngineTime) copy(iv[8:], sp.PrivacyParameters) block, err := aes.NewCipher(sp.PrivacyKey[:16]) if err != nil { return nil, err } stream := cipher.NewCFBDecrypter(block, iv[:]) plaintext := make([]byte, len(packet[cursorTmp:])) stream.XORKeyStream(plaintext, packet[cursorTmp:]) copy(packet[cursor:], plaintext) packet = packet[:cursor+len(plaintext)] default: if len(packet[cursorTmp:])%des.BlockSize != 0 { return nil, fmt.Errorf("error decrypting ScopedPDU: not multiple of des block size") } preiv := sp.PrivacyKey[8:] var iv [8]byte for i := 0; i < len(iv); i++ { iv[i] = preiv[i] ^ sp.PrivacyParameters[i] } block, err := des.NewCipher(sp.PrivacyKey[:8]) if err != nil { return nil, err } mode := cipher.NewCBCDecrypter(block, iv[:]) plaintext := make([]byte, len(packet[cursorTmp:])) mode.CryptBlocks(plaintext, packet[cursorTmp:]) copy(packet[cursor:], plaintext) // truncate packet to remove extra space caused by the // octetstring/length header that was just replaced packet = packet[:cursor+len(plaintext)] } return packet, nil } // marshal a snmp version 3 security parameters field for the User Security Model func (sp *UsmSecurityParameters) marshal(flags SnmpV3MsgFlags) ([]byte, error) { var buf bytes.Buffer var err error // msgAuthoritativeEngineID buf.Write([]byte{byte(OctetString), byte(len(sp.AuthoritativeEngineID))}) buf.WriteString(sp.AuthoritativeEngineID) // msgAuthoritativeEngineBoots msgAuthoritativeEngineBoots := marshalUvarInt(sp.AuthoritativeEngineBoots) buf.Write([]byte{byte(Integer), byte(len(msgAuthoritativeEngineBoots))}) buf.Write(msgAuthoritativeEngineBoots) // msgAuthoritativeEngineTime msgAuthoritativeEngineTime := marshalUvarInt(sp.AuthoritativeEngineTime) buf.Write([]byte{byte(Integer), byte(len(msgAuthoritativeEngineTime))}) buf.Write(msgAuthoritativeEngineTime) // msgUserName buf.Write([]byte{byte(OctetString), byte(len(sp.UserName))}) buf.WriteString(sp.UserName) // msgAuthenticationParameters if flags&AuthNoPriv > 0 { if len(sp.AuthenticationParameters) == 0 { buf.Write([]byte{byte(OctetString), 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) } else { authlen, err := marshalLength(len(sp.AuthenticationParameters)) if err != nil { return nil, err } buf.Write([]byte{byte(OctetString)}) buf.Write(authlen) buf.Write([]byte(sp.AuthenticationParameters)) } } else { buf.Write([]byte{byte(OctetString), 0}) } // msgPrivacyParameters if flags&AuthPriv > AuthNoPriv { privlen, err := marshalLength(len(sp.PrivacyParameters)) if err != nil { return nil, err } buf.Write([]byte{byte(OctetString)}) buf.Write(privlen) buf.Write(sp.PrivacyParameters) } else { buf.Write([]byte{byte(OctetString), 0}) } // wrap security parameters in a sequence paramLen, err := marshalLength(buf.Len()) if err != nil { return nil, err } tmpseq := append([]byte{byte(Sequence)}, paramLen...) tmpseq = append(tmpseq, buf.Bytes()...) return tmpseq, nil } func (sp *UsmSecurityParameters) unmarshal(flags SnmpV3MsgFlags, packet []byte, cursor int) (int, error) { var err error if PDUType(packet[cursor]) != Sequence { return 0, fmt.Errorf("error parsing SNMPV3 User Security Model parameters") } _, cursorTmp := parseLength(packet[cursor:]) cursor += cursorTmp if cursorTmp > len(packet) { return 0, fmt.Errorf("error parsing SNMPV3 User Security Model parameters: truncated packet") } rawMsgAuthoritativeEngineID, count, err := parseRawField(packet[cursor:], "msgAuthoritativeEngineID") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgAuthoritativeEngineID: %s", err.Error()) } cursor += count if AuthoritativeEngineID, ok := rawMsgAuthoritativeEngineID.(string); ok { if sp.AuthoritativeEngineID != AuthoritativeEngineID { sp.AuthoritativeEngineID = AuthoritativeEngineID sp.Logger.Printf("Parsed authoritativeEngineID %s", AuthoritativeEngineID) if sp.AuthenticationProtocol > NoAuth && len(sp.SecretKey) == 0 { sp.SecretKey, err = genlocalkey(sp.AuthenticationProtocol, sp.AuthenticationPassphrase, sp.AuthoritativeEngineID) if err != nil { return 0, err } } if sp.PrivacyProtocol > NoPriv && len(sp.PrivacyKey) == 0 { sp.PrivacyKey, err = genlocalkey(sp.AuthenticationProtocol, sp.PrivacyPassphrase, sp.AuthoritativeEngineID) if err != nil { return 0, err } } } } rawMsgAuthoritativeEngineBoots, count, err := parseRawField(packet[cursor:], "msgAuthoritativeEngineBoots") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgAuthoritativeEngineBoots: %s", err.Error()) } cursor += count if AuthoritativeEngineBoots, ok := rawMsgAuthoritativeEngineBoots.(int); ok { sp.AuthoritativeEngineBoots = uint32(AuthoritativeEngineBoots) sp.Logger.Printf("Parsed authoritativeEngineBoots %d", AuthoritativeEngineBoots) } rawMsgAuthoritativeEngineTime, count, err := parseRawField(packet[cursor:], "msgAuthoritativeEngineTime") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgAuthoritativeEngineTime: %s", err.Error()) } cursor += count if AuthoritativeEngineTime, ok := rawMsgAuthoritativeEngineTime.(int); ok { sp.AuthoritativeEngineTime = uint32(AuthoritativeEngineTime) sp.Logger.Printf("Parsed authoritativeEngineTime %d", AuthoritativeEngineTime) } rawMsgUserName, count, err := parseRawField(packet[cursor:], "msgUserName") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgUserName: %s", err.Error()) } cursor += count if msgUserName, ok := rawMsgUserName.(string); ok { sp.UserName = msgUserName sp.Logger.Printf("Parsed userName %s", msgUserName) } rawMsgAuthParameters, count, err := parseRawField(packet[cursor:], "msgAuthenticationParameters") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgAuthenticationParameters: %s", err.Error()) } if msgAuthenticationParameters, ok := rawMsgAuthParameters.(string); ok { sp.AuthenticationParameters = msgAuthenticationParameters sp.Logger.Printf("Parsed authenticationParameters %s", msgAuthenticationParameters) } // blank msgAuthenticationParameters to prepare for authentication check later if flags&AuthNoPriv > 0 { blank := make([]byte, 12) copy(packet[cursor+2:cursor+14], blank) } cursor += count rawMsgPrivacyParameters, count, err := parseRawField(packet[cursor:], "msgPrivacyParameters") if err != nil { return 0, fmt.Errorf("Error parsing SNMPV3 User Security Model msgPrivacyParameters: %s", err.Error()) } cursor += count if msgPrivacyParameters, ok := rawMsgPrivacyParameters.(string); ok { sp.PrivacyParameters = []byte(msgPrivacyParameters) sp.Logger.Printf("Parsed privacyParameters %s", msgPrivacyParameters) } return cursor, nil } gosnmp-1.24.0/walk.go000066400000000000000000000057611362404504000143750ustar00rootroot00000000000000// Copyright 2012-2020 The GoSNMP Authors. All rights reserved. Use of this // source code is governed by a BSD-style license that can be found in the // LICENSE file. package gosnmp import ( "fmt" "strings" ) func (x *GoSNMP) walk(getRequestType PDUType, rootOid string, walkFn WalkFunc) error { if rootOid == "" || rootOid == "." { rootOid = baseOid } if !strings.HasPrefix(rootOid, ".") { rootOid = string(".") + rootOid } oid := rootOid requests := 0 maxReps := x.MaxRepetitions if maxReps == 0 { maxReps = defaultMaxRepetitions } // AppOpt 'c: do not check returned OIDs are increasing' checkIncreasing := true if x.AppOpts != nil { if _, ok := x.AppOpts["c"]; ok { if getRequestType == GetBulkRequest || getRequestType == GetNextRequest { checkIncreasing = false } } } RequestLoop: for { requests++ var response *SnmpPacket var err error switch getRequestType { case GetBulkRequest: response, err = x.GetBulk([]string{oid}, uint8(x.NonRepeaters), uint8(maxReps)) case GetNextRequest: response, err = x.GetNext([]string{oid}) case GetRequest: response, err = x.Get([]string{oid}) default: response, err = nil, fmt.Errorf("Unsupported request type: %d", getRequestType) } if err != nil { return err } if len(response.Variables) == 0 { break RequestLoop } if response.Error == NoSuchName { x.Logger.Print("Walk terminated with NoSuchName") break RequestLoop } for i, pdu := range response.Variables { if pdu.Type == EndOfMibView || pdu.Type == NoSuchObject || pdu.Type == NoSuchInstance { x.Logger.Printf("BulkWalk terminated with type 0x%x", pdu.Type) break RequestLoop } if !strings.HasPrefix(pdu.Name, rootOid+".") { // Not in the requested root range. // if this is the first request, and the first variable in that request // and this condition is triggered - the first result is out of range // need to perform a regular get request // this request has been too narrowly defined to be found with a getNext // Issue #78 #93 if requests == 1 && i == 0 { getRequestType = GetRequest continue RequestLoop } else if pdu.Name == rootOid && pdu.Type != NoSuchInstance { // Call walk function if the pdu instance is found // considering that the rootOid is a leafOid if err := walkFn(pdu); err != nil { return err } } break RequestLoop } if checkIncreasing && pdu.Name == oid { return fmt.Errorf("OID not increasing: %s", pdu.Name) } // Report our pdu if err := walkFn(pdu); err != nil { return err } } // Save last oid for next request oid = response.Variables[len(response.Variables)-1].Name } x.Logger.Printf("BulkWalk completed in %d requests", requests) return nil } func (x *GoSNMP) walkAll(getRequestType PDUType, rootOid string) (results []SnmpPDU, err error) { err = x.walk(getRequestType, rootOid, func(dataUnit SnmpPDU) error { results = append(results, dataUnit) return nil }) return results, err }