gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/netaddress.go (about)

     1  package modules
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"gitlab.com/SiaPrime/SiaPrime/build"
    10  )
    11  
    12  // MaxEncodedNetAddressLength is the maximum length of a NetAddress encoded
    13  // with the encode package. 266 was chosen because the maximum length for the
    14  // hostname is 254 + 1 for the separating colon + 5 for the port + 8 byte
    15  // string length prefix.
    16  const MaxEncodedNetAddressLength = 266
    17  
    18  // A NetAddress contains the information needed to contact a peer.
    19  type NetAddress string
    20  
    21  // Host removes the port from a NetAddress, returning just the host. If the
    22  // address is not of the form "host:port" the empty string is returned. The
    23  // port will still be returned for invalid NetAddresses (e.g. "unqualified:0"
    24  // will return "unqualified"), but in general you should only call Host on
    25  // valid addresses.
    26  func (na NetAddress) Host() string {
    27  	host, _, err := net.SplitHostPort(string(na))
    28  	// 'host' is not always the empty string if an error is returned.
    29  	if err != nil {
    30  		return ""
    31  	}
    32  	return host
    33  }
    34  
    35  // Port returns the NetAddress object's port number. If the address is not of
    36  // the form "host:port" the empty string is returned. The port will still be
    37  // returned for invalid NetAddresses (e.g. "localhost:0" will return "0"), but
    38  // in general you should only call Port on valid addresses.
    39  func (na NetAddress) Port() string {
    40  	_, port, err := net.SplitHostPort(string(na))
    41  	// 'port' will not always be the empty string if an error is returned.
    42  	if err != nil {
    43  		return ""
    44  	}
    45  	return port
    46  }
    47  
    48  // IsLoopback returns true for IP addresses that are on the same machine.
    49  func (na NetAddress) IsLoopback() bool {
    50  	host, _, err := net.SplitHostPort(string(na))
    51  	if err != nil {
    52  		return false
    53  	}
    54  	if host == "localhost" {
    55  		return true
    56  	}
    57  	if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
    58  		return true
    59  	}
    60  	return false
    61  }
    62  
    63  // IsLocal returns true if the input IP address belongs to a local address
    64  // range such as 192.168.x.x or 127.x.x.x
    65  func (na NetAddress) IsLocal() bool {
    66  	// Loopback counts as private.
    67  	if na.IsLoopback() {
    68  		return true
    69  	}
    70  
    71  	// Grab the IP address of the net address. If there is an error parsing,
    72  	// return false, as it's not a private ip address range.
    73  	ip := net.ParseIP(na.Host())
    74  	if ip == nil {
    75  		return false
    76  	}
    77  
    78  	// Determine whether or not the ip is in a CIDR that is considered to be
    79  	// local.
    80  	localCIDRs := []string{
    81  		"10.0.0.0/8",
    82  		"172.16.0.0/12",
    83  		"192.168.0.0/16",
    84  		"fd00::/8",
    85  	}
    86  	for _, cidr := range localCIDRs {
    87  		_, ipnet, _ := net.ParseCIDR(cidr)
    88  		if ipnet.Contains(ip) {
    89  			return true
    90  		}
    91  	}
    92  	return false
    93  }
    94  
    95  // IsValid is an extension to IsStdValid that also forbids the loopback
    96  // address. IsValid is being phased out in favor of allowing the loopback
    97  // address but verifying through other means that the connection is not to
    98  // yourself (which is the original reason that the loopback address was
    99  // banned).
   100  func (na NetAddress) IsValid() error {
   101  	// Check the loopback address.
   102  	if na.IsLoopback() && build.Release != "testing" {
   103  		return errors.New("host is a loopback address")
   104  	}
   105  	return na.IsStdValid()
   106  }
   107  
   108  // IsStdValid returns an error if the NetAddress is invalid. A valid NetAddress
   109  // is of the form "host:port", such that "host" is either a valid IPv4/IPv6
   110  // address or a valid hostname, and "port" is an integer in the range
   111  // [1,65535]. Valid IPv4 addresses, IPv6 addresses, and hostnames are detailed
   112  // in RFCs 791, 2460, and 952, respectively.
   113  func (na NetAddress) IsStdValid() error {
   114  	// Verify the port number.
   115  	host, port, err := net.SplitHostPort(string(na))
   116  	if err != nil {
   117  		return err
   118  	}
   119  	portInt, err := strconv.Atoi(port)
   120  	if err != nil {
   121  		return errors.New("port is not an integer")
   122  	} else if portInt < 1 || portInt > 65535 {
   123  		return errors.New("port is invalid")
   124  	}
   125  
   126  	// Loopback addresses don't always pass the requirements below, and
   127  	// therefore must be checked separately.
   128  	if na.IsLoopback() {
   129  		return nil
   130  	}
   131  
   132  	// First try to parse host as an IP address; if that fails, assume it is a
   133  	// hostname.
   134  	if ip := net.ParseIP(host); ip != nil {
   135  		if ip.IsUnspecified() {
   136  			return errors.New("host is the unspecified address")
   137  		}
   138  	} else {
   139  		// Hostnames can have a trailing dot (which indicates that the hostname is
   140  		// fully qualified), but we ignore it for validation purposes.
   141  		if strings.HasSuffix(host, ".") {
   142  			host = host[:len(host)-1]
   143  		}
   144  		if len(host) < 1 || len(host) > 253 {
   145  			return errors.New("invalid hostname length")
   146  		}
   147  		labels := strings.Split(host, ".")
   148  		if len(labels) == 1 {
   149  			return errors.New("unqualified hostname")
   150  		}
   151  		for _, label := range labels {
   152  			if len(label) < 1 || len(label) > 63 {
   153  				return errors.New("hostname contains label with invalid length")
   154  			}
   155  			if strings.HasPrefix(label, "-") || strings.HasSuffix(label, "-") {
   156  				return errors.New("hostname contains label that starts or ends with a hyphen")
   157  			}
   158  			for _, r := range strings.ToLower(label) {
   159  				isLetter := 'a' <= r && r <= 'z'
   160  				isNumber := '0' <= r && r <= '9'
   161  				isHyphen := r == '-'
   162  				if !(isLetter || isNumber || isHyphen) {
   163  					return errors.New("host contains invalid characters")
   164  				}
   165  			}
   166  		}
   167  	}
   168  
   169  	return nil
   170  }