add DHCP lease renewal at T/2

- Add Renew() to dhcp.Client: sends REQUEST with ciaddr (RENEWING state)
- Start renewal goroutine in session at lease_time/2
- On IP change: flush TAP, reconfigure address/routes/DNS/policy routes
- On renewal failure: retry at T/4 (min 60s)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Git Sagar 2026-06-06 22:05:34 +05:30
parent 61237283f5
commit 6416159164
3 changed files with 105 additions and 0 deletions

View file

@ -150,6 +150,49 @@ func (c *Client) Run(sendFrame func([]byte) error, timeout time.Duration) (*Leas
return lease, nil
}
// Renew sends a DHCP REQUEST to renew the current lease.
// In RENEWING state: ciaddr = current IP, no requested-IP or server-ID options.
// Returns the renewed lease on ACK, or error on NAK/timeout.
func (c *Client) Renew(currentIP net.IP, sendFrame func([]byte) error, timeout time.Duration) (*Lease, error) {
var ciaddr [4]byte
copy(ciaddr[:], currentIP.To4())
opts := []dhcpOption{
{optMessageType, []byte{dhcpRequest}},
{optParamRequest, []byte{optSubnetMask, optRouter, optDNS, optLeaseTime, optClasslessRoutes, optMSClasslessRoutes}},
}
frame := c.buildFrame(opts, ciaddr, nil)
if err := sendFrame(frame); err != nil {
return nil, fmt.Errorf("send renew: %w", err)
}
ack, err := c.waitForType(dhcpAck, timeout)
if err != nil {
return nil, fmt.Errorf("waiting for renew ack: %w", err)
}
lease := &Lease{
ClientIP: net.IP(ack.yiaddr[:]).To4(),
ServerIP: ack.getOptionIP(optServerID),
SubnetMask: net.IPMask(ack.getOptionRaw(optSubnetMask)),
Gateway: ack.getOptionIP(optRouter),
DNS: ack.getOptionIPs(optDNS),
}
if lt := ack.getOptionUint32(optLeaseTime); lt > 0 {
lease.LeaseTime = time.Duration(lt) * time.Second
}
if lease.SubnetMask == nil {
lease.SubnetMask = net.CIDRMask(24, 32)
}
if routes := parseClasslessRoutes(ack.getOptionRaw(optClasslessRoutes)); len(routes) > 0 {
lease.Routes = routes
} else if routes := parseClasslessRoutes(ack.getOptionRaw(optMSClasslessRoutes)); len(routes) > 0 {
lease.Routes = routes
}
return lease, nil
}
func (c *Client) waitForType(msgType byte, timeout time.Duration) (*dhcpMsg, error) {
deadline := time.After(timeout)
for {