github.com/cilium/cilium@v1.16.2/pkg/proxy/proxy_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package proxy
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/cilium/cilium/pkg/completion"
    14  	"github.com/cilium/cilium/pkg/envoy"
    15  	"github.com/cilium/cilium/pkg/policy"
    16  	endpointtest "github.com/cilium/cilium/pkg/proxy/endpoint/test"
    17  	"github.com/cilium/cilium/pkg/proxy/types"
    18  	"github.com/cilium/cilium/pkg/time"
    19  	"github.com/cilium/cilium/pkg/trigger"
    20  	"github.com/cilium/cilium/pkg/u8proto"
    21  )
    22  
    23  type MockDatapathUpdater struct{}
    24  
    25  func (m *MockDatapathUpdater) InstallProxyRules(proxyPort uint16, name string) {
    26  }
    27  
    28  func (m *MockDatapathUpdater) SupportsOriginalSourceAddr() bool {
    29  	return true
    30  }
    31  
    32  func (m *MockDatapathUpdater) GetProxyPorts() map[string]uint16 {
    33  	return nil
    34  }
    35  
    36  func proxyForTest() (*Proxy, func()) {
    37  	mockDatapathUpdater := &MockDatapathUpdater{}
    38  	p := createProxy(10000, 20000, mockDatapathUpdater, nil, nil)
    39  	triggerDone := make(chan struct{})
    40  	p.proxyPortsTrigger, _ = trigger.NewTrigger(trigger.Parameters{
    41  		MinInterval:  10 * time.Second,
    42  		TriggerFunc:  func(reasons []string) {},
    43  		ShutdownFunc: func() { close(triggerDone) },
    44  	})
    45  	return p, func() {
    46  		p.proxyPortsTrigger.Shutdown()
    47  		<-triggerDone
    48  	}
    49  }
    50  
    51  func TestPortAllocator(t *testing.T) {
    52  	testRunDir := t.TempDir()
    53  	socketDir := envoy.GetSocketDir(testRunDir)
    54  	err := os.MkdirAll(socketDir, 0700)
    55  	require.NoError(t, err)
    56  
    57  	p, cleaner := proxyForTest()
    58  	defer cleaner()
    59  
    60  	port, err := p.AllocateCRDProxyPort("listener1")
    61  	require.NoError(t, err)
    62  	require.NotEqual(t, 0, port)
    63  
    64  	port1, _, err := p.GetProxyPort("listener1")
    65  	require.NoError(t, err)
    66  	require.Equal(t, port, port1)
    67  
    68  	// Another allocation for the same name gets the same port
    69  	port1a, err := p.AllocateCRDProxyPort("listener1")
    70  	require.NoError(t, err)
    71  	require.Equal(t, port1, port1a)
    72  
    73  	name, pp := p.findProxyPortByType(types.ProxyTypeCRD, "listener1", false)
    74  	require.Equal(t, "listener1", name)
    75  	require.Equal(t, types.ProxyTypeCRD, pp.ProxyType)
    76  	require.Equal(t, port, pp.ProxyPort)
    77  	require.Equal(t, false, pp.Ingress)
    78  	require.Equal(t, true, pp.configured)
    79  	require.Equal(t, false, pp.isStatic)
    80  	require.Equal(t, 0, pp.nRedirects)
    81  	require.Equal(t, uint16(0), pp.rulesPort)
    82  
    83  	err = p.ReleaseProxyPort("listener1")
    84  	require.NoError(t, err)
    85  
    86  	// ProxyPort lingers and can still be found, but it's port is zeroed
    87  	port1b, _, err := p.GetProxyPort("listener1")
    88  	require.NoError(t, err)
    89  	require.Equal(t, uint16(0), port1b)
    90  	require.Equal(t, uint16(0), pp.ProxyPort)
    91  	require.Equal(t, false, pp.configured)
    92  	require.Equal(t, 0, pp.nRedirects)
    93  
    94  	// the port was never acked, so rulesPort is 0
    95  	require.Equal(t, uint16(0), pp.rulesPort)
    96  
    97  	// Allocates a different port (due to port was never acked)
    98  	port2, err := p.AllocateCRDProxyPort("listener1")
    99  	require.NoError(t, err)
   100  	require.NotEqual(t, port, port2)
   101  	name2, pp2 := p.findProxyPortByType(types.ProxyTypeCRD, "listener1", false)
   102  	require.Equal(t, name2, name)
   103  	require.Equal(t, pp2, pp)
   104  	require.Equal(t, types.ProxyTypeCRD, pp.ProxyType)
   105  	require.Equal(t, false, pp.Ingress)
   106  	require.Equal(t, port2, pp.ProxyPort)
   107  	require.Equal(t, true, pp.configured)
   108  	require.Equal(t, false, pp.isStatic)
   109  	require.Equal(t, 0, pp.nRedirects)
   110  	require.Equal(t, uint16(0), pp.rulesPort)
   111  
   112  	// Ack configures the port to the datapath
   113  	err = p.AckProxyPort(context.TODO(), "listener1")
   114  	require.NoError(t, err)
   115  	require.Equal(t, 1, pp.nRedirects)
   116  	require.Equal(t, port2, pp.rulesPort)
   117  
   118  	// Another Ack takes another reference
   119  	err = p.AckProxyPort(context.TODO(), "listener1")
   120  	require.NoError(t, err)
   121  	require.Equal(t, 2, pp.nRedirects)
   122  	require.Equal(t, port2, pp.rulesPort)
   123  
   124  	// 1st release decreases the count
   125  	err = p.ReleaseProxyPort("listener1")
   126  	require.NoError(t, err)
   127  	require.Equal(t, 1, pp.nRedirects)
   128  	require.Equal(t, true, pp.configured)
   129  	require.Equal(t, port2, pp.ProxyPort)
   130  
   131  	// 2nd release decreases the count to zero
   132  	err = p.ReleaseProxyPort("listener1")
   133  	require.NoError(t, err)
   134  	require.Equal(t, 0, pp.nRedirects)
   135  	require.Equal(t, false, pp.configured)
   136  	require.Equal(t, uint16(0), pp.ProxyPort)
   137  	require.Equal(t, port2, pp.rulesPort)
   138  
   139  	// extra releases are idempotent
   140  	err = p.ReleaseProxyPort("listener1")
   141  	require.NoError(t, err)
   142  	require.Equal(t, 0, pp.nRedirects)
   143  	require.Equal(t, false, pp.configured)
   144  	require.Equal(t, uint16(0), pp.ProxyPort)
   145  	require.Equal(t, port2, pp.rulesPort)
   146  
   147  	// mimic some other process taking the port
   148  	p.allocatedPorts[port2] = true
   149  
   150  	// Allocate again, this time a different port is allocated
   151  	port3, err := p.AllocateCRDProxyPort("listener1")
   152  	require.NoError(t, err)
   153  	require.NotEqual(t, uint16(0), port3)
   154  	require.NotEqual(t, port2, port3)
   155  	require.NotEqual(t, port1, port3)
   156  	name2, pp2 = p.findProxyPortByType(types.ProxyTypeCRD, "listener1", false)
   157  	require.Equal(t, name2, name)
   158  	require.Equal(t, pp2, pp)
   159  	require.Equal(t, types.ProxyTypeCRD, pp.ProxyType)
   160  	require.Equal(t, false, pp.Ingress)
   161  	require.Equal(t, port3, pp.ProxyPort)
   162  	require.Equal(t, true, pp.configured)
   163  	require.Equal(t, false, pp.isStatic)
   164  	require.Equal(t, 0, pp.nRedirects)
   165  	require.Equal(t, port2, pp.rulesPort)
   166  
   167  	// Ack configures the port to the datapath
   168  	err = p.AckProxyPort(context.TODO(), "listener1")
   169  	require.NoError(t, err)
   170  	require.Equal(t, 1, pp.nRedirects)
   171  	require.Equal(t, port3, pp.rulesPort)
   172  
   173  	// Release marks the port as unallocated
   174  	err = p.ReleaseProxyPort("listener1")
   175  	require.NoError(t, err)
   176  	require.Equal(t, 0, pp.nRedirects)
   177  	require.Equal(t, false, pp.configured)
   178  	require.Equal(t, uint16(0), pp.ProxyPort)
   179  	require.Equal(t, port3, pp.rulesPort)
   180  
   181  	inuse, exists := p.allocatedPorts[port3]
   182  	require.Equal(t, true, exists)
   183  	require.Equal(t, false, inuse)
   184  
   185  	// No-one used the port so next allocation gets the same port again
   186  	port4, err := p.AllocateCRDProxyPort("listener1")
   187  	require.NoError(t, err)
   188  	require.Equal(t, port3, port4)
   189  	require.Equal(t, types.ProxyTypeCRD, pp.ProxyType)
   190  	require.Equal(t, false, pp.Ingress)
   191  	require.Equal(t, port4, pp.ProxyPort)
   192  	require.Equal(t, true, pp.configured)
   193  	require.Equal(t, false, pp.isStatic)
   194  	require.Equal(t, 0, pp.nRedirects)
   195  	require.Equal(t, port3, pp.rulesPort)
   196  }
   197  
   198  type fakeProxyPolicy struct{}
   199  
   200  func (p *fakeProxyPolicy) CopyL7RulesPerEndpoint() policy.L7DataMap {
   201  	return policy.L7DataMap{}
   202  }
   203  
   204  func (p *fakeProxyPolicy) GetL7Parser() policy.L7ParserType {
   205  	return policy.ParserTypeCRD
   206  }
   207  
   208  func (p *fakeProxyPolicy) GetIngress() bool {
   209  	return false
   210  }
   211  
   212  func (p *fakeProxyPolicy) GetPort() uint16 {
   213  	return uint16(80)
   214  }
   215  
   216  func (p *fakeProxyPolicy) GetProtocol() uint8 {
   217  	return uint8(u8proto.UDP)
   218  }
   219  
   220  func (p *fakeProxyPolicy) GetListener() string {
   221  	return "nonexisting-listener"
   222  }
   223  
   224  func TestCreateOrUpdateRedirectMissingListener(t *testing.T) {
   225  	testRunDir := t.TempDir()
   226  	socketDir := envoy.GetSocketDir(testRunDir)
   227  	err := os.MkdirAll(socketDir, 0700)
   228  	require.NoError(t, err)
   229  
   230  	p, cleaner := proxyForTest()
   231  	defer cleaner()
   232  
   233  	ep := &endpointtest.ProxyUpdaterMock{
   234  		Id:   1000,
   235  		Ipv4: "10.0.0.1",
   236  		Ipv6: "f00d::1",
   237  	}
   238  
   239  	l4 := &fakeProxyPolicy{}
   240  
   241  	ctx := context.TODO()
   242  	wg := completion.NewWaitGroup(ctx)
   243  
   244  	proxyPort, err, finalizeFunc, revertFunc := p.CreateOrUpdateRedirect(ctx, l4, "dummy-proxy-id", ep, wg)
   245  	require.Equal(t, uint16(0), proxyPort)
   246  	require.Error(t, err)
   247  	require.Nil(t, finalizeFunc)
   248  	require.Nil(t, revertFunc)
   249  }