diff --git a/pkg/provider/loadbalancer/iputil/prefix_test.go b/pkg/provider/loadbalancer/iputil/prefix_test.go index d27fa91c6b..30661c4a4e 100644 --- a/pkg/provider/loadbalancer/iputil/prefix_test.go +++ b/pkg/provider/loadbalancer/iputil/prefix_test.go @@ -203,7 +203,7 @@ func TestAggregatePrefixes(t *testing.T) { Name: "Overlap IPv4", Input: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/16"), - netip.MustParsePrefix("192.169.0.0/16"), + netip.MustParsePrefix("192.170.0.0/16"), netip.MustParsePrefix("10.10.0.1/32"), netip.MustParsePrefix("192.168.1.0/24"), @@ -211,10 +211,49 @@ func TestAggregatePrefixes(t *testing.T) { }, Output: []netip.Prefix{ netip.MustParsePrefix("192.168.0.0/16"), - netip.MustParsePrefix("192.169.0.0/16"), + netip.MustParsePrefix("192.170.0.0/16"), netip.MustParsePrefix("10.10.0.1/32"), }, }, + { + Name: "Collapse IPv4", + Input: []netip.Prefix{ + netip.MustParsePrefix("192.168.0.0/24"), + netip.MustParsePrefix("192.168.1.0/24"), + netip.MustParsePrefix("192.168.2.0/24"), + netip.MustParsePrefix("192.168.3.0/24"), + netip.MustParsePrefix("10.0.0.0/8"), + netip.MustParsePrefix("172.16.0.0/12"), + netip.MustParsePrefix("192.168.4.0/24"), + netip.MustParsePrefix("192.168.5.0/24"), + }, + Output: []netip.Prefix{ + netip.MustParsePrefix("10.0.0.0/8"), + netip.MustParsePrefix("172.16.0.0/12"), + netip.MustParsePrefix("192.168.0.0/22"), + netip.MustParsePrefix("192.168.4.0/23"), + }, + }, + { + Name: "Collapse IPv6", + Input: []netip.Prefix{ + netip.MustParsePrefix("2001:db8::/32"), + netip.MustParsePrefix("2001:db8:1::/48"), + netip.MustParsePrefix("2001:db8:2::/48"), + netip.MustParsePrefix("2001:db8:3::/48"), + netip.MustParsePrefix("2001:db8:4::/48"), + netip.MustParsePrefix("2001:db8:5::/48"), + netip.MustParsePrefix("2001:db8:6::/48"), + netip.MustParsePrefix("2001:db8:7::/48"), + netip.MustParsePrefix("2001:dbf::/32"), + netip.MustParsePrefix("2001:dba::/32"), + }, + Output: []netip.Prefix{ + netip.MustParsePrefix("2001:db8::/32"), + netip.MustParsePrefix("2001:dbf::/32"), + netip.MustParsePrefix("2001:dba::/32"), + }, + }, } for _, tt := range tests { diff --git a/pkg/provider/loadbalancer/iputil/prefix_tree.go b/pkg/provider/loadbalancer/iputil/prefix_tree.go index f8cb78c96d..2399c09ebd 100644 --- a/pkg/provider/loadbalancer/iputil/prefix_tree.go +++ b/pkg/provider/loadbalancer/iputil/prefix_tree.go @@ -16,14 +16,37 @@ limitations under the License. package iputil -import "net/netip" +import ( + "net/netip" +) type prefixTreeNode struct { masked bool prefix netip.Prefix - l *prefixTreeNode - r *prefixTreeNode + p *prefixTreeNode // parent node + l *prefixTreeNode // left child node + r *prefixTreeNode // right child node +} + +// pruneToRoot prunes the tree to the root. +// If a node's left and right children are both masked, +// it is masked and its children are pruned. +// This is done recursively up to the root. +func (n *prefixTreeNode) pruneToRoot() { + var node = n + for node.p != nil { + p := node.p + if p.l == nil || !p.l.masked { + break + } + if p.r == nil || !p.r.masked { + break + } + p.masked = true + p.l, p.r = nil, nil + node = p + } } type prefixTree struct { @@ -70,6 +93,7 @@ func (t *prefixTree) Add(prefix netip.Prefix) { } n.l = &prefixTreeNode{ prefix: next, + p: n, } } n = n.l @@ -81,6 +105,7 @@ func (t *prefixTree) Add(prefix netip.Prefix) { } n.r = &prefixTreeNode{ prefix: next, + p: n, } } n = n.r @@ -90,10 +115,18 @@ func (t *prefixTree) Add(prefix netip.Prefix) { } n.masked = true + n.l, n.r = nil, nil + n.pruneToRoot() } // List returns all prefixes in the tree. // Overlapping prefixes are merged. +// It will also collapse the neighboring prefixes. +// The order of the prefixes in the output is guaranteed. +// +// Example: +// - [192.168.0.0/16, 192.168.1.0/24, 192.168.0.1/32] -> [192.168.0.0/16] +// - [192.168.0.0/32, 192.168.0.1/32] -> [192.168.0.0/31] func (t *prefixTree) List() []netip.Prefix { var ( rv []netip.Prefix diff --git a/pkg/provider/loadbalancer/iputil/prefix_tree_test.go b/pkg/provider/loadbalancer/iputil/prefix_tree_test.go index 5a21a98eff..fe3f3dddf1 100644 --- a/pkg/provider/loadbalancer/iputil/prefix_tree_test.go +++ b/pkg/provider/loadbalancer/iputil/prefix_tree_test.go @@ -51,7 +51,7 @@ func TestPrefixTreeIPv4(t *testing.T) { "Overlap", []string{ "192.168.0.0/16", - "192.169.0.0/16", + "192.170.0.0/16", "10.10.0.1/32", "192.168.1.0/24", @@ -59,10 +59,29 @@ func TestPrefixTreeIPv4(t *testing.T) { }, []string{ "192.168.0.0/16", - "192.169.0.0/16", + "192.170.0.0/16", "10.10.0.1/32", }, }, + { + "Collapse", + []string{ + "192.168.0.0/24", + "192.168.1.0/24", + "192.168.2.0/24", + "192.168.3.0/24", + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.4.0/24", + "192.168.5.0/24", + }, + []string{ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/22", + "192.168.4.0/23", + }, + }, } for _, tt := range tests { @@ -101,13 +120,13 @@ func TestPrefixTreeIPv6(t *testing.T) { "NoOverlap", []string{ "2001:db8:0:1::/64", - "2001:db8:0:2::/64", "2001:db8:0:3::/64", + "2001:db8:0:5::/64", }, []string{ "2001:db8:0:1::/64", - "2001:db8:0:2::/64", "2001:db8:0:3::/64", + "2001:db8:0:5::/64", }, }, { @@ -115,13 +134,40 @@ func TestPrefixTreeIPv6(t *testing.T) { []string{ "2001:db8::/32", "2001:db8:0:1::/64", - "2001:db8:0:2::/64", "2001:db8:0:3::/64", }, []string{ "2001:db8::/32", }, }, + { + "Collapse", + []string{ + "2001:db8::/32", + "2001:db8:1::/48", + "2001:db8:2::/48", + "2001:db8:3::/48", + "2001:db8:4::/48", + "2001:db8:5::/48", + "2001:db8:6::/48", + "2001:db8:7::/48", + "2001:db8:8::/48", + "2001:db8:9::/48", + "2001:db8:a::/48", + "2001:db8:b::/48", + "2001:db8:c::/48", + "2001:db8:d::/48", + "2001:db8:e::/48", + "2001:db8:f::/48", + "2001:dbf::/32", // Noise data + "2001:dba::/32", // Noise data + }, + []string{ + "2001:db8::/32", + "2001:dbf::/32", + "2001:dba::/32", + }, + }, } for _, tt := range tests {