blob: f3f25aa2870778e74a8134512cc51df83c594722 [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"
21
22 "github.com/insomniacslk/dhcp/dhcpv4"
23 "github.com/insomniacslk/dhcp/dhcpv4/nclient4"
24 "github.com/vishvananda/netlink"
25 "go.uber.org/zap"
26)
27
28type dhcpClient struct {
29 reqC chan *dhcpStatusReq
30 logger *zap.Logger
31}
32
33func newDHCPClient(logger *zap.Logger) *dhcpClient {
34 return &dhcpClient{
35 logger: logger,
36 reqC: make(chan *dhcpStatusReq),
37 }
38}
39
40type dhcpStatusReq struct {
41 resC chan *dhcpv4.DHCPv4
42 wait bool
43}
44
45func (r *dhcpStatusReq) fulfill(p *dhcpv4.DHCPv4) {
46 go func() {
47 r.resC <- p
48 }()
49}
50
51func (c *dhcpClient) run(ctx context.Context, iface netlink.Link) {
52 // Channel updated with address once one gets assigned/updated
53 newC := make(chan *dhcpv4.DHCPv4)
54 // Status requests waiting for configuration
55 waiters := []*dhcpStatusReq{}
56
57 // Start lease acquisition
58 // TODO(q3k): actually maintain the lease instead of hoping we never get
59 // kicked off.
60 client, err := nclient4.New(iface.Attrs().Name)
61 if err != nil {
62 panic(err)
63 }
64 go func() {
65 _, ack, err := client.Request(ctx)
66 if err != nil {
67 c.logger.Error("DHCP lease request failed", zap.Error(err))
68 // TODO(q3k): implement retry logic with full state machine
69 }
70 newC <- ack
71 }()
72
73 // State machine
74 // Two implicit states: WAITING -> ASSIGNED
75 // We start at WAITING, once we get a current config we move to ASSIGNED
76 // Once this becomes more complex (ie. has to handle link state changes)
77 // this should grow into a real state machine.
78 var current *dhcpv4.DHCPv4
79 c.logger.Info("DHCP client WAITING")
80 for {
81 select {
82 case <-ctx.Done():
83 // TODO(q3k): don't leave waiters hanging
84 return
85
86 case cfg := <-newC:
87 current = cfg
88 c.logger.Info("DHCP client ASSIGNED", zap.String("ip", current.String()))
89 for _, w := range waiters {
90 w.fulfill(current)
91 }
92 waiters = []*dhcpStatusReq{}
93
94 case r := <-c.reqC:
95 if current != nil || !r.wait {
96 r.fulfill(current)
97 } else {
98 waiters = append(waiters, r)
99 }
100 }
101 }
102}
103
104func (c *dhcpClient) status(ctx context.Context, wait bool) (*dhcpv4.DHCPv4, error) {
105 resC := make(chan *dhcpv4.DHCPv4)
106 c.reqC <- &dhcpStatusReq{
107 resC: resC,
108 wait: wait,
109 }
110 select {
111 case <-ctx.Done():
112 return nil, ctx.Err()
113 case r := <-resC:
114 return r, nil
115 }
116}