blob: 9a2ba8c0ba30e5e296e997691fac93a163cc660e [file] [log] [blame]
Serge Bazanskicdb8c782020-02-17 12:34:02 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package network
18
19import (
20 "context"
Serge Bazanski1a5a6672020-02-18 10:09:43 +010021 "fmt"
22 "net"
Serge Bazanskicdb8c782020-02-17 12:34:02 +010023
24 "github.com/insomniacslk/dhcp/dhcpv4"
25 "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
26 "github.com/vishvananda/netlink"
27 "go.uber.org/zap"
28)
29
30type dhcpClient struct {
31 reqC chan *dhcpStatusReq
32 logger *zap.Logger
33}
34
35func newDHCPClient(logger *zap.Logger) *dhcpClient {
36 return &dhcpClient{
37 logger: logger,
38 reqC: make(chan *dhcpStatusReq),
39 }
40}
41
42type dhcpStatusReq struct {
Serge Bazanski1a5a6672020-02-18 10:09:43 +010043 resC chan *dhcpStatus
Serge Bazanskicdb8c782020-02-17 12:34:02 +010044 wait bool
45}
46
Serge Bazanski1a5a6672020-02-18 10:09:43 +010047func (r *dhcpStatusReq) fulfill(s *dhcpStatus) {
Serge Bazanskicdb8c782020-02-17 12:34:02 +010048 go func() {
Serge Bazanski1a5a6672020-02-18 10:09:43 +010049 r.resC <- s
Serge Bazanskicdb8c782020-02-17 12:34:02 +010050 }()
51}
52
Serge Bazanski1a5a6672020-02-18 10:09:43 +010053// dhcpStatus is the IPv4 configuration provisioned via DHCP for a given interface. It does not necessarily represent
54// a configuration that is active or even valid.
55type dhcpStatus struct {
56 // address is 'our' (the node's) IPv4 address on the network.
57 address net.IPNet
58 // gateway is the default gateway/router of this network, or 0.0.0.0 if none was given.
59 gateway net.IP
60 // dns is a list of IPv4 DNS servers to use.
61 dns []net.IP
62}
63
64func (s *dhcpStatus) String() string {
65 return fmt.Sprintf("Address: %s, Gateway: %s, DNS: %v", s.address.String(), s.gateway.String(), s.dns)
66}
67
Serge Bazanskicdb8c782020-02-17 12:34:02 +010068func (c *dhcpClient) run(ctx context.Context, iface netlink.Link) {
69 // Channel updated with address once one gets assigned/updated
Serge Bazanski1a5a6672020-02-18 10:09:43 +010070 newC := make(chan *dhcpStatus)
Serge Bazanskicdb8c782020-02-17 12:34:02 +010071 // Status requests waiting for configuration
72 waiters := []*dhcpStatusReq{}
73
74 // Start lease acquisition
75 // TODO(q3k): actually maintain the lease instead of hoping we never get
76 // kicked off.
77 client, err := nclient4.New(iface.Attrs().Name)
78 if err != nil {
79 panic(err)
80 }
81 go func() {
82 _, ack, err := client.Request(ctx)
83 if err != nil {
84 c.logger.Error("DHCP lease request failed", zap.Error(err))
85 // TODO(q3k): implement retry logic with full state machine
86 }
Serge Bazanski1a5a6672020-02-18 10:09:43 +010087 newC <- parseAck(ack)
Serge Bazanskicdb8c782020-02-17 12:34:02 +010088 }()
89
90 // State machine
91 // Two implicit states: WAITING -> ASSIGNED
92 // We start at WAITING, once we get a current config we move to ASSIGNED
93 // Once this becomes more complex (ie. has to handle link state changes)
94 // this should grow into a real state machine.
Serge Bazanski1a5a6672020-02-18 10:09:43 +010095 var current *dhcpStatus
Serge Bazanskicdb8c782020-02-17 12:34:02 +010096 c.logger.Info("DHCP client WAITING")
97 for {
98 select {
99 case <-ctx.Done():
100 // TODO(q3k): don't leave waiters hanging
101 return
102
103 case cfg := <-newC:
104 current = cfg
105 c.logger.Info("DHCP client ASSIGNED", zap.String("ip", current.String()))
106 for _, w := range waiters {
107 w.fulfill(current)
108 }
109 waiters = []*dhcpStatusReq{}
110
111 case r := <-c.reqC:
112 if current != nil || !r.wait {
113 r.fulfill(current)
114 } else {
115 waiters = append(waiters, r)
116 }
117 }
118 }
119}
120
Serge Bazanski1a5a6672020-02-18 10:09:43 +0100121// parseAck turns an internal status (from the dhcpv4 library) into a dhcpStatus
122func parseAck(ack *dhcpv4.DHCPv4) *dhcpStatus {
123 address := net.IPNet{IP: ack.YourIPAddr, Mask: ack.SubnetMask()}
124
125 // DHCP routers are optional - if none are provided, assume no router and set gateway to 0.0.0.0
126 // (this makes gateway.IsUnspecified() == true)
127 gateway, _, _ := net.ParseCIDR("0.0.0.0/0")
128 if routers := ack.Router(); len(routers) > 0 {
129 gateway = routers[0]
130 }
131 return &dhcpStatus{
132 address: address,
133 gateway: gateway,
134 dns: ack.DNS(),
135 }
136}
137
138// status returns the DHCP configuration requested from us by the local DHCP server.
139// If wait is true, this function will block until a DHCP configuration is available. Otherwise, a nil status may be
140// returned to indicate that no configuration has been received yet.
141func (c *dhcpClient) status(ctx context.Context, wait bool) (*dhcpStatus, error) {
142 resC := make(chan *dhcpStatus)
Serge Bazanskicdb8c782020-02-17 12:34:02 +0100143 c.reqC <- &dhcpStatusReq{
144 resC: resC,
145 wait: wait,
146 }
147 select {
148 case <-ctx.Done():
149 return nil, ctx.Err()
150 case r := <-resC:
151 return r, nil
152 }
153}