From aab2ea71fcc58d2c863589f1ab8429ec1a503cfc Mon Sep 17 00:00:00 2001 From: jettwang Date: Mon, 8 Jun 2026 12:46:22 +0800 Subject: [PATCH] Tolerate non-zero bits beyond the prefix in IPv6Prefix IPv6Prefix rejected any Framed-IPv6-Prefix/Delegated-IPv6-Prefix attribute whose bits past the Prefix-Length were non-zero, returning "invalid prefix data". Real-world equipment frequently leaves those padding bits set (for example sending a /64 with the host identifier still present, or a non-octet-aligned prefix with a trailing bit), so valid prefixes failed to decode. Clear the bits beyond the prefix length instead of erroring: mask the partial byte and zero every whole byte after it. This restores the lenient behaviour that predated the issue #118 fix while still keeping that fix's tolerance for trailing zero bytes being omitted. Closes #119 Refs #118 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- attribute.go | 16 ++++++++-------- attribute_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/attribute.go b/attribute.go index 366da72b..986e191d 100644 --- a/attribute.go +++ b/attribute.go @@ -474,14 +474,14 @@ func IPv6Prefix(a Attribute) (*net.IPNet, error) { ip := make(net.IP, net.IPv6len) copy(ip, a[2:]) - bit := uint(prefixLength % 8) - for octet := prefixLength / 8; octet < len(ip); octet++ { - for ; bit < 8; bit++ { - if ip[octet]&(1<<(7-bit)) != 0 { - return nil, errors.New("invalid prefix data") - } - } - bit = 0 + // Clear any bits beyond the prefix length. RFC 3162 requires these bits to + // be zero, but some equipment leaves them set; tolerate that here instead + // of rejecting the attribute. + if bit := uint(prefixLength % 8); bit != 0 { + ip[prefixLength/8] &^= 0xff >> bit + } + for octet := (prefixLength + 7) / 8; octet < len(ip); octet++ { + ip[octet] = 0 } return &net.IPNet{ diff --git a/attribute_test.go b/attribute_test.go index 20655d0b..0811d207 100644 --- a/attribute_test.go +++ b/attribute_test.go @@ -143,6 +143,39 @@ func TestIPv6Prefix_issue118(t *testing.T) { } } +func TestIPv6Prefix_issue119(t *testing.T) { + tests := []struct { + Name string + Attr []byte + Expected string + }{ + { + // /64 prefix sent with the host bits (::1) still set. + Name: "host bits set", + Attr: []byte{0x00, 0x40, 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, + Expected: "2001:db8::/64", + }, + { + // /47 prefix with a non-zero bit beyond the prefix length. + Name: "non-octet boundary with trailing bit", + Attr: []byte{0x00, 0x2f, 0x20, 0x01, 0x15, 0x30, 0x10, 0x0f}, + Expected: "2001:1530:100e::/47", + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + ipNet, err := IPv6Prefix(tt.Attr) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if got := ipNet.String(); got != tt.Expected { + t.Fatalf("got %s, expected %s", got, tt.Expected) + } + }) + } +} + func ipNetEquals(a, b *net.IPNet) bool { if a == nil && b == nil { return true