github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/http/listener_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package http
    19  
    20  import (
    21  	"context"
    22  	"crypto/tls"
    23  	"net"
    24  	"runtime"
    25  	"strconv"
    26  	"strings"
    27  	"sync/atomic"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/minio/minio-go/v7/pkg/set"
    32  )
    33  
    34  var serverPort uint32 = 60000
    35  
    36  var getCert = func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    37  	certificate, err := getTLSCert()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	return &certificate, nil
    42  }
    43  
    44  func getTLSCert() (tls.Certificate, error) {
    45  	keyPEMBlock := []byte(`-----BEGIN RSA PRIVATE KEY-----
    46  MIIEpAIBAAKCAQEApEkbPrT6wzcWK1W5atQiGptvuBsRdf8MCg4u6SN10QbslA5k
    47  6BYRdZfFeRpwAwYyzkumug6+eBJatDZEd7+0FF86yxB7eMTSiHKRZ5Mi5ZyCFsez
    48  dndknGBeK6I80s1jd5ZsLLuMKErvbNwSbfX+X6d2mBeYW8Scv9N+qYnNrHHHohvX
    49  oxy1gZ18EhhogQhrD22zaqg/jtmOT8ImUiXzB1mKInt2LlSkoRYuBzepkDJrsE1L
    50  /cyYZbtcO/ASDj+/qQAuQ66v9pNyJkIQ7bDOUyxaT5Hx9XvbqI1OqUVAdGLLi+eZ
    51  IFguFyYd0lemwdN/IDvxftzegTO3cO0D28d1UQIDAQABAoIBAB42x8j3lerTNcOQ
    52  h4JLM157WcedSs/NsVQkGaKM//0KbfYo04wPivR6jjngj9suh6eDKE2tqoAAuCfO
    53  lzcCzca1YOW5yUuDv0iS8YT//XoHF7HC1pGiEaHk40zZEKCgX3u98XUkpPlAFtqJ
    54  euY4SKkk7l24cS/ncACjj/b0PhxJoT/CncuaaJKqmCc+vdL4wj1UcrSNPZqRjDR/
    55  sh5DO0LblB0XrqVjpNxqxM60/IkbftB8YTnyGgtO2tbTPr8KdQ8DhHQniOp+WEPV
    56  u/iXt0LLM7u62LzadkGab2NDWS3agnmdvw2ADtv5Tt8fZ7WnPqiOpNyD5Bv1a3/h
    57  YBw5HsUCgYEA0Sfv6BiSAFEby2KusRoq5UeUjp/SfL7vwpO1KvXeiYkPBh2XYVq2
    58  azMnOw7Rz5ixFhtUtto2XhYdyvvr3dZu1fNHtxWo9ITBivqTGGRNwfiaQa58Bugo
    59  gy7vCdIE/f6xE5LYIovBnES2vs/ZayMyhTX84SCWd0pTY0kdDA8ePGsCgYEAyRSA
    60  OTzX43KUR1G/trpuM6VBc0W6YUNYzGRa1TcUxBP4K7DfKMpPGg6ulqypfoHmu8QD
    61  L+z+iQmG9ySSuvScIW6u8LgkrTwZga8y2eb/A2FAVYY/bnelef1aMkis+bBX2OQ4
    62  QAg2uq+pkhpW1k5NSS9lVCPkj4e5Ur9RCm9fRDMCgYAf3CSIR03eLHy+Y37WzXSh
    63  TmELxL6sb+1Xx2Y+cAuBCda3CMTpeIb3F2ivb1d4dvrqsikaXW0Qse/B3tQUC7kA
    64  cDmJYwxEiwBsajUD7yuFE5hzzt9nse+R5BFXfp1yD1zr7V9tC7rnUfRAZqrozgjB
    65  D/NAW9VvwGupYRbCon7plwKBgQCRPfeoYGRoa9ji8w+Rg3QaZeGyy8jmfGjlqg9a
    66  NyEOyIXXuThYFFmyrqw5NZpwQJBTTDApK/xnK7SLS6WY2Rr1oydFxRzo7KJX5B7M
    67  +md1H4gCvqeOuWmThgbij1AyQsgRaDehOM2fZ0cKu2/B+Gkm1c9RSWPMsPKR7JMz
    68  AGNFtQKBgQCRCFIdGJHnvz35vJfLoihifCejBWtZbAnZoBHpF3xMCtV755J96tUf
    69  k1Tv9hz6WfSkOSlwLq6eGZY2dCENJRW1ft1UelpFvCjbfrfLvoFFLs3gu0lfqXHi
    70  CS6fjhn9Ahvz10yD6fd4ixRUjoJvULzI0Sxc1O95SYVF1lIAuVr9Hw==
    71  -----END RSA PRIVATE KEY-----`)
    72  	certPEMBlock := []byte(`-----BEGIN CERTIFICATE-----
    73  MIIDXTCCAkWgAwIBAgIJAKlqK5HKlo9MMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
    74  BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
    75  aWRnaXRzIFB0eSBMdGQwHhcNMTcwNjE5MTA0MzEyWhcNMjcwNjE3MTA0MzEyWjBF
    76  MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
    77  ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
    78  CgKCAQEApEkbPrT6wzcWK1W5atQiGptvuBsRdf8MCg4u6SN10QbslA5k6BYRdZfF
    79  eRpwAwYyzkumug6+eBJatDZEd7+0FF86yxB7eMTSiHKRZ5Mi5ZyCFsezdndknGBe
    80  K6I80s1jd5ZsLLuMKErvbNwSbfX+X6d2mBeYW8Scv9N+qYnNrHHHohvXoxy1gZ18
    81  EhhogQhrD22zaqg/jtmOT8ImUiXzB1mKInt2LlSkoRYuBzepkDJrsE1L/cyYZbtc
    82  O/ASDj+/qQAuQ66v9pNyJkIQ7bDOUyxaT5Hx9XvbqI1OqUVAdGLLi+eZIFguFyYd
    83  0lemwdN/IDvxftzegTO3cO0D28d1UQIDAQABo1AwTjAdBgNVHQ4EFgQUqMVdMIA1
    84  68Dv+iwGugAaEGUSd0IwHwYDVR0jBBgwFoAUqMVdMIA168Dv+iwGugAaEGUSd0Iw
    85  DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAjQVoqRv2HlE5PJIX/qk5
    86  oMOKZlHTyJP+s2HzOOVt+eCE/jNdfC7+8R/HcPldQs7p9GqH2F6hQ9aOtDhJVEaU
    87  pjxCi4qKeZ1kWwqv8UMBXW92eHGysBvE2Gmm/B1JFl8S2GR5fBmheZVnYW893MoI
    88  gp+bOoCcIuMJRqCra4vJgrOsQjgRElQvd2OlP8qQzInf/fRqO/AnZPwMkGr3+KZ0
    89  BKEOXtmSZaPs3xEsnvJd8wrTgA0NQK7v48E+gHSXzQtaHmOLqisRXlUOu2r1gNCJ
    90  rr3DRiUP6V/10CZ/ImeSJ72k69VuTw9vq2HzB4x6pqxF2X7JQSLUCS2wfNN13N0d
    91  9A==
    92  -----END CERTIFICATE-----`)
    93  
    94  	return tls.X509KeyPair(certPEMBlock, keyPEMBlock)
    95  }
    96  
    97  func getNextPort() string {
    98  	return strconv.Itoa(int(atomic.AddUint32(&serverPort, 1)))
    99  }
   100  
   101  func getNonLoopBackIP(t *testing.T) string {
   102  	localIP4 := set.NewStringSet()
   103  	addrs, err := net.InterfaceAddrs()
   104  	if err != nil {
   105  		t.Fatalf("%s.  Unable to get IP addresses of this host.", err)
   106  	}
   107  
   108  	for _, addr := range addrs {
   109  		var ip net.IP
   110  		switch v := addr.(type) {
   111  		case *net.IPNet:
   112  			ip = v.IP
   113  		case *net.IPAddr:
   114  			ip = v.IP
   115  		}
   116  
   117  		if ip.To4() != nil {
   118  			localIP4.Add(ip.String())
   119  		}
   120  	}
   121  
   122  	// Filter ipList by IPs those do not start with '127.'.
   123  	nonLoopBackIPs := localIP4.FuncMatch(func(ip string, matchString string) bool {
   124  		return !strings.HasPrefix(ip, "127.")
   125  	}, "")
   126  	if len(nonLoopBackIPs) == 0 {
   127  		t.Fatalf("No non-loop back IP address found for this host")
   128  	}
   129  	nonLoopBackIP := nonLoopBackIPs.ToSlice()[0]
   130  	return nonLoopBackIP
   131  }
   132  
   133  func TestNewHTTPListener(t *testing.T) {
   134  	testCases := []struct {
   135  		serverAddrs         []string
   136  		tcpKeepAliveTimeout time.Duration
   137  		readTimeout         time.Duration
   138  		writeTimeout        time.Duration
   139  		expectedListenErrs  []bool
   140  	}{
   141  		{[]string{"93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}},                           // 1
   142  		{[]string{"example.org:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}},                             // 2
   143  		{[]string{"unknown-host"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}},                                  // 3
   144  		{[]string{"unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}},                            // 4
   145  		{[]string{"localhost:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}},                              // 5
   146  		{[]string{"localhost:65432", "93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false, true}}, // 6
   147  		{[]string{"localhost:65432", "unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false, true}},  // 7
   148  		{[]string{"[::1:65432", "unknown-host:-1"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true, true}},           // 7
   149  		{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}},                                  // 8
   150  		{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}},                                  // 9
   151  		{[]string{"[::1]:9090", "127.0.0.1:90900"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}},                // 10
   152  		{[]string{"[::1]:9090", "localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}},                    // 10
   153  	}
   154  
   155  	for testIdx, testCase := range testCases {
   156  		listener, listenErrs := newHTTPListener(context.Background(),
   157  			testCase.serverAddrs,
   158  			TCPOptions{},
   159  		)
   160  		for i, expectedListenErr := range testCase.expectedListenErrs {
   161  			if !expectedListenErr {
   162  				if listenErrs[i] != nil {
   163  					t.Fatalf("Test %d:, listenErrs[%d] error: expected = <nil>, got = %v", testIdx+1, i, listenErrs[i])
   164  				}
   165  			} else if listenErrs[i] == nil {
   166  				t.Fatalf("Test %d: listenErrs[%d]: expected = %v, got = <nil>", testIdx+1, i, expectedListenErr)
   167  			}
   168  		}
   169  		if listener != nil {
   170  			listener.Close()
   171  		}
   172  	}
   173  }
   174  
   175  func TestHTTPListenerStartClose(t *testing.T) {
   176  	if runtime.GOOS == "windows" {
   177  		t.Skip()
   178  	}
   179  
   180  	nonLoopBackIP := getNonLoopBackIP(t)
   181  
   182  	testCases := []struct {
   183  		serverAddrs []string
   184  	}{
   185  		{[]string{"localhost:0"}},
   186  		{[]string{nonLoopBackIP + ":0"}},
   187  		{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}},
   188  		{[]string{"localhost:0"}},
   189  		{[]string{nonLoopBackIP + ":0"}},
   190  		{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}},
   191  	}
   192  
   193  nextTest:
   194  	for i, testCase := range testCases {
   195  		listener, errs := newHTTPListener(context.Background(),
   196  			testCase.serverAddrs,
   197  			TCPOptions{},
   198  		)
   199  		for _, err := range errs {
   200  			if err != nil {
   201  				if strings.Contains(err.Error(), "The requested address is not valid in its context") {
   202  					// Ignore if IP is unbindable.
   203  					continue nextTest
   204  				}
   205  				if strings.Contains(err.Error(), "bind: address already in use") {
   206  					continue nextTest
   207  				}
   208  				t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
   209  			}
   210  		}
   211  
   212  		for _, serverAddr := range listener.Addrs() {
   213  			conn, err := net.Dial("tcp", serverAddr.String())
   214  			if err != nil {
   215  				t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
   216  			}
   217  			conn.Close()
   218  		}
   219  
   220  		listener.Close()
   221  	}
   222  }
   223  
   224  func TestHTTPListenerAddr(t *testing.T) {
   225  	if runtime.GOOS == "windows" {
   226  		t.Skip()
   227  	}
   228  
   229  	nonLoopBackIP := getNonLoopBackIP(t)
   230  	var casePorts []string
   231  	for i := 0; i < 6; i++ {
   232  		casePorts = append(casePorts, getNextPort())
   233  	}
   234  
   235  	testCases := []struct {
   236  		serverAddrs  []string
   237  		expectedAddr string
   238  	}{
   239  		{[]string{"localhost:" + casePorts[0]}, "127.0.0.1:" + casePorts[0]},
   240  		{[]string{nonLoopBackIP + ":" + casePorts[1]}, nonLoopBackIP + ":" + casePorts[1]},
   241  		{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, "0.0.0.0:" + casePorts[2]},
   242  		{[]string{"localhost:" + casePorts[3]}, "127.0.0.1:" + casePorts[3]},
   243  		{[]string{nonLoopBackIP + ":" + casePorts[4]}, nonLoopBackIP + ":" + casePorts[4]},
   244  		{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, "0.0.0.0:" + casePorts[5]},
   245  	}
   246  
   247  nextTest:
   248  	for i, testCase := range testCases {
   249  		listener, errs := newHTTPListener(context.Background(),
   250  			testCase.serverAddrs,
   251  			TCPOptions{},
   252  		)
   253  		for _, err := range errs {
   254  			if err != nil {
   255  				if strings.Contains(err.Error(), "The requested address is not valid in its context") {
   256  					// Ignore if IP is unbindable.
   257  					continue nextTest
   258  				}
   259  				if strings.Contains(err.Error(), "bind: address already in use") {
   260  					continue nextTest
   261  				}
   262  				t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
   263  			}
   264  		}
   265  
   266  		addr := listener.Addr()
   267  		if addr.String() != testCase.expectedAddr {
   268  			t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddr, addr)
   269  		}
   270  
   271  		listener.Close()
   272  	}
   273  }
   274  
   275  func TestHTTPListenerAddrs(t *testing.T) {
   276  	if runtime.GOOS == "windows" {
   277  		t.Skip()
   278  	}
   279  
   280  	nonLoopBackIP := getNonLoopBackIP(t)
   281  	var casePorts []string
   282  	for i := 0; i < 6; i++ {
   283  		casePorts = append(casePorts, getNextPort())
   284  	}
   285  
   286  	testCases := []struct {
   287  		serverAddrs   []string
   288  		expectedAddrs set.StringSet
   289  	}{
   290  		{[]string{"localhost:" + casePorts[0]}, set.CreateStringSet("127.0.0.1:" + casePorts[0])},
   291  		{[]string{nonLoopBackIP + ":" + casePorts[1]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[1])},
   292  		{[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, set.CreateStringSet("127.0.0.1:"+casePorts[2], nonLoopBackIP+":"+casePorts[2])},
   293  		{[]string{"localhost:" + casePorts[3]}, set.CreateStringSet("127.0.0.1:" + casePorts[3])},
   294  		{[]string{nonLoopBackIP + ":" + casePorts[4]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[4])},
   295  		{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])},
   296  	}
   297  
   298  nextTest:
   299  	for i, testCase := range testCases {
   300  		listener, errs := newHTTPListener(context.Background(),
   301  			testCase.serverAddrs,
   302  			TCPOptions{},
   303  		)
   304  		for _, err := range errs {
   305  			if err != nil {
   306  				if strings.Contains(err.Error(), "The requested address is not valid in its context") {
   307  					// Ignore if IP is unbindable.
   308  					continue nextTest
   309  				}
   310  				if strings.Contains(err.Error(), "bind: address already in use") {
   311  					continue nextTest
   312  				}
   313  				t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
   314  			}
   315  		}
   316  
   317  		addrs := listener.Addrs()
   318  		addrSet := set.NewStringSet()
   319  		for _, addr := range addrs {
   320  			addrSet.Add(addr.String())
   321  		}
   322  
   323  		if !addrSet.Equals(testCase.expectedAddrs) {
   324  			t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddrs, addrs)
   325  		}
   326  
   327  		listener.Close()
   328  	}
   329  }