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
|
package netboot
import (
"errors"
"fmt"
"log"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/client4"
"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/dhcpv6/client6"
)
var sleeper = func(d time.Duration) {
time.Sleep(d)
}
// 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
err error
)
modifiers = append(modifiers, dhcpv6.WithNetboot)
delay := 2 * time.Second
for i := 0; i <= retries; i++ {
log.Printf("sending request, attempt #%d", i+1)
client := client6.NewClient()
client.ReadTimeout = timeout
conversation, err = client.Exchange(ifname, modifiers...)
if err != nil {
log.Printf("Client.Exchange failed: %v", err)
if i >= retries {
// don't wait at the end of the last attempt
return nil, fmt.Errorf("netboot failed after %d attempts: %v", retries+1, err)
}
log.Printf("sleeping %v before retrying", delay)
sleeper(delay)
// TODO add random splay
delay = delay * 2
continue
}
break
}
return conversation, nil
}
// RequestNetbootv4 sends a netboot request via DHCPv4 and returns the exchanged packets. Additional modifiers
// can be passed to manipulate both the discover and offer packets.
func RequestNetbootv4(ifname string, timeout time.Duration, retries int, modifiers ...dhcpv4.Modifier) ([]*dhcpv4.DHCPv4, error) {
var (
conversation []*dhcpv4.DHCPv4
err error
)
delay := 2 * time.Second
modifiers = append(modifiers, dhcpv4.WithNetboot)
for i := 0; i <= retries; i++ {
log.Printf("sending request, attempt #%d", i+1)
client := client4.NewClient()
client.ReadTimeout = timeout
conversation, err = client.Exchange(ifname, 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
}
sleeper(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.Message))
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
}
// ConversationToNetconfv4 extracts network configuration and boot file URL from a
// DHCPv4 4-way conversation and returns them, or an error if any.
func ConversationToNetconfv4(conversation []*dhcpv4.DHCPv4) (*NetConf, string, error) {
var reply *dhcpv4.DHCPv4
var bootFileURL string
for _, m := range conversation {
// look for a BootReply packet of type Offer containing the bootfile URL.
// Normally both packets with Message Type OFFER or ACK do contain
// the bootfile URL.
if m.OpCode == dhcpv4.OpcodeBootReply && m.MessageType() == dhcpv4.MessageTypeOffer {
bootFileURL = m.BootFileName
reply = m
break
}
}
if reply == nil {
return nil, "", errors.New("no OFFER with valid bootfile URL received")
}
netconf, err := GetNetConfFromPacketv4(reply)
if err != nil {
return nil, "", fmt.Errorf("could not get netconf: %v", err)
}
return netconf, bootFileURL, nil
}
|