k8s.io/kubernetes@v1.29.3/pkg/registry/core/service/ipallocator/ipallocator_test.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package ipallocator
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"net"
    23  	"net/netip"
    24  	"reflect"
    25  	"testing"
    26  	"time"
    27  
    28  	networkingv1alpha1 "k8s.io/api/networking/v1alpha1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	"k8s.io/client-go/informers"
    32  	"k8s.io/client-go/kubernetes/fake"
    33  	k8stesting "k8s.io/client-go/testing"
    34  	"k8s.io/component-base/metrics/testutil"
    35  	api "k8s.io/kubernetes/pkg/apis/core"
    36  	netutils "k8s.io/utils/net"
    37  )
    38  
    39  func newTestAllocator(cidr *net.IPNet) (*Allocator, error) {
    40  	client := fake.NewSimpleClientset()
    41  
    42  	informerFactory := informers.NewSharedInformerFactory(client, 0*time.Second)
    43  	ipInformer := informerFactory.Networking().V1alpha1().IPAddresses()
    44  	ipStore := ipInformer.Informer().GetIndexer()
    45  
    46  	client.PrependReactor("create", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
    47  		ip := action.(k8stesting.CreateAction).GetObject().(*networkingv1alpha1.IPAddress)
    48  		_, exists, err := ipStore.GetByKey(ip.Name)
    49  		if exists && err != nil {
    50  			return false, nil, fmt.Errorf("ip already exist")
    51  		}
    52  		ip.Generation = 1
    53  		err = ipStore.Add(ip)
    54  		return false, ip, err
    55  	}))
    56  	client.PrependReactor("delete", "ipaddresses", k8stesting.ReactionFunc(func(action k8stesting.Action) (bool, runtime.Object, error) {
    57  		name := action.(k8stesting.DeleteAction).GetName()
    58  		obj, exists, err := ipStore.GetByKey(name)
    59  		ip := &networkingv1alpha1.IPAddress{}
    60  		if exists && err == nil {
    61  			ip = obj.(*networkingv1alpha1.IPAddress)
    62  			err = ipStore.Delete(ip)
    63  		}
    64  		return false, ip, err
    65  	}))
    66  
    67  	c, err := NewIPAllocator(cidr, client.NetworkingV1alpha1(), ipInformer)
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  	c.ipAddressSynced = func() bool { return true }
    72  	return c, nil
    73  }
    74  
    75  func TestAllocateIPAllocator(t *testing.T) {
    76  	testCases := []struct {
    77  		name             string
    78  		cidr             string
    79  		family           api.IPFamily
    80  		free             int
    81  		released         string
    82  		outOfRange       []string
    83  		alreadyAllocated string
    84  	}{
    85  		{
    86  			name:     "IPv4",
    87  			cidr:     "192.168.1.0/24",
    88  			free:     254,
    89  			released: "192.168.1.5",
    90  			outOfRange: []string{
    91  				"192.168.0.1",   // not in 192.168.1.0/24
    92  				"192.168.1.0",   // reserved (base address)
    93  				"192.168.1.255", // reserved (broadcast address)
    94  				"192.168.2.2",   // not in 192.168.1.0/24
    95  			},
    96  			alreadyAllocated: "192.168.1.1",
    97  		},
    98  		{
    99  			name:     "IPv6",
   100  			cidr:     "2001:db8:1::/116",
   101  			free:     4095,
   102  			released: "2001:db8:1::5",
   103  			outOfRange: []string{
   104  				"2001:db8::1",   // not in 2001:db8:1::/48
   105  				"2001:db8:1::",  // reserved (base address)
   106  				"2001:db8:2::2", // not in 2001:db8:1::/48
   107  			},
   108  			alreadyAllocated: "2001:db8:1::1",
   109  		},
   110  	}
   111  	for _, tc := range testCases {
   112  		t.Run(tc.name, func(t *testing.T) {
   113  			_, cidr, err := netutils.ParseCIDRSloppy(tc.cidr)
   114  			if err != nil {
   115  				t.Fatal(err)
   116  			}
   117  			r, err := newTestAllocator(cidr)
   118  			if err != nil {
   119  				t.Fatal(err)
   120  			}
   121  			defer r.Destroy()
   122  			if f := r.Free(); f != tc.free {
   123  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f)
   124  			}
   125  
   126  			if f := r.Used(); f != 0 {
   127  				t.Errorf("[%s]: wrong used: expected %d, got %d", tc.name, 0, f)
   128  			}
   129  			found := sets.NewString()
   130  			count := 0
   131  			for r.Free() > 0 {
   132  				ip, err := r.AllocateNext()
   133  				if err != nil {
   134  					t.Fatalf("[%s] error @ free: %d used: %d count: %d: %v", tc.name, r.Free(), r.Used(), count, err)
   135  				}
   136  				count++
   137  				//if !cidr.Contains(ip) {
   138  				//	t.Fatalf("[%s] allocated %s which is outside of %s", tc.name, ip, cidr)
   139  				//}
   140  				if found.Has(ip.String()) {
   141  					t.Fatalf("[%s] allocated %s twice @ %d", tc.name, ip, count)
   142  				}
   143  				found.Insert(ip.String())
   144  			}
   145  			if _, err := r.AllocateNext(); err == nil {
   146  				t.Fatal(err)
   147  			}
   148  
   149  			if !found.Has(tc.released) {
   150  				t.Fatalf("not allocated address to be releases %s found %d", tc.released, len(found))
   151  			}
   152  			released := netutils.ParseIPSloppy(tc.released)
   153  			if err := r.Release(released); err != nil {
   154  				t.Fatal(err)
   155  			}
   156  			if f := r.Free(); f != 1 {
   157  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f)
   158  			}
   159  			if f := r.Used(); f != (tc.free - 1) {
   160  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f)
   161  			}
   162  			ip, err := r.AllocateNext()
   163  			if err != nil {
   164  				t.Fatal(err)
   165  			}
   166  			if !released.Equal(ip) {
   167  				t.Errorf("[%s] unexpected %s : %s", tc.name, ip, released)
   168  			}
   169  
   170  			if err := r.Release(released); err != nil {
   171  				t.Fatal(err)
   172  			}
   173  			for _, outOfRange := range tc.outOfRange {
   174  				err = r.Allocate(netutils.ParseIPSloppy(outOfRange))
   175  				if err == nil {
   176  					t.Fatalf("unexpacted allocating of %s", outOfRange)
   177  				}
   178  			}
   179  			if err := r.Allocate(netutils.ParseIPSloppy(tc.alreadyAllocated)); err == nil {
   180  				t.Fatalf("unexpected allocation of %s", tc.alreadyAllocated)
   181  			}
   182  			if f := r.Free(); f != 1 {
   183  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 1, f)
   184  			}
   185  			if f := r.Used(); f != (tc.free - 1) {
   186  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free-1, f)
   187  			}
   188  			if err := r.Allocate(released); err != nil {
   189  				t.Fatal(err)
   190  			}
   191  			if f := r.Free(); f != 0 {
   192  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, 0, f)
   193  			}
   194  			if f := r.Used(); f != tc.free {
   195  				t.Errorf("[%s] wrong free: expected %d, got %d", tc.name, tc.free, f)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestAllocateTinyIPAllocator(t *testing.T) {
   202  	_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/32")
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  
   207  	r, err := newTestAllocator(cidr)
   208  	if err != nil {
   209  		t.Fatal(err)
   210  	}
   211  	defer r.Destroy()
   212  
   213  	if f := r.Free(); f != 0 {
   214  		t.Errorf("free: %d", f)
   215  	}
   216  	if _, err := r.AllocateNext(); err == nil {
   217  		t.Error(err)
   218  	}
   219  }
   220  
   221  func TestAllocateReservedIPAllocator(t *testing.T) {
   222  	_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/25")
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	r, err := newTestAllocator(cidr)
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	defer r.Destroy()
   231  	// allocate all addresses on the dynamic block
   232  	// subnet /25 = 128 ; dynamic block size is min(max(16,128/16),256) = 16
   233  	dynamicOffset := calculateRangeOffset(cidr)
   234  	dynamicBlockSize := int(r.size) - dynamicOffset
   235  	for i := 0; i < dynamicBlockSize; i++ {
   236  		_, err := r.AllocateNext()
   237  		if err != nil {
   238  			t.Errorf("Unexpected error trying to allocate: %v", err)
   239  		}
   240  	}
   241  	for i := dynamicOffset; i < int(r.size); i++ {
   242  		ip := fmt.Sprintf("192.168.1.%d", i+1)
   243  		if !r.Has(netutils.ParseIPSloppy(ip)) {
   244  			t.Errorf("IP %s expected to be allocated", ip)
   245  		}
   246  	}
   247  	if f := r.Free(); f != dynamicOffset {
   248  		t.Errorf("expected %d free addresses, got %d", dynamicOffset, f)
   249  	}
   250  	// allocate all addresses on the static block
   251  	for i := 0; i < dynamicOffset; i++ {
   252  		ip := fmt.Sprintf("192.168.1.%d", i+1)
   253  		if err := r.Allocate(netutils.ParseIPSloppy(ip)); err != nil {
   254  			t.Errorf("Unexpected error trying to allocate IP %s: %v", ip, err)
   255  		}
   256  	}
   257  	if f := r.Free(); f != 0 {
   258  		t.Errorf("expected free equal to 0 got: %d", f)
   259  	}
   260  	// release one address in the allocated block and another a new one randomly
   261  	if err := r.Release(netutils.ParseIPSloppy("192.168.1.10")); err != nil {
   262  		t.Fatalf("Unexpected error trying to release ip 192.168.1.10: %v", err)
   263  	}
   264  	if _, err := r.AllocateNext(); err != nil {
   265  		t.Error(err)
   266  	}
   267  	if f := r.Free(); f != 0 {
   268  		t.Errorf("expected free equal to 0 got: %d", f)
   269  	}
   270  }
   271  
   272  func TestAllocateSmallIPAllocator(t *testing.T) {
   273  	_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.240/30")
   274  	if err != nil {
   275  		t.Fatal(err)
   276  	}
   277  	r, err := newTestAllocator(cidr)
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	defer r.Destroy()
   282  
   283  	if f := r.Free(); f != 2 {
   284  		t.Errorf("expected free equal to 2 got: %d", f)
   285  	}
   286  	found := sets.NewString()
   287  	for i := 0; i < 2; i++ {
   288  		ip, err := r.AllocateNext()
   289  		if err != nil {
   290  			t.Fatalf("error allocating %s try %d : %v", ip, i, err)
   291  		}
   292  		if found.Has(ip.String()) {
   293  			t.Fatalf("address %s has been already allocated", ip)
   294  		}
   295  		found.Insert(ip.String())
   296  	}
   297  	for s := range found {
   298  		if !r.Has(netutils.ParseIPSloppy(s)) {
   299  			t.Fatalf("missing: %s", s)
   300  		}
   301  		if err := r.Allocate(netutils.ParseIPSloppy(s)); err == nil {
   302  			t.Fatal(err)
   303  		}
   304  	}
   305  	if f := r.Free(); f != 0 {
   306  		t.Errorf("expected free equal to 0 got: %d", f)
   307  	}
   308  
   309  	for i := 0; i < 100; i++ {
   310  		if ip, err := r.AllocateNext(); err == nil {
   311  			t.Fatalf("suddenly became not-full: %s", ip.String())
   312  		}
   313  	}
   314  
   315  }
   316  
   317  func TestForEachIPAllocator(t *testing.T) {
   318  	_, cidr, err := netutils.ParseCIDRSloppy("192.168.1.0/24")
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  	testCases := []sets.String{
   323  		sets.NewString(),
   324  		sets.NewString("192.168.1.1"),
   325  		sets.NewString("192.168.1.1", "192.168.1.254"),
   326  		sets.NewString("192.168.1.1", "192.168.1.128", "192.168.1.254"),
   327  	}
   328  
   329  	for i, tc := range testCases {
   330  		r, err := newTestAllocator(cidr)
   331  		if err != nil {
   332  			t.Fatal(err)
   333  		}
   334  		defer r.Destroy()
   335  
   336  		for ips := range tc {
   337  			ip := netutils.ParseIPSloppy(ips)
   338  			if err := r.Allocate(ip); err != nil {
   339  				t.Errorf("[%d] error allocating IP %v: %v", i, ip, err)
   340  			}
   341  			if !r.Has(ip) {
   342  				t.Errorf("[%d] expected IP %v allocated", i, ip)
   343  			}
   344  		}
   345  		calls := sets.NewString()
   346  		r.ForEach(func(ip net.IP) {
   347  			calls.Insert(ip.String())
   348  		})
   349  		if len(calls) != len(tc) {
   350  			t.Errorf("[%d] expected %d calls, got %d", i, len(tc), len(calls))
   351  		}
   352  		if !calls.Equal(tc) {
   353  			t.Errorf("[%d] expected calls to equal testcase: %v vs %v", i, calls.List(), tc.List())
   354  		}
   355  	}
   356  }
   357  
   358  func TestIPAllocatorClusterIPMetrics(t *testing.T) {
   359  	clearMetrics()
   360  	// create IPv4 allocator
   361  	cidrIPv4 := "10.0.0.0/24"
   362  	_, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4)
   363  	a, err := newTestAllocator(clusterCIDRv4)
   364  	if err != nil {
   365  		t.Fatal(err)
   366  	}
   367  	a.EnableMetrics()
   368  	// create IPv6 allocator
   369  	cidrIPv6 := "2001:db8::/112"
   370  	_, clusterCIDRv6, _ := netutils.ParseCIDRSloppy(cidrIPv6)
   371  	b, err := newTestAllocator(clusterCIDRv6)
   372  	if err != nil {
   373  		t.Fatalf("unexpected error creating CidrSet: %v", err)
   374  	}
   375  	b.EnableMetrics()
   376  
   377  	// Check initial state
   378  	em := testMetrics{
   379  		free:      0,
   380  		used:      0,
   381  		allocated: 0,
   382  		errors:    0,
   383  	}
   384  	expectMetrics(t, cidrIPv4, em)
   385  	em = testMetrics{
   386  		free:      0,
   387  		used:      0,
   388  		allocated: 0,
   389  		errors:    0,
   390  	}
   391  	expectMetrics(t, cidrIPv6, em)
   392  
   393  	// allocate 2 IPv4 addresses
   394  	found := sets.NewString()
   395  	for i := 0; i < 2; i++ {
   396  		ip, err := a.AllocateNext()
   397  		if err != nil {
   398  			t.Fatal(err)
   399  		}
   400  		if found.Has(ip.String()) {
   401  			t.Fatalf("already reserved: %s", ip)
   402  		}
   403  		found.Insert(ip.String())
   404  	}
   405  
   406  	em = testMetrics{
   407  		free:      252,
   408  		used:      2,
   409  		allocated: 2,
   410  		errors:    0,
   411  	}
   412  	expectMetrics(t, cidrIPv4, em)
   413  
   414  	// try to allocate the same IP addresses
   415  	for s := range found {
   416  		if !a.Has(netutils.ParseIPSloppy(s)) {
   417  			t.Fatalf("missing: %s", s)
   418  		}
   419  		if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated {
   420  			t.Fatal(err)
   421  		}
   422  	}
   423  	em = testMetrics{
   424  		free:      252,
   425  		used:      2,
   426  		allocated: 2,
   427  		errors:    2,
   428  	}
   429  	expectMetrics(t, cidrIPv4, em)
   430  
   431  	// release the addresses allocated
   432  	for s := range found {
   433  		if !a.Has(netutils.ParseIPSloppy(s)) {
   434  			t.Fatalf("missing: %s", s)
   435  		}
   436  		if err := a.Release(netutils.ParseIPSloppy(s)); err != nil {
   437  			t.Fatal(err)
   438  		}
   439  	}
   440  	em = testMetrics{
   441  		free:      254,
   442  		used:      0,
   443  		allocated: 2,
   444  		errors:    2,
   445  	}
   446  	expectMetrics(t, cidrIPv4, em)
   447  
   448  	// allocate 264 addresses for each allocator
   449  	// the full range and 10 more (254 + 10 = 264) for IPv4
   450  	for i := 0; i < 264; i++ {
   451  		a.AllocateNext()
   452  		b.AllocateNext()
   453  	}
   454  	em = testMetrics{
   455  		free:      0,
   456  		used:      254,
   457  		allocated: 256, // this is a counter, we already had 2 allocations and we did 254 more
   458  		errors:    12,
   459  	}
   460  	expectMetrics(t, cidrIPv4, em)
   461  	em = testMetrics{
   462  		free:      65271, // IPv6 clusterIP range is capped to 2^16 and consider the broadcast address as valid
   463  		used:      264,
   464  		allocated: 264,
   465  		errors:    0,
   466  	}
   467  	expectMetrics(t, cidrIPv6, em)
   468  }
   469  
   470  func TestIPAllocatorClusterIPAllocatedMetrics(t *testing.T) {
   471  	clearMetrics()
   472  	// create IPv4 allocator
   473  	cidrIPv4 := "10.0.0.0/25"
   474  	_, clusterCIDRv4, _ := netutils.ParseCIDRSloppy(cidrIPv4)
   475  	a, err := newTestAllocator(clusterCIDRv4)
   476  	if err != nil {
   477  		t.Fatal(err)
   478  	}
   479  	a.EnableMetrics()
   480  
   481  	em := testMetrics{
   482  		free:      0,
   483  		used:      0,
   484  		allocated: 0,
   485  		errors:    0,
   486  	}
   487  	expectMetrics(t, cidrIPv4, em)
   488  
   489  	// allocate 2 dynamic IPv4 addresses
   490  	found := sets.NewString()
   491  	for i := 0; i < 2; i++ {
   492  		ip, err := a.AllocateNext()
   493  		if err != nil {
   494  			t.Fatal(err)
   495  		}
   496  		if found.Has(ip.String()) {
   497  			t.Fatalf("already reserved: %s", ip)
   498  		}
   499  		found.Insert(ip.String())
   500  	}
   501  
   502  	dynamic_allocated, err := testutil.GetCounterMetricValue(clusterIPAllocations.WithLabelValues(cidrIPv4, "dynamic"))
   503  	if err != nil {
   504  		t.Errorf("failed to get %s value, err: %v", clusterIPAllocations.Name, err)
   505  	}
   506  	if dynamic_allocated != 2 {
   507  		t.Fatalf("Expected 2 received %f", dynamic_allocated)
   508  	}
   509  
   510  	// try to allocate the same IP addresses
   511  	for s := range found {
   512  		if !a.Has(netutils.ParseIPSloppy(s)) {
   513  			t.Fatalf("missing: %s", s)
   514  		}
   515  		if err := a.Allocate(netutils.ParseIPSloppy(s)); err != ErrAllocated {
   516  			t.Fatal(err)
   517  		}
   518  	}
   519  
   520  	static_errors, err := testutil.GetCounterMetricValue(clusterIPAllocationErrors.WithLabelValues(cidrIPv4, "static"))
   521  	if err != nil {
   522  		t.Errorf("failed to get %s value, err: %v", clusterIPAllocationErrors.Name, err)
   523  	}
   524  	if static_errors != 2 {
   525  		t.Fatalf("Expected 2 received %f", dynamic_allocated)
   526  	}
   527  }
   528  
   529  func Test_addOffsetAddress(t *testing.T) {
   530  	tests := []struct {
   531  		name    string
   532  		address netip.Addr
   533  		offset  uint64
   534  		want    netip.Addr
   535  	}{
   536  		{
   537  			name:    "IPv4 offset 0",
   538  			address: netip.MustParseAddr("192.168.0.0"),
   539  			offset:  0,
   540  			want:    netip.MustParseAddr("192.168.0.0"),
   541  		},
   542  		{
   543  			name:    "IPv4 offset 0 not nibble boundary",
   544  			address: netip.MustParseAddr("192.168.0.11"),
   545  			offset:  0,
   546  			want:    netip.MustParseAddr("192.168.0.11"),
   547  		},
   548  		{
   549  			name:    "IPv4 offset 1",
   550  			address: netip.MustParseAddr("192.168.0.0"),
   551  			offset:  1,
   552  			want:    netip.MustParseAddr("192.168.0.1"),
   553  		},
   554  		{
   555  			name:    "IPv4 offset 1 not nibble boundary",
   556  			address: netip.MustParseAddr("192.168.0.11"),
   557  			offset:  1,
   558  			want:    netip.MustParseAddr("192.168.0.12"),
   559  		},
   560  		{
   561  			name:    "IPv6 offset 1",
   562  			address: netip.MustParseAddr("fd00:1:2:3::"),
   563  			offset:  1,
   564  			want:    netip.MustParseAddr("fd00:1:2:3::1"),
   565  		},
   566  		{
   567  			name:    "IPv6 offset 1 not nibble boundary",
   568  			address: netip.MustParseAddr("fd00:1:2:3::a"),
   569  			offset:  1,
   570  			want:    netip.MustParseAddr("fd00:1:2:3::b"),
   571  		},
   572  		{
   573  			name:    "IPv4 offset last",
   574  			address: netip.MustParseAddr("192.168.0.0"),
   575  			offset:  255,
   576  			want:    netip.MustParseAddr("192.168.0.255"),
   577  		},
   578  		{
   579  			name:    "IPv6 offset last",
   580  			address: netip.MustParseAddr("fd00:1:2:3::"),
   581  			offset:  0x7FFFFFFFFFFFFFFF,
   582  			want:    netip.MustParseAddr("fd00:1:2:3:7FFF:FFFF:FFFF:FFFF"),
   583  		},
   584  		{
   585  			name:    "IPv4 offset middle",
   586  			address: netip.MustParseAddr("192.168.0.0"),
   587  			offset:  128,
   588  			want:    netip.MustParseAddr("192.168.0.128"),
   589  		},
   590  		{
   591  			name:    "IPv4 with leading zeros",
   592  			address: netip.MustParseAddr("0.0.1.8"),
   593  			offset:  138,
   594  			want:    netip.MustParseAddr("0.0.1.146"),
   595  		},
   596  		{
   597  			name:    "IPv6 with leading zeros",
   598  			address: netip.MustParseAddr("00fc::1"),
   599  			offset:  255,
   600  			want:    netip.MustParseAddr("fc::100"),
   601  		},
   602  		{
   603  			name:    "IPv6 offset 255",
   604  			address: netip.MustParseAddr("2001:db8:1::101"),
   605  			offset:  255,
   606  			want:    netip.MustParseAddr("2001:db8:1::200"),
   607  		},
   608  		{
   609  			name:    "IPv6 offset 1025",
   610  			address: netip.MustParseAddr("fd00:1:2:3::"),
   611  			offset:  1025,
   612  			want:    netip.MustParseAddr("fd00:1:2:3::401"),
   613  		},
   614  	}
   615  	for _, tt := range tests {
   616  		t.Run(tt.name, func(t *testing.T) {
   617  			got, err := addOffsetAddress(tt.address, tt.offset)
   618  			if !reflect.DeepEqual(got, tt.want) || err != nil {
   619  				t.Errorf("offsetAddress() = %v, want %v", got, tt.want)
   620  			}
   621  			// double check to avoid mistakes on the hardcoded values
   622  			// avoid large numbers or it will timeout the test
   623  			if tt.offset < 2048 {
   624  				want := tt.address
   625  				var i uint64
   626  				for i = 0; i < tt.offset; i++ {
   627  					want = want.Next()
   628  				}
   629  				if !reflect.DeepEqual(got, tt.want) || err != nil {
   630  					t.Errorf("offsetAddress() = %v, want %v", got, tt.want)
   631  				}
   632  			}
   633  		})
   634  	}
   635  }
   636  
   637  func Test_broadcastAddress(t *testing.T) {
   638  	tests := []struct {
   639  		name   string
   640  		subnet netip.Prefix
   641  		want   netip.Addr
   642  	}{
   643  		{
   644  			name:   "ipv4",
   645  			subnet: netip.MustParsePrefix("192.168.0.0/24"),
   646  			want:   netip.MustParseAddr("192.168.0.255"),
   647  		},
   648  		{
   649  			name:   "ipv4 no nibble boundary",
   650  			subnet: netip.MustParsePrefix("10.0.0.0/12"),
   651  			want:   netip.MustParseAddr("10.15.255.255"),
   652  		},
   653  		{
   654  			name:   "ipv6",
   655  			subnet: netip.MustParsePrefix("fd00:1:2:3::/64"),
   656  			want:   netip.MustParseAddr("fd00:1:2:3:FFFF:FFFF:FFFF:FFFF"),
   657  		},
   658  		{
   659  			name:   "ipv6 00fc::/112",
   660  			subnet: netip.MustParsePrefix("00fc::/112"),
   661  			want:   netip.MustParseAddr("fc::ffff"),
   662  		},
   663  		{
   664  			name:   "ipv6 fc00::/112",
   665  			subnet: netip.MustParsePrefix("fc00::/112"),
   666  			want:   netip.MustParseAddr("fc00::ffff"),
   667  		},
   668  	}
   669  	for _, tt := range tests {
   670  		t.Run(tt.name, func(t *testing.T) {
   671  			if got, err := broadcastAddress(tt.subnet); !reflect.DeepEqual(got, tt.want) || err != nil {
   672  				t.Errorf("broadcastAddress() = %v, want %v", got, tt.want)
   673  			}
   674  		})
   675  	}
   676  }
   677  
   678  func Test_hostsPerNetwork(t *testing.T) {
   679  	testCases := []struct {
   680  		name  string
   681  		cidr  string
   682  		addrs uint64
   683  	}{
   684  		{
   685  			name:  "supported IPv4 cidr",
   686  			cidr:  "192.168.1.0/24",
   687  			addrs: 254,
   688  		},
   689  		{
   690  			name:  "single IPv4 host",
   691  			cidr:  "192.168.1.0/32",
   692  			addrs: 0,
   693  		},
   694  		{
   695  			name:  "small IPv4 cidr",
   696  			cidr:  "192.168.1.0/31",
   697  			addrs: 0,
   698  		},
   699  		{
   700  			name:  "very large IPv4 cidr",
   701  			cidr:  "0.0.0.0/1",
   702  			addrs: math.MaxInt32 - 1,
   703  		},
   704  		{
   705  			name:  "full IPv4 range",
   706  			cidr:  "0.0.0.0/0",
   707  			addrs: math.MaxUint32 - 1,
   708  		},
   709  		{
   710  			name:  "supported IPv6 cidr",
   711  			cidr:  "2001:db2::/112",
   712  			addrs: 65535,
   713  		},
   714  		{
   715  			name:  "single IPv6 host",
   716  			cidr:  "2001:db8::/128",
   717  			addrs: 0,
   718  		},
   719  		{
   720  			name:  "small IPv6 cidr",
   721  			cidr:  "2001:db8::/127",
   722  			addrs: 1,
   723  		},
   724  		{
   725  			name:  "largest IPv6 for Int64",
   726  			cidr:  "2001:db8::/65",
   727  			addrs: math.MaxInt64,
   728  		},
   729  		{
   730  			name:  "largest IPv6 for Uint64",
   731  			cidr:  "2001:db8::/64",
   732  			addrs: math.MaxUint64,
   733  		},
   734  		{
   735  			name:  "very large IPv6 cidr",
   736  			cidr:  "2001:db8::/1",
   737  			addrs: math.MaxUint64,
   738  		},
   739  	}
   740  
   741  	for _, tc := range testCases {
   742  		_, cidr, err := netutils.ParseCIDRSloppy(tc.cidr)
   743  		if err != nil {
   744  			t.Errorf("failed to parse cidr for test %s, unexpected error: '%s'", tc.name, err)
   745  		}
   746  		if size := hostsPerNetwork(cidr); size != tc.addrs {
   747  			t.Errorf("test %s failed. %s should have a range size of %d, got %d",
   748  				tc.name, tc.cidr, tc.addrs, size)
   749  		}
   750  	}
   751  }
   752  
   753  func Test_ipIterator(t *testing.T) {
   754  	tests := []struct {
   755  		name   string
   756  		first  netip.Addr
   757  		last   netip.Addr
   758  		offset uint64
   759  		want   []string
   760  	}{
   761  		{
   762  			name:   "start from first address small range",
   763  			first:  netip.MustParseAddr("192.168.0.1"),
   764  			last:   netip.MustParseAddr("192.168.0.2"),
   765  			offset: 0,
   766  			want:   []string{"192.168.0.1", "192.168.0.2"},
   767  		}, {
   768  			name:   "start from last address small range",
   769  			first:  netip.MustParseAddr("192.168.0.1"),
   770  			last:   netip.MustParseAddr("192.168.0.2"),
   771  			offset: 1,
   772  			want:   []string{"192.168.0.2", "192.168.0.1"},
   773  		}, {
   774  			name:   "start from offset out of range address small range",
   775  			first:  netip.MustParseAddr("192.168.0.1"),
   776  			last:   netip.MustParseAddr("192.168.0.2"),
   777  			offset: 10,
   778  			want:   []string{"192.168.0.1", "192.168.0.2"},
   779  		}, {
   780  			name:   "start from first address",
   781  			first:  netip.MustParseAddr("192.168.0.1"),
   782  			last:   netip.MustParseAddr("192.168.0.7"),
   783  			offset: 0,
   784  			want:   []string{"192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7"},
   785  		}, {
   786  			name:   "start from middle address",
   787  			first:  netip.MustParseAddr("192.168.0.1"),
   788  			last:   netip.MustParseAddr("192.168.0.7"),
   789  			offset: 2,
   790  			want:   []string{"192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6", "192.168.0.7", "192.168.0.1", "192.168.0.2"},
   791  		}, {
   792  			name:   "start from last address",
   793  			first:  netip.MustParseAddr("192.168.0.1"),
   794  			last:   netip.MustParseAddr("192.168.0.7"),
   795  			offset: 6,
   796  			want:   []string{"192.168.0.7", "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4", "192.168.0.5", "192.168.0.6"},
   797  		},
   798  	}
   799  	for _, tt := range tests {
   800  		t.Run(tt.name, func(t *testing.T) {
   801  			got := []string{}
   802  			iterator := ipIterator(tt.first, tt.last, tt.offset)
   803  
   804  			for {
   805  				ip := iterator()
   806  				if !ip.IsValid() {
   807  					break
   808  				}
   809  				got = append(got, ip.String())
   810  			}
   811  			if !reflect.DeepEqual(got, tt.want) {
   812  				t.Errorf("ipIterator() = %v, want %v", got, tt.want)
   813  			}
   814  			// check the iterator is fully stopped
   815  			for i := 0; i < 5; i++ {
   816  				if ip := iterator(); ip.IsValid() {
   817  					t.Errorf("iterator should not return more addresses: %v", ip)
   818  				}
   819  			}
   820  		})
   821  	}
   822  }
   823  
   824  func Test_ipIterator_Number(t *testing.T) {
   825  	tests := []struct {
   826  		name   string
   827  		first  netip.Addr
   828  		last   netip.Addr
   829  		offset uint64
   830  		want   uint64
   831  	}{
   832  		{
   833  			name:   "start from first address small range",
   834  			first:  netip.MustParseAddr("192.168.0.1"),
   835  			last:   netip.MustParseAddr("192.168.0.2"),
   836  			offset: 0,
   837  			want:   2,
   838  		}, {
   839  			name:   "start from last address small range",
   840  			first:  netip.MustParseAddr("192.168.0.1"),
   841  			last:   netip.MustParseAddr("192.168.0.2"),
   842  			offset: 1,
   843  			want:   2,
   844  		}, {
   845  			name:   "start from offset out of range small range",
   846  			first:  netip.MustParseAddr("192.168.0.1"),
   847  			last:   netip.MustParseAddr("192.168.0.2"),
   848  			offset: 10,
   849  			want:   2,
   850  		}, {
   851  			name:   "start from first address",
   852  			first:  netip.MustParseAddr("192.168.0.1"),
   853  			last:   netip.MustParseAddr("192.168.0.7"),
   854  			offset: 0,
   855  			want:   7,
   856  		}, {
   857  			name:   "start from middle address",
   858  			first:  netip.MustParseAddr("192.168.0.1"),
   859  			last:   netip.MustParseAddr("192.168.0.7"),
   860  			offset: 2,
   861  			want:   7,
   862  		}, {
   863  			name:   "start from last address",
   864  			first:  netip.MustParseAddr("192.168.0.1"),
   865  			last:   netip.MustParseAddr("192.168.0.7"),
   866  			offset: 6,
   867  			want:   7,
   868  		}, {
   869  			name:   "start from first address large range",
   870  			first:  netip.MustParseAddr("2001:db8:1::101"),
   871  			last:   netip.MustParseAddr("2001:db8:1::fff"),
   872  			offset: 0,
   873  			want:   3839,
   874  		}, {
   875  			name:   "start from address in the middle",
   876  			first:  netip.MustParseAddr("2001:db8:1::101"),
   877  			last:   netip.MustParseAddr("2001:db8:1::fff"),
   878  			offset: 255,
   879  			want:   3839,
   880  		}, {
   881  			name:   "start from last address",
   882  			first:  netip.MustParseAddr("2001:db8:1::101"),
   883  			last:   netip.MustParseAddr("2001:db8:1::fff"),
   884  			offset: 3838,
   885  			want:   3839,
   886  		},
   887  	}
   888  	for _, tt := range tests {
   889  		t.Run(tt.name, func(t *testing.T) {
   890  			var got uint64
   891  			iterator := ipIterator(tt.first, tt.last, tt.offset)
   892  
   893  			for {
   894  				ip := iterator()
   895  				if !ip.IsValid() {
   896  					break
   897  				}
   898  				got++
   899  			}
   900  			if got != tt.want {
   901  				t.Errorf("ipIterator() = %d, want %d", got, tt.want)
   902  			}
   903  			// check the iterator is fully stopped
   904  			for i := 0; i < 5; i++ {
   905  				if ip := iterator(); ip.IsValid() {
   906  					t.Errorf("iterator should not return more addresses: %v", ip)
   907  				}
   908  			}
   909  		})
   910  	}
   911  }
   912  
   913  func TestAllocateNextFC(t *testing.T) {
   914  	_, cidr, err := netutils.ParseCIDRSloppy("fc::/112")
   915  	if err != nil {
   916  		t.Fatal(err)
   917  	}
   918  	t.Logf("CIDR %s", cidr)
   919  
   920  	r, err := newTestAllocator(cidr)
   921  	if err != nil {
   922  		t.Fatal(err)
   923  	}
   924  	defer r.Destroy()
   925  	ip, err := r.AllocateNext()
   926  	if err != nil {
   927  		t.Fatalf("wrong ip %s : %v", ip, err)
   928  	}
   929  	t.Log(ip.String())
   930  }
   931  
   932  func BenchmarkIPAllocatorAllocateNextIPv4Size1048574(b *testing.B) {
   933  	_, cidr, err := netutils.ParseCIDRSloppy("10.0.0.0/12")
   934  	if err != nil {
   935  		b.Fatal(err)
   936  	}
   937  	r, err := newTestAllocator(cidr)
   938  	if err != nil {
   939  		b.Fatal(err)
   940  	}
   941  	defer r.Destroy()
   942  
   943  	for n := 0; n < b.N; n++ {
   944  		r.AllocateNext()
   945  	}
   946  }
   947  
   948  func BenchmarkIPAllocatorAllocateNextIPv6Size65535(b *testing.B) {
   949  	_, cidr, err := netutils.ParseCIDRSloppy("fd00::/120")
   950  	if err != nil {
   951  		b.Fatal(err)
   952  	}
   953  	r, err := newTestAllocator(cidr)
   954  	if err != nil {
   955  		b.Fatal(err)
   956  	}
   957  	defer r.Destroy()
   958  
   959  	for n := 0; n < b.N; n++ {
   960  		r.AllocateNext()
   961  	}
   962  }