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  }