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  }