@@ -3,6 +3,8 @@ package networkutils_test
33import (
44 "errors"
55 "net"
6+ "strings"
7+ "syscall"
68 "testing"
79 "time"
810
@@ -19,12 +21,148 @@ func (n *DummyNetClient) DialTimeout(network, address string, timeout time.Durat
1921 return nil , errors .New ("" )
2022}
2123
24+ // MockNetClientAllInUse simulates all IPs being in use.
25+ type MockNetClientAllInUse struct {}
26+
27+ func (n * MockNetClientAllInUse ) DialTimeout (network , address string , timeout time.Duration ) (net.Conn , error ) {
28+ // Simulate all IPs are in use by returning ECONNREFUSED
29+ // IsIPInUse checks for: err == nil OR errors.Is(err, syscall.ECONNREFUSED) OR errors.Is(err, syscall.ECONNRESET)
30+ return nil , & net.OpError {
31+ Op : "dial" ,
32+ Net : "tcp" ,
33+ Err : syscall .ECONNREFUSED ,
34+ }
35+ }
36+
37+ // MockNetClientSomeInUse simulates specific IPs being in use.
38+ type MockNetClientSomeInUse struct {
39+ inUseIPs map [string ]bool
40+ }
41+
42+ func (n * MockNetClientSomeInUse ) DialTimeout (network , address string , timeout time.Duration ) (net.Conn , error ) {
43+ // Extract IP from address (format is "IP:port")
44+ ip := strings .Split (address , ":" )[0 ]
45+ if n .inUseIPs [ip ] {
46+ // Simulate IP in use - return ECONNREFUSED
47+ return nil , & net.OpError {
48+ Op : "dial" ,
49+ Net : "tcp" ,
50+ Err : syscall .ECONNREFUSED ,
51+ }
52+ }
53+ // Simulate IP not in use - return generic error
54+ return nil , errors .New ("connection timeout" )
55+ }
56+
2257func TestGenerateUniqueIP (t * testing.T ) {
2358 cidrBlock := "1.2.3.4/16"
2459
2560 ipgen := networkutils .NewIPGenerator (& DummyNetClient {})
26- ip , err := ipgen .GenerateUniqueIP (cidrBlock )
61+ ip , err := ipgen .GenerateUniqueIP (cidrBlock , nil )
2762 if err != nil {
2863 t .Fatalf ("GenerateUniqueIP() ip = %v error: %v" , ip , err )
2964 }
3065}
66+
67+ func TestGenerateUniqueIPWithUsedIPsMap (t * testing.T ) {
68+ cidrBlock := "192.168.1.0/29" // Small range: .0 to .7 (6 usable IPs)
69+
70+ // Mark first 3 IPs as used
71+ usedIPs := map [string ]bool {
72+ "192.168.1.1" : true ,
73+ "192.168.1.2" : true ,
74+ "192.168.1.3" : true ,
75+ }
76+
77+ ipgen := networkutils .NewIPGenerator (& DummyNetClient {})
78+ ip , err := ipgen .GenerateUniqueIP (cidrBlock , usedIPs )
79+ if err != nil {
80+ t .Fatalf ("GenerateUniqueIP() error = %v" , err )
81+ }
82+
83+ // Should skip the used IPs and return .4 or later
84+ if ip == "192.168.1.1" || ip == "192.168.1.2" || ip == "192.168.1.3" {
85+ t .Errorf ("GenerateUniqueIP() returned used IP: %v" , ip )
86+ }
87+ }
88+
89+ func TestGenerateUniqueIPExhaustion (t * testing.T ) {
90+ cidrBlock := "10.0.0.0/30" // Very small range: only .0 to .3 (2 usable IPs)
91+
92+ // Use a client that marks all IPs as in use
93+ ipgen := networkutils .NewIPGenerator (& MockNetClientAllInUse {})
94+ _ , err := ipgen .GenerateUniqueIP (cidrBlock , nil )
95+ if err == nil {
96+ t .Fatal ("GenerateUniqueIP() expected error for exhausted IP pool, got nil" )
97+ }
98+
99+ // Verify error message mentions the CIDR
100+ if ! strings .Contains (err .Error (), cidrBlock ) {
101+ t .Errorf ("Error message should mention CIDR %s, got: %v" , cidrBlock , err )
102+ }
103+ }
104+
105+ func TestGenerateUniqueIPWithNetworkInUse (t * testing.T ) {
106+ cidrBlock := "10.1.1.0/29" // Small range for testing
107+
108+ // Mark first 2 IPs as in use on network
109+ mockClient := & MockNetClientSomeInUse {
110+ inUseIPs : map [string ]bool {
111+ "10.1.1.1" : true ,
112+ "10.1.1.2" : true ,
113+ },
114+ }
115+
116+ ipgen := networkutils .NewIPGenerator (mockClient )
117+ ip , err := ipgen .GenerateUniqueIP (cidrBlock , nil )
118+ if err != nil {
119+ t .Fatalf ("GenerateUniqueIP() error = %v" , err )
120+ }
121+
122+ // Should skip the in-use IPs
123+ if ip == "10.1.1.1" || ip == "10.1.1.2" {
124+ t .Errorf ("GenerateUniqueIP() returned in-use IP: %v" , ip )
125+ }
126+ }
127+
128+ func TestGenerateUniqueIPCombinedUsedAndNetwork (t * testing.T ) {
129+ cidrBlock := "172.16.0.0/29"
130+
131+ // Mark some IPs as used in map
132+ usedIPs := map [string ]bool {
133+ "172.16.0.1" : true ,
134+ "172.16.0.2" : true ,
135+ }
136+
137+ // Mark some IPs as in use on network
138+ mockClient := & MockNetClientSomeInUse {
139+ inUseIPs : map [string ]bool {
140+ "172.16.0.3" : true ,
141+ "172.16.0.4" : true ,
142+ },
143+ }
144+
145+ ipgen := networkutils .NewIPGenerator (mockClient )
146+ ip , err := ipgen .GenerateUniqueIP (cidrBlock , usedIPs )
147+ if err != nil {
148+ t .Fatalf ("GenerateUniqueIP() error = %v" , err )
149+ }
150+
151+ // Should skip all used and in-use IPs, return .5 or later
152+ usedOrInUse := []string {"172.16.0.1" , "172.16.0.2" , "172.16.0.3" , "172.16.0.4" }
153+ for _ , badIP := range usedOrInUse {
154+ if ip == badIP {
155+ t .Errorf ("GenerateUniqueIP() returned used/in-use IP: %v" , ip )
156+ }
157+ }
158+ }
159+
160+ func TestGenerateUniqueIPInvalidCIDR (t * testing.T ) {
161+ cidrBlock := "invalid-cidr"
162+
163+ ipgen := networkutils .NewIPGenerator (& DummyNetClient {})
164+ _ , err := ipgen .GenerateUniqueIP (cidrBlock , nil )
165+ if err == nil {
166+ t .Fatal ("GenerateUniqueIP() expected error for invalid CIDR, got nil" )
167+ }
168+ }
0 commit comments