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
|
package netboot
import (
"errors"
"fmt"
"log"
"time"
"github.com/insomniacslk/dhcp/dhcpv6"
)
// RequestNetbootv6 sends a netboot request via DHCPv6 and returns the exchanged packets. Additional modifiers
// can be passed to manipulate both solicit and advertise packets.
func RequestNetbootv6(ifname string, timeout time.Duration, retries int, modifiers ...dhcpv6.Modifier) ([]dhcpv6.DHCPv6, error) {
var (
conversation []dhcpv6.DHCPv6
)
delay := 2 * time.Second
for i := 0; i <= retries; i++ {
log.Printf("sending request, attempt #%d", i+1)
solicit, err := dhcpv6.NewSolicitForInterface(ifname, modifiers...)
if err != nil {
return nil, fmt.Errorf("failed to create SOLICIT for interface %s: %v", ifname, err)
}
client := dhcpv6.NewClient()
client.ReadTimeout = timeout
// WithNetboot is added only later, to avoid applying it twice (one
// here and one in the above call to NewSolicitForInterface)
modifiers = append(modifiers, dhcpv6.WithNetboot)
conversation, err = client.Exchange(ifname, solicit, modifiers...)
if err != nil {
log.Printf("Client.Exchange failed: %v", err)
log.Printf("sleeping %v before retrying", delay)
if i >= retries {
// don't wait at the end of the last attempt
break
}
time.Sleep(delay)
// TODO add random splay
delay = delay * 2
continue
}
break
}
return conversation, nil
}
// ConversationToNetconf extracts network configuration and boot file URL from a
// DHCPv6 4-way conversation and returns them, or an error if any.
func ConversationToNetconf(conversation []dhcpv6.DHCPv6) (*NetConf, string, error) {
var reply dhcpv6.DHCPv6
for _, m := range conversation {
// look for a REPLY
if m.Type() == dhcpv6.MessageTypeReply {
reply = m
break
}
}
if reply == nil {
return nil, "", errors.New("no REPLY received")
}
netconf, err := GetNetConfFromPacketv6(reply.(*dhcpv6.DHCPv6Message))
if err != nil {
return nil, "", fmt.Errorf("cannot get netconf from packet: %v", err)
}
// look for boot file
var (
opt dhcpv6.Option
bootfile string
)
opt = reply.GetOneOption(dhcpv6.OptionBootfileURL)
if opt == nil {
log.Printf("no bootfile URL option found in REPLY, looking for it in ADVERTISE")
// as a fallback, look for bootfile URL in the advertise
var advertise dhcpv6.DHCPv6
for _, m := range conversation {
// look for an ADVERTISE
if m.Type() == dhcpv6.MessageTypeAdvertise {
advertise = m
break
}
}
if advertise == nil {
return nil, "", errors.New("no ADVERTISE found")
}
opt = advertise.GetOneOption(dhcpv6.OptionBootfileURL)
if opt == nil {
return nil, "", errors.New("no bootfile URL option found in ADVERTISE")
}
}
if opt != nil {
obf := opt.(*dhcpv6.OptBootFileURL)
bootfile = string(obf.BootFileURL)
}
return netconf, bootfile, nil
}
|