gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/tests/integration/istio_test.go (about) 1 // Copyright 2021 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package istio_test 16 17 import ( 18 "fmt" 19 "io" 20 "net" 21 "net/http" 22 "strconv" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "gvisor.dev/gvisor/pkg/context" 27 "gvisor.dev/gvisor/pkg/rand" 28 "gvisor.dev/gvisor/pkg/sync" 29 "gvisor.dev/gvisor/pkg/tcpip" 30 "gvisor.dev/gvisor/pkg/tcpip/adapters/gonet" 31 "gvisor.dev/gvisor/pkg/tcpip/header" 32 "gvisor.dev/gvisor/pkg/tcpip/link/loopback" 33 "gvisor.dev/gvisor/pkg/tcpip/link/pipe" 34 "gvisor.dev/gvisor/pkg/tcpip/link/sniffer" 35 "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" 36 "gvisor.dev/gvisor/pkg/tcpip/stack" 37 "gvisor.dev/gvisor/pkg/tcpip/testutil" 38 "gvisor.dev/gvisor/pkg/tcpip/transport/tcp" 39 ) 40 41 // testContext encapsulates the state required to run tests that simulate 42 // an istio like environment. 43 // 44 // A diagram depicting the setup is shown below. 45 // 46 // +-----------------------------------------------------------------------+ 47 // | +-------------------------------------------------+ | 48 // | + ----------+ | + -----------------+ PROXY +----------+ | | 49 // | | clientEP | | | serverListeningEP|--accepted-> | serverEP |-+ | | 50 // | + ----------+ | + -----------------+ +----------+ | | | 51 // | | -------|-------------+ +----------+ | | | 52 // | | | | | proxyEP |-+ | | 53 // | +-----redirect | +----------+ | | 54 // | + ------------+---|------+---+ | 55 // | | | 56 // | Local Stack. | | 57 // +-------------------------------------------------------|---------------+ 58 // | 59 // +-----------------------------------------------------------------------+ 60 // | remoteStack | | 61 // | +-------------SYN ---------------| | 62 // | | | | 63 // | +-------------------|--------------------------------|-_---+ | 64 // | | + -----------------+ + ----------+ | | | 65 // | | | remoteListeningEP|--accepted--->| remoteEP |<++ | | 66 // | | + -----------------+ + ----------+ | | 67 // | | Remote HTTP Server | | 68 // | +----------------------------------------------------------+ | 69 // +-----------------------------------------------------------------------+ 70 type testContext struct { 71 // localServerListener is the listening port for the server which will proxy 72 // all traffic to the remote EP. 73 localServerListener *gonet.TCPListener 74 75 // remoteListenListener is the remote listening endpoint that will receive 76 // connections from server. 77 remoteServerListener *gonet.TCPListener 78 79 // localStack is the stack used to create client/server endpoints and 80 // also the stack on which we install NAT redirect rules. 81 localStack *stack.Stack 82 83 // remoteStack is the stack that represents a *remote* server. 84 remoteStack *stack.Stack 85 86 // defaultResponse is the response served by the HTTP server for all GET 87 defaultResponse []byte 88 89 // requests. wg is used to wait for HTTP server and Proxy to terminate before 90 // returning from cleanup. 91 wg sync.WaitGroup 92 } 93 94 func (ctx *testContext) cleanup() { 95 ctx.localServerListener.Close() 96 ctx.localStack.Destroy() 97 ctx.remoteServerListener.Close() 98 ctx.remoteStack.Destroy() 99 ctx.wg.Wait() 100 } 101 102 const ( 103 localServerPort = 8080 104 remoteServerPort = 9090 105 ) 106 107 var ( 108 localIPv4Addr1 = testutil.MustParse4("10.0.0.1") 109 localIPv4Addr2 = testutil.MustParse4("10.0.0.2") 110 loopbackIPv4Addr = testutil.MustParse4("127.0.0.1") 111 remoteIPv4Addr1 = testutil.MustParse4("10.0.0.3") 112 ) 113 114 func newTestContext(t *testing.T) *testContext { 115 t.Helper() 116 localNIC, remoteNIC := pipe.New("" /* linkAddr1 */, "" /* linkAddr2 */, 1500) 117 118 localStack := stack.New(stack.Options{ 119 NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol}, 120 TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol}, 121 HandleLocal: true, 122 }) 123 remoteStack := stack.New(stack.Options{ 124 NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol}, 125 TransportProtocols: []stack.TransportProtocolFactory{tcp.NewProtocol}, 126 HandleLocal: true, 127 }) 128 129 // Add loopback NIC. We need a loopback NIC as NAT redirect rule redirect to 130 // loopback address + specified port. 131 loopbackNIC := loopback.New() 132 const loopbackNICID = tcpip.NICID(1) 133 if err := localStack.CreateNIC(loopbackNICID, sniffer.New(loopbackNIC)); err != nil { 134 t.Fatalf("localStack.CreateNIC(%d, _): %s", loopbackNICID, err) 135 } 136 loopbackAddr := tcpip.ProtocolAddress{ 137 Protocol: header.IPv4ProtocolNumber, 138 AddressWithPrefix: loopbackIPv4Addr.WithPrefix(), 139 } 140 if err := localStack.AddProtocolAddress(loopbackNICID, loopbackAddr, stack.AddressProperties{}); err != nil { 141 t.Fatalf("localStack.AddProtocolAddress(%d, %+v, {}): %s", loopbackNICID, loopbackAddr, err) 142 } 143 144 // Create linked NICs that connects the local and remote stack. 145 const localNICID = tcpip.NICID(2) 146 const remoteNICID = tcpip.NICID(3) 147 if err := localStack.CreateNIC(localNICID, sniffer.New(localNIC)); err != nil { 148 t.Fatalf("localStack.CreateNIC(%d, _): %s", localNICID, err) 149 } 150 if err := remoteStack.CreateNIC(remoteNICID, sniffer.New(remoteNIC)); err != nil { 151 t.Fatalf("remoteStack.CreateNIC(%d, _): %s", remoteNICID, err) 152 } 153 154 for _, addr := range []tcpip.Address{localIPv4Addr1, localIPv4Addr2} { 155 localProtocolAddr := tcpip.ProtocolAddress{ 156 Protocol: header.IPv4ProtocolNumber, 157 AddressWithPrefix: addr.WithPrefix(), 158 } 159 if err := localStack.AddProtocolAddress(localNICID, localProtocolAddr, stack.AddressProperties{}); err != nil { 160 t.Fatalf("localStack.AddProtocolAddress(%d, %+v, {}): %s", localNICID, localProtocolAddr, err) 161 } 162 } 163 164 remoteProtocolAddr := tcpip.ProtocolAddress{ 165 Protocol: header.IPv4ProtocolNumber, 166 AddressWithPrefix: remoteIPv4Addr1.WithPrefix(), 167 } 168 if err := remoteStack.AddProtocolAddress(remoteNICID, remoteProtocolAddr, stack.AddressProperties{}); err != nil { 169 t.Fatalf("remoteStack.AddProtocolAddress(%d, %+v, {}): %s", remoteNICID, remoteProtocolAddr, err) 170 } 171 172 // Setup route table for local and remote stacks. 173 localStack.SetRouteTable([]tcpip.Route{ 174 { 175 Destination: header.IPv4LoopbackSubnet, 176 NIC: loopbackNICID, 177 }, 178 { 179 Destination: header.IPv4EmptySubnet, 180 NIC: localNICID, 181 }, 182 }) 183 remoteStack.SetRouteTable([]tcpip.Route{ 184 { 185 Destination: header.IPv4EmptySubnet, 186 NIC: remoteNICID, 187 }, 188 }) 189 190 const netProto = ipv4.ProtocolNumber 191 localServerAddress := tcpip.FullAddress{ 192 Port: localServerPort, 193 } 194 195 localServerListener, err := gonet.ListenTCP(localStack, localServerAddress, netProto) 196 if err != nil { 197 t.Fatalf("gonet.ListenTCP(_, %+v, %d) = %s", localServerAddress, netProto, err) 198 } 199 200 remoteServerAddress := tcpip.FullAddress{ 201 Port: remoteServerPort, 202 } 203 remoteServerListener, err := gonet.ListenTCP(remoteStack, remoteServerAddress, netProto) 204 if err != nil { 205 t.Fatalf("gonet.ListenTCP(_, %+v, %d) = %s", remoteServerAddress, netProto, err) 206 } 207 208 // Initialize a random default response served by the HTTP server. 209 defaultResponse := make([]byte, 512<<10) 210 if _, err := rand.Read(defaultResponse); err != nil { 211 t.Fatalf("rand.Read(buf) failed: %s", err) 212 } 213 214 tc := &testContext{ 215 localServerListener: localServerListener, 216 remoteServerListener: remoteServerListener, 217 localStack: localStack, 218 remoteStack: remoteStack, 219 defaultResponse: defaultResponse, 220 } 221 222 tc.startServers(t) 223 return tc 224 } 225 226 func (ctx *testContext) startServers(t *testing.T) { 227 ctx.wg.Add(1) 228 go func() { 229 defer ctx.wg.Done() 230 ctx.startHTTPServer() 231 }() 232 ctx.wg.Add(1) 233 go func() { 234 defer ctx.wg.Done() 235 ctx.startTCPProxyServer(t) 236 }() 237 } 238 239 func (ctx *testContext) startTCPProxyServer(t *testing.T) { 240 t.Helper() 241 for { 242 conn, err := ctx.localServerListener.Accept() 243 if err != nil { 244 t.Logf("terminating local proxy server: %s", err) 245 return 246 } 247 // Start a goroutine to handle this inbound connection. 248 go func() { 249 remoteServerAddr := tcpip.FullAddress{ 250 Addr: remoteIPv4Addr1, 251 Port: remoteServerPort, 252 } 253 localServerAddr := tcpip.FullAddress{ 254 Addr: localIPv4Addr2, 255 } 256 serverConn, err := gonet.DialTCPWithBind(context.Background(), ctx.localStack, localServerAddr, remoteServerAddr, ipv4.ProtocolNumber) 257 if err != nil { 258 t.Logf("gonet.DialTCP(_, %+v, %d) = %s", remoteServerAddr, ipv4.ProtocolNumber, err) 259 return 260 } 261 proxy(conn, serverConn) 262 t.Logf("proxying completed") 263 }() 264 } 265 } 266 267 // proxy transparently proxies the TCP payload from conn1 to conn2 268 // and vice versa. 269 func proxy(conn1, conn2 net.Conn) { 270 var wg sync.WaitGroup 271 wg.Add(1) 272 go func() { 273 io.Copy(conn2, conn1) 274 conn1.Close() 275 conn2.Close() 276 }() 277 wg.Add(1) 278 go func() { 279 io.Copy(conn1, conn2) 280 conn1.Close() 281 conn2.Close() 282 }() 283 wg.Wait() 284 } 285 286 func (ctx *testContext) startHTTPServer() { 287 handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 288 w.Write([]byte(ctx.defaultResponse)) 289 }) 290 s := &http.Server{ 291 Handler: handlerFunc, 292 } 293 s.Serve(ctx.remoteServerListener) 294 } 295 296 func TestOutboundNATRedirect(t *testing.T) { 297 ctx := newTestContext(t) 298 defer ctx.cleanup() 299 300 // Install an IPTable rule to redirect all TCP traffic with the sourceIP of 301 // localIPv4Addr1 to the tcp proxy port. 302 ipt := ctx.localStack.IPTables() 303 tbl := ipt.GetTable(stack.NATID, false /* ipv6 */) 304 ruleIdx := tbl.BuiltinChains[stack.Output] 305 tbl.Rules[ruleIdx].Filter = stack.IPHeaderFilter{ 306 Protocol: tcp.ProtocolNumber, 307 CheckProtocol: true, 308 Src: localIPv4Addr1, 309 SrcMask: tcpip.AddrFromSlice([]byte("\xff\xff\xff\xff")), 310 } 311 tbl.Rules[ruleIdx].Target = &stack.RedirectTarget{ 312 Port: localServerPort, 313 NetworkProtocol: ipv4.ProtocolNumber, 314 } 315 tbl.Rules[ruleIdx+1].Target = &stack.AcceptTarget{} 316 ipt.ReplaceTable(stack.NATID, tbl, false /* ipv6 */) 317 318 dialFunc := func(protocol, address string) (net.Conn, error) { 319 host, port, err := net.SplitHostPort(address) 320 if err != nil { 321 return nil, fmt.Errorf("unable to parse address: %s, err: %s", address, err) 322 } 323 324 remoteServerIP := net.ParseIP(host) 325 remoteServerPort, err := strconv.Atoi(port) 326 if err != nil { 327 return nil, fmt.Errorf("unable to parse port from string %s, err: %s", port, err) 328 } 329 remoteAddress := tcpip.FullAddress{ 330 Addr: tcpip.AddrFrom4Slice(remoteServerIP.To4()), 331 Port: uint16(remoteServerPort), 332 } 333 334 // Dial with an explicit source address bound so that the redirect rule will 335 // be able to correctly redirect these packets. 336 localAddr := tcpip.FullAddress{Addr: localIPv4Addr1} 337 return gonet.DialTCPWithBind(context.Background(), ctx.localStack, localAddr, remoteAddress, ipv4.ProtocolNumber) 338 } 339 340 httpClient := &http.Client{ 341 Transport: &http.Transport{ 342 Dial: dialFunc, 343 }, 344 } 345 346 serverURL := fmt.Sprintf("http://[%s]:%d/", net.IP(remoteIPv4Addr1.AsSlice()), remoteServerPort) 347 response, err := httpClient.Get(serverURL) 348 if err != nil { 349 t.Fatalf("httpClient.Get(\"/\") failed: %s", err) 350 } 351 if got, want := response.StatusCode, http.StatusOK; got != want { 352 t.Fatalf("unexpected status code got: %d, want: %d", got, want) 353 } 354 body, err := io.ReadAll(response.Body) 355 if err != nil { 356 t.Fatalf("io.ReadAll(response.Body) failed: %s", err) 357 } 358 response.Body.Close() 359 if diff := cmp.Diff(body, ctx.defaultResponse); diff != "" { 360 t.Fatalf("unexpected response (-want +got): \n %s", diff) 361 } 362 }