blob: 67651a70eb4cca848fb87f91178cc02152c33800 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun56a7ae62020-10-29 11:03:30 +01002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun56a7ae62020-10-29 11:03:30 +01003
4package dhcp4c
5
6import (
7 "context"
8 "fmt"
9 "net"
10 "testing"
11 "time"
12
13 "github.com/cenkalti/backoff/v4"
14 "github.com/insomniacslk/dhcp/dhcpv4"
15 "github.com/stretchr/testify/assert"
16
Jan Schär07a39e22025-09-04 11:16:59 +020017 "source.monogon.dev/osbase/net/dhcp4c/transport"
Lorenz Brun56a7ae62020-10-29 11:03:30 +010018)
19
20type fakeTime struct {
21 time time.Time
22}
23
24func newFakeTime(t time.Time) *fakeTime {
25 return &fakeTime{
26 time: t,
27 }
28}
29
30func (ft *fakeTime) Now() time.Time {
31 return ft.time
32}
33
34func (ft *fakeTime) Advance(d time.Duration) {
35 ft.time = ft.time.Add(d)
36}
37
38type mockTransport struct {
39 sentPacket *dhcpv4.DHCPv4
40 sendError error
41 setDeadline time.Time
42 receivePackets []*dhcpv4.DHCPv4
43 receiveError error
44 receiveIdx int
45 closed bool
46}
47
48func (mt *mockTransport) sendPackets(pkts ...*dhcpv4.DHCPv4) {
49 mt.receiveIdx = 0
50 mt.receivePackets = pkts
51}
52
53func (mt *mockTransport) Open() error {
54 mt.closed = false
55 return nil
56}
57
58func (mt *mockTransport) Send(payload *dhcpv4.DHCPv4) error {
59 mt.sentPacket = payload
60 return mt.sendError
61}
62
63func (mt *mockTransport) Receive() (*dhcpv4.DHCPv4, error) {
64 if mt.receiveError != nil {
65 return nil, mt.receiveError
66 }
67 if len(mt.receivePackets) > mt.receiveIdx {
68 packet := mt.receivePackets[mt.receiveIdx]
69 packet, err := dhcpv4.FromBytes(packet.ToBytes()) // Clone packet
70 if err != nil {
71 panic("ToBytes => FromBytes failed")
72 }
73 packet.TransactionID = mt.sentPacket.TransactionID
74 mt.receiveIdx++
75 return packet, nil
76 }
Tim Windelschmidt51daf252024-04-18 23:18:43 +020077 return nil, transport.ErrDeadlineExceeded
Lorenz Brun56a7ae62020-10-29 11:03:30 +010078}
79
80func (mt *mockTransport) SetReceiveDeadline(t time.Time) error {
81 mt.setDeadline = t
82 return nil
83}
84
85func (mt *mockTransport) Close() error {
86 mt.closed = true
87 return nil
88}
89
90type unicastMockTransport struct {
91 mockTransport
92 serverIP net.IP
93 bindIP net.IP
94}
95
96func (umt *unicastMockTransport) Open(serverIP, bindIP net.IP) error {
97 if umt.serverIP != nil {
98 panic("double-open of unicast transport")
99 }
100 umt.serverIP = serverIP
101 umt.bindIP = bindIP
102 return nil
103}
104
105func (umt *unicastMockTransport) Close() error {
106 umt.serverIP = nil
107 umt.bindIP = nil
108 return umt.mockTransport.Close()
109}
110
111type mockBackoff struct {
112 indefinite bool
113 values []time.Duration
114 idx int
115}
116
117func newMockBackoff(vals []time.Duration, indefinite bool) *mockBackoff {
118 return &mockBackoff{values: vals, indefinite: indefinite}
119}
120
121func (mb *mockBackoff) NextBackOff() time.Duration {
122 if mb.idx < len(mb.values) || mb.indefinite {
123 val := mb.values[mb.idx%len(mb.values)]
124 mb.idx++
125 return val
126 }
127 return backoff.Stop
128}
129
130func (mb *mockBackoff) Reset() {
131 mb.idx = 0
132}
133
134func TestClient_runTransactionState(t *testing.T) {
135 ft := newFakeTime(time.Date(2020, 10, 28, 15, 02, 32, 352, time.UTC))
136 c := Client{
137 now: ft.Now,
138 iface: &net.Interface{MTU: 9324, HardwareAddr: net.HardwareAddr{0x12, 0x23, 0x34, 0x45, 0x56, 0x67}},
139 }
140 mt := &mockTransport{}
141 err := c.runTransactionState(transactionStateSpec{
142 ctx: context.Background(),
143 transport: mt,
144 backoff: newMockBackoff([]time.Duration{1 * time.Second}, true),
145 requestType: dhcpv4.MessageTypeDiscover,
146 setExtraOptions: func(msg *dhcpv4.DHCPv4) error {
147 msg.UpdateOption(dhcpv4.OptDomainName("just.testing.invalid"))
148 return nil
149 },
150 handleMessage: func(msg *dhcpv4.DHCPv4, sentTime time.Time) error {
151 return nil
152 },
153 stateDeadlineExceeded: func() error {
154 panic("shouldn't be called")
155 },
156 })
157 assert.NoError(t, err)
158 assert.Equal(t, "just.testing.invalid", mt.sentPacket.DomainName())
159 assert.Equal(t, dhcpv4.MessageTypeDiscover, mt.sentPacket.MessageType())
160}
161
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200162// TestAcceptableLease tests if a minimal valid lease is accepted by
163// acceptableLease
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100164func TestAcceptableLease(t *testing.T) {
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +0200165 var c Client
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100166 offer := &dhcpv4.DHCPv4{
167 OpCode: dhcpv4.OpcodeBootReply,
168 }
169 offer.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
170 offer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 1}))
171 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(10 * time.Second))
172 offer.YourIPAddr = net.IP{192, 0, 2, 2}
173 assert.True(t, c.acceptableLease(offer), "Valid lease is not acceptable")
174}
175
176type dhcpClientPuppet struct {
177 ft *fakeTime
178 bmt *mockTransport
179 umt *unicastMockTransport
180 c *Client
181}
182
183func newPuppetClient(initState state) *dhcpClientPuppet {
184 ft := newFakeTime(time.Date(2020, 10, 28, 15, 02, 32, 352, time.UTC))
185 bmt := &mockTransport{}
186 umt := &unicastMockTransport{}
187 c := &Client{
188 state: initState,
189 now: ft.Now,
190 iface: &net.Interface{MTU: 9324, HardwareAddr: net.HardwareAddr{0x12, 0x23, 0x34, 0x45, 0x56, 0x67}},
191 broadcastConn: bmt,
192 unicastConn: umt,
193 DiscoverBackoff: newMockBackoff([]time.Duration{1 * time.Second}, true),
194 AcceptOfferBackoff: newMockBackoff([]time.Duration{1 * time.Second, 2 * time.Second}, false),
195 RenewBackoff: newMockBackoff([]time.Duration{1 * time.Second}, true),
196 RebindBackoff: newMockBackoff([]time.Duration{1 * time.Second}, true),
197 }
198 return &dhcpClientPuppet{
199 ft: ft,
200 bmt: bmt,
201 umt: umt,
202 c: c,
203 }
204}
205
206func newResponse(m dhcpv4.MessageType) *dhcpv4.DHCPv4 {
207 o := &dhcpv4.DHCPv4{
208 OpCode: dhcpv4.OpcodeBootReply,
209 }
210 o.UpdateOption(dhcpv4.OptMessageType(m))
211 return o
212}
213
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200214// TestDiscoverOffer tests if the DHCP state machine in discovering state
215// properly selects the first valid lease and transitions to requesting state
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100216func TestDiscoverRequesting(t *testing.T) {
217 p := newPuppetClient(stateDiscovering)
218
219 // A minimal valid lease
220 offer := newResponse(dhcpv4.MessageTypeOffer)
221 testIP := net.IP{192, 0, 2, 2}
222 offer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 1}))
223 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(10 * time.Second))
224 offer.YourIPAddr = testIP
225
226 // Intentionally bad offer with no lease time.
227 terribleOffer := newResponse(dhcpv4.MessageTypeOffer)
228 terribleOffer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 2}))
229 terribleOffer.YourIPAddr = net.IPv4(192, 0, 2, 2)
230
231 // Send the bad offer first, then the valid offer
232 p.bmt.sendPackets(terribleOffer, offer)
233
234 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100235 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100236 }
237 assert.Equal(t, stateRequesting, p.c.state, "DHCP client didn't process offer")
238 assert.Equal(t, testIP, p.c.offer.YourIPAddr, "DHCP client requested invalid offer")
239}
240
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200241// TestOfferBound tests if the DHCP state machine in requesting state processes
242// a valid DHCPACK and transitions to bound state.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100243func TestRequestingBound(t *testing.T) {
244 p := newPuppetClient(stateRequesting)
245
246 offer := newResponse(dhcpv4.MessageTypeAck)
247 testIP := net.IP{192, 0, 2, 2}
248 offer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 1}))
249 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(10 * time.Second))
250 offer.YourIPAddr = testIP
251
252 p.bmt.sendPackets(offer)
253 p.c.offer = offer
Jan Schärba404a62024-07-11 10:46:27 +0200254 p.c.LeaseCallback = func(lease *Lease) error {
255 assert.Equal(t, testIP, lease.AssignedIP, "new lease has wrong IP")
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100256 return nil
257 }
258
259 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100260 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100261 }
262 assert.Equal(t, stateBound, p.c.state, "DHCP client didn't process offer")
263 assert.Equal(t, testIP, p.c.lease.YourIPAddr, "DHCP client requested invalid offer")
264}
265
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200266// TestRequestingDiscover tests if the DHCP state machine in requesting state
267// transitions back to discovering if it takes too long to get a valid DHCPACK.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100268func TestRequestingDiscover(t *testing.T) {
269 p := newPuppetClient(stateRequesting)
270
271 offer := newResponse(dhcpv4.MessageTypeOffer)
272 testIP := net.IP{192, 0, 2, 2}
273 offer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 1}))
274 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(10 * time.Second))
275 offer.YourIPAddr = testIP
276 p.c.offer = offer
277
278 for i := 0; i < 10; i++ {
279 p.bmt.sendPackets()
280 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100281 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100282 }
283 assert.Equal(t, dhcpv4.MessageTypeRequest, p.bmt.sentPacket.MessageType(), "Invalid message type for requesting")
284 if p.c.state == stateDiscovering {
285 break
286 }
287 p.ft.time = p.bmt.setDeadline
288
289 if i == 9 {
290 t.Fatal("Too many tries while requesting, backoff likely wrong")
291 }
292 }
293 assert.Equal(t, stateDiscovering, p.c.state, "DHCP client didn't switch back to offer after requesting expired")
294}
295
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200296// TestDiscoverRapidCommit tests if the DHCP state machine in discovering state
297// transitions directly to bound if a rapid commit response (DHCPACK) is
298// received.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100299func TestDiscoverRapidCommit(t *testing.T) {
300 testIP := net.IP{192, 0, 2, 2}
301 offer := newResponse(dhcpv4.MessageTypeAck)
302 offer.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 0, 2, 1}))
303 leaseTime := 10 * time.Second
304 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(leaseTime))
305 offer.YourIPAddr = testIP
306
307 p := newPuppetClient(stateDiscovering)
Jan Schärba404a62024-07-11 10:46:27 +0200308 p.c.LeaseCallback = func(lease *Lease) error {
309 assert.Equal(t, testIP, lease.AssignedIP, "callback called with wrong IP")
310 assert.Equal(t, p.ft.Now().Add(leaseTime), lease.ExpiresAt, "invalid ExpiresAt")
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100311 return nil
312 }
313 p.bmt.sendPackets(offer)
314 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100315 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100316 }
317 assert.Equal(t, stateBound, p.c.state, "DHCP client didn't process offer")
318 assert.Equal(t, testIP, p.c.lease.YourIPAddr, "DHCP client requested invalid offer")
319 assert.Equal(t, 5*time.Second, p.c.leaseBoundDeadline.Sub(p.ft.Now()), "Renewal time was incorrectly defaulted")
320}
321
322type TestOption uint8
323
324func (o TestOption) Code() uint8 {
325 return uint8(o) + 224 // Private options
326}
327func (o TestOption) String() string {
328 return fmt.Sprintf("Test Option %d", uint8(o))
329}
330
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200331// TestBoundRenewingBound tests if the DHCP state machine in bound correctly
332// transitions to renewing after leaseBoundDeadline expires, sends a
333// DHCPREQUEST and after it gets a DHCPACK response calls LeaseCallback and
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100334// transitions back to bound with correct new deadlines.
335func TestBoundRenewingBound(t *testing.T) {
336 offer := newResponse(dhcpv4.MessageTypeAck)
337 testIP := net.IP{192, 0, 2, 2}
338 serverIP := net.IP{192, 0, 2, 1}
339 offer.UpdateOption(dhcpv4.OptServerIdentifier(serverIP))
340 leaseTime := 10 * time.Second
341 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(leaseTime))
342 offer.YourIPAddr = testIP
343
344 p := newPuppetClient(stateBound)
345 p.umt.Open(serverIP, testIP)
346 p.c.lease, _ = dhcpv4.FromBytes(offer.ToBytes())
347 // Other deadlines are intentionally empty to make sure they aren't used
348 p.c.leaseRenewDeadline = p.ft.Now().Add(8500 * time.Millisecond)
349 p.c.leaseBoundDeadline = p.ft.Now().Add(5000 * time.Millisecond)
350
351 p.ft.Advance(5*time.Second - 5*time.Millisecond)
352 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100353 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100354 }
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200355 // We cannot intercept time.After so we just advance the clock by the time slept
356 p.ft.Advance(5 * time.Millisecond)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100357 assert.Equal(t, stateRenewing, p.c.state, "DHCP client not renewing")
358 offer.UpdateOption(dhcpv4.OptGeneric(TestOption(1), []byte{0x12}))
359 p.umt.sendPackets(offer)
Jan Schärba404a62024-07-11 10:46:27 +0200360 p.c.LeaseCallback = func(lease *Lease) error {
361 assert.Equal(t, testIP, lease.AssignedIP, "callback called with wrong IP")
362 assert.Equal(t, p.ft.Now().Add(leaseTime), lease.ExpiresAt, "invalid ExpiresAt")
363 assert.Equal(t, []byte{0x12}, lease.Options.Get(TestOption(1)), "renewal didn't add new option")
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100364 return nil
365 }
366 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100367 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100368 }
369 assert.Equal(t, stateBound, p.c.state, "DHCP client didn't renew")
370 assert.Equal(t, p.ft.Now().Add(leaseTime), p.c.leaseDeadline, "lease deadline not updated")
371 assert.Equal(t, dhcpv4.MessageTypeRequest, p.umt.sentPacket.MessageType(), "Invalid message type for renewal")
372}
373
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200374// TestRenewingRebinding tests if the DHCP state machine in renewing state
375// correctly sends DHCPREQUESTs and transitions to the rebinding state when it
376// hasn't received a valid response until the deadline expires.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100377func TestRenewingRebinding(t *testing.T) {
378 offer := newResponse(dhcpv4.MessageTypeAck)
379 testIP := net.IP{192, 0, 2, 2}
380 serverIP := net.IP{192, 0, 2, 1}
381 offer.UpdateOption(dhcpv4.OptServerIdentifier(serverIP))
382 leaseTime := 10 * time.Second
383 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(leaseTime))
384 offer.YourIPAddr = testIP
385
386 p := newPuppetClient(stateRenewing)
387 p.umt.Open(serverIP, testIP)
388 p.c.lease, _ = dhcpv4.FromBytes(offer.ToBytes())
389 // Other deadlines are intentionally empty to make sure they aren't used
390 p.c.leaseRenewDeadline = p.ft.Now().Add(8500 * time.Millisecond)
391 p.c.leaseDeadline = p.ft.Now().Add(10000 * time.Millisecond)
392
393 startTime := p.ft.Now()
394 p.ft.Advance(5 * time.Second)
395
Jan Schärba404a62024-07-11 10:46:27 +0200396 p.c.LeaseCallback = func(*Lease) error {
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100397 t.Fatal("Lease callback called without valid offer")
398 return nil
399 }
400
401 for i := 0; i < 10; i++ {
402 p.umt.sendPackets()
403 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100404 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100405 }
406 assert.Equal(t, dhcpv4.MessageTypeRequest, p.umt.sentPacket.MessageType(), "Invalid message type for renewal")
407 p.ft.time = p.umt.setDeadline
408
409 if p.c.state == stateRebinding {
410 break
411 }
412 if i == 9 {
413 t.Fatal("Too many tries while renewing, backoff likely wrong")
414 }
415 }
416 assert.Equal(t, startTime.Add(8500*time.Millisecond), p.umt.setDeadline, "wrong listen deadline when renewing")
417 assert.Equal(t, stateRebinding, p.c.state, "DHCP client not renewing")
418 assert.False(t, p.bmt.closed)
419 assert.True(t, p.umt.closed)
420}
421
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200422// TestRebindingBound tests if the DHCP state machine in rebinding state sends
423// DHCPREQUESTs to the network and if it receives a valid DHCPACK correctly
424// transitions back to bound state.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100425func TestRebindingBound(t *testing.T) {
426 offer := newResponse(dhcpv4.MessageTypeAck)
427 testIP := net.IP{192, 0, 2, 2}
428 serverIP := net.IP{192, 0, 2, 1}
429 offer.UpdateOption(dhcpv4.OptServerIdentifier(serverIP))
430 leaseTime := 10 * time.Second
431 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(leaseTime))
432 offer.YourIPAddr = testIP
433
434 p := newPuppetClient(stateRebinding)
435 p.c.lease, _ = dhcpv4.FromBytes(offer.ToBytes())
436 // Other deadlines are intentionally empty to make sure they aren't used
437 p.c.leaseDeadline = p.ft.Now().Add(10000 * time.Millisecond)
438
439 p.ft.Advance(9 * time.Second)
440 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100441 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100442 }
443 assert.Equal(t, dhcpv4.MessageTypeRequest, p.bmt.sentPacket.MessageType(), "DHCP rebind sent invalid message type")
444 assert.Equal(t, stateRebinding, p.c.state, "DHCP client transferred out of rebinding state without trigger")
445 offer.UpdateOption(dhcpv4.OptGeneric(TestOption(1), []byte{0x12})) // Mark answer
446 p.bmt.sendPackets(offer)
447 p.bmt.sentPacket = nil
Jan Schärba404a62024-07-11 10:46:27 +0200448 p.c.LeaseCallback = func(lease *Lease) error {
449 assert.Equal(t, testIP, lease.AssignedIP, "callback called with wrong IP")
450 assert.Equal(t, p.ft.Now().Add(leaseTime), lease.ExpiresAt, "invalid ExpiresAt")
451 assert.Equal(t, []byte{0x12}, lease.Options.Get(TestOption(1)), "renewal didn't add new option")
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100452 return nil
453 }
454 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100455 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100456 }
457 assert.Equal(t, dhcpv4.MessageTypeRequest, p.bmt.sentPacket.MessageType())
458 assert.Equal(t, stateBound, p.c.state, "DHCP client didn't go back to bound")
459}
460
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200461// TestRebindingBound tests if the DHCP state machine in rebinding state
462// transitions to discovering state if leaseDeadline expires and calls
463// LeaseCallback with an empty new lease.
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100464func TestRebindingDiscovering(t *testing.T) {
465 offer := newResponse(dhcpv4.MessageTypeAck)
466 testIP := net.IP{192, 0, 2, 2}
467 serverIP := net.IP{192, 0, 2, 1}
468 offer.UpdateOption(dhcpv4.OptServerIdentifier(serverIP))
469 leaseTime := 10 * time.Second
470 offer.UpdateOption(dhcpv4.OptIPAddressLeaseTime(leaseTime))
471 offer.YourIPAddr = testIP
472
473 p := newPuppetClient(stateRebinding)
474 p.c.lease, _ = dhcpv4.FromBytes(offer.ToBytes())
475 // Other deadlines are intentionally empty to make sure they aren't used
476 p.c.leaseDeadline = p.ft.Now().Add(10000 * time.Millisecond)
477
478 p.ft.Advance(9 * time.Second)
Jan Schärba404a62024-07-11 10:46:27 +0200479 p.c.LeaseCallback = func(lease *Lease) error {
480 assert.Nil(t, lease, "transition to discovering didn't clear new lease on callback")
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100481 return nil
482 }
483 for i := 0; i < 10; i++ {
484 p.bmt.sendPackets()
485 p.bmt.sentPacket = nil
486 if err := p.c.runState(context.Background()); err != nil {
Tim Windelschmidtd0d5d9d2025-03-26 22:07:11 +0100487 t.Fatal(err)
Lorenz Brun56a7ae62020-10-29 11:03:30 +0100488 }
489 if p.c.state == stateDiscovering {
490 assert.Nil(t, p.bmt.sentPacket)
491 break
492 }
493 assert.Equal(t, dhcpv4.MessageTypeRequest, p.bmt.sentPacket.MessageType(), "Invalid message type for rebind")
494 p.ft.time = p.bmt.setDeadline
495 if i == 9 {
496 t.Fatal("Too many tries while rebinding, backoff likely wrong")
497 }
498 }
499 assert.Nil(t, p.c.lease, "Lease not zeroed on transition to discovering")
500 assert.Equal(t, stateDiscovering, p.c.state, "DHCP client didn't transition to discovering after loosing lease")
501}