forked from lclarkmichalek/etcdhcp
/
dhcp.go
130 lines (114 loc) · 3.42 KB
/
dhcp.go
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
package main
import (
"context"
"net"
"time"
"github.com/golang/glog"
dhcp "github.com/krolaw/dhcp4"
"github.com/pkg/errors"
etcd "go.etcd.io/etcd/clientv3"
)
type DHCPHandler struct {
client *etcd.Client
prefix string
timeout time.Duration
iface string
ip net.IP
options dhcp.Options
start net.IP
end net.IP
leaseDuration time.Duration
conflictDetector *ConflictDetector
}
func (h *DHCPHandler) ServeDHCP(p dhcp.Packet, msgType dhcp.MessageType, options dhcp.Options) dhcp.Packet {
ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
defer cancel()
switch msgType {
case dhcp.Discover:
glog.Infof("handling discover")
nic := p.CHAddr().String()
ip, err := h.handleDiscover(ctx, nic)
if err != nil {
glog.Errorf("failed to respond to discover request: %v", err)
return nil
}
glog.Infof("offering %v to %v", ip, nic)
return dhcp.ReplyPacket(p, dhcp.Offer, h.ip, ip, h.leaseDuration,
h.options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList]))
case dhcp.Request:
if server, ok := options[dhcp.OptionServerIdentifier]; ok && !net.IP(server).Equal(h.ip) {
return nil // Message not for this dhcp server
}
reqIP := net.IP(options[dhcp.OptionRequestedIPAddress])
if reqIP == nil {
reqIP = net.IP(p.CIAddr())
}
nic := p.CHAddr().String()
ip, err := h.handleRequest(ctx, reqIP, nic)
if err != nil {
glog.Errorf("could not lease: %v", err)
return dhcp.ReplyPacket(p, dhcp.NAK, h.ip, nil, 0, nil)
}
opts := p.ParseOptions()
glog.V(2).Infof("client provided options %v", opts)
if h.recordClientInfo(ctx, nic, clientInfo(opts)) != nil {
glog.Warningf("error recording client options for %v", nic)
}
glog.Infof("leased %v to %v", reqIP, nic)
return dhcp.ReplyPacket(p, dhcp.ACK, h.ip, ip, h.leaseDuration,
h.options.SelectOrderOrAll(options[dhcp.OptionParameterRequestList]))
case dhcp.Release, dhcp.Decline:
err := h.revokeLease(ctx, p.CHAddr().String())
if err != nil {
glog.Errorf("could not revoke lease for %v: %v", p.CHAddr().String(), err)
}
}
return nil
}
func (h *DHCPHandler) handleDiscover(ctx context.Context, nic string) (net.IP, error) {
ip, err := h.nicLeasedIP(ctx, nic)
if err != nil {
return nil, errors.Wrap(err, "could not lookup existing nic lease")
}
var filter []net.IP
if ip != nil {
if h.wouldConflict(ctx, nic, ip) {
filter = append(filter, ip)
} else {
glog.Infof("found previous lease for %v", nic)
return ip, nil
}
}
for {
ip, err = h.freeIP(ctx, filter)
if err != nil {
return nil, errors.Wrap(err, "could not find next free ip")
}
if h.wouldConflict(ctx, nic, ip) {
filter = append(filter, ip)
continue
}
return ip, nil
}
}
func (h *DHCPHandler) wouldConflict(ctx context.Context, nic string, ip net.IP) bool {
if h.conflictDetector == nil {
return false
}
mac, err := net.ParseMAC(nic)
if err != nil {
glog.Fatalf("could not parse MAC that we String()'d ourselves %q: %v", nic, err)
}
return h.conflictDetector.WouldConflict(ctx, ip, mac)
}
func (h *DHCPHandler) handleRequest(ctx context.Context, ip net.IP, nic string) (net.IP, error) {
glog.Infof("handling request for %v from %v", ip, nic)
if len(ip) != 4 || ip.Equal(net.IPv4zero) {
return nil, errors.New("invalid ip requested")
}
err := h.leaseIP(ctx, ip, nic, h.leaseDuration*2)
if err != nil {
return nil, errors.Wrap(err, "could not update lease")
}
return ip, nil
}