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 }