storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/net.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 cmd 18 19 import ( 20 "errors" 21 "fmt" 22 "net" 23 "net/url" 24 "sort" 25 "strings" 26 27 "github.com/minio/minio-go/v7/pkg/set" 28 29 "storj.io/minio/cmd/config" 30 "storj.io/minio/cmd/logger" 31 xnet "storj.io/minio/pkg/net" 32 ) 33 34 // IPv4 addresses of local host. 35 var localIP4 = mustGetLocalIP4() 36 37 // mustSplitHostPort is a wrapper to net.SplitHostPort() where error is assumed to be a fatal. 38 func mustSplitHostPort(hostPort string) (host, port string) { 39 xh, err := xnet.ParseHost(hostPort) 40 if err != nil { 41 logger.FatalIf(err, "Unable to split host port %s", hostPort) 42 } 43 return xh.Name, xh.Port.String() 44 } 45 46 // mustGetLocalIP4 returns IPv4 addresses of localhost. It panics on error. 47 func mustGetLocalIP4() (ipList set.StringSet) { 48 ipList = set.NewStringSet() 49 addrs, err := net.InterfaceAddrs() 50 logger.FatalIf(err, "Unable to get IP addresses of this host") 51 52 for _, addr := range addrs { 53 var ip net.IP 54 switch v := addr.(type) { 55 case *net.IPNet: 56 ip = v.IP 57 case *net.IPAddr: 58 ip = v.IP 59 } 60 61 if ip.To4() != nil { 62 ipList.Add(ip.String()) 63 } 64 } 65 66 return ipList 67 } 68 69 // mustGetLocalIP6 returns IPv6 addresses of localhost. It panics on error. 70 func mustGetLocalIP6() (ipList set.StringSet) { 71 ipList = set.NewStringSet() 72 addrs, err := net.InterfaceAddrs() 73 logger.FatalIf(err, "Unable to get IP addresses of this host") 74 75 for _, addr := range addrs { 76 var ip net.IP 77 switch v := addr.(type) { 78 case *net.IPNet: 79 ip = v.IP 80 case *net.IPAddr: 81 ip = v.IP 82 } 83 84 if ip.To4() == nil { 85 ipList.Add(ip.String()) 86 } 87 } 88 89 return ipList 90 } 91 92 // getHostIP returns IP address of given host. 93 func getHostIP(host string) (ipList set.StringSet, err error) { 94 var ips []net.IP 95 96 if ips, err = net.LookupIP(host); err != nil { 97 return ipList, err 98 } 99 100 ipList = set.NewStringSet() 101 for _, ip := range ips { 102 ipList.Add(ip.String()) 103 } 104 105 return ipList, err 106 } 107 108 // byLastOctetValue implements sort.Interface used in sorting a list 109 // of ip address by their last octet value in descending order. 110 type byLastOctetValue []net.IP 111 112 func (n byLastOctetValue) Len() int { return len(n) } 113 func (n byLastOctetValue) Swap(i, j int) { n[i], n[j] = n[j], n[i] } 114 func (n byLastOctetValue) Less(i, j int) bool { 115 // This case is needed when all ips in the list 116 // have same last octets, Following just ensures that 117 // 127.0.0.1 is moved to the end of the list. 118 if n[i].IsLoopback() { 119 return false 120 } 121 if n[j].IsLoopback() { 122 return true 123 } 124 return []byte(n[i].To4())[3] > []byte(n[j].To4())[3] 125 } 126 127 // sortIPs - sort ips based on higher octects. 128 // The logic to sort by last octet is implemented to 129 // prefer CIDRs with higher octects, this in-turn skips the 130 // localhost/loopback address to be not preferred as the 131 // first ip on the list. Subsequently this list helps us print 132 // a user friendly message with appropriate values. 133 func sortIPs(ipList []string) []string { 134 if len(ipList) == 1 { 135 return ipList 136 } 137 138 var ipV4s []net.IP 139 var nonIPs []string 140 for _, ip := range ipList { 141 nip := net.ParseIP(ip) 142 if nip != nil { 143 ipV4s = append(ipV4s, nip) 144 } else { 145 nonIPs = append(nonIPs, ip) 146 } 147 } 148 149 sort.Sort(byLastOctetValue(ipV4s)) 150 151 var ips []string 152 for _, ip := range ipV4s { 153 ips = append(ips, ip.String()) 154 } 155 156 return append(nonIPs, ips...) 157 } 158 159 func getAPIEndpoints() (apiEndpoints []string) { 160 var ipList []string 161 if globalMinioHost == "" { 162 ipList = sortIPs(mustGetLocalIP4().ToSlice()) 163 ipList = append(ipList, mustGetLocalIP6().ToSlice()...) 164 } else { 165 ipList = []string{globalMinioHost} 166 } 167 168 for _, ip := range ipList { 169 endpoint := fmt.Sprintf("%s://%s", getURLScheme(GlobalIsTLS), net.JoinHostPort(ip, globalMinioPort)) 170 apiEndpoints = append(apiEndpoints, endpoint) 171 } 172 173 return apiEndpoints 174 } 175 176 // isHostIP - helper for validating if the provided arg is an ip address. 177 func isHostIP(ipAddress string) bool { 178 host, _, err := net.SplitHostPort(ipAddress) 179 if err != nil { 180 host = ipAddress 181 } 182 // Strip off IPv6 zone information. 183 if i := strings.Index(host, "%"); i > -1 { 184 host = host[:i] 185 } 186 return net.ParseIP(host) != nil 187 } 188 189 // checkPortAvailability - check if given host and port is already in use. 190 // Note: The check method tries to listen on given port and closes it. 191 // It is possible to have a disconnected client in this tiny window of time. 192 func checkPortAvailability(host, port string) (err error) { 193 l, err := net.Listen("tcp", net.JoinHostPort(host, port)) 194 if err != nil { 195 return err 196 } 197 // As we are able to listen on this network, the port is not in use. 198 // Close the listener and continue check other networks. 199 return l.Close() 200 } 201 202 // extractHostPort - extracts host/port from many address formats 203 // such as, ":9000", "localhost:9000", "http://localhost:9000/" 204 func extractHostPort(hostAddr string) (string, string, error) { 205 var addr, scheme string 206 207 if hostAddr == "" { 208 return "", "", errors.New("unable to process empty address") 209 } 210 211 // Simplify the work of url.Parse() and always send a url with 212 if !strings.HasPrefix(hostAddr, "http://") && !strings.HasPrefix(hostAddr, "https://") { 213 hostAddr = "//" + hostAddr 214 } 215 216 // Parse address to extract host and scheme field 217 u, err := url.Parse(hostAddr) 218 if err != nil { 219 return "", "", err 220 } 221 222 addr = u.Host 223 scheme = u.Scheme 224 225 // Use the given parameter again if url.Parse() 226 // didn't return any useful result. 227 if addr == "" { 228 addr = hostAddr 229 scheme = "http" 230 } 231 232 // At this point, addr can be one of the following form: 233 // ":9000" 234 // "localhost:9000" 235 // "localhost" <- in this case, we check for scheme 236 237 host, port, err := net.SplitHostPort(addr) 238 if err != nil { 239 if !strings.Contains(err.Error(), "missing port in address") { 240 return "", "", err 241 } 242 243 host = addr 244 245 switch scheme { 246 case "https": 247 port = "443" 248 case "http": 249 port = "80" 250 default: 251 return "", "", errors.New("unable to guess port from scheme") 252 } 253 } 254 255 return host, port, nil 256 } 257 258 // isLocalHost - checks if the given parameter 259 // correspond to one of the local IP of the 260 // current machine 261 func isLocalHost(host string, port string, localPort string) (bool, error) { 262 hostIPs, err := getHostIP(host) 263 if err != nil { 264 return false, err 265 } 266 267 nonInterIPV4s := mustGetLocalIP4().Intersection(hostIPs) 268 if nonInterIPV4s.IsEmpty() { 269 hostIPs = hostIPs.ApplyFunc(func(ip string) string { 270 if net.ParseIP(ip).IsLoopback() { 271 // Any loopback IP which is not 127.0.0.1 272 // convert it to check for intersections. 273 return "127.0.0.1" 274 } 275 return ip 276 }) 277 nonInterIPV4s = mustGetLocalIP4().Intersection(hostIPs) 278 } 279 nonInterIPV6s := mustGetLocalIP6().Intersection(hostIPs) 280 281 // If intersection of two IP sets is not empty, then the host is localhost. 282 isLocalv4 := !nonInterIPV4s.IsEmpty() 283 isLocalv6 := !nonInterIPV6s.IsEmpty() 284 if port != "" { 285 return (isLocalv4 || isLocalv6) && (port == localPort), nil 286 } 287 return isLocalv4 || isLocalv6, nil 288 } 289 290 // sameLocalAddrs - returns true if two addresses, even with different 291 // formats, point to the same machine, e.g: 292 // ':9000' and 'http://localhost:9000/' will return true 293 func sameLocalAddrs(addr1, addr2 string) (bool, error) { 294 295 // Extract host & port from given parameters 296 host1, port1, err := extractHostPort(addr1) 297 if err != nil { 298 return false, err 299 } 300 host2, port2, err := extractHostPort(addr2) 301 if err != nil { 302 return false, err 303 } 304 305 var addr1Local, addr2Local bool 306 307 if host1 == "" { 308 // If empty host means it is localhost 309 addr1Local = true 310 } else { 311 // Host not empty, check if it is local 312 if addr1Local, err = isLocalHost(host1, port1, port1); err != nil { 313 return false, err 314 } 315 } 316 317 if host2 == "" { 318 // If empty host means it is localhost 319 addr2Local = true 320 } else { 321 // Host not empty, check if it is local 322 if addr2Local, err = isLocalHost(host2, port2, port2); err != nil { 323 return false, err 324 } 325 } 326 327 // If both of addresses point to the same machine, check if 328 // have the same port 329 if addr1Local && addr2Local { 330 if port1 == port2 { 331 return true, nil 332 } 333 } 334 return false, nil 335 } 336 337 // CheckLocalServerAddr - checks if serverAddr is valid and local host. 338 func CheckLocalServerAddr(serverAddr string) error { 339 host, err := xnet.ParseHost(serverAddr) 340 if err != nil { 341 return config.ErrInvalidAddressFlag(err) 342 } 343 344 // 0.0.0.0 is a wildcard address and refers to local network 345 // addresses. I.e, 0.0.0.0:9000 like ":9000" refers to port 346 // 9000 on localhost. 347 if host.Name != "" && host.Name != net.IPv4zero.String() && host.Name != net.IPv6zero.String() { 348 localHost, err := isLocalHost(host.Name, host.Port.String(), host.Port.String()) 349 if err != nil { 350 return err 351 } 352 if !localHost { 353 return config.ErrInvalidAddressFlag(nil).Msg("host in server address should be this server") 354 } 355 } 356 357 return nil 358 }