summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/client.go
blob: 145a7ef9a9b40d49fb2a1025d5047a529cc2816e (plain)
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
}