knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/network/transports_test.go (about) 1 /* 2 Copyright 2019 The Knative Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package network 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "errors" 24 "fmt" 25 "io" 26 "net" 27 "net/http" 28 "net/http/httptest" 29 "strings" 30 "syscall" 31 "testing" 32 "time" 33 34 "k8s.io/apimachinery/pkg/util/sets" 35 "k8s.io/apimachinery/pkg/util/wait" 36 ) 37 38 func TestHTTPRoundTripper(t *testing.T) { 39 wants := sets.NewString() 40 frt := func(key string) http.RoundTripper { 41 return RoundTripperFunc(func(r *http.Request) (*http.Response, error) { 42 wants.Insert(key) 43 return nil, nil 44 }) 45 } 46 47 rt := newAutoTransport(frt("v1"), frt("v2")) 48 49 examples := []struct { 50 label string 51 protoMajor int 52 want string 53 }{{ 54 label: "use default transport for HTTP1", 55 protoMajor: 1, 56 want: "v1", 57 }, { 58 label: "use h2c transport for HTTP2", 59 protoMajor: 2, 60 want: "v2", 61 }, { 62 label: "use default transport for all others", 63 protoMajor: 99, 64 want: "v1", 65 }} 66 67 for _, e := range examples { 68 t.Run(e.label, func(t *testing.T) { 69 wants.Delete(e.want) 70 r := &http.Request{ProtoMajor: e.protoMajor} 71 resp, err := rt.RoundTrip(r) 72 if err != nil { 73 defer resp.Body.Close() 74 } 75 76 if !wants.Has(e.want) { 77 t.Error("Wrong transport selected for request.") 78 } 79 }) 80 } 81 } 82 83 func TestDialWithBackoffConnectionRefused(t *testing.T) { 84 testDialWithBackoffConnectionRefused(nil, t) 85 } 86 87 func TestDialWithBackoffTimeout(t *testing.T) { 88 testDialWithBackoffTimeout(nil, t) 89 } 90 91 func TestDialWithBackoffSuccess(t *testing.T) { 92 testDialWithBackoffSuccess(nil, t) 93 } 94 95 func TestDialTLSWithBackoffConnectionRefused(t *testing.T) { 96 testDialWithBackoffConnectionRefused(exampleTLSConf(), t) 97 } 98 99 func TestDialTLSWithBackoffTimeout(t *testing.T) { 100 testDialWithBackoffTimeout(exampleTLSConf(), t) 101 } 102 103 func TestDialTLSWithBackoffSuccess(t *testing.T) { 104 testDialWithBackoffSuccess(exampleTLSConf(), t) 105 } 106 107 func testDialWithBackoffConnectionRefused(tlsConf *tls.Config, t testingT) { 108 ctx := context.TODO() 109 port := findUnusedPortOrFail(t) 110 addr := fmt.Sprintf("127.0.0.1:%d", port) 111 dialer := newDialer(ctx, tlsConf) 112 c, err := dialer(addr) 113 closeOrFail(t, c) 114 if !errors.Is(err, syscall.ECONNREFUSED) { 115 t.Fatalf("Unexpected error: %+v", err) 116 } 117 } 118 119 func testDialWithBackoffTimeout(tlsConf *tls.Config, t testingT) { 120 ctx := context.TODO() 121 closer, addr, err := listenOne() 122 if err != nil { 123 t.Fatal("Unable to create listener:", err) 124 } 125 defer closer() 126 127 for { 128 // This seems really strange... we're listening with a backlog of one 129 // connection, and we keep creating connections and holding onto them 130 // until we get a connection timeout. 131 // 132 // It turns out that darwin (MacOS) and Linux implement the Listen 133 // backlog argument slightly differently, and MacOS needs one more 134 // connection than Linux to saturate the backlog. Rather than sniffing 135 // the OS, we simply ensure that the backlog is saturated. 136 c1, err := net.DialTimeout("tcp4", addr.String(), 10*time.Millisecond) 137 if err != nil { 138 var neterr net.Error 139 if errors.As(err, &neterr) && neterr.Timeout() { 140 // Waiting for a timeout 141 break 142 } 143 t.Fatalf("Unable to connect to server on %s: %s", addr, err) 144 } 145 defer closeOrFail(t, c1) 146 } 147 148 // Since the backlog is full, the next request must time out. 149 dialer := newDialer(ctx, tlsConf) 150 c, err := dialer(addr.String()) 151 if err == nil { 152 closeOrFail(t, c) 153 t.Fatal("Unexpected success dialing") 154 } 155 if !errors.Is(err, ErrTimeoutDialing) { 156 t.Fatalf("Unexpected error: %+v", err) 157 } 158 } 159 160 func testDialWithBackoffSuccess(tlsConf *tls.Config, t testingT) { 161 //goland:noinspection HttpUrlsUsage 162 const ( 163 prefixHTTP = "http://" 164 prefixHTTPS = "https://" 165 ) 166 ctx := context.TODO() 167 var s *httptest.Server 168 servFn := httptest.NewServer 169 if tlsConf != nil { 170 servFn = httptest.NewTLSServer 171 } 172 s = servFn(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 173 defer s.Close() 174 prefix := prefixHTTP 175 if tlsConf != nil { 176 prefix = prefixHTTPS 177 rootCAs := x509.NewCertPool() 178 rootCAs.AddCert(s.Certificate()) 179 tlsConf.RootCAs = rootCAs 180 } 181 addr := strings.TrimPrefix(s.URL, prefix) 182 183 dialer := newDialer(ctx, tlsConf) 184 c, err := dialer(addr) 185 if err != nil { 186 t.Fatal("Dial error =", err) 187 } 188 closeOrFail(t, c) 189 } 190 191 func exampleTLSConf() *tls.Config { 192 return &tls.Config{ 193 InsecureSkipVerify: false, 194 ServerName: "example.com", 195 MinVersion: tls.VersionTLS13, 196 } 197 } 198 199 func newDialer(ctx context.Context, tlsConf *tls.Config) func(addr string) (net.Conn, error) { 200 // Make the test short. 201 bo := wait.Backoff{ 202 Duration: time.Millisecond, 203 Factor: 1.4, 204 Jitter: 0.1, // At most 10% jitter. 205 Steps: 1, 206 } 207 208 dialFn := func(addr string) (net.Conn, error) { 209 return NewBackoffDialer(bo)(ctx, "tcp4", addr) 210 } 211 if tlsConf != nil { 212 dialFn = func(addr string) (net.Conn, error) { 213 bo.Duration = 50 * time.Millisecond 214 bo.Steps = 3 215 return NewTLSBackoffDialer(bo)(ctx, "tcp4", addr, tlsConf) 216 } 217 } 218 return dialFn 219 } 220 221 func closeOrFail(t testingT, con io.Closer) { 222 if con == nil { 223 return 224 } 225 if err := con.Close(); err != nil { 226 t.Fatal(err) 227 } 228 } 229 230 func findUnusedPortOrFail(t testingT) int { 231 l, err := net.Listen("tcp", "localhost:0") 232 if err != nil { 233 t.Fatal(err) 234 } 235 defer closeOrFail(t, l) 236 return l.Addr().(*net.TCPAddr).Port 237 } 238 239 var errTest = errors.New("testing") 240 241 func newTestErr(msg string, err error) error { 242 return fmt.Errorf("%w: %s: %w", errTest, msg, err) 243 } 244 245 // listenOne creates a socket with backlog of one, and use that socket, so 246 // any other connection will guarantee to timeout. 247 // 248 // Golang doesn't allow us to set the backlog argument on syscall.Listen from 249 // net.ListenTCP, so we need to get directly into syscall land. 250 func listenOne() (func(), *net.TCPAddr, error) { 251 fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0) 252 if err != nil { 253 return nil, nil, newTestErr("Couldn't get socket", err) 254 } 255 sa := &syscall.SockaddrInet4{ 256 Port: 0, 257 Addr: [4]byte{127, 0, 0, 1}, 258 } 259 if err = syscall.Bind(fd, sa); err != nil { 260 return nil, nil, newTestErr("Unable to bind", err) 261 } 262 if err = syscall.Listen(fd, 1); err != nil { 263 return nil, nil, newTestErr("Unable to Listen", err) 264 } 265 closer := func() { _ = syscall.Close(fd) } 266 listenaddr, err := syscall.Getsockname(fd) 267 if err != nil { 268 closer() 269 return nil, nil, newTestErr("Could not get sockname", err) 270 } 271 sa = listenaddr.(*syscall.SockaddrInet4) 272 addr := &net.TCPAddr{ 273 IP: sa.Addr[:], 274 Port: sa.Port, 275 } 276 return closer, addr, nil 277 } 278 279 type testingT interface { 280 Fatal(args ...interface{}) 281 Fatalf(format string, args ...interface{}) 282 }