fix: dhcpv6 should nack confirm if addr changed (#23168)

Co-authored-by: Qiu Jian <qiujian@yunionyun.com>
This commit is contained in:
Jian Qiu
2025-08-26 15:35:03 +08:00
committed by GitHub
parent 3bd9c12610
commit 2732cc3cf9
+135 -25
View File
@@ -313,11 +313,15 @@ func optionsToBytes(opts []Option6) []byte {
}
func (p Packet) GetOption6s() []Option6 {
options := make([]Option6, 0)
offset := 4
if p.IsRelayMsg() {
offset = 16*2 + 2
}
return decodeDHCP6Options(p, offset)
}
func decodeDHCP6Options(p []byte, offset int) []Option6 {
options := make([]Option6, 0)
i := offset
for i < len(p) {
code := binary.BigEndian.Uint16(p[i : i+2])
@@ -378,28 +382,137 @@ func makeIAAddr(ip net.IP, preferLT, validLT uint32, opts []Option6) []byte {
return buf
}
func responseIANA(buf []byte, opts []Option6) []byte {
func decodeIAAddr(buf []byte) (net.IP, uint32, uint32, []Option6) {
ipBuf := make([]byte, 16)
copy(ipBuf, buf[0:16])
ip := net.IP(ipBuf)
preferLT := binary.BigEndian.Uint32(buf[16:20])
validLT := binary.BigEndian.Uint32(buf[20:24])
opts := decodeDHCP6Options(buf, 24)
return ip, preferLT, validLT, opts
}
/*
+---------------+------+--------------------------------------------+
| Name | Code | Description |
+---------------+------+--------------------------------------------+
| Success | 0 | Success. |
| | | |
| UnspecFail | 1 | Failure, reason unspecified; this status |
| | | code is sent by either a client or a |
| | | server to indicate a failure not |
| | | explicitly specified in this document. |
| | | |
| NoAddrsAvail | 2 | The server has no addresses available to |
| | | assign to the IA(s). |
| | | |
| NoBinding | 3 | Client record (binding) unavailable. |
| | | |
| NotOnLink | 4 | The prefix for the address is not |
| | | appropriate for the link to which the |
| | | client is attached. |
| | | |
| UseMulticast | 5 | Sent by a server to a client to force the |
| | | client to send messages to the server |
| | | using the |
| | | All_DHCP_Relay_Agents_and_Servers |
| | | multicast address. |
| | | |
| NoPrefixAvail | 6 | The server has no prefixes available to |
| | | assign to the IA_PD(s). |
+---------------+------+--------------------------------------------+
*/
type DHCP6StatusCode uint16
const (
Dhcp6StatusSuccess DHCP6StatusCode = 0
Dhcp6StatusUnspecFail DHCP6StatusCode = 1
Dhcp6StatusNoAddrsAvail DHCP6StatusCode = 2
Dhcp6StatusNoBinding DHCP6StatusCode = 3
Dhcp6StatusNotOnLink DHCP6StatusCode = 4
Dhcp6StatusUseMulticast DHCP6StatusCode = 5
Dhcp6StatusNoPrefixAvail DHCP6StatusCode = 6
)
func (code DHCP6StatusCode) Encode() []byte {
var msg string
switch code {
case Dhcp6StatusSuccess:
msg = "Success"
case Dhcp6StatusUnspecFail:
msg = "UnspecFail"
case Dhcp6StatusNoAddrsAvail:
msg = "NoAddrsAvail"
case Dhcp6StatusNoBinding:
msg = "NoBinding"
case Dhcp6StatusNotOnLink:
msg = "NotOnLink"
case Dhcp6StatusUseMulticast:
msg = "UseMulticast"
case Dhcp6StatusNoPrefixAvail:
msg = "NoPrefixAvail"
}
buf := make([]byte, 2)
binary.BigEndian.PutUint16(buf, uint16(code))
buf = append(buf, []byte(msg)...)
return buf
}
func responseIANA(buf []byte, ip net.IP, preferLT, validLT uint32) ([]byte, DHCP6StatusCode) {
// IA_NA structure: IAID (4 bytes) + T1 (4 bytes) + T2 (4 bytes)
// Preserve the original IAID, T1, and T2 values from the client request
opts := make([]Option6, 0)
status := Dhcp6StatusSuccess
resp := make([]byte, 12)
if len(buf) < 12 {
copy(resp, buf)
// If buffer is too short, pad with zeros
padding := make([]byte, 12-len(buf))
buf = append(buf, padding...)
} else if len(buf) > 12 {
// If buffer is too long, truncate to 12 bytes (IAID + T1 + T2)
buf = buf[:12]
}
// Log the IA_NA parameters for debugging
if len(buf) >= 12 {
// padding := make([]byte, 12-len(buf))
// copy(resp[len(buf):], padding)
} else {
// Log the IA_NA parameters for debugging
iaID := binary.BigEndian.Uint32(buf[0:4])
t1 := binary.BigEndian.Uint32(buf[4:8])
t2 := binary.BigEndian.Uint32(buf[8:12])
log.Debugf("responseIANA IA_NA IAID %d t1 %d t2 %d", iaID, t1, t2)
if len(buf) > 12 {
iaOpts := decodeDHCP6Options(buf, 12)
for i := range iaOpts {
if iaOpts[i].Code == DHCPV6_OPTION_IAADDR {
oldIp, oldPreferLT, oldValidLT, _ := decodeIAAddr(iaOpts[i].Value)
if !oldIp.Equal(ip) {
// send NotOnLink
opts = append(opts, Option6{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(oldIp, oldPreferLT, oldValidLT, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: Dhcp6StatusNotOnLink.Encode(),
},
}),
})
if status == Dhcp6StatusSuccess {
status = Dhcp6StatusNotOnLink
}
}
}
}
}
copy(resp, buf[:12])
}
buf = append(buf, optionsToBytes(opts)...)
return buf
opts = append(opts, Option6{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(ip, preferLT, validLT, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: Dhcp6StatusSuccess.Encode(),
},
}),
})
resp = append(resp, optionsToBytes(opts)...)
return resp, status
}
func makeIPv6s(ips []net.IP) []byte {
@@ -473,19 +586,11 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType)
validLifetime := uint32(conf.LeaseTime.Seconds()) // Valid lifetime should be longer than preferred
preferredLifetime := validLifetime / 2
ianaResp, status := responseIANA(ianaOpt.Value, conf.ClientIP6, preferredLifetime, validLifetime)
options = append(options, Option6{
Code: DHCPV6_OPTION_IA_NA,
Value: responseIANA(ianaOpt.Value, []Option6{
{
Code: DHCPV6_OPTION_IAADDR,
Value: makeIAAddr(conf.ClientIP6, preferredLifetime, validLifetime, []Option6{
{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: []byte{0, 0, 'S', 'u', 'c', 'c', 'e', 's', 's'},
},
}),
},
}),
Code: DHCPV6_OPTION_IA_NA,
Value: ianaResp,
})
if len(conf.DNSServers6) > 0 {
@@ -514,6 +619,11 @@ func makeDHCPReplyPacket6(pkt Packet, conf *ResponseConfig, msgType MessageType)
}
}
options = append(options, Option6{
Code: DHCPV6_OPTION_STATUS_CODE,
Value: status.Encode(),
})
resp := NewPacket6(msgType, tid)
resp = append(resp, optionsToBytes(options)...)