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  }