netaddress.go 5.04 KB
Newer Older
1 2 3
package modules

import (
4
	"errors"
5
	"net"
6 7
	"strconv"
	"strings"
8

9
	"gitlab.com/NebulousLabs/Sia/build"
10 11
)

12 13 14 15 16 17
// MaxEncodedNetAddressLength is the maximum length of a NetAddress encoded
// with the encode package. 266 was chosen because the maximum length for the
// hostname is 254 + 1 for the separating colon + 5 for the port + 8 byte
// string length prefix.
const MaxEncodedNetAddressLength = 266

18 19 20
// A NetAddress contains the information needed to contact a peer.
type NetAddress string

21
// Host removes the port from a NetAddress, returning just the host. If the
22 23 24 25
// address is not of the form "host:port" the empty string is returned. The
// port will still be returned for invalid NetAddresses (e.g. "unqualified:0"
// will return "unqualified"), but in general you should only call Host on
// valid addresses.
26
func (na NetAddress) Host() string {
David Vorick's avatar
David Vorick committed
27
	host, _, err := net.SplitHostPort(string(na))
28
	// 'host' is not always the empty string if an error is returned.
David Vorick's avatar
David Vorick committed
29
	if err != nil {
30
		return ""
David Vorick's avatar
David Vorick committed
31
	}
32 33 34
	return host
}

35 36 37 38
// Port returns the NetAddress object's port number. If the address is not of
// the form "host:port" the empty string is returned. The port will still be
// returned for invalid NetAddresses (e.g. "localhost:0" will return "0"), but
// in general you should only call Port on valid addresses.
39
func (na NetAddress) Port() string {
40 41 42 43 44
	_, port, err := net.SplitHostPort(string(na))
	// 'port' will not always be the empty string if an error is returned.
	if err != nil {
		return ""
	}
45 46 47
	return port
}

48 49
// IsLoopback returns true for IP addresses that are on the same machine.
func (na NetAddress) IsLoopback() bool {
50 51
	host, _, err := net.SplitHostPort(string(na))
	if err != nil {
52 53
		return false
	}
54
	if host == "localhost" {
55 56
		return true
	}
57
	if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
58 59 60 61 62
		return true
	}
	return false
}

63 64 65 66
// IsLocal returns true if the input IP address belongs to a local address
// range such as 192.168.x.x or 127.x.x.x
func (na NetAddress) IsLocal() bool {
	// Loopback counts as private.
67
	if na.IsLoopback() {
68
		return true
69 70 71 72 73 74 75 76 77
	}

	// Grab the IP address of the net address. If there is an error parsing,
	// return false, as it's not a private ip address range.
	ip := net.ParseIP(na.Host())
	if ip == nil {
		return false
	}

David Vorick's avatar
David Vorick committed
78 79 80 81 82 83 84
	// Determine whether or not the ip is in a CIDR that is considered to be
	// local.
	localCIDRs := []string{
		"10.0.0.0/8",
		"172.16.0.0/12",
		"192.168.0.0/16",
		"fd00::/8",
85
	}
David Vorick's avatar
David Vorick committed
86 87 88 89 90
	for _, cidr := range localCIDRs {
		_, ipnet, _ := net.ParseCIDR(cidr)
		if ipnet.Contains(ip) {
			return true
		}
91 92 93 94
	}
	return false
}

95 96 97 98 99 100 101 102 103 104 105 106 107 108
// IsValid is an extension to IsStdValid that also forbids the loopback
// address. IsValid is being phased out in favor of allowing the loopback
// address but verifying through other means that the connection is not to
// yourself (which is the original reason that the loopback address was
// banned).
func (na NetAddress) IsValid() error {
	// Check the loopback address.
	if na.IsLoopback() && build.Release != "testing" {
		return errors.New("host is a loopback address")
	}
	return na.IsStdValid()
}

// IsStdValid returns an error if the NetAddress is invalid. A valid NetAddress
109 110
// is of the form "host:port", such that "host" is either a valid IPv4/IPv6
// 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 114
func (na NetAddress) IsStdValid() error {
	// Verify the port number.
115 116
	host, port, err := net.SplitHostPort(string(na))
	if err != nil {
117 118
		return err
	}
119 120 121 122 123 124 125
	portInt, err := strconv.Atoi(port)
	if err != nil {
		return errors.New("port is not an integer")
	} else if portInt < 1 || portInt > 65535 {
		return errors.New("port is invalid")
	}

126 127
	// Loopback addresses don't always pass the requirements below, and
	// therefore must be checked separately.
128
	if na.IsLoopback() {
129
		return nil
130 131
	}

132 133 134 135 136
	// First try to parse host as an IP address; if that fails, assume it is a
	// hostname.
	if ip := net.ParseIP(host); ip != nil {
		if ip.IsUnspecified() {
			return errors.New("host is the unspecified address")
137
		}
138
	} else {
139 140 141 142 143
		// Hostnames can have a trailing dot (which indicates that the hostname is
		// fully qualified), but we ignore it for validation purposes.
		if strings.HasSuffix(host, ".") {
			host = host[:len(host)-1]
		}
144 145 146
		if len(host) < 1 || len(host) > 253 {
			return errors.New("invalid hostname length")
		}
147 148 149 150 151
		labels := strings.Split(host, ".")
		if len(labels) == 1 {
			return errors.New("unqualified hostname")
		}
		for _, label := range labels {
152 153 154
			if len(label) < 1 || len(label) > 63 {
				return errors.New("hostname contains label with invalid length")
			}
155 156 157
			if strings.HasPrefix(label, "-") || strings.HasSuffix(label, "-") {
				return errors.New("hostname contains label that starts or ends with a hyphen")
			}
158 159 160 161 162 163 164 165
			for _, r := range strings.ToLower(label) {
				isLetter := 'a' <= r && r <= 'z'
				isNumber := '0' <= r && r <= '9'
				isHyphen := r == '-'
				if !(isLetter || isNumber || isHyphen) {
					return errors.New("host contains invalid characters")
				}
			}
166 167 168 169
		}
	}

	return nil
170
}