github.com/cilium/cilium@v1.16.2/pkg/ipmasq/ipmasq_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package ipmasq 5 6 import ( 7 "fmt" 8 "net" 9 "os" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 15 "github.com/cilium/cilium/pkg/ip" 16 "github.com/cilium/cilium/pkg/lock" 17 ) 18 19 type ipMasqMapMock struct { 20 lock.RWMutex 21 ipv4Enabled bool 22 ipv6Enabled bool 23 cidrsIPv4 map[string]net.IPNet 24 cidrsIPv6 map[string]net.IPNet 25 } 26 27 func (m *ipMasqMapMock) Update(cidr net.IPNet) error { 28 m.Lock() 29 defer m.Unlock() 30 31 cidrStr := cidr.String() 32 if ip.IsIPv4(cidr.IP) { 33 if m.ipv4Enabled { 34 if _, ok := m.cidrsIPv4[cidrStr]; ok { 35 return fmt.Errorf("CIDR already exists: %s", cidrStr) 36 } 37 m.cidrsIPv4[cidrStr] = cidr 38 } else { 39 return fmt.Errorf("IPv4 disabled, but required for this CIDR: %s", cidrStr) 40 } 41 } else { 42 if m.ipv6Enabled { 43 if _, ok := m.cidrsIPv6[cidrStr]; ok { 44 return fmt.Errorf("CIDR already exists: %s", cidrStr) 45 } 46 m.cidrsIPv6[cidrStr] = cidr 47 } else { 48 return fmt.Errorf("IPv6 disabled, but required for this CIDR: %s", cidrStr) 49 } 50 } 51 52 return nil 53 } 54 55 func (m *ipMasqMapMock) Delete(cidr net.IPNet) error { 56 m.Lock() 57 defer m.Unlock() 58 59 cidrStr := cidr.String() 60 if ip.IsIPv4(cidr.IP) { 61 if m.ipv4Enabled { 62 if _, ok := m.cidrsIPv4[cidrStr]; !ok { 63 return fmt.Errorf("CIDR not found: %s", cidrStr) 64 } 65 delete(m.cidrsIPv4, cidrStr) 66 } else { 67 return fmt.Errorf("IPv4 disabled, but required for this CIDR: %s", cidrStr) 68 } 69 } else { 70 if m.ipv6Enabled { 71 if _, ok := m.cidrsIPv6[cidrStr]; !ok { 72 return fmt.Errorf("CIDR not found: %s", cidrStr) 73 } 74 delete(m.cidrsIPv6, cidrStr) 75 } else { 76 return fmt.Errorf("IPv6 disabled, but required for this CIDR: %s", cidrStr) 77 } 78 } 79 80 return nil 81 } 82 83 func (m *ipMasqMapMock) Dump() ([]net.IPNet, error) { 84 m.RLock() 85 defer m.RUnlock() 86 87 cidrs := make([]net.IPNet, 0, len(m.cidrsIPv4)+len(m.cidrsIPv6)) 88 if m.ipv4Enabled { 89 for _, cidr := range m.cidrsIPv4 { 90 cidrs = append(cidrs, cidr) 91 } 92 } 93 if m.ipv6Enabled { 94 for _, cidr := range m.cidrsIPv6 { 95 cidrs = append(cidrs, cidr) 96 } 97 } 98 99 return cidrs, nil 100 } 101 102 func (m *ipMasqMapMock) dumpToSet() map[string]struct{} { 103 m.RLock() 104 defer m.RUnlock() 105 106 length := 0 107 if m.ipv4Enabled { 108 length += len(m.cidrsIPv4) 109 } 110 if m.ipv6Enabled { 111 length += len(m.cidrsIPv6) 112 } 113 114 cidrs := make(map[string]struct{}, length) 115 if m.ipv4Enabled { 116 for cidrStr := range m.cidrsIPv4 { 117 cidrs[cidrStr] = struct{}{} 118 } 119 } 120 if m.ipv6Enabled { 121 for cidrStr := range m.cidrsIPv6 { 122 cidrs[cidrStr] = struct{}{} 123 } 124 } 125 126 return cidrs 127 } 128 129 type IPMasqTestSuite struct { 130 ipMasqMap *ipMasqMapMock 131 ipMasqAgent *IPMasqAgent 132 configFilePath string 133 } 134 135 func setUpTest(tb testing.TB) *IPMasqTestSuite { 136 i := &IPMasqTestSuite{} 137 i.ipMasqMap = &ipMasqMapMock{ 138 cidrsIPv4: map[string]net.IPNet{}, 139 cidrsIPv6: map[string]net.IPNet{}, 140 } 141 142 configFile, err := os.CreateTemp("", "ipmasq-test") 143 require.NoError(tb, err) 144 i.configFilePath = configFile.Name() 145 146 agent, err := newIPMasqAgent(i.configFilePath, i.ipMasqMap) 147 require.NoError(tb, err) 148 i.ipMasqAgent = agent 149 150 tb.Cleanup(func() { 151 i.ipMasqAgent.Stop() 152 os.Remove(i.configFilePath) 153 }) 154 155 return i 156 } 157 158 func (i *IPMasqTestSuite) writeConfig(t *testing.T, cfg string) { 159 err := os.WriteFile(i.configFilePath, []byte(cfg), 0644) 160 require.NoError(t, err) 161 } 162 163 func TestUpdateIPv4(t *testing.T) { 164 i := setUpTest(t) 165 166 i.ipMasqMap.ipv4Enabled = true 167 i.ipMasqMap.ipv6Enabled = false 168 i.ipMasqAgent.Start() 169 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 1.1.1.1/32\n- 2.2.2.2/16") 170 time.Sleep(300 * time.Millisecond) 171 172 ipnets := i.ipMasqMap.dumpToSet() 173 require.Len(t, ipnets, 3) 174 _, ok := ipnets["1.1.1.1/32"] 175 require.True(t, ok) 176 _, ok = ipnets["2.2.0.0/16"] 177 require.True(t, ok) 178 179 _, ok = ipnets[linkLocalCIDRIPv4Str] 180 require.True(t, ok) 181 182 // Write new config 183 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 8.8.0.0/16\n- 2.2.2.2/16") 184 time.Sleep(300 * time.Millisecond) 185 186 ipnets = i.ipMasqMap.dumpToSet() 187 require.Len(t, ipnets, 3) 188 _, ok = ipnets["8.8.0.0/16"] 189 require.True(t, ok) 190 _, ok = ipnets["2.2.0.0/16"] 191 require.True(t, ok) 192 _, ok = ipnets[linkLocalCIDRIPv4Str] 193 require.True(t, ok) 194 195 // Write config with no CIDRs 196 i.writeConfig(t, "nonMasqueradeCIDRs:\n") 197 time.Sleep(300 * time.Millisecond) 198 199 ipnets = i.ipMasqMap.dumpToSet() 200 require.Len(t, ipnets, 1) 201 _, ok = ipnets[linkLocalCIDRIPv4Str] 202 require.True(t, ok) 203 204 // Write new config in JSON 205 i.writeConfig(t, `{"nonMasqueradeCIDRs": ["8.8.0.0/16", "1.1.2.3/16"], "masqLinkLocal": true}`) 206 time.Sleep(300 * time.Millisecond) 207 208 ipnets = i.ipMasqMap.dumpToSet() 209 require.Len(t, ipnets, 2) 210 _, ok = ipnets["8.8.0.0/16"] 211 require.True(t, ok) 212 _, ok = ipnets["1.1.0.0/16"] 213 require.True(t, ok) 214 215 // Delete file, should remove the CIDRs and add default nonMasq CIDRs 216 err := os.Remove(i.configFilePath) 217 require.NoError(t, err) 218 time.Sleep(300 * time.Millisecond) 219 ipnets = i.ipMasqMap.dumpToSet() 220 require.Len(t, ipnets, len(defaultNonMasqCIDRs)+1) 221 for cidrStr := range defaultNonMasqCIDRs { 222 _, ok := ipnets[cidrStr] 223 require.True(t, ok) 224 } 225 _, ok = ipnets[linkLocalCIDRIPv4Str] 226 require.True(t, ok) 227 } 228 229 func TestUpdateIPv6(t *testing.T) { 230 i := setUpTest(t) 231 232 i.ipMasqMap.ipv4Enabled = false 233 i.ipMasqMap.ipv6Enabled = true 234 i.ipMasqAgent.Start() 235 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 1:1:1:1::/64\n- 2:2::/32") 236 time.Sleep(300 * time.Millisecond) 237 238 ipnets := i.ipMasqMap.dumpToSet() 239 require.Len(t, ipnets, 3) 240 _, ok := ipnets["1:1:1:1::/64"] 241 require.True(t, ok) 242 _, ok = ipnets["2:2::/32"] 243 require.True(t, ok) 244 _, ok = ipnets[linkLocalCIDRIPv6Str] 245 require.True(t, ok) 246 247 // Write new config 248 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 8:8:8:8::/64\n- 2:2::/32") 249 time.Sleep(300 * time.Millisecond) 250 251 ipnets = i.ipMasqMap.dumpToSet() 252 require.Len(t, ipnets, 3) 253 _, ok = ipnets["8:8:8:8::/64"] 254 require.True(t, ok) 255 _, ok = ipnets["2:2::/32"] 256 require.True(t, ok) 257 _, ok = ipnets[linkLocalCIDRIPv6Str] 258 require.True(t, ok) 259 260 // Write config with no CIDRs 261 i.writeConfig(t, "nonMasqueradeCIDRs:\n") 262 time.Sleep(300 * time.Millisecond) 263 264 ipnets = i.ipMasqMap.dumpToSet() 265 require.Len(t, ipnets, 1) 266 _, ok = ipnets[linkLocalCIDRIPv6Str] 267 require.True(t, ok) 268 269 // Write new config in JSON 270 i.writeConfig(t, `{"nonMasqueradeCIDRs": ["8:8:8:8::/64", "1:2:3:4::/64"], "masqLinkLocalIPv6": true}`) 271 time.Sleep(300 * time.Millisecond) 272 273 ipnets = i.ipMasqMap.dumpToSet() 274 require.Len(t, ipnets, 2) 275 _, ok = ipnets["8:8:8:8::/64"] 276 require.True(t, ok) 277 _, ok = ipnets["1:2:3:4::/64"] 278 require.True(t, ok) 279 280 // Delete file, should remove the CIDRs and add default nonMasq CIDRs 281 err := os.Remove(i.configFilePath) 282 require.NoError(t, err) 283 time.Sleep(300 * time.Millisecond) 284 ipnets = i.ipMasqMap.dumpToSet() 285 require.Len(t, ipnets, 1) 286 _, ok = ipnets[linkLocalCIDRIPv6Str] 287 require.True(t, ok) 288 } 289 290 func TestUpdate(t *testing.T) { 291 i := setUpTest(t) 292 i.ipMasqMap.ipv4Enabled = true 293 i.ipMasqMap.ipv6Enabled = true 294 i.ipMasqAgent.Start() 295 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 1.1.1.1/32\n- 2:2::/32") 296 time.Sleep(300 * time.Millisecond) 297 298 ipnets := i.ipMasqMap.dumpToSet() 299 require.Len(t, ipnets, 4) 300 _, ok := ipnets["1.1.1.1/32"] 301 require.True(t, ok) 302 _, ok = ipnets["2:2::/32"] 303 require.True(t, ok) 304 _, ok = ipnets[linkLocalCIDRIPv4Str] 305 require.True(t, ok) 306 _, ok = ipnets[linkLocalCIDRIPv6Str] 307 require.True(t, ok) 308 309 // Write new config 310 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 8:8:8:8::/64\n- 2.2.0.0/16") 311 time.Sleep(300 * time.Millisecond) 312 313 ipnets = i.ipMasqMap.dumpToSet() 314 require.Len(t, ipnets, 4) 315 _, ok = ipnets["8:8:8:8::/64"] 316 require.True(t, ok) 317 _, ok = ipnets["2.2.0.0/16"] 318 require.True(t, ok) 319 _, ok = ipnets[linkLocalCIDRIPv4Str] 320 require.True(t, ok) 321 _, ok = ipnets[linkLocalCIDRIPv6Str] 322 require.True(t, ok) 323 324 // Write config with no CIDRs 325 i.writeConfig(t, "nonMasqueradeCIDRs:\n") 326 time.Sleep(300 * time.Millisecond) 327 328 ipnets = i.ipMasqMap.dumpToSet() 329 require.Len(t, ipnets, 2) 330 _, ok = ipnets[linkLocalCIDRIPv4Str] 331 require.True(t, ok) 332 _, ok = ipnets[linkLocalCIDRIPv6Str] 333 require.True(t, ok) 334 335 // Write new config in JSON 336 i.writeConfig(t, `{"nonMasqueradeCIDRs": ["1.2.3.4/32", "1:2:3:4::/64"], "masqLinkLocalIPv6": true}`) 337 time.Sleep(300 * time.Millisecond) 338 339 ipnets = i.ipMasqMap.dumpToSet() 340 require.Len(t, ipnets, 3) 341 _, ok = ipnets["1.2.3.4/32"] 342 require.True(t, ok) 343 _, ok = ipnets["1:2:3:4::/64"] 344 require.True(t, ok) 345 _, ok = ipnets[linkLocalCIDRIPv4Str] 346 require.True(t, ok) 347 348 // Delete file, should remove the CIDRs and add default nonMasq CIDRs 349 err := os.Remove(i.configFilePath) 350 require.NoError(t, err) 351 time.Sleep(300 * time.Millisecond) 352 ipnets = i.ipMasqMap.dumpToSet() 353 require.Len(t, ipnets, len(defaultNonMasqCIDRs)+1+1) 354 _, ok = ipnets[linkLocalCIDRIPv4Str] 355 require.True(t, ok) 356 _, ok = ipnets[linkLocalCIDRIPv6Str] 357 require.True(t, ok) 358 } 359 360 func TestRestoreIPv4(t *testing.T) { 361 var err error 362 363 i := setUpTest(t) 364 i.ipMasqMap.ipv4Enabled = true 365 i.ipMasqMap.ipv6Enabled = false 366 i.ipMasqAgent.Start() 367 // Check that stale entry is removed from the map after restore 368 i.ipMasqAgent.Stop() 369 370 _, cidr, _ := net.ParseCIDR("3.3.3.0/24") 371 i.ipMasqMap.cidrsIPv4[cidr.String()] = *cidr 372 _, cidr, _ = net.ParseCIDR("4.4.0.0/16") 373 i.ipMasqMap.cidrsIPv4[cidr.String()] = *cidr 374 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 4.4.0.0/16") 375 376 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 377 require.NoError(t, err) 378 i.ipMasqAgent.Start() 379 time.Sleep(300 * time.Millisecond) 380 381 ipnets := i.ipMasqMap.dumpToSet() 382 require.Len(t, ipnets, 2) 383 _, ok := ipnets["4.4.0.0/16"] 384 require.True(t, ok) 385 _, ok = ipnets[linkLocalCIDRIPv4Str] 386 require.True(t, ok) 387 388 // Now stop the goroutine, and also remove the maps. It should bootstrap from 389 // the config 390 i.ipMasqAgent.Stop() 391 i.ipMasqMap = &ipMasqMapMock{ 392 cidrsIPv4: map[string]net.IPNet{}, 393 ipv4Enabled: true, 394 ipv6Enabled: false, 395 } 396 i.ipMasqAgent.ipMasqMap = i.ipMasqMap 397 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 3.3.0.0/16\nmasqLinkLocal: true") 398 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 399 require.NoError(t, err) 400 i.ipMasqAgent.Start() 401 402 ipnets = i.ipMasqMap.dumpToSet() 403 require.Len(t, ipnets, 1) 404 _, ok = ipnets["3.3.0.0/16"] 405 require.True(t, ok) 406 } 407 408 func TestRestoreIPv6(t *testing.T) { 409 var err error 410 411 i := setUpTest(t) 412 i.ipMasqMap.ipv4Enabled = false 413 i.ipMasqMap.ipv6Enabled = true 414 i.ipMasqAgent.Start() 415 // Check that stale entry is removed from the map after restore 416 i.ipMasqAgent.Stop() 417 418 _, cidr, _ := net.ParseCIDR("3:3:3:3::/64") 419 i.ipMasqMap.cidrsIPv6[cidr.String()] = *cidr 420 _, cidr, _ = net.ParseCIDR("4:4::/32") 421 i.ipMasqMap.cidrsIPv6[cidr.String()] = *cidr 422 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 4:4::/32") 423 424 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 425 require.NoError(t, err) 426 i.ipMasqAgent.Start() 427 time.Sleep(300 * time.Millisecond) 428 429 ipnets := i.ipMasqMap.dumpToSet() 430 require.Len(t, ipnets, 2) 431 _, ok := ipnets["4:4::/32"] 432 require.True(t, ok) 433 _, ok = ipnets[linkLocalCIDRIPv6Str] 434 require.True(t, ok) 435 436 // Now stop the goroutine, and also remove the maps. It should bootstrap from 437 // the config 438 i.ipMasqAgent.Stop() 439 i.ipMasqMap = &ipMasqMapMock{ 440 cidrsIPv6: map[string]net.IPNet{}, 441 ipv4Enabled: false, 442 ipv6Enabled: true, 443 } 444 i.ipMasqAgent.ipMasqMap = i.ipMasqMap 445 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 3:3::/96\nmasqLinkLocalIPv6: true") 446 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 447 require.NoError(t, err) 448 i.ipMasqAgent.Start() 449 450 ipnets = i.ipMasqMap.dumpToSet() 451 require.Len(t, ipnets, 1) 452 _, ok = ipnets["3:3::/96"] 453 require.True(t, ok) 454 } 455 456 func TestRestore(t *testing.T) { 457 var err error 458 459 i := setUpTest(t) 460 i.ipMasqMap.ipv4Enabled = true 461 i.ipMasqMap.ipv6Enabled = true 462 i.ipMasqAgent.Start() 463 // Check that stale entry is removed from the map after restore 464 i.ipMasqAgent.Stop() 465 466 _, cidr, _ := net.ParseCIDR("3:3:3:3::/64") 467 i.ipMasqMap.cidrsIPv6[cidr.String()] = *cidr 468 _, cidr, _ = net.ParseCIDR("4:4::/32") 469 i.ipMasqMap.cidrsIPv6[cidr.String()] = *cidr 470 _, cidr, _ = net.ParseCIDR("3.3.3.0/24") 471 i.ipMasqMap.cidrsIPv4[cidr.String()] = *cidr 472 _, cidr, _ = net.ParseCIDR("4.4.0.0/16") 473 i.ipMasqMap.cidrsIPv4[cidr.String()] = *cidr 474 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 4.4.0.0/16\n- 4:4::/32") 475 476 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 477 require.NoError(t, err) 478 i.ipMasqAgent.Start() 479 time.Sleep(300 * time.Millisecond) 480 481 ipnets := i.ipMasqMap.dumpToSet() 482 require.Len(t, ipnets, 4) 483 _, ok := ipnets["4.4.0.0/16"] 484 require.True(t, ok) 485 _, ok = ipnets["4:4::/32"] 486 require.True(t, ok) 487 _, ok = ipnets[linkLocalCIDRIPv4Str] 488 require.True(t, ok) 489 _, ok = ipnets[linkLocalCIDRIPv6Str] 490 require.True(t, ok) 491 492 // Now stop the goroutine, and also remove the maps. It should bootstrap from 493 // the config 494 i.ipMasqAgent.Stop() 495 i.ipMasqMap = &ipMasqMapMock{ 496 cidrsIPv4: map[string]net.IPNet{}, 497 cidrsIPv6: map[string]net.IPNet{}, 498 ipv4Enabled: true, 499 ipv6Enabled: true, 500 } 501 i.ipMasqAgent.ipMasqMap = i.ipMasqMap 502 i.writeConfig(t, "nonMasqueradeCIDRs:\n- 3.3.0.0/16\n- 3:3:3:3::/96\nmasqLinkLocal: true\nmasqLinkLocalIPv6: true") 503 i.ipMasqAgent, err = newIPMasqAgent(i.configFilePath, i.ipMasqMap) 504 require.NoError(t, err) 505 i.ipMasqAgent.Start() 506 507 ipnets = i.ipMasqMap.dumpToSet() 508 require.Len(t, ipnets, 2) 509 _, ok = ipnets["3.3.0.0/16"] 510 require.True(t, ok) 511 _, ok = ipnets["3:3:3:3::/96"] 512 require.True(t, ok) 513 }