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  }