storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/http/listener_test.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2017 MinIO, Inc. 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 http 18 19 import ( 20 "crypto/tls" 21 "errors" 22 "fmt" 23 "io" 24 "net" 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 expectedErr bool 140 }{ 141 {[]string{"93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 142 {[]string{"example.org:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 143 {[]string{"unknown-host"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 144 {[]string{"unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 145 {[]string{"localhost:65432", "93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 146 {[]string{"localhost:65432", "unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, 147 {[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, 148 {[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, 149 } 150 151 for _, testCase := range testCases { 152 listener, err := newHTTPListener( 153 testCase.serverAddrs, 154 ) 155 156 if !testCase.expectedErr { 157 if err != nil { 158 t.Fatalf("error: expected = <nil>, got = %v", err) 159 } 160 } else if err == nil { 161 t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr) 162 } 163 164 if err == nil { 165 listener.Close() 166 } 167 } 168 } 169 170 func TestHTTPListenerStartClose(t *testing.T) { 171 nonLoopBackIP := getNonLoopBackIP(t) 172 173 testCases := []struct { 174 serverAddrs []string 175 }{ 176 {[]string{"localhost:0"}}, 177 {[]string{nonLoopBackIP + ":0"}}, 178 {[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}}, 179 {[]string{"localhost:0"}}, 180 {[]string{nonLoopBackIP + ":0"}}, 181 {[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}}, 182 } 183 184 for i, testCase := range testCases { 185 listener, err := newHTTPListener( 186 testCase.serverAddrs, 187 ) 188 if err != nil { 189 if strings.Contains(err.Error(), "The requested address is not valid in its context") { 190 // Ignore if IP is unbindable. 191 continue 192 } 193 t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) 194 } 195 196 for _, serverAddr := range listener.Addrs() { 197 conn, err := net.Dial("tcp", serverAddr.String()) 198 if err != nil { 199 t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) 200 } 201 conn.Close() 202 } 203 204 listener.Close() 205 } 206 } 207 208 func TestHTTPListenerAddr(t *testing.T) { 209 nonLoopBackIP := getNonLoopBackIP(t) 210 var casePorts []string 211 for i := 0; i < 6; i++ { 212 casePorts = append(casePorts, getNextPort()) 213 } 214 215 testCases := []struct { 216 serverAddrs []string 217 expectedAddr string 218 }{ 219 {[]string{"localhost:" + casePorts[0]}, "127.0.0.1:" + casePorts[0]}, 220 {[]string{nonLoopBackIP + ":" + casePorts[1]}, nonLoopBackIP + ":" + casePorts[1]}, 221 {[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, "0.0.0.0:" + casePorts[2]}, 222 {[]string{"localhost:" + casePorts[3]}, "127.0.0.1:" + casePorts[3]}, 223 {[]string{nonLoopBackIP + ":" + casePorts[4]}, nonLoopBackIP + ":" + casePorts[4]}, 224 {[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, "0.0.0.0:" + casePorts[5]}, 225 } 226 227 for i, testCase := range testCases { 228 listener, err := newHTTPListener( 229 testCase.serverAddrs, 230 ) 231 if err != nil { 232 if strings.Contains(err.Error(), "The requested address is not valid in its context") { 233 // Ignore if IP is unbindable. 234 continue 235 } 236 t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) 237 } 238 239 addr := listener.Addr() 240 if addr.String() != testCase.expectedAddr { 241 t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddr, addr) 242 } 243 244 listener.Close() 245 } 246 } 247 248 func TestHTTPListenerAddrs(t *testing.T) { 249 nonLoopBackIP := getNonLoopBackIP(t) 250 var casePorts []string 251 for i := 0; i < 6; i++ { 252 casePorts = append(casePorts, getNextPort()) 253 } 254 255 testCases := []struct { 256 serverAddrs []string 257 expectedAddrs set.StringSet 258 }{ 259 {[]string{"localhost:" + casePorts[0]}, set.CreateStringSet("127.0.0.1:" + casePorts[0])}, 260 {[]string{nonLoopBackIP + ":" + casePorts[1]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[1])}, 261 {[]string{"127.0.0.1:" + casePorts[2], nonLoopBackIP + ":" + casePorts[2]}, set.CreateStringSet("127.0.0.1:"+casePorts[2], nonLoopBackIP+":"+casePorts[2])}, 262 {[]string{"localhost:" + casePorts[3]}, set.CreateStringSet("127.0.0.1:" + casePorts[3])}, 263 {[]string{nonLoopBackIP + ":" + casePorts[4]}, set.CreateStringSet(nonLoopBackIP + ":" + casePorts[4])}, 264 {[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])}, 265 } 266 267 for i, testCase := range testCases { 268 listener, err := newHTTPListener( 269 testCase.serverAddrs, 270 ) 271 if err != nil { 272 if strings.Contains(err.Error(), "The requested address is not valid in its context") { 273 // Ignore if IP is unbindable. 274 continue 275 } 276 t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) 277 } 278 279 addrs := listener.Addrs() 280 addrSet := set.NewStringSet() 281 for _, addr := range addrs { 282 addrSet.Add(addr.String()) 283 } 284 285 if !addrSet.Equals(testCase.expectedAddrs) { 286 t.Fatalf("Test %d: addr: expected = %v, got = %v", i+1, testCase.expectedAddrs, addrs) 287 } 288 289 listener.Close() 290 } 291 } 292 293 type myTimeoutErr struct { 294 timeout bool 295 } 296 297 func (m *myTimeoutErr) Error() string { return fmt.Sprintf("myTimeoutErr: %v", m.timeout) } 298 func (m *myTimeoutErr) Timeout() bool { return m.timeout } 299 300 // Test for ignoreErr helper function 301 func TestIgnoreErr(t *testing.T) { 302 testCases := []struct { 303 err error 304 want bool 305 }{ 306 { 307 err: io.EOF, 308 want: true, 309 }, 310 { 311 err: &net.OpError{Err: &myTimeoutErr{timeout: true}}, 312 want: true, 313 }, 314 { 315 err: errors.New("EOF"), 316 want: true, 317 }, 318 { 319 err: &net.OpError{Err: &myTimeoutErr{timeout: false}}, 320 want: false, 321 }, 322 { 323 err: io.ErrUnexpectedEOF, 324 want: false, 325 }, 326 { 327 err: nil, 328 want: false, 329 }, 330 } 331 332 for i, tc := range testCases { 333 if actual := isRoutineNetErr(tc.err); actual != tc.want { 334 t.Errorf("Test case %d: Expected %v but got %v for %v", i+1, 335 tc.want, actual, tc.err) 336 } 337 } 338 }