mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
400 lines
9.7 KiB
Go
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{}
|