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
|
package dhcpv4
import (
"encoding/binary"
"golang.org/x/net/ipv4"
"net"
"syscall"
"time"
)
const (
maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
)
type Client struct {
Network string
Dialer *net.Dialer
Timeout time.Duration
}
func makeRawBroadcastPacket(payload []byte) ([]byte, error) {
udp := make([]byte, 8)
binary.BigEndian.PutUint16(udp[:2], ClientPort)
binary.BigEndian.PutUint16(udp[2:4], ServerPort)
binary.BigEndian.PutUint16(udp[4:6], uint16(8+len(payload)))
binary.BigEndian.PutUint16(udp[6:8], 0) // try to offload the checksum
h := ipv4.Header{
Version: 4,
Len: 20,
TotalLen: 20 + len(udp) + len(payload),
TTL: 64,
Protocol: 17, // UDP
Dst: net.IPv4bcast,
Src: net.IPv4zero,
}
ret, err := h.Marshal()
if err != nil {
return nil, err
}
ret = append(ret, udp...)
ret = append(ret, payload...)
return ret, nil
}
// Run a full DORA transaction: Discovery, Offer, Request, Acknowledge, over
// UDP. Does not retry in case of failures.
// Returns a list of DHCPv4 structures representing the exchange. It can contain
// up to four elements, ordered as Discovery, Offer, Request and Acknowledge.
// In case of errors, an error is returned, and the list of DHCPv4 objects will
// be shorted than 4, containing all the sent and received DHCPv4 messages.
func (c *Client) Exchange(ifname string, d *DHCPv4) ([]DHCPv4, error) {
conversation := make([]DHCPv4, 1)
var err error
// Discovery
if d == nil {
d, err = NewDiscoveryForInterface(ifname)
}
conversation[0] = *d
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
if err != nil {
return conversation, err
}
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if err != nil {
return conversation, err
}
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1)
if err != nil {
return conversation, err
}
err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1)
if err != nil {
return conversation, err
}
err = syscall.BindToDevice(fd, ifname)
if err != nil {
return conversation, err
}
daddr := syscall.SockaddrInet4{Port: ClientPort, Addr: [4]byte{255, 255, 255, 255}}
packet, err := makeRawBroadcastPacket(d.ToBytes())
if err != nil {
return conversation, err
}
err = syscall.Sendto(fd, packet, 0, &daddr)
if err != nil {
return conversation, err
}
// Offer
conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: ClientPort})
if err != nil {
return conversation, err
}
defer conn.Close()
buf := make([]byte, maxUDPReceivedPacketSize)
oobdata := []byte{} // ignoring oob data
n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata)
offer, err := FromBytes(buf[:n])
if err != nil {
return conversation, err
}
// TODO match the packet content
// TODO check that the peer address matches the declared server IP and port
conversation = append(conversation, *offer)
// Request
request, err := RequestFromOffer(*offer)
if err != nil {
return conversation, err
}
conversation = append(conversation, *request)
packet, err = makeRawBroadcastPacket(request.ToBytes())
if err != nil {
return conversation, err
}
err = syscall.Sendto(fd, packet, 0, &daddr)
if err != nil {
return conversation, err
}
// Acknowledge
buf = make([]byte, maxUDPReceivedPacketSize)
n, _, _, _, err = conn.ReadMsgUDP(buf, oobdata)
acknowledge, err := FromBytes(buf[:n])
if err != nil {
return conversation, err
}
// TODO match the packet content
// TODO check that the peer address matches the declared server IP and port
conversation = append(conversation, *acknowledge)
return conversation, nil
}
|