bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/snmp/snmp.go (about) 1 // Package snmp provides an implementation of the SNMP specification. 2 package snmp 3 4 import ( 5 "fmt" 6 "math/rand" 7 "net" 8 "time" 9 10 "bosun.org/snmp/asn1" 11 ) 12 13 //Timeout is the number of seconds to use for conn.SetReadDeadline 14 var Timeout = 30 15 16 // reserved binding values. 17 var ( 18 null = asn1.RawValue{Class: 0, Tag: 5} 19 noSuchObject = asn1.RawValue{Class: 2, Tag: 0} 20 noSuchInstance = asn1.RawValue{Class: 2, Tag: 1} 21 endOfMibView = asn1.RawValue{Class: 2, Tag: 2} 22 ) 23 24 // binding represents an assignment to a variable, a.k.a. managed object. 25 type binding struct { 26 Name asn1.ObjectIdentifier 27 Value asn1.RawValue 28 } 29 30 // unmarshal stores in v the value part of binding b. 31 func (b *binding) unmarshal(v interface{}) error { 32 convertClass(&b.Value) 33 _, err := asn1.Unmarshal(b.Value.FullBytes, v) 34 if err != nil { 35 return err 36 } 37 v = convertType(v) 38 return nil 39 } 40 41 // convertClass converts the encoding of values in SNMP response from 42 // "custom" class to the corresponding "universal" class, thus enabling 43 // use of the asn1 parser from the encoding/asn1 package. 44 func convertClass(v *asn1.RawValue) { 45 if v.Class != 1 { 46 // Not a custom type. 47 return 48 } 49 switch v.Tag { 50 case 0, 4: 51 // IpAddress ::= [APPLICATION 0] IMPLICIT OCTET STRING (SIZE (4)) 52 // Opaque ::= [APPLICATION 4] IMPLICIT OCTET STRING 53 v.FullBytes[0] = 0x04 54 v.Class = 0 55 v.Tag = 4 56 case 1, 2, 3, 6: 57 // Counter32 ::= [APPLICATION 1] IMPLICIT INTEGER (0..4294967295) 58 // Unsigned32 ::= [APPLICATION 2] IMPLICIT INTEGER (0..4294967295) 59 // TimeTicks ::= [APPLICATION 3] IMPLICIT INTEGER (0..4294967295) 60 // Counter64 ::= [APPLICATION 6] IMPLICIT INTEGER (0..18446744073709551615) 61 v.FullBytes[0] = 0x02 62 v.Class = 0 63 v.Tag = 2 64 } 65 } 66 67 // convertType converts value in SNMP response to a Go type that is 68 // easier to manipulate. 69 func convertType(v interface{}) interface{} { 70 switch v := v.(type) { 71 case []byte: 72 s, ok := toString(v) 73 if !ok { 74 return v 75 } 76 return s 77 default: 78 return v 79 } 80 } 81 82 // less checks if a precedes b in the MIB tree. 83 func (a binding) less(b binding) bool { 84 switch { 85 case len(a.Name) < len(b.Name): 86 for i := 0; i < len(a.Name); i++ { 87 switch { 88 case a.Name[i] < b.Name[i]: 89 return true 90 case a.Name[i] == b.Name[i]: 91 continue 92 case a.Name[i] > b.Name[i]: 93 return false 94 } 95 } 96 return true 97 98 case len(a.Name) == len(b.Name): 99 for i := 0; i < len(a.Name); i++ { 100 switch { 101 case a.Name[i] < b.Name[i]: 102 return true 103 case a.Name[i] == b.Name[i]: 104 continue 105 case a.Name[i] > b.Name[i]: 106 return false 107 } 108 } 109 // Identical, so not less. 110 return false 111 112 case len(a.Name) > len(b.Name): 113 for i := 0; i < len(b.Name); i++ { 114 switch { 115 case a.Name[i] < b.Name[i]: 116 return true 117 case a.Name[i] == b.Name[i]: 118 continue 119 case a.Name[i] > b.Name[i]: 120 return false 121 } 122 } 123 return false 124 125 } 126 panic("unreached") 127 } 128 129 // request represents an SNMP request to be sent over a Transport. 130 type request struct { 131 ID int32 132 Type string // "Get", "GetNext", "GetBulk" 133 Bindings []binding 134 NonRepeaters int 135 MaxRepetitions int 136 } 137 138 // response represents the response from an SNMP request. 139 type response struct { 140 ID int32 141 ErrorStatus int 142 ErrorIndex int 143 Bindings []binding 144 } 145 146 // SNMP performs SNMPv2 requests as defined by RFC 3416. 147 type SNMP struct { 148 // Community is the SNMP community. 149 Community string 150 // Addr is the UDP address of the SNMP host. 151 Addr *net.UDPAddr 152 } 153 154 // New creates a new SNMP which connects to host with specified community. 155 func New(host, community string) (*SNMP, error) { 156 hostport := host 157 if _, _, err := net.SplitHostPort(hostport); err != nil { 158 hostport = host + ":161" 159 } 160 addr, err := net.ResolveUDPAddr("udp", hostport) 161 if err != nil { 162 return nil, err 163 } 164 return &SNMP{ 165 Community: community, 166 Addr: addr, 167 }, nil 168 } 169 170 func (s *SNMP) do(req *request) (*response, error) { 171 for i := range req.Bindings { 172 req.Bindings[i].Value = null 173 } 174 var buf []byte 175 var err error 176 switch req.Type { 177 case "Get": 178 var p struct { 179 Version int 180 Community []byte 181 Data struct { 182 RequestID int32 183 ErrorStatus int 184 ErrorIndex int 185 Bindings []binding 186 } `asn1:"application,tag:0"` 187 } 188 p.Version = 1 189 p.Community = []byte(s.Community) 190 p.Data.RequestID = req.ID 191 p.Data.Bindings = req.Bindings 192 buf, err = asn1.Marshal(p) 193 case "GetNext": 194 var p struct { 195 Version int 196 Community []byte 197 Data struct { 198 RequestID int32 199 ErrorStatus int 200 ErrorIndex int 201 Bindings []binding 202 } `asn1:"application,tag:1"` 203 } 204 p.Version = 1 205 p.Community = []byte(s.Community) 206 p.Data.RequestID = req.ID 207 p.Data.Bindings = req.Bindings 208 buf, err = asn1.Marshal(p) 209 case "GetBulk": 210 var p struct { 211 Version int 212 Community []byte 213 Data struct { 214 RequestID int32 215 NonRepeaters int 216 MaxRepetitions int 217 Bindings []binding 218 } `asn1:"application,tag:5"` 219 } 220 p.Version = 1 221 p.Community = []byte(s.Community) 222 p.Data.RequestID = req.ID 223 p.Data.NonRepeaters = 0 224 p.Data.MaxRepetitions = req.MaxRepetitions 225 p.Data.Bindings = req.Bindings 226 buf, err = asn1.Marshal(p) 227 default: 228 panic("unsupported type " + req.Type) 229 } 230 if err != nil { 231 return nil, err 232 } 233 conn, err := net.DialUDP("udp", nil, s.Addr) 234 if err != nil { 235 return nil, err 236 } 237 defer conn.Close() 238 if _, err := conn.Write(buf); err != nil { 239 return nil, err 240 } 241 buf = make([]byte, 10000, 10000) 242 if err := conn.SetReadDeadline(time.Now().Add(time.Duration(Timeout) * time.Second)); err != nil { 243 return nil, err 244 } 245 n, err := conn.Read(buf) 246 if err != nil { 247 return nil, err 248 } 249 if n == len(buf) { 250 return nil, fmt.Errorf("response too big") 251 } 252 var p struct { 253 Version int 254 Community []byte 255 Data struct { 256 RequestID int32 257 ErrorStatus int 258 ErrorIndex int 259 Bindings []binding 260 } `asn1:"tag:2"` 261 } 262 if _, err = asn1.Unmarshal(buf[:n], &p); err != nil { 263 return nil, err 264 } 265 resp := &response{p.Data.RequestID, p.Data.ErrorStatus, p.Data.ErrorIndex, p.Data.Bindings} 266 return resp, nil 267 } 268 269 // check checks the response PDU for basic correctness. 270 // Valid with all PDU types. 271 func check(resp *response, req *request) (err error) { 272 defer func() { 273 if err != nil { 274 err = fmt.Errorf("invalid response: %v", err) 275 } 276 }() 277 278 if resp.ID != req.ID { 279 return fmt.Errorf("id mismatch") 280 } 281 282 if e, i := resp.ErrorStatus, resp.ErrorIndex; e != 0 { 283 err := fmt.Errorf("server error: %v", errorStatus(e)) 284 if i >= 0 && i < len(resp.Bindings) { 285 err = fmt.Errorf("binding %+v: %v", resp.Bindings[i], err) 286 } 287 return err 288 } 289 290 switch n := len(resp.Bindings); { 291 case n == 0: 292 return fmt.Errorf("no bindings") 293 case n < len(req.Bindings): 294 return fmt.Errorf("missing bindings") 295 case n > len(req.Bindings) && req.Type != "GetBulk": 296 return fmt.Errorf("extraneous bindings") 297 } 298 299 eq := func(a, b asn1.RawValue) bool { 300 return a.Class == b.Class && a.Tag == b.Tag 301 } 302 for _, b := range resp.Bindings { 303 switch v := b.Value; { 304 case eq(v, noSuchObject): 305 return fmt.Errorf("%v: no such object", b.Name) 306 case eq(v, noSuchInstance): 307 return fmt.Errorf("%v: no such instance", b.Name) 308 case eq(v, endOfMibView): 309 return fmt.Errorf("%v: end of mib view", b.Name) 310 case eq(v, null): 311 return fmt.Errorf("%v: unexpected null", b.Name) 312 } 313 } 314 315 return nil 316 } 317 318 // hasPrefix tests if given object instance id falls within the mib subtree 319 // defined by the prefix. 320 func hasPrefix(instance, prefix []int) bool { 321 if len(instance) < len(prefix) { 322 return false 323 } 324 for i := range prefix { 325 if instance[i] != prefix[i] { 326 return false 327 } 328 } 329 return true 330 } 331 332 // errorText is the set of response errors specified in RFC 3416. 333 var errorText = map[errorStatus]string{ 334 0: "no error", 335 1: "too big", 336 2: "no such name", 337 3: "bad value", 338 4: "read only", 339 5: "gen err", 340 6: "no access", 341 7: "wrong type", 342 8: "wrong length", 343 9: "wrong encoding", 344 10: "wrong value", 345 11: "no creation", 346 12: "inconsistent value", 347 13: "resource unavailable", 348 14: "commit failed", 349 15: "undo failed", 350 16: "authorization error", 351 17: "not writable", 352 18: "inconsistent name", 353 } 354 355 // errorStatus represents response error code. 356 type errorStatus int 357 358 // String returns the text form of error e. 359 func (e errorStatus) String() string { 360 s := errorText[e] 361 if s == "" { 362 s = fmt.Sprintf("code %d", e) 363 } 364 return s 365 } 366 367 // toString attempts to convert a byte string to ascii string of 368 // printable characters. 369 func toString(x []byte) (string, bool) { 370 if len(x) == 0 { 371 return "", false 372 } 373 if int(x[0]) != len(x[1:]) { 374 return "", false 375 } 376 buf := make([]byte, len(x[1:])) 377 for i, c := range x[1:] { 378 if c < 0x20 || c > 0x7e { 379 return "", false 380 } 381 buf[i] = byte(c) 382 } 383 return string(buf), true 384 } 385 386 // toStringInt attempts to convert an int string to ascii string of 387 // printable characters. 388 func toStringInt(x []int) (string, bool) { 389 if len(x) == 0 { 390 return "", false 391 } 392 if int(x[0]) != len(x[1:]) { 393 return "", false 394 } 395 buf := make([]byte, len(x[1:])) 396 for i, c := range x[1:] { 397 if c < 0x20 || c > 0x7e { 398 return "", false 399 } 400 buf[i] = byte(c) 401 } 402 return string(buf), true 403 } 404 405 // nextID generates random request IDs. Randomness prevents eavesdroppers 406 // from inferring application startup time. 407 var nextID = make(chan int32) 408 409 func init() { 410 rand.Seed(time.Now().UnixNano()) 411 go func() { 412 for { 413 nextID <- rand.Int31() 414 } 415 }() 416 }