istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/endpoint/tcp.go (about)

     1  // Copyright Istio 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 endpoint
    16  
    17  import (
    18  	"crypto/tls"
    19  	"fmt"
    20  	"io"
    21  	"net"
    22  	"net/http"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/google/uuid"
    29  
    30  	"istio.io/istio/pkg/test/echo"
    31  	"istio.io/istio/pkg/test/echo/common"
    32  	"istio.io/istio/pkg/test/util/retry"
    33  )
    34  
    35  var _ Instance = &tcpInstance{}
    36  
    37  type tcpInstance struct {
    38  	Config
    39  	l net.Listener
    40  }
    41  
    42  func newTCP(config Config) Instance {
    43  	return &tcpInstance{
    44  		Config: config,
    45  	}
    46  }
    47  
    48  func (s *tcpInstance) GetConfig() Config {
    49  	return s.Config
    50  }
    51  
    52  func (s *tcpInstance) Start(onReady OnReadyFunc) error {
    53  	var listener net.Listener
    54  	var port int
    55  	var err error
    56  	if s.Port.TLS {
    57  		cert, cerr := tls.LoadX509KeyPair(s.TLSCert, s.TLSKey)
    58  		if cerr != nil {
    59  			return fmt.Errorf("could not load TLS keys: %v", cerr)
    60  		}
    61  		config := &tls.Config{Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12}
    62  		// Listen on the given port and update the port if it changed from what was passed in.
    63  		listener, port, err = listenOnAddressTLS(s.ListenerIP, s.Port.Port, config)
    64  		// Store the actual listening port back to the argument.
    65  		s.Port.Port = port
    66  	} else {
    67  		// Listen on the given port and update the port if it changed from what was passed in.
    68  		listener, port, err = listenOnAddress(s.ListenerIP, s.Port.Port)
    69  		// Store the actual listening port back to the argument.
    70  		s.Port.Port = port
    71  	}
    72  	if err != nil {
    73  		return err
    74  	}
    75  
    76  	s.l = listener
    77  	if s.Port.TLS {
    78  		epLog.Infof("Listening TCP (over TLS) on %v\n", port)
    79  	} else {
    80  		epLog.Infof("Listening TCP on %v\n", port)
    81  	}
    82  
    83  	// Start serving TCP traffic.
    84  	go func() {
    85  		for {
    86  			conn, err := listener.Accept()
    87  			if err != nil {
    88  				epLog.Warn("TCP accept failed: " + err.Error())
    89  				return
    90  			}
    91  
    92  			id := uuid.New()
    93  			epLog.WithLabels("remote", conn.RemoteAddr(), "id", id).Infof("TCP Request")
    94  
    95  			done := make(chan struct{})
    96  			go func() {
    97  				s.echo(id, conn)
    98  				close(done)
    99  			}()
   100  
   101  			go func() {
   102  				select {
   103  				case <-done:
   104  					return
   105  				case <-time.After(requestTimeout):
   106  					epLog.WithLabels("id", id).Warnf("TCP forcing connection closed after request timeout")
   107  					_ = forceClose(conn)
   108  					return
   109  				}
   110  			}()
   111  		}
   112  	}()
   113  
   114  	// Notify the WaitGroup once the port has transitioned to ready.
   115  	go s.awaitReady(onReady, listener.Addr().String())
   116  
   117  	return nil
   118  }
   119  
   120  // Handles incoming connection.
   121  func (s *tcpInstance) echo(id uuid.UUID, conn net.Conn) {
   122  	common.Metrics.TCPRequests.With(common.PortLabel.Value(strconv.Itoa(s.Port.Port))).Increment()
   123  
   124  	var err error
   125  	defer func() {
   126  		if err != nil && err != io.EOF {
   127  			_ = forceClose(conn)
   128  		} else {
   129  			_ = conn.Close()
   130  		}
   131  	}()
   132  
   133  	// If this is server first, client expects a message from server. Send the magic string.
   134  	if s.Port.ServerFirst {
   135  		if _, err = conn.Write([]byte(common.ServerFirstMagicString)); err != nil {
   136  			epLog.WithLabels("id", id).Warnf("TCP server-first write failed: %v", err)
   137  			return
   138  		}
   139  	}
   140  
   141  	firstReply := true
   142  	responseFields := ""
   143  	buf := make([]byte, 4096)
   144  	for {
   145  		var n int
   146  		n, err = conn.Read(buf)
   147  
   148  		// important not to start sending any response until we've started reading the message,
   149  		// otherwise the response could be read when we expect the magic string
   150  		if firstReply {
   151  			responseFields = s.getResponseFields(conn)
   152  			if _, writeErr := conn.Write([]byte(responseFields)); writeErr != nil {
   153  				epLog.WithLabels("id", id).Warnf("TCP failed writing response fields: %v", writeErr)
   154  			}
   155  			firstReply = false
   156  		}
   157  
   158  		if err != nil && err != io.EOF {
   159  			epLog.WithLabels("id", id).Warnf("TCP read failed: %v", err)
   160  			break
   161  		}
   162  
   163  		// echo the message from the request
   164  		if n > 0 {
   165  			out := buf[:n]
   166  			if _, err = conn.Write(out); err != nil {
   167  				epLog.WithLabels("id", id).Warnf("TCP failed writing echo response: %v", err)
   168  				break
   169  			}
   170  		}
   171  
   172  		// Read can return n > 0 with EOF, do this last.
   173  		if err == io.EOF {
   174  			break
   175  		}
   176  	}
   177  
   178  	epLog.WithLabels("id", id).Infof("TCP Response Fields:\n%s", responseFields)
   179  }
   180  
   181  func (s *tcpInstance) getResponseFields(conn net.Conn) string {
   182  	ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
   183  	// Write non-request fields specific to the instance
   184  	out := &strings.Builder{}
   185  	echo.StatusCodeField.Write(out, strconv.Itoa(http.StatusOK))
   186  	echo.ClusterField.WriteNonEmpty(out, s.Cluster)
   187  	echo.IstioVersionField.WriteNonEmpty(out, s.IstioVersion)
   188  	echo.NamespaceField.WriteNonEmpty(out, s.Namespace)
   189  	echo.ServiceVersionField.Write(out, s.Version)
   190  	echo.ServicePortField.Write(out, strconv.Itoa(s.Port.Port))
   191  	echo.IPField.Write(out, ip)
   192  	echo.ProtocolField.Write(out, "TCP")
   193  
   194  	if hostname, err := os.Hostname(); err == nil {
   195  		echo.HostnameField.Write(out, hostname)
   196  	}
   197  
   198  	return out.String()
   199  }
   200  
   201  func (s *tcpInstance) Close() error {
   202  	if s.l != nil {
   203  		_ = s.l.Close()
   204  	}
   205  	return nil
   206  }
   207  
   208  func (s *tcpInstance) awaitReady(onReady OnReadyFunc, address string) {
   209  	defer onReady()
   210  
   211  	err := retry.UntilSuccess(func() error {
   212  		conn, err := net.Dial("tcp", address)
   213  		if err != nil {
   214  			return err
   215  		}
   216  		defer func() { _ = conn.Close() }()
   217  
   218  		// Server is up now, we're ready.
   219  		return nil
   220  	}, retry.Timeout(readyTimeout), retry.Delay(readyInterval))
   221  
   222  	if err != nil {
   223  		epLog.Errorf("readiness failed for endpoint %s: %v", address, err)
   224  	} else {
   225  		epLog.Infof("ready for TCP endpoint %s", address)
   226  	}
   227  }