summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/nclient4/lease.go
blob: 8b1d9e6be67aa8fcf33c2fdcd34b722841f2cb3c (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
// This is lease support for nclient4

package nclient4

import (
	"context"
	"fmt"
	"net"
	"time"

	"github.com/insomniacslk/dhcp/dhcpv4"
)

// Lease contains a DHCPv4 lease after DORA.
// note: Lease doesn't include binding interface name
type Lease struct {
	Offer        *dhcpv4.DHCPv4
	ACK          *dhcpv4.DHCPv4
	CreationTime time.Time
}

// Release send DHCPv4 release messsage to server, based on specified lease.
// release is sent as unicast per RFC2131, section 4.4.4.
// Note: some DHCP server requries of using assigned IP address as source IP,
// use nclient4.WithUnicast to create client for such case.
func (c *Client) Release(lease *Lease, modifiers ...dhcpv4.Modifier) error {
	if lease == nil {
		return fmt.Errorf("lease is nil")
	}
	req, err := dhcpv4.NewReleaseFromACK(lease.ACK, modifiers...)
	if err != nil {
		return fmt.Errorf("fail to create release request,%w", err)
	}
	_, err = c.conn.WriteTo(req.ToBytes(), &net.UDPAddr{IP: lease.ACK.Options.Get(dhcpv4.OptionServerIdentifier), Port: ServerPort})
	if err == nil {
		c.logger.PrintMessage("sent message:", req)
	}
	return err
}

// Renew sends a DHCPv4 request to the server to renew the given lease. The renewal information is
// sourced from the initial offer in the lease, and the ACK of the lease is updated to the ACK of
// the latest renewal. This avoids issues with DHCP servers that omit information needed to build a
// completely new lease from their renewal ACK (such as the Windows DHCP Server).
func (c *Client) Renew(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Modifier) (*Lease, error) {
	if lease == nil {
		return nil, fmt.Errorf("lease is nil")
	}

	request, err := dhcpv4.NewRenewFromAck(lease.ACK, dhcpv4.PrependModifiers(modifiers,
		dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
	if err != nil {
		return nil, fmt.Errorf("unable to create a request: %w", err)
	}

	// Servers are supposed to only respond to Requests containing their server identifier,
	// but sometimes non-compliant servers respond anyway.
	// Clients are not required to validate this field, but servers are required to
	// include the server identifier in their Offer per RFC 2131 Section 4.3.1 Table 3.
	response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
		IsCorrectServer(lease.Offer.ServerIdentifier()),
		IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
	if err != nil {
		return nil, fmt.Errorf("got an error while processing the request: %w", err)
	}
	if response.MessageType() == dhcpv4.MessageTypeNak {
		return nil, &ErrNak{
			Offer: lease.Offer,
			Nak:   response,
		}
	}

	// Return a new lease with the latest ACK and updated creation time
	return &Lease{
		Offer:        lease.Offer,
		ACK:          response,
		CreationTime: time.Now(),
	}, nil
}