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 }