Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dhcpv4/dhcpv4.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,24 @@ func NewRenewFromAck(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
)...)
}

func NewRebindFromAck(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(ack),
WithMessageType(MessageTypeRequest),
// The client IP must be filled in with the IP offered to the client
WithClientIP(ack.YourIPAddr),
// The renewal request must use unicast
WithBroadcast(true),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
OptionServerIdentifier,
),
)...)
}

// NewReplyFromRequest builds a DHCPv4 reply from a request.
func NewReplyFromRequest(request *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
Expand Down
50 changes: 49 additions & 1 deletion dhcpv4/nclient4/lease.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,17 @@ func (c *Client) Renew(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Mo
return nil, fmt.Errorf("unable to create a request: %w", err)
}

// Force unicasting to the original DHCP server
destAddr := &net.UDPAddr{
IP: lease.ACK.Options.Get(dhcpv4.OptionServerIdentifier),
Port: ServerPort,
}

// 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(
response, err := c.SendAndRead(ctx, destAddr, request, IsAll(
IsCorrectServer(lease.Offer.ServerIdentifier()),
IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
if err != nil {
Expand All @@ -77,3 +83,45 @@ func (c *Client) Renew(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Mo
CreationTime: time.Now(),
}, nil
}

func (c *Client) Rebind(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Modifier) (*Lease, error) {
if lease == nil {
return nil, fmt.Errorf("lease is nil")
}

request, err := dhcpv4.NewRebindFromAck(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.
// For the matcher, the server identifier should be checked as another DHCP server
// could answer instead of the original one. The responding server is defacto the new
// authoritative DHCP for our client.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
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,
}
}

// Modify the initial offer, as we switched to another DHCP server.
// This allow further Renew or request to succeed with the new DHCP server.
lease.Offer.UpdateOption(dhcpv4.OptServerIdentifier(response.ServerIdentifier()))

// Return a new lease with the latest ACK and updated creation time
return &Lease{
Offer: lease.Offer,
ACK: response,
CreationTime: time.Now(),
}, nil
}
3 changes: 2 additions & 1 deletion dhcpv4/server4/server_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build go1.12
// +build go1.12

package server4
Expand Down Expand Up @@ -109,7 +110,7 @@ func TestServer(t *testing.T) {
require.Equal(t, ifaces[0].HardwareAddr, p.ClientHWAddr)
}

lease, err = c.Renew(context.Background(), lease, modifiers...)
lease, err = c.Rebind(context.Background(), lease, modifiers...)
require.NoError(t, err)
require.NotNil(t, lease.Offer)
require.NotNil(t, lease.ACK)
Expand Down
Loading