kaspad/infrastructure/network/addressmanager/localaddressmanager.go
2021-03-16 14:22:52 +02:00

400 lines
9.7 KiB
Go

package addressmanager
import (
"net"
"runtime"
"strconv"
"strings"
"sync"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/pkg/errors"
)
// AddressPriority type is used to describe the hierarchy of local address
// discovery methods.
type AddressPriority int
const (
// InterfacePrio signifies the address is on a local interface
InterfacePrio AddressPriority = iota
// BoundPrio signifies the address has been explicitly bounded to.
BoundPrio
// UpnpPrio signifies the address was obtained from UPnP.
UpnpPrio
// HTTPPrio signifies the address was obtained from an external HTTP service.
HTTPPrio
// ManualPrio signifies the address was provided by --externalip.
ManualPrio
)
type localAddress struct {
netAddress *appmessage.NetAddress
score AddressPriority
}
type localAddressManager struct {
localAddresses map[addressKey]*localAddress
lookupFunc func(string) ([]net.IP, error)
cfg *Config
mutex sync.Mutex
}
func newLocalAddressManager(cfg *Config) (*localAddressManager, error) {
localAddressManager := localAddressManager{
localAddresses: map[addressKey]*localAddress{},
cfg: cfg,
lookupFunc: cfg.Lookup,
}
err := localAddressManager.initListeners()
if err != nil {
return nil, err
}
return &localAddressManager, nil
}
// addLocalNetAddress adds netAddress to the list of known local addresses to advertise
// with the given priority.
func (lam *localAddressManager) addLocalNetAddress(netAddress *appmessage.NetAddress, priority AddressPriority) error {
if !IsRoutable(netAddress, lam.cfg.AcceptUnroutable) {
return errors.Errorf("address %s is not routable", netAddress.IP)
}
lam.mutex.Lock()
defer lam.mutex.Unlock()
addressKey := netAddressKey(netAddress)
address, ok := lam.localAddresses[addressKey]
if !ok || address.score < priority {
if ok {
address.score = priority + 1
} else {
lam.localAddresses[addressKey] = &localAddress{
netAddress: netAddress,
score: priority,
}
}
}
return nil
}
// bestLocalAddress returns the most appropriate local address to use
// for the given remote address.
func (lam *localAddressManager) bestLocalAddress(remoteAddress *appmessage.NetAddress) *appmessage.NetAddress {
lam.mutex.Lock()
defer lam.mutex.Unlock()
bestReach := 0
var bestScore AddressPriority
var bestAddress *appmessage.NetAddress
for _, localAddress := range lam.localAddresses {
reach := reachabilityFrom(localAddress.netAddress, remoteAddress, lam.cfg.AcceptUnroutable)
if reach > bestReach ||
(reach == bestReach && localAddress.score > bestScore) {
bestReach = reach
bestScore = localAddress.score
bestAddress = localAddress.netAddress
}
}
if bestAddress == nil {
// Send something unroutable if nothing suitable.
var ip net.IP
if !IsIPv4(remoteAddress) {
ip = net.IPv6zero
} else {
ip = net.IPv4zero
}
bestAddress = appmessage.NewNetAddressIPPort(ip, 0)
}
return bestAddress
}
// addLocalAddress adds an address that this node is listening on to the
// address manager so that it may be relayed to peers.
func (lam *localAddressManager) addLocalAddress(addr string) error {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return err
}
port, err := strconv.ParseUint(portStr, 10, 16)
if err != nil {
return err
}
if ip := net.ParseIP(host); ip != nil && ip.IsUnspecified() {
// If bound to unspecified address, advertise all local interfaces
addrs, err := net.InterfaceAddrs()
if err != nil {
return err
}
for _, addr := range addrs {
ifaceIP, _, err := net.ParseCIDR(addr.String())
if err != nil {
continue
}
// If bound to 0.0.0.0, do not add IPv6 interfaces and if bound to
// ::, do not add IPv4 interfaces.
if (ip.To4() == nil) != (ifaceIP.To4() == nil) {
continue
}
netAddr := appmessage.NewNetAddressIPPort(ifaceIP, uint16(port))
lam.addLocalNetAddress(netAddr, BoundPrio)
}
} else {
netAddr, err := lam.hostToNetAddress(host, uint16(port))
if err != nil {
return err
}
lam.addLocalNetAddress(netAddr, BoundPrio)
}
return nil
}
// initListeners initializes the configured net listeners and adds any bound
// addresses to the address manager
func (lam *localAddressManager) initListeners() error {
if len(lam.cfg.ExternalIPs) != 0 {
defaultPort, err := strconv.ParseUint(lam.cfg.DefaultPort, 10, 16)
if err != nil {
log.Errorf("Can not parse default port %s for active DAG: %s",
lam.cfg.DefaultPort, err)
return err
}
for _, sip := range lam.cfg.ExternalIPs {
eport := uint16(defaultPort)
host, portstr, err := net.SplitHostPort(sip)
if err != nil {
// no port, use default.
host = sip
} else {
port, err := strconv.ParseUint(portstr, 10, 16)
if err != nil {
log.Warnf("Can not parse port from %s for "+
"externalip: %s", sip, err)
continue
}
eport = uint16(port)
}
na, err := lam.hostToNetAddress(host, eport)
if err != nil {
log.Warnf("Not adding %s as externalip: %s", sip, err)
continue
}
err = lam.addLocalNetAddress(na, ManualPrio)
if err != nil {
log.Warnf("Skipping specified external IP: %s", err)
}
}
} else {
// Listen for TCP connections at the configured addresses
netAddrs, err := parseListeners(lam.cfg.Listeners)
if err != nil {
return err
}
// Add bound addresses to address manager to be advertised to peers.
for _, addr := range netAddrs {
listener, err := net.Listen(addr.Network(), addr.String())
if err != nil {
log.Warnf("Can't listen on %s: %s", addr, err)
continue
}
addr := listener.Addr().String()
err = listener.Close()
if err != nil {
return err
}
err = lam.addLocalAddress(addr)
if err != nil {
log.Warnf("Skipping bound address %s: %s", addr, err)
}
}
}
return nil
}
// hostToNetAddress returns a netaddress given a host address. If
// the host is not an IP address it will be resolved.
func (lam *localAddressManager) hostToNetAddress(host string, port uint16) (*appmessage.NetAddress, error) {
ip := net.ParseIP(host)
if ip == nil {
ips, err := lam.lookupFunc(host)
if err != nil {
return nil, err
}
if len(ips) == 0 {
return nil, errors.Errorf("no addresses found for %s", host)
}
ip = ips[0]
}
return appmessage.NewNetAddressIPPort(ip, port), nil
}
// parseListeners determines whether each listen address is IPv4 and IPv6 and
// returns a slice of appropriate net.Addrs to listen on with TCP. It also
// properly detects addresses which apply to "all interfaces" and adds the
// address as both IPv4 and IPv6.
func parseListeners(addrs []string) ([]net.Addr, error) {
netAddrs := make([]net.Addr, 0, len(addrs)*2)
for _, addr := range addrs {
host, _, err := net.SplitHostPort(addr)
if err != nil {
// Shouldn't happen due to already being normalized.
return nil, err
}
// Empty host or host of * on plan9 is both IPv4 and IPv6.
if host == "" || (host == "*" && runtime.GOOS == "plan9") {
netAddrs = append(netAddrs, simpleAddr{net: "tcp4", addr: addr})
netAddrs = append(netAddrs, simpleAddr{net: "tcp6", addr: addr})
continue
}
// Strip IPv6 zone id if present since net.ParseIP does not
// handle it.
zoneIndex := strings.LastIndex(host, "%")
if zoneIndex > 0 {
host = host[:zoneIndex]
}
// Parse the IP.
ip := net.ParseIP(host)
if ip == nil {
hostAddrs, err := net.LookupHost(host)
if err != nil {
return nil, err
}
ip = net.ParseIP(hostAddrs[0])
if ip == nil {
return nil, errors.Errorf("Cannot resolve IP address for host '%s'", host)
}
}
// To4 returns nil when the IP is not an IPv4 address, so use
// this determine the address type.
if ip.To4() == nil {
netAddrs = append(netAddrs, simpleAddr{net: "tcp6", addr: addr})
} else {
netAddrs = append(netAddrs, simpleAddr{net: "tcp4", addr: addr})
}
}
return netAddrs, nil
}
// reachabilityFrom returns the relative reachability of the provided local
// address to the provided remote address.
func reachabilityFrom(localAddress, remoteAddress *appmessage.NetAddress, acceptUnroutable bool) int {
const (
Unreachable = 0
Default = iota
Teredo
Ipv6Weak
Ipv4
Ipv6Strong
Private
)
IsRoutable := func(na *appmessage.NetAddress) bool {
if acceptUnroutable {
return !IsLocal(na)
}
return IsValid(na) && !(IsRFC1918(na) || IsRFC2544(na) ||
IsRFC3927(na) || IsRFC4862(na) || IsRFC3849(na) ||
IsRFC4843(na) || IsRFC5737(na) || IsRFC6598(na) ||
IsLocal(na) || (IsRFC4193(na)))
}
if !IsRoutable(remoteAddress) {
return Unreachable
}
if IsRFC4380(remoteAddress) {
if !IsRoutable(localAddress) {
return Default
}
if IsRFC4380(localAddress) {
return Teredo
}
if IsIPv4(localAddress) {
return Ipv4
}
return Ipv6Weak
}
if IsIPv4(remoteAddress) {
if IsRoutable(localAddress) && IsIPv4(localAddress) {
return Ipv4
}
return Unreachable
}
/* ipv6 */
var tunnelled bool
// Is our v6 is tunnelled?
if IsRFC3964(localAddress) || IsRFC6052(localAddress) || IsRFC6145(localAddress) {
tunnelled = true
}
if !IsRoutable(localAddress) {
return Default
}
if IsRFC4380(localAddress) {
return Teredo
}
if IsIPv4(localAddress) {
return Ipv4
}
if tunnelled {
// only prioritise ipv6 if we aren't tunnelling it.
return Ipv6Weak
}
return Ipv6Strong
}
// simpleAddr implements the net.Addr interface with two struct fields
type simpleAddr struct {
net, addr string
}
// String returns the address.
//
// This is part of the net.Addr interface.
func (a simpleAddr) String() string {
return a.addr
}
// Network returns the network.
//
// This is part of the net.Addr interface.
func (a simpleAddr) Network() string {
return a.net
}
// Ensure simpleAddr implements the net.Addr interface.
var _ net.Addr = simpleAddr{}