bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/snmp/walk.go (about) 1 package snmp 2 3 import ( 4 "fmt" 5 "io" 6 7 "bosun.org/snmp/asn1" 8 "bosun.org/snmp/mib" 9 ) 10 11 // Walk is a wrapper for SNMP.Walk. 12 func Walk(host, community string, oids ...string) (*Rows, error) { 13 s, err := New(host, community) 14 if err != nil { 15 return nil, err 16 } 17 return s.Walk(oids...) 18 } 19 20 // Rows is the result of a walk. Its cursor starts before the first 21 // row of the result set. Use Next to advance through the rows: 22 // 23 // rows, err := snmp.Walk(host, community, "ifName") 24 // ... 25 // for rows.Next() { 26 // var name []byte 27 // err = rows.Scan(&name) 28 // ... 29 // } 30 // err = rows.Err() // get any error encountered during iteration 31 // ... 32 type Rows struct { 33 avail []row 34 last row 35 walkFn walkFunc 36 headText []string 37 head []asn1.ObjectIdentifier 38 err error 39 request requestFunc 40 } 41 42 // row represents individual row. 43 type row struct { 44 instance []int 45 bindings []binding 46 } 47 48 // Walk executes a query against host authenticated by the community string, 49 // retrieving the MIB sub-tree defined by the the given root oids. 50 func (s *SNMP) Walk(oids ...string) (*Rows, error) { 51 rows := &Rows{ 52 avail: nil, 53 walkFn: walkN, 54 headText: oids, 55 head: lookup(oids...), 56 request: s.do, 57 } 58 for _, oid := range rows.head { 59 rows.last.bindings = append(rows.last.bindings, binding{Name: oid}) 60 } 61 return rows, nil 62 } 63 64 // Next prepares the next result row for reading with the Scan method. 65 // It returns true on success, false if there is no next result row. 66 // Every call to Scan, even the first one, must be preceded by a call 67 // to Next. 68 func (rows *Rows) Next() bool { 69 if len(rows.avail) > 0 { 70 return true 71 } 72 73 if rows.err != nil { 74 if rows.err == io.EOF { 75 rows.err = nil 76 } 77 return false 78 } 79 80 row, err := rows.walkFn(rows.last.bindings, rows.request) 81 if err != nil { 82 if err == io.EOF { 83 rows.err = err 84 } else { 85 rows.err = fmt.Errorf("snmp.Walk: %v", err) 86 } 87 return false 88 } 89 rows.avail = row 90 91 for i, r := range rows.avail { 92 eof := 0 93 for i, b := range r.bindings { 94 if !hasPrefix(b.Name, rows.head[i]) { 95 eof++ 96 } 97 } 98 if eof > 0 { 99 if eof < len(r.bindings) { 100 rows.err = fmt.Errorf("invalid response: pre-mature end of a column") 101 return false 102 } 103 rows.avail = rows.avail[:i] 104 rows.err = io.EOF 105 break 106 } 107 } 108 109 return len(rows.avail) > 0 110 } 111 112 // Scan copies the columns in the current row into the values pointed at by v. 113 // On success, the id return variable will hold the row id of the current row. 114 // It is typically an integer or a string. 115 func (rows *Rows) Scan(v ...interface{}) (id interface{}, err error) { 116 if len(v) != len(rows.last.bindings) { 117 panic("snmp.Scan: invalid argument count") 118 } 119 120 cur := rows.avail[0] 121 rows.avail = rows.avail[1:] 122 123 last := rows.last 124 rows.last = cur 125 126 for i, a := range last.bindings { 127 b := cur.bindings[i] 128 if !a.less(b) { 129 return nil, fmt.Errorf("invalid response: %v: unordered binding: req=%+v >= resp=%+v", 130 rows.headText[i], a.Name, b.Name) 131 } 132 } 133 134 for i, b := range cur.bindings { 135 if err := b.unmarshal(v[i]); err != nil { 136 return nil, err 137 } 138 } 139 140 var want []int 141 for i, b := range cur.bindings { 142 offset := len(rows.head[i]) 143 // BUG: out of bounds access 144 have := b.Name[offset:] 145 if i == 0 { 146 want = have 147 continue 148 } 149 if len(have) != len(want) || !hasPrefix(have, want) { 150 return nil, fmt.Errorf("invalid response: inconsistent instances") 151 } 152 } 153 id = convertInstance(want) 154 155 return id, nil 156 } 157 158 // convertInstance optionally converts the object instance id from the 159 // general []byte form to simplified form: either a simple int, or a 160 // string. 161 func convertInstance(x []int) interface{} { 162 switch { 163 case len(x) == 1: 164 return x[0] 165 default: 166 s, ok := toStringInt(x) 167 if !ok { 168 return x 169 } 170 return s 171 } 172 } 173 174 // Err returns the error, if any, that was encountered during iteration. 175 func (rows *Rows) Err() error { 176 return rows.err 177 } 178 179 type requestFunc func(*request) (*response, error) 180 181 // walkFunc is a function that can request one or more rows. 182 type walkFunc func([]binding, requestFunc) ([]row, error) 183 184 // walk1 requests one row. 185 func walk1(have []binding, rf requestFunc) ([]row, error) { 186 req := &request{ 187 Type: "GetNext", 188 ID: <-nextID, 189 Bindings: have, 190 } 191 resp, err := rf(req) 192 if err != nil { 193 return nil, err 194 } 195 if err := check(resp, req); err != nil { 196 return nil, err 197 } 198 r := row{bindings: resp.Bindings} 199 return []row{r}, nil 200 } 201 202 // walkN requests a range of rows. 203 func walkN(have []binding, rf requestFunc) ([]row, error) { 204 req := &request{ 205 Type: "GetBulk", 206 ID: <-nextID, 207 Bindings: have, 208 NonRepeaters: 0, 209 MaxRepetitions: 15, 210 } 211 resp, err := rf(req) 212 if err != nil { 213 return nil, err 214 } 215 if err := check(resp, req); err != nil { 216 return nil, err 217 } 218 received := resp.Bindings 219 sent := req.Bindings 220 if len(received)%len(sent) != 0 { 221 return nil, fmt.Errorf("invalid response: truncated bindings list") 222 } 223 var list []row 224 for len(received) > 0 { 225 list = append(list, row{bindings: received[:len(sent)]}) 226 received = received[len(sent):] 227 } 228 if len(list) > req.MaxRepetitions { 229 return nil, fmt.Errorf("invalid response: peer violated MaxRepetitions, received %d rows, expected at most %d", 230 len(list), req.MaxRepetitions) 231 } 232 return list, nil 233 } 234 235 // lookup maps oids in their symbolic format into numeric format. 236 func lookup(oids ...string) []asn1.ObjectIdentifier { 237 list := make([]asn1.ObjectIdentifier, 0, len(oids)) 238 for _, o := range oids { 239 oid, err := mib.Lookup(o) 240 if err != nil { 241 panic(err) 242 } 243 list = append(list, oid) 244 } 245 return list 246 }