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 }