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  }