Merge ~cgrabowski/maas:rack_spike_power_drivers into maas:rack_region_exploration
- Git
- lp:~cgrabowski/maas
- rack_spike_power_drivers
- Merge into rack_region_exploration
Proposed by
Christian Grabowski
Status: | Merged |
---|---|
Approved by: | Alexsander de Souza |
Approved revision: | 69f139c38789997c1e8f7f9dd3e580286328bc36 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~cgrabowski/maas:rack_spike_power_drivers |
Merge into: | maas:rack_region_exploration |
Diff against target: |
1480 lines (+1444/-0) 6 files modified
src/rackd_spike/internal/drivers/ip_extractor.go (+20/-0) src/rackd_spike/internal/drivers/power/driver.go (+194/-0) src/rackd_spike/internal/drivers/power/ipmi.go (+248/-0) src/rackd_spike/internal/drivers/power/ipmi/conn.go (+656/-0) src/rackd_spike/internal/drivers/power/ipmi/packet.go (+287/-0) src/rackd_spike/internal/machine_helpers/arp.go (+39/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexsander de Souza | Approve | ||
MAAS Lander | Approve | ||
Review via email: mp+407084@code.launchpad.net |
Commit message
add fixes based on tcpdump results
add power on / off via IPMI
ipmi requests
define interfaces for power drivers
Description of the change
To post a comment you must log in.
Revision history for this message
Alexsander de Souza (alexsander-souza) wrote : | # |
LGTM
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/rackd_spike/internal/drivers/ip_extractor.go b/src/rackd_spike/internal/drivers/ip_extractor.go |
2 | new file mode 100644 |
3 | index 0000000..8be4858 |
4 | --- /dev/null |
5 | +++ b/src/rackd_spike/internal/drivers/ip_extractor.go |
6 | @@ -0,0 +1,20 @@ |
7 | +package drivers |
8 | + |
9 | +import "regexp" |
10 | + |
11 | +var ( |
12 | + IPExtractorIdentity = regexp.MustCompile("^(?P<address>.+?)$") |
13 | + IPExtractorURL = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`) |
14 | +) |
15 | + |
16 | +type IPExtractor struct { |
17 | + Field string |
18 | + Pattern *regexp.Regexp |
19 | +} |
20 | + |
21 | +func NewIPExtractor(fieldName string, pattern *regexp.Regexp) IPExtractor { |
22 | + return IPExtractor{ |
23 | + Field: fieldName, |
24 | + Pattern: pattern, |
25 | + } |
26 | +} |
27 | diff --git a/src/rackd_spike/internal/drivers/power/driver.go b/src/rackd_spike/internal/drivers/power/driver.go |
28 | new file mode 100644 |
29 | index 0000000..0764268 |
30 | --- /dev/null |
31 | +++ b/src/rackd_spike/internal/drivers/power/driver.go |
32 | @@ -0,0 +1,194 @@ |
33 | +package power |
34 | + |
35 | +import ( |
36 | + "context" |
37 | + "errors" |
38 | + "fmt" |
39 | + "net" |
40 | + "sync" |
41 | +) |
42 | + |
43 | +const ( |
44 | + ConfigTypeUnknown = iota |
45 | + ConfigTypeString |
46 | + ConfigTypeMACAddress |
47 | + ConfigTypeChoice |
48 | + ConfigTypePassword |
49 | +) |
50 | + |
51 | +const ( |
52 | + ConfigScopeBMC = iota |
53 | + ConfigScopeNode |
54 | +) |
55 | + |
56 | +var ( |
57 | + ErrFieldAlreadyDefined = errors.New("the given field is already defined") |
58 | + ErrTooManyOptionalAttrs = errors.New("too many optional attributes, only one set of optional attributes to a given field may be provied") |
59 | + ErrMustBeTypeChoice = errors.New("choices can only be provided for a field of type choice") |
60 | + ErrFieldNotFound = errors.New("the given field is not found in the given config") |
61 | + ErrFieldWrongType = errors.New("the given field is of a different type than requested") |
62 | + ErrInvalidChoice = errors.New("invalid choice provided") |
63 | + ErrInvalidType = errors.New("the given field is defined with an invalid type") |
64 | +) |
65 | + |
66 | +type powerConfigField struct { |
67 | + Key string |
68 | + Val string |
69 | + Required bool |
70 | + Default string |
71 | + Type int |
72 | + Choices map[string]string |
73 | + Scope int |
74 | +} |
75 | + |
76 | +func (p powerConfigField) ValOrDefault() string { |
77 | + if len(p.Val) > 0 { |
78 | + return p.Val |
79 | + } |
80 | + return p.Default |
81 | +} |
82 | + |
83 | +type OptionalFieldAttrs struct { |
84 | + Default string |
85 | + Choices map[string]string |
86 | + Scope int |
87 | +} |
88 | + |
89 | +type PowerConfig struct { |
90 | + sync.RWMutex |
91 | + cfg map[string]powerConfigField |
92 | +} |
93 | + |
94 | +func (p *PowerConfig) CreateField(name string, fieldType int, required bool, optional ...OptionalFieldAttrs) error { |
95 | + p.Lock() |
96 | + defer p.Unlock() |
97 | + |
98 | + if p.cfg == nil { |
99 | + p.cfg = make(map[string]powerConfigField) |
100 | + } |
101 | + |
102 | + if _, ok := p.cfg[name]; ok { |
103 | + return fmt.Errorf("%w: %s", ErrFieldAlreadyDefined, name) |
104 | + } |
105 | + if len(optional) > 1 { |
106 | + return ErrTooManyOptionalAttrs |
107 | + } |
108 | + |
109 | + field := powerConfigField{ |
110 | + Key: name, |
111 | + Type: fieldType, |
112 | + Required: required, |
113 | + } |
114 | + if len(optional) == 1 { |
115 | + field.Default = optional[0].Default |
116 | + field.Scope = optional[0].Scope |
117 | + if optional[0].Choices != nil { |
118 | + if field.Type != ConfigTypeChoice { |
119 | + return fmt.Errorf("%w: %s", ErrMustBeTypeChoice, name) |
120 | + } |
121 | + field.Choices = optional[0].Choices |
122 | + } |
123 | + } |
124 | + |
125 | + p.cfg[name] = field |
126 | + return nil |
127 | +} |
128 | + |
129 | +func (p *PowerConfig) Get(name string) (string, error) { |
130 | + p.RLock() |
131 | + defer p.RUnlock() |
132 | + |
133 | + field, ok := p.cfg[name] |
134 | + if !ok { |
135 | + return "", fmt.Errorf("%w: %s", ErrFieldNotFound, name) |
136 | + } |
137 | + return field.ValOrDefault(), nil |
138 | +} |
139 | + |
140 | +func (p *PowerConfig) Set(name, val string) error { |
141 | + p.Lock() |
142 | + defer p.Unlock() |
143 | + |
144 | + field, ok := p.cfg[name] |
145 | + if !ok { |
146 | + return fmt.Errorf("%w: %s", ErrFieldNotFound, name) |
147 | + } |
148 | + switch field.Type { |
149 | + case ConfigTypeString: |
150 | + field.Val = val |
151 | + case ConfigTypeChoice: |
152 | + _, ok := field.Choices[val] |
153 | + if !ok { |
154 | + return fmt.Errorf("%w: %s is an invalid choice for %s", ErrInvalidChoice, val, name) |
155 | + } |
156 | + field.Val = val |
157 | + case ConfigTypeMACAddress: |
158 | + _, err := net.ParseMAC(val) |
159 | + if err != nil { |
160 | + return err |
161 | + } |
162 | + field.Val = val |
163 | + case ConfigTypePassword: |
164 | + field.Val = val |
165 | + default: |
166 | + return fmt.Errorf("%w: %s", ErrInvalidType, name) |
167 | + } |
168 | + p.cfg[name] = field |
169 | + return nil |
170 | +} |
171 | + |
172 | +func (p *PowerConfig) GetMAC(name string) (net.HardwareAddr, error) { |
173 | + field, ok := p.cfg[name] |
174 | + if !ok { |
175 | + return nil, fmt.Errorf("%w: %s", ErrFieldNotFound, name) |
176 | + } |
177 | + if field.Type != ConfigTypeMACAddress { |
178 | + return nil, fmt.Errorf("%w: %s is not of type MAC Address", ErrFieldWrongType, name) |
179 | + } |
180 | + return net.ParseMAC(field.ValOrDefault()) |
181 | +} |
182 | + |
183 | +func (p *PowerConfig) GetChoice(name string) (string, error) { |
184 | + field, ok := p.cfg[name] |
185 | + if !ok { |
186 | + return "", fmt.Errorf("%w: %s", ErrFieldNotFound, name) |
187 | + } |
188 | + if field.Type != ConfigTypeChoice { |
189 | + return "", fmt.Errorf("%w: %s is not of type Choice", ErrFieldWrongType, name) |
190 | + } |
191 | + return field.Choices[field.ValOrDefault()], nil |
192 | +} |
193 | + |
194 | +func (p *PowerConfig) GetPassword(name string) (string, error) { |
195 | + field, ok := p.cfg[name] |
196 | + if !ok { |
197 | + return "", fmt.Errorf("%w: %s", ErrFieldNotFound, name) |
198 | + } |
199 | + if field.Type != ConfigTypePassword { |
200 | + return "", fmt.Errorf("%w: %s is not of type Password", ErrFieldWrongType, name) |
201 | + } |
202 | + return field.ValOrDefault(), nil |
203 | +} |
204 | + |
205 | +type PowerDriver interface { |
206 | + Name() string |
207 | + Description() string |
208 | + Settings() PowerConfig |
209 | + IPExactor() (string, string) |
210 | + Queryable() bool |
211 | + Chassis() bool |
212 | + CanProbe() bool |
213 | + CanSetBootOrder() bool |
214 | + DetectMissingPackages() error |
215 | + Schema() map[string]interface{} |
216 | + GetSetting(string) (string, error) |
217 | + PowerOn(context.Context, string, PowerConfig) error |
218 | + PowerOff(context.Context, string, PowerConfig) error |
219 | + PowerCycle(context.Context, string, PowerConfig) error |
220 | + PowerQuery(context.Context, string, PowerConfig) (string, error) |
221 | +} |
222 | + |
223 | +type OrderableBootPowerDriver interface { |
224 | + PowerDriver |
225 | + SetBootOrder(context.Context, string, PowerConfig, []string) |
226 | +} |
227 | diff --git a/src/rackd_spike/internal/drivers/power/ipmi.go b/src/rackd_spike/internal/drivers/power/ipmi.go |
228 | new file mode 100644 |
229 | index 0000000..165e10e |
230 | --- /dev/null |
231 | +++ b/src/rackd_spike/internal/drivers/power/ipmi.go |
232 | @@ -0,0 +1,248 @@ |
233 | +package power |
234 | + |
235 | +import ( |
236 | + "context" |
237 | + "net" |
238 | + |
239 | + "rackd/internal/drivers" |
240 | + "rackd/internal/drivers/power/ipmi" |
241 | + machinehelpers "rackd/internal/machine_helpers" |
242 | +) |
243 | + |
244 | +const ( |
245 | + IPMIDriverLan = "LAN" |
246 | + IPMIDriverLan2 = "LAN_2_0" |
247 | + |
248 | + IPMIBootTypeDefault = "auto" |
249 | + IPMIBootTypeLegacy = "legacy" |
250 | + IPMIBootTypeEFI = "efi" |
251 | + |
252 | + IPMIPrivilegeLevelUser = "USER" |
253 | + IPMIPrivilegeLevelOperator = "OPERATOR" |
254 | + IPMIPrivilegeLevelAdmin = "ADMIN" |
255 | +) |
256 | + |
257 | +var ( |
258 | + IPMIWaitTime = [4]int{4, 8, 16, 32} |
259 | + |
260 | + IPMIDriverChoices = map[string]string{ |
261 | + IPMIDriverLan: "LAN [IPMI 1.5]", |
262 | + IPMIDriverLan2: "LAN_2_0 [IPMI 2.0]", |
263 | + } |
264 | + |
265 | + IPMIBootTypeChoices = map[string]string{ |
266 | + IPMIBootTypeDefault: "Automatic", |
267 | + IPMIBootTypeLegacy: "Legacy boot", |
268 | + IPMIBootTypeEFI: "EFI boot", |
269 | + } |
270 | + |
271 | + IPMIBootTypeMapping = map[string]string{ |
272 | + IPMIBootTypeEFI: "EFI", |
273 | + IPMIBootTypeLegacy: "PC-COMPATIBLE", |
274 | + } |
275 | + |
276 | + IPMICipherSuiteIDChoices = map[string]string{ |
277 | + "": "freeipmi-tools default", |
278 | + "17": "17 - HMAC-SHA256::HMAC_SHA256_128::AES-CBC-128", |
279 | + "3": "3 - HMAC-SHA1::HMAC-SHA1-96::AES-CBC-128", |
280 | + "8": "8 - HMAC-MD5::HMAC-MD5-128::AES-CBC-128", |
281 | + "12": "12 - HMAC-MD5::MD5-128::AES-CBC-128", |
282 | + } |
283 | + |
284 | + IPMIPrivilegeLevelChoices = map[string]string{ |
285 | + IPMIPrivilegeLevelUser: "User", |
286 | + IPMIPrivilegeLevelOperator: "Operator", |
287 | + IPMIPrivilegeLevelAdmin: "Administrator", |
288 | + } |
289 | +) |
290 | + |
291 | +type IPMIDriver struct { |
292 | + settings *PowerConfig |
293 | +} |
294 | + |
295 | +func NewIPMIDriver() *IPMIDriver { |
296 | + settings := &PowerConfig{} |
297 | + err := settings.CreateField("power_driver", ConfigTypeChoice, true, |
298 | + OptionalFieldAttrs{Default: IPMIDriverLan, Choices: IPMIDriverChoices}) |
299 | + if err != nil { |
300 | + panic(err) // All these errors should be the result of something defined at compile-time, so better to error hard |
301 | + } |
302 | + err = settings.CreateField("power_boot_type", ConfigTypeChoice, false, |
303 | + OptionalFieldAttrs{Default: IPMIBootTypeDefault, Choices: IPMIBootTypeChoices}) |
304 | + if err != nil { |
305 | + panic(err) |
306 | + } |
307 | + err = settings.CreateField("mac_address", ConfigTypeMACAddress, true) |
308 | + if err != nil { |
309 | + panic(err) |
310 | + } |
311 | + err = settings.CreateField("power_address", ConfigTypeMACAddress, false) |
312 | + if err != nil { |
313 | + panic(err) |
314 | + } |
315 | + err = settings.CreateField("power_user", ConfigTypeString, false) |
316 | + if err != nil { |
317 | + panic(err) |
318 | + } |
319 | + err = settings.CreateField("power_pass", ConfigTypePassword, false) |
320 | + if err != nil { |
321 | + panic(err) |
322 | + } |
323 | + err = settings.CreateField("k_g", ConfigTypePassword, false) |
324 | + if err != nil { |
325 | + panic(err) |
326 | + } |
327 | + err = settings.CreateField("cipher_suite_id", ConfigTypeChoice, false, |
328 | + OptionalFieldAttrs{Default: "3", Choices: IPMICipherSuiteIDChoices}) |
329 | + if err != nil { |
330 | + panic(err) |
331 | + } |
332 | + err = settings.CreateField("privilege_level", ConfigTypeChoice, false, |
333 | + OptionalFieldAttrs{Default: IPMIPrivilegeLevelOperator, Choices: IPMIPrivilegeLevelChoices}) |
334 | + if err != nil { |
335 | + panic(err) |
336 | + } |
337 | + return &IPMIDriver{ |
338 | + settings: settings, |
339 | + } |
340 | +} |
341 | + |
342 | +func (i *IPMIDriver) Name() string { |
343 | + return "ipmi" |
344 | +} |
345 | + |
346 | +func (i *IPMIDriver) Description() string { |
347 | + return "IPMI" |
348 | +} |
349 | + |
350 | +func (i *IPMIDriver) Settings() *PowerConfig { |
351 | + return i.settings |
352 | +} |
353 | + |
354 | +func (i *IPMIDriver) IPExtractor() drivers.IPExtractor { |
355 | + return drivers.NewIPExtractor("power_address", drivers.IPExtractorIdentity) |
356 | +} |
357 | + |
358 | +func (i *IPMIDriver) Queryable() bool { |
359 | + return false |
360 | +} |
361 | + |
362 | +func (i *IPMIDriver) Chassis() bool { |
363 | + return false |
364 | +} |
365 | + |
366 | +func (i *IPMIDriver) CanProbe() bool { |
367 | + return false |
368 | +} |
369 | + |
370 | +func (i *IPMIDriver) CanSetBootOrder() bool { |
371 | + return false |
372 | +} |
373 | + |
374 | +func (i *IPMIDriver) DetectMissingPackages() error { |
375 | + return nil |
376 | +} |
377 | + |
378 | +func (i *IPMIDriver) Schema() map[string]interface{} { |
379 | + return nil |
380 | +} |
381 | + |
382 | +func (i *IPMIDriver) GetSetting(field string) (string, error) { |
383 | + return "", nil |
384 | +} |
385 | + |
386 | +func (i *IPMIDriver) connFromCfg(ctx context.Context, cfg *PowerConfig) (*ipmi.LanConn, error) { |
387 | + remoteIPStr, err := cfg.Get("power_address") |
388 | + if err != nil { |
389 | + return nil, err |
390 | + } |
391 | + |
392 | + var remoteIP net.IP |
393 | + if len(remoteIPStr) == 0 { |
394 | + mac, err := cfg.GetMAC("mac_address") |
395 | + if err != nil { |
396 | + return nil, err |
397 | + } |
398 | + remoteIP, err = machinehelpers.FindIPByArp(ctx, mac) |
399 | + if err != nil { |
400 | + return nil, err |
401 | + } |
402 | + } |
403 | + user, err := cfg.Get("power_user") |
404 | + if err != nil { |
405 | + return nil, err |
406 | + } |
407 | + password, err := cfg.Get("power_pass") |
408 | + if err != nil { |
409 | + return nil, err |
410 | + } |
411 | + privLvl, err := cfg.GetChoice("privilege_level") |
412 | + if err != nil { |
413 | + return nil, err |
414 | + } |
415 | + info := ipmi.ConnInfo{ |
416 | + Port: 623, |
417 | + IP: remoteIP.String(), |
418 | + BindAddr: "0.0.0.0", |
419 | + Username: user, |
420 | + Password: password, |
421 | + PrivLvl: privLvl, |
422 | + } |
423 | + return ipmi.NewLanConn(info), nil |
424 | +} |
425 | + |
426 | +func (i *IPMIDriver) applyConfig(conn *ipmi.LanConn, cfg *PowerConfig) error { |
427 | + err := conn.SetBootDevice(ipmi.IPMIBootDevicePxe) |
428 | + if err != nil { |
429 | + return err |
430 | + } |
431 | + return nil |
432 | +} |
433 | + |
434 | +func (i *IPMIDriver) PowerOn(ctx context.Context, systemID string, cfg *PowerConfig) error { |
435 | + conn, err := i.connFromCfg(ctx, cfg) |
436 | + if err != nil { |
437 | + return err |
438 | + } |
439 | + err = conn.Open() |
440 | + if err != nil { |
441 | + return err |
442 | + } |
443 | + defer conn.Close() |
444 | + err = conn.StartSession() |
445 | + if err != nil { |
446 | + return err |
447 | + } |
448 | + defer conn.EndSession() |
449 | + err = i.applyConfig(conn, cfg) |
450 | + if err != nil { |
451 | + return err |
452 | + } |
453 | + return conn.PowerCtrl(ipmi.IPMIPowerStateOn) |
454 | +} |
455 | + |
456 | +func (i *IPMIDriver) PowerOff(ctx context.Context, systemID string, cfg *PowerConfig) error { |
457 | + conn, err := i.connFromCfg(ctx, cfg) |
458 | + if err != nil { |
459 | + return err |
460 | + } |
461 | + err = conn.Open() |
462 | + if err != nil { |
463 | + return err |
464 | + } |
465 | + defer conn.Close() |
466 | + err = conn.StartSession() |
467 | + if err != nil { |
468 | + return err |
469 | + } |
470 | + defer conn.EndSession() |
471 | + err = i.applyConfig(conn, cfg) |
472 | + if err != nil { |
473 | + return err |
474 | + } |
475 | + return conn.PowerCtrl(ipmi.IPMIPowerStateOff) |
476 | +} |
477 | + |
478 | +func (i *IPMIDriver) PowerQuery(ctx context.Context, systemID string, cfg *PowerConfig) error { |
479 | + return nil |
480 | +} |
481 | diff --git a/src/rackd_spike/internal/drivers/power/ipmi/conn.go b/src/rackd_spike/internal/drivers/power/ipmi/conn.go |
482 | new file mode 100644 |
483 | index 0000000..62eaf7e |
484 | --- /dev/null |
485 | +++ b/src/rackd_spike/internal/drivers/power/ipmi/conn.go |
486 | @@ -0,0 +1,656 @@ |
487 | +package ipmi |
488 | + |
489 | +import ( |
490 | + "bytes" |
491 | + "crypto/md5" |
492 | + "crypto/rand" |
493 | + "encoding" |
494 | + "encoding/binary" |
495 | + "io" |
496 | + "net" |
497 | + "strconv" |
498 | + "strings" |
499 | + "sync/atomic" |
500 | + "time" |
501 | +) |
502 | + |
503 | +const ( |
504 | + recvBufferSize = 1024 |
505 | +) |
506 | + |
507 | +const ( |
508 | + IPMICMDGetDeviceID uint8 = 0x01 |
509 | + IPMICMDChassisStatus uint8 = 0x01 |
510 | + IPMICMDChassisControl uint8 = 0x02 |
511 | + IPMICMDSetSystemBootOptions uint8 = 0x08 |
512 | + IPMICMDGetSystemBootOptions uint8 = 0x09 |
513 | + IPMICMDAuthCapabilities uint8 = 0x38 |
514 | + IPMICMDGetSessionChallenge uint8 = 0x39 |
515 | + IPMICMDActivateSession uint8 = 0x3a |
516 | + IPMICMDSetSessionPrivilegeLevel uint8 = 0x3b |
517 | + IPMICMDCloseSession uint8 = 0x3c |
518 | + IPMICMDSetUsername uint8 = 0x45 |
519 | + IPMICMDGetUsername uint8 = 0x46 |
520 | + |
521 | + IPMIBootDeviceNone uint8 = 0x00 |
522 | + IPMIBootDevicePxe uint8 = 0x04 |
523 | + IPMIBootDeviceDisk uint8 = 0x08 |
524 | + IPMIBootDeviceSafe uint8 = 0x0c |
525 | + IPMIBootDeviceDiag uint8 = 0x10 |
526 | + IPMIBootDeviceCDROM uint8 = 0x14 |
527 | + IPMIBootDeviceBios uint8 = 0x18 |
528 | + IPMIBootDeviceRemoteFloppy uint8 = 0x1c |
529 | + IPMIBootDeviceRemoteCDROM uint8 = 0x20 |
530 | + IPMIBootDeviceRemotePrimary uint8 = 0x24 |
531 | + IPMIBootDeviceRemoteDisk uint8 = 0x2c |
532 | + IPMIBootDeviceFloppy uint8 = 0x3c |
533 | + |
534 | + IPMIAuthTypeNone uint8 = 0x0 |
535 | + IPMIAuthTypeMD2 uint8 = 0x1 |
536 | + IPMIAuthTypeMD5 uint8 = 0x2 |
537 | + IPMIAuthTypeKey uint8 = 0x4 |
538 | + IPMIAuthTypePassword uint8 = 0x4 |
539 | + IPMIAuthTypeOEM uint8 = 0x5 |
540 | + IPMIAuthTypeRMCPPlus uint8 = 0x6 |
541 | +) |
542 | + |
543 | +const ( |
544 | + IPMIInProgressSetComplete uint8 = iota |
545 | + IPMIInProgressInProgress |
546 | + IPMIInProgressCommit |
547 | +) |
548 | + |
549 | +const ( |
550 | + IPMIPowerStateOff uint8 = iota |
551 | + IPMIPowerStateOn |
552 | + IPMIPowerStateCycle |
553 | + IPMIPowerStateHardReset |
554 | + IPMIPowerStatePulseDiag |
555 | + IPMIPowerStateAcpiSoft |
556 | +) |
557 | + |
558 | +const ( |
559 | + IPMIBootParamInProgress uint8 = iota |
560 | + IPMIBootParamSvcPartSelect |
561 | + IPMIBootParamSvcPartScan |
562 | + IPMIBootParamFlagValid |
563 | + IPMIBootParamInfoAck |
564 | + IPMIBootParamBootFlags |
565 | + IPMIBootParamInitInfo |
566 | + IPMIBootParamInitMbox |
567 | +) |
568 | + |
569 | +const ( |
570 | + IPMIPrivLevelNone = iota |
571 | + IPMIPrivLevelCallback |
572 | + IPMIPrivLevelUser |
573 | + IPMIPrivLevelOperator |
574 | + IPMIPrivLevelAdmin |
575 | + IPMIPrivLevelOEM |
576 | +) |
577 | + |
578 | +type ConnInfo struct { |
579 | + Port int |
580 | + Path string |
581 | + IP string |
582 | + BindAddr string |
583 | + Username string |
584 | + Password string |
585 | + PrivLvl string |
586 | +} |
587 | + |
588 | +type Request struct { |
589 | + NetworkFn uint8 |
590 | + Cmd uint8 |
591 | + Data interface{} |
592 | +} |
593 | + |
594 | +type Response interface { |
595 | + encoding.BinaryUnmarshaler |
596 | + Data() interface{} |
597 | +} |
598 | + |
599 | +type LanConn struct { |
600 | + Session session |
601 | + IPMIHeader ipmiHeader |
602 | + Info ConnInfo |
603 | + rqSeq uint8 |
604 | + transport net.Conn |
605 | + connected bool |
606 | + authCode [16]byte |
607 | + username [16]byte |
608 | + priv uint8 |
609 | + lun uint8 |
610 | + timeout time.Duration |
611 | +} |
612 | + |
613 | +func NewLanConn(info ConnInfo) *LanConn { |
614 | + conn := &LanConn{ |
615 | + Info: info, |
616 | + } |
617 | + switch strings.ToLower(info.PrivLvl) { |
618 | + case "user": |
619 | + conn.priv = IPMIPrivLevelUser |
620 | + case "operator": |
621 | + conn.priv = IPMIPrivLevelOperator |
622 | + case "administrator": |
623 | + conn.priv = IPMIPrivLevelAdmin |
624 | + } |
625 | + copy(conn.username[:], info.Username[:]) |
626 | + copy(conn.authCode[:], info.Password[:]) |
627 | + return conn |
628 | +} |
629 | + |
630 | +func (l *LanConn) Open() error { |
631 | + lAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(l.Info.BindAddr, "0")) |
632 | + if err != nil { |
633 | + return err |
634 | + } |
635 | + rAddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort(l.Info.IP, strconv.Itoa(l.Info.Port))) |
636 | + if err != nil { |
637 | + return err |
638 | + } |
639 | + conn, err := net.DialUDP("udp", lAddr, rAddr) |
640 | + if err != nil { |
641 | + return err |
642 | + } |
643 | + l.transport = conn |
644 | + l.connected = true |
645 | + return nil |
646 | +} |
647 | + |
648 | +func (l *LanConn) Close() error { |
649 | + if l.connected { |
650 | + l.Session = session{} |
651 | + return l.transport.Close() |
652 | + } |
653 | + return nil |
654 | +} |
655 | + |
656 | +func (l *LanConn) nextSeq() uint32 { |
657 | + if l.Session.Seq != 0 { |
658 | + atomic.AddUint32(&l.Session.Seq, 1) |
659 | + } |
660 | + return atomic.LoadUint32(&l.Session.Seq) << 2 |
661 | +} |
662 | + |
663 | +func (l *LanConn) nextRqSeq() uint8 { |
664 | + l.IPMIHeader.RqSeq++ |
665 | + return l.IPMIHeader.RqSeq |
666 | +} |
667 | + |
668 | +func (l *LanConn) sigMD5(data []byte) []byte { |
669 | + hash := md5.New() |
670 | + binary.Write(hash, binary.BigEndian, l.authCode) |
671 | + binary.Write(hash, binary.BigEndian, l.Session.ID) |
672 | + binary.Write(hash, binary.BigEndian, data) |
673 | + binary.Write(hash, binary.BigEndian, l.Session.Seq) |
674 | + binary.Write(hash, binary.BigEndian, l.authCode) |
675 | + return hash.Sum(nil) |
676 | +} |
677 | + |
678 | +func (l *LanConn) Send(req *Request, resp Response) error { |
679 | + pkt := &Packet{ |
680 | + RMCPHeader: rmcpHeader{ |
681 | + Version: rmcpV1, |
682 | + Sequence: 0xff, |
683 | + Class: rmcpClassIPMI, |
684 | + }, |
685 | + Session: session{ |
686 | + AuthType: l.Session.AuthType, |
687 | + Seq: l.nextSeq(), |
688 | + ID: l.Session.ID, |
689 | + }, |
690 | + IPMIHeader: ipmiHeader{ |
691 | + RsAddr: 0x20, |
692 | + NetFnRsLUN: req.NetworkFn<<2 | l.IPMIHeader.NetFnRsLUN&3, |
693 | + Cmd: req.Cmd, |
694 | + RqAddr: 0x81, |
695 | + RqSeq: l.nextRqSeq(), |
696 | + }, |
697 | + } |
698 | + |
699 | + if l.Session.AuthType != 0 { |
700 | + copy(pkt.AuthCode[:], l.authCode[:]) |
701 | + } |
702 | + |
703 | + err := pkt.SetData(req.Data) |
704 | + if err != nil { |
705 | + return err |
706 | + } |
707 | + |
708 | + msg, err := MarshalPacket(pkt) |
709 | + if err != nil { |
710 | + return err |
711 | + } |
712 | + |
713 | + if l.Session.AuthType == IPMIAuthTypeMD5 { |
714 | + hlen := binary.Size(rmcpHeader{}) + binary.Size(session{}) |
715 | + offset := hlen + len(pkt.AuthCode) + 1 |
716 | + sig := l.sigMD5(msg[offset:]) |
717 | + copy(msg[hlen:], sig) |
718 | + } |
719 | + |
720 | + _, err = l.transport.Write(msg) |
721 | + if err != nil { |
722 | + return err |
723 | + } |
724 | + |
725 | + buf := make([]byte, recvBufferSize) |
726 | + if l.timeout > 0 { |
727 | + err = l.transport.SetReadDeadline(time.Now().Add(l.timeout)) |
728 | + if err != nil { |
729 | + return err |
730 | + } |
731 | + } |
732 | + n, err := l.transport.Read(buf) |
733 | + if err != nil && err != io.EOF { |
734 | + return err |
735 | + } |
736 | + rPkt, err := UnmarshalPacket(buf[:n]) |
737 | + if err != nil { |
738 | + return err |
739 | + } |
740 | + if resp != nil { |
741 | + return resp.UnmarshalBinary(rPkt.Data) |
742 | + } |
743 | + return nil |
744 | +} |
745 | + |
746 | +func (l *LanConn) genSeq() ([4]byte, error) { |
747 | + var seq [4]byte |
748 | + _, err := rand.Read(seq[:]) |
749 | + if err != nil { |
750 | + return seq, err |
751 | + } |
752 | + return seq, nil |
753 | +} |
754 | + |
755 | +func (l *LanConn) Ping() (err error) { |
756 | + pkt := &ASFPacket{ |
757 | + RMCPHeader: rmcpHeader{ |
758 | + Version: rmcpV1, |
759 | + Class: rmcpClassASF, |
760 | + Sequence: 0xff, |
761 | + }, |
762 | + ASFHeader: asfHeader{ |
763 | + IANANum: ianaASF, |
764 | + Type: asfTypePing, |
765 | + }, |
766 | + } |
767 | + |
768 | + payload := MarshalASFPacket(pkt) |
769 | + _, err = l.transport.Write(payload) |
770 | + if err != nil { |
771 | + return err |
772 | + } |
773 | + buf := make([]byte, recvBufferSize) |
774 | + if l.timeout > 0 { |
775 | + err = l.transport.SetReadDeadline(time.Now().Add(l.timeout)) |
776 | + if err != nil { |
777 | + return err |
778 | + } |
779 | + } |
780 | + |
781 | + n, err := l.transport.Read(buf) |
782 | + if err != nil { |
783 | + return err |
784 | + } |
785 | + pkt, err = UnmarshalASFPacket(buf[n:]) |
786 | + if err != nil { |
787 | + return err |
788 | + } |
789 | + return pkt.ValidatePong() |
790 | +} |
791 | + |
792 | +type AuthCapReq struct { |
793 | + Channel uint8 |
794 | + PrivLevel uint8 |
795 | +} |
796 | + |
797 | +type AuthCapResp struct { |
798 | + Channel uint8 |
799 | + AuthTypeSupport uint8 |
800 | + Flags uint8 |
801 | + V1 uint8 |
802 | + V2 uint8 |
803 | + OEMID [3]byte |
804 | + OEMAux uint8 |
805 | +} |
806 | + |
807 | +func (a *AuthCapResp) UnmarshalBinary(b []byte) error { |
808 | + buf := bytes.NewReader(b) |
809 | + err := binary.Read(buf, binary.BigEndian, a) |
810 | + if err != nil && err != io.EOF { |
811 | + return err |
812 | + } |
813 | + return nil |
814 | +} |
815 | + |
816 | +func (a *AuthCapResp) Data() interface{} { |
817 | + return a |
818 | +} |
819 | + |
820 | +func (l *LanConn) GetAuthCaps() (*AuthCapResp, error) { |
821 | + req := &Request{ |
822 | + NetworkFn: NetworkFnApp, |
823 | + Cmd: IPMICMDAuthCapabilities, |
824 | + Data: AuthCapReq{ |
825 | + Channel: 0x0e, |
826 | + PrivLevel: l.priv, |
827 | + }, |
828 | + } |
829 | + var resp AuthCapResp |
830 | + |
831 | + err := l.Send(req, &resp) |
832 | + if err != nil { |
833 | + return nil, err |
834 | + } |
835 | + |
836 | + return &resp, nil |
837 | +} |
838 | + |
839 | +type SessionChallengeRequest struct { |
840 | + AuthType uint8 |
841 | + Username [16]byte |
842 | +} |
843 | + |
844 | +type SessionChallengeResp struct { |
845 | + Code uint8 |
846 | + SessionID uint32 |
847 | + Challenge [16]byte |
848 | +} |
849 | + |
850 | +func (s *SessionChallengeResp) UnmarshalBinary(b []byte) error { |
851 | + buf := bytes.NewReader(b) |
852 | + return binary.Read(buf, binary.BigEndian, s) |
853 | +} |
854 | + |
855 | +func (s *SessionChallengeResp) Data() interface{} { |
856 | + return s |
857 | +} |
858 | + |
859 | +func (l *LanConn) GetSessionChallenge(authType uint8) (*SessionChallengeResp, error) { |
860 | + req := &Request{ |
861 | + NetworkFn: NetworkFnApp, |
862 | + Cmd: IPMICMDGetSessionChallenge, |
863 | + Data: SessionChallengeRequest{ |
864 | + AuthType: authType, |
865 | + Username: l.username, |
866 | + }, |
867 | + } |
868 | + var resp SessionChallengeResp |
869 | + |
870 | + err := l.Send(req, &resp) |
871 | + if err != nil { |
872 | + return nil, err |
873 | + } |
874 | + l.Session.AuthType = authType |
875 | + return &resp, nil |
876 | +} |
877 | + |
878 | +type ActivateSessionReq struct { |
879 | + AuthType uint8 |
880 | + PrivLevel uint8 |
881 | + AuthCode [16]byte |
882 | + Seq [4]byte |
883 | +} |
884 | + |
885 | +type ActivateSessionResp struct { |
886 | + Code uint8 |
887 | + AuthType uint8 |
888 | + SessionID uint32 |
889 | + Seq uint32 |
890 | + MaxPriv uint8 |
891 | +} |
892 | + |
893 | +func (a *ActivateSessionResp) UnmarshalBinary(b []byte) error { |
894 | + buf := bytes.NewReader(b) |
895 | + return binary.Read(buf, binary.BigEndian, a) |
896 | +} |
897 | + |
898 | +func (a *ActivateSessionResp) Data() interface{} { |
899 | + return a |
900 | +} |
901 | + |
902 | +func (l *LanConn) ActivateSession(challenge *SessionChallengeResp) error { |
903 | + outboundSeq, err := l.genSeq() |
904 | + if err != nil { |
905 | + return err |
906 | + } |
907 | + req := &Request{ |
908 | + NetworkFn: NetworkFnApp, |
909 | + Cmd: IPMICMDActivateSession, |
910 | + Data: ActivateSessionReq{ |
911 | + AuthType: l.Session.AuthType, |
912 | + PrivLevel: l.priv, |
913 | + AuthCode: challenge.Challenge, |
914 | + Seq: outboundSeq, |
915 | + }, |
916 | + } |
917 | + var resp ActivateSessionResp |
918 | + err = l.Send(req, &resp) |
919 | + if err != nil { |
920 | + return err |
921 | + } |
922 | + |
923 | + l.connected = true |
924 | + l.Session.ID = resp.SessionID |
925 | + l.Session.AuthType = resp.AuthType |
926 | + l.Session.Seq = resp.Seq |
927 | + return nil |
928 | +} |
929 | + |
930 | +type SetPrivilegeReq struct { |
931 | + Priv uint8 |
932 | +} |
933 | + |
934 | +type SetPrivilegeResponse struct { |
935 | + Code uint8 |
936 | + Priv uint8 |
937 | +} |
938 | + |
939 | +func (s *SetPrivilegeResponse) UnmarshalBinary(b []byte) error { |
940 | + buf := bytes.NewReader(b) |
941 | + return binary.Read(buf, binary.BigEndian, s) |
942 | +} |
943 | + |
944 | +func (s *SetPrivilegeResponse) Data() interface{} { |
945 | + return s |
946 | +} |
947 | + |
948 | +func (l *LanConn) SetPrivilege() error { |
949 | + req := &Request{ |
950 | + NetworkFn: NetworkFnApp, |
951 | + Cmd: IPMICMDSetSessionPrivilegeLevel, |
952 | + Data: SetPrivilegeReq{ |
953 | + Priv: l.priv, |
954 | + }, |
955 | + } |
956 | + var resp SetPrivilegeResponse |
957 | + err := l.Send(req, &resp) |
958 | + if err != nil { |
959 | + return err |
960 | + } |
961 | + l.priv = resp.Priv |
962 | + return nil |
963 | +} |
964 | + |
965 | +func (l *LanConn) StartSession() error { |
966 | + err := l.Ping() |
967 | + if err != nil { |
968 | + return err |
969 | + } |
970 | + authCaps, err := l.GetAuthCaps() |
971 | + if err != nil { |
972 | + return err |
973 | + } |
974 | + |
975 | + authType := IPMIAuthTypeNone |
976 | + for _, authT := range []uint8{IPMIAuthTypeMD5, IPMIAuthTypePassword, IPMIAuthTypeMD2} { |
977 | + if authCaps.AuthTypeSupport&authT == 0 { |
978 | + authType = authT |
979 | + break |
980 | + } |
981 | + } |
982 | + |
983 | + challenge, err := l.GetSessionChallenge(authType) |
984 | + if err != nil { |
985 | + return err |
986 | + } |
987 | + |
988 | + err = l.ActivateSession(challenge) |
989 | + if err != nil { |
990 | + return err |
991 | + } |
992 | + return l.SetPrivilege() |
993 | +} |
994 | + |
995 | +type CloseSessionReq struct { |
996 | + SessionID uint32 |
997 | +} |
998 | + |
999 | +func (l *LanConn) EndSession() error { |
1000 | + req := &Request{ |
1001 | + NetworkFn: NetworkFnApp, |
1002 | + Cmd: IPMICMDCloseSession, |
1003 | + Data: CloseSessionReq{ |
1004 | + SessionID: l.Session.ID, |
1005 | + }, |
1006 | + } |
1007 | + return l.Send(req, nil) |
1008 | +} |
1009 | + |
1010 | +type DeviceIDResp struct { |
1011 | + Code uint8 |
1012 | + DeviceID uint8 |
1013 | + DeviceRevision uint8 |
1014 | + FirmwareRevision1 uint8 |
1015 | + FirmwareRevision2 uint8 |
1016 | + IPMIVersion uint8 |
1017 | + AdditionalDeviceSupport uint8 |
1018 | + ManufacturerID uint16 |
1019 | + ProductID uint16 |
1020 | +} |
1021 | + |
1022 | +func (d *DeviceIDResp) UnmarshalBinary(b []byte) error { |
1023 | + buf := bytes.NewReader(b) |
1024 | + return binary.Read(buf, binary.BigEndian, d) |
1025 | +} |
1026 | + |
1027 | +func (d *DeviceIDResp) Data() interface{} { |
1028 | + return d |
1029 | +} |
1030 | + |
1031 | +func (l *LanConn) DeviceID() (*DeviceIDResp, error) { |
1032 | + req := &Request{ |
1033 | + NetworkFn: NetworkFnApp, |
1034 | + Cmd: IPMICMDGetDeviceID, |
1035 | + } |
1036 | + var resp DeviceIDResp |
1037 | + err := l.Send(req, &resp) |
1038 | + if err != nil { |
1039 | + return nil, err |
1040 | + } |
1041 | + return &resp, nil |
1042 | +} |
1043 | + |
1044 | +type SetBootOptionsReq struct { |
1045 | + Param uint8 |
1046 | + Data []uint8 |
1047 | +} |
1048 | + |
1049 | +func (l *LanConn) SetBootParam(param uint8, vals ...uint8) error { |
1050 | + req := &Request{ |
1051 | + NetworkFn: NetworkFnChassis, |
1052 | + Cmd: IPMICMDSetSystemBootOptions, |
1053 | + Data: SetBootOptionsReq{ |
1054 | + Param: param, |
1055 | + Data: vals, |
1056 | + }, |
1057 | + } |
1058 | + return l.Send(req, nil) |
1059 | +} |
1060 | + |
1061 | +func (l *LanConn) SetBootDevice(bootDev uint8) error { |
1062 | + err := l.SetBootParam(IPMIBootParamInProgress, IPMIInProgressInProgress) |
1063 | + if err != nil { |
1064 | + return err |
1065 | + } |
1066 | + defer l.SetBootParam(IPMIBootParamInProgress, IPMIInProgressSetComplete) |
1067 | + |
1068 | + err = l.SetBootParam(IPMIBootParamInfoAck, 0x01, 0x01) |
1069 | + if err != nil { |
1070 | + return err |
1071 | + } |
1072 | + |
1073 | + err = l.SetBootParam(IPMIBootParamBootFlags, 0x80, bootDev, 0x00, 0x00, 0x00) |
1074 | + if err != nil { |
1075 | + return err |
1076 | + } |
1077 | + err = l.SetBootParam(IPMIBootParamInProgress, IPMIInProgressCommit) |
1078 | + if err != nil { |
1079 | + return err |
1080 | + } |
1081 | + return nil |
1082 | +} |
1083 | + |
1084 | +func (l *LanConn) PowerCtrl(state uint8) error { |
1085 | + req := &Request{ |
1086 | + NetworkFn: NetworkFnChassis, |
1087 | + Cmd: IPMICMDChassisControl, |
1088 | + Data: state, |
1089 | + } |
1090 | + return l.Send(req, nil) |
1091 | +} |
1092 | + |
1093 | +type GetUsernameReq struct { |
1094 | + UserID uint8 |
1095 | +} |
1096 | + |
1097 | +type GetUsernameResp struct { |
1098 | + Code uint8 |
1099 | + Username string |
1100 | +} |
1101 | + |
1102 | +func (g *GetUsernameResp) UnmarshalBinary(b []byte) error { |
1103 | + buf := bytes.NewReader(b) |
1104 | + return binary.Read(buf, binary.BigEndian, g) |
1105 | +} |
1106 | + |
1107 | +func (g *GetUsernameResp) Data() interface{} { |
1108 | + return g |
1109 | +} |
1110 | + |
1111 | +func (l *LanConn) GetUserName(id uint8) (string, error) { |
1112 | + req := &Request{ |
1113 | + NetworkFn: NetworkFnApp, |
1114 | + Cmd: IPMICMDGetUsername, |
1115 | + Data: GetUsernameReq{ |
1116 | + UserID: id, |
1117 | + }, |
1118 | + } |
1119 | + var resp GetUsernameResp |
1120 | + err := l.Send(req, &resp) |
1121 | + if err != nil { |
1122 | + return "", err |
1123 | + } |
1124 | + return resp.Username, nil |
1125 | +} |
1126 | + |
1127 | +type SetUsernameReq struct { |
1128 | + UserID uint8 |
1129 | + Username string |
1130 | +} |
1131 | + |
1132 | +func (l *LanConn) SetUsername(id uint8, username string) error { |
1133 | + req := &Request{ |
1134 | + NetworkFn: NetworkFnApp, |
1135 | + Cmd: IPMICMDSetUsername, |
1136 | + Data: SetUsernameReq{ |
1137 | + UserID: id, |
1138 | + Username: username, |
1139 | + }, |
1140 | + } |
1141 | + return l.Send(req, nil) |
1142 | +} |
1143 | diff --git a/src/rackd_spike/internal/drivers/power/ipmi/packet.go b/src/rackd_spike/internal/drivers/power/ipmi/packet.go |
1144 | new file mode 100644 |
1145 | index 0000000..a6a1fa5 |
1146 | --- /dev/null |
1147 | +++ b/src/rackd_spike/internal/drivers/power/ipmi/packet.go |
1148 | @@ -0,0 +1,287 @@ |
1149 | +package ipmi |
1150 | + |
1151 | +import ( |
1152 | + "bytes" |
1153 | + "encoding" |
1154 | + "encoding/binary" |
1155 | + "errors" |
1156 | + "fmt" |
1157 | +) |
1158 | + |
1159 | +const ( |
1160 | + rmcpClassASF uint8 = 0x06 |
1161 | + rmcpClassIPMI uint8 = 0x07 |
1162 | + rmcpClassNoAckSeq uint8 = 0xff |
1163 | + |
1164 | + rmcpV1 uint8 = 0x06 |
1165 | + |
1166 | + ianaASF uint32 = 0x000011be |
1167 | + |
1168 | + asfTypePing uint8 = 0x80 |
1169 | + asfTypePong uint8 = 0x40 |
1170 | + |
1171 | + NetworkFnChassis uint8 = 0x00 |
1172 | + NetworkFnApp uint8 = 0x06 |
1173 | +) |
1174 | + |
1175 | +var ( |
1176 | + minPktSize = binary.Size(rmcpHeader{}) + binary.Size(session{}) + binary.Size(ipmiHeader{}) |
1177 | + |
1178 | + ErrInvalidPacket = errors.New("the received packet is invalid") |
1179 | +) |
1180 | + |
1181 | +func computeChecksum(data ...uint8) uint8 { |
1182 | + var checksum uint8 |
1183 | + for _, d := range data { |
1184 | + checksum += d |
1185 | + } |
1186 | + return -checksum |
1187 | +} |
1188 | + |
1189 | +type rmcpHeader struct { |
1190 | + Version uint8 |
1191 | + Reserved uint8 |
1192 | + Sequence uint8 |
1193 | + Class uint8 |
1194 | +} |
1195 | + |
1196 | +func (r *rmcpHeader) IsAck() bool { |
1197 | + return r.Class&0x80 != 0 |
1198 | +} |
1199 | + |
1200 | +type session struct { |
1201 | + AuthType uint8 |
1202 | + Seq uint32 |
1203 | + ID uint32 |
1204 | +} |
1205 | + |
1206 | +type ipmiHeader struct { |
1207 | + Len uint8 |
1208 | + RsAddr uint8 |
1209 | + NetFnRsLUN uint8 |
1210 | + Checksum uint8 |
1211 | + RqAddr uint8 |
1212 | + RqSeq uint8 |
1213 | + Cmd uint8 |
1214 | +} |
1215 | + |
1216 | +type Packet struct { |
1217 | + RMCPHeader rmcpHeader |
1218 | + Session session |
1219 | + AuthCode [16]byte |
1220 | + IPMIHeader ipmiHeader |
1221 | + Data []byte |
1222 | +} |
1223 | + |
1224 | +func (p *Packet) SetData(data interface{}) error { |
1225 | + if data == nil { |
1226 | + return nil |
1227 | + } |
1228 | + if encoder, ok := data.(encoding.BinaryMarshaler); ok { |
1229 | + buf, err := encoder.MarshalBinary() |
1230 | + if err != nil { |
1231 | + return err |
1232 | + } |
1233 | + p.Data = buf |
1234 | + return nil |
1235 | + } |
1236 | + byteBuf := &bytes.Buffer{} |
1237 | + err := binary.Write(byteBuf, binary.BigEndian, data) |
1238 | + if err != nil { |
1239 | + return err |
1240 | + } |
1241 | + p.Data = byteBuf.Bytes() |
1242 | + return nil |
1243 | +} |
1244 | + |
1245 | +func MarshalPacket(p *Packet) ([]byte, error) { |
1246 | + buf := &bytes.Buffer{} |
1247 | + |
1248 | + err := binary.Write(buf, binary.BigEndian, &p.RMCPHeader) |
1249 | + if err != nil { |
1250 | + return nil, err |
1251 | + } |
1252 | + |
1253 | + err = binary.Write(buf, binary.BigEndian, &p.Session) |
1254 | + if err != nil { |
1255 | + return nil, err |
1256 | + } |
1257 | + |
1258 | + if p.Session.AuthType != IPMIAuthTypeNone { |
1259 | + err = binary.Write(buf, binary.BigEndian, p.AuthCode) |
1260 | + if err != nil { |
1261 | + return nil, err |
1262 | + } |
1263 | + } |
1264 | + |
1265 | + p.IPMIHeader.Len = uint8(binary.Size(p.IPMIHeader) + len(p.Data)) |
1266 | + p.IPMIHeader.Checksum = computeChecksum(p.IPMIHeader.RsAddr, p.IPMIHeader.NetFnRsLUN) |
1267 | + err = binary.Write(buf, binary.BigEndian, &p.IPMIHeader) |
1268 | + if err != nil { |
1269 | + return nil, err |
1270 | + } |
1271 | + |
1272 | + _, err = buf.Write(p.Data) |
1273 | + if err != nil { |
1274 | + return nil, err |
1275 | + } |
1276 | + |
1277 | + payloadChecksum := computeChecksum(p.IPMIHeader.RqAddr, p.IPMIHeader.RqSeq, p.IPMIHeader.Cmd) + computeChecksum(p.Data...) |
1278 | + |
1279 | + err = binary.Write(buf, binary.BigEndian, payloadChecksum) |
1280 | + if err != nil { |
1281 | + return nil, err |
1282 | + } |
1283 | + |
1284 | + return buf.Bytes(), nil |
1285 | +} |
1286 | + |
1287 | +func UnmarshalPacket(buf []byte) (*Packet, error) { |
1288 | + if len(buf) < minPktSize { |
1289 | + return nil, fmt.Errorf("%w: too short", ErrInvalidPacket) |
1290 | + } |
1291 | + |
1292 | + pkt := &Packet{} |
1293 | + reader := bytes.NewReader(buf) |
1294 | + |
1295 | + err := binary.Read(reader, binary.BigEndian, &pkt.RMCPHeader) |
1296 | + if err != nil { |
1297 | + return nil, err |
1298 | + } |
1299 | + |
1300 | + err = binary.Read(reader, binary.BigEndian, &pkt.Session) |
1301 | + if err != nil { |
1302 | + return nil, err |
1303 | + } |
1304 | + if pkt.Session.AuthType != 0 { |
1305 | + err = binary.Read(reader, binary.BigEndian, pkt.AuthCode) |
1306 | + if err != nil { |
1307 | + return nil, err |
1308 | + } |
1309 | + } |
1310 | + |
1311 | + err = binary.Read(reader, binary.BigEndian, &pkt.IPMIHeader) |
1312 | + if err != nil { |
1313 | + return nil, err |
1314 | + } |
1315 | + |
1316 | + if computeChecksum(pkt.IPMIHeader.RsAddr, pkt.IPMIHeader.NetFnRsLUN) != pkt.IPMIHeader.Checksum { |
1317 | + return nil, fmt.Errorf("%w: checksum does not match", ErrInvalidPacket) |
1318 | + } |
1319 | + if pkt.IPMIHeader.Len <= 0 { |
1320 | + return nil, fmt.Errorf("%w: malformed message len", ErrInvalidPacket) |
1321 | + } |
1322 | + |
1323 | + dataLen := int(pkt.IPMIHeader.Len) - binary.Size(ipmiHeader{}) |
1324 | + data := make([]byte, dataLen+1) |
1325 | + _, err = reader.Read(data) |
1326 | + if err != nil { |
1327 | + return nil, err |
1328 | + } |
1329 | + |
1330 | + dataChecksum := data[len(data)-1] |
1331 | + localChecksum := computeChecksum( |
1332 | + pkt.IPMIHeader.RqAddr, |
1333 | + pkt.IPMIHeader.RqSeq, |
1334 | + pkt.IPMIHeader.Cmd, |
1335 | + ) + computeChecksum(data[:len(data)-1]...) |
1336 | + if dataChecksum != localChecksum { |
1337 | + return nil, fmt.Errorf("%w: checksum does not match", ErrInvalidPacket) |
1338 | + } |
1339 | + |
1340 | + pkt.Data = data[:len(data)-1] |
1341 | + return pkt, nil |
1342 | +} |
1343 | + |
1344 | +func Marshal(data interface{}) ([]byte, error) { |
1345 | + pkt := &Packet{} |
1346 | + pkt.SetData(data) |
1347 | + return MarshalPacket(pkt) |
1348 | +} |
1349 | + |
1350 | +func Unmarshal(buf []byte, data interface{}) error { |
1351 | + pkt, err := UnmarshalPacket(buf) |
1352 | + if err != nil { |
1353 | + return err |
1354 | + } |
1355 | + if decoder, ok := data.(encoding.BinaryUnmarshaler); ok { |
1356 | + return decoder.UnmarshalBinary(pkt.Data) |
1357 | + } |
1358 | + reader := bytes.NewReader(buf) |
1359 | + err = binary.Read(reader, binary.BigEndian, pkt.Data) |
1360 | + if err != nil { |
1361 | + return err |
1362 | + } |
1363 | + return nil |
1364 | +} |
1365 | + |
1366 | +type asfHeader struct { |
1367 | + IANANum uint32 |
1368 | + Type uint8 |
1369 | + Tag uint8 |
1370 | + Reserved uint8 |
1371 | + DataLen uint8 |
1372 | +} |
1373 | + |
1374 | +type ASFPong struct { |
1375 | + IANANum uint32 |
1376 | + OEM uint32 |
1377 | + Entities uint8 |
1378 | + SupportedInteractions uint8 |
1379 | + Reserved [6]uint8 |
1380 | +} |
1381 | + |
1382 | +type ASFPacket struct { |
1383 | + RMCPHeader rmcpHeader |
1384 | + ASFHeader asfHeader |
1385 | + Data []byte |
1386 | +} |
1387 | + |
1388 | +func (a *ASFPacket) SetData(data interface{}) (err error) { |
1389 | + if encoder, ok := data.(encoding.BinaryMarshaler); ok { |
1390 | + a.Data, err = encoder.MarshalBinary() |
1391 | + if err != nil { |
1392 | + return err |
1393 | + } |
1394 | + return nil |
1395 | + } |
1396 | + buf := &bytes.Buffer{} |
1397 | + err = binary.Write(buf, binary.BigEndian, data) |
1398 | + if err != nil { |
1399 | + return err |
1400 | + } |
1401 | + a.Data = buf.Bytes() |
1402 | + return nil |
1403 | +} |
1404 | + |
1405 | +func (a *ASFPacket) ValidatePong() error { |
1406 | + return nil |
1407 | +} |
1408 | + |
1409 | +func MarshalASFPacket(pkt *ASFPacket) []byte { |
1410 | + buf := &bytes.Buffer{} |
1411 | + binary.Write(buf, binary.BigEndian, pkt.RMCPHeader) |
1412 | + binary.Write(buf, binary.BigEndian, pkt.ASFHeader) |
1413 | + buf.Write(pkt.Data) |
1414 | + return buf.Bytes() |
1415 | +} |
1416 | + |
1417 | +func UnmarshalASFPacket(buf []byte) (*ASFPacket, error) { |
1418 | + hlen := binary.Size(rmcpHeader{}) + binary.Size(asfHeader{}) |
1419 | + if len(buf) < hlen { |
1420 | + return nil, fmt.Errorf("%w: too short", ErrInvalidPacket) |
1421 | + } |
1422 | + var pkt ASFPacket |
1423 | + reader := bytes.NewReader(buf) |
1424 | + fullLen := reader.Len() |
1425 | + err := binary.Read(reader, binary.BigEndian, &pkt.RMCPHeader) |
1426 | + if err != nil { |
1427 | + return nil, err |
1428 | + } |
1429 | + err = binary.Read(reader, binary.BigEndian, &pkt.ASFHeader) |
1430 | + remainder := fullLen - reader.Len() |
1431 | + if remainder > 0 { |
1432 | + copy(pkt.Data[:], buf[remainder:]) |
1433 | + } |
1434 | + return &pkt, nil |
1435 | +} |
1436 | diff --git a/src/rackd_spike/internal/machine_helpers/arp.go b/src/rackd_spike/internal/machine_helpers/arp.go |
1437 | new file mode 100644 |
1438 | index 0000000..3c9e6c5 |
1439 | --- /dev/null |
1440 | +++ b/src/rackd_spike/internal/machine_helpers/arp.go |
1441 | @@ -0,0 +1,39 @@ |
1442 | +package machinehelpers |
1443 | + |
1444 | +import ( |
1445 | + "context" |
1446 | + "errors" |
1447 | + "net" |
1448 | + "os/exec" |
1449 | + "strings" |
1450 | +) |
1451 | + |
1452 | +var ( |
1453 | + ErrIPNotFound = errors.New("ip not found for given mac address") |
1454 | +) |
1455 | + |
1456 | +func FindIPByArp(ctx context.Context, mac net.HardwareAddr) (net.IP, error) { |
1457 | + cmd := exec.CommandContext(ctx, "arp", "-n") |
1458 | + |
1459 | + out, err := cmd.CombinedOutput() |
1460 | + if err != nil { |
1461 | + return nil, err |
1462 | + } |
1463 | + |
1464 | + for _, line := range strings.Split(string(out), "\n") { |
1465 | + columns := strings.Split(line, " ") |
1466 | + var strippedColumns []string |
1467 | + for _, column := range columns { |
1468 | + column = strings.TrimSpace(column) |
1469 | + if column == "" { |
1470 | + continue |
1471 | + } |
1472 | + strippedColumns = append(strippedColumns, column) |
1473 | + } |
1474 | + columns = strippedColumns |
1475 | + if len(columns) == 5 && strings.ToLower(columns[2]) == strings.ToLower(mac.String()) { |
1476 | + return net.ParseIP(columns[0]), nil |
1477 | + } |
1478 | + } |
1479 | + return nil, ErrIPNotFound |
1480 | +} |
UNIT TESTS power_drivers lp:~cgrabowski/maas/+git/maas into -b rack_region_ exploration lp:~maas-committers/maas
-b rack_spike_
STATUS: SUCCESS c1e8f7f9dd3e580 286328bc36
COMMIT: 69f139c38789997