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