diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index 6b781110..2487ae53 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -1013,19 +1013,44 @@ func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPrior return nil } +// HasLocalAddress asserts if the manager has the provided local address. +func (a *AddrManager) HasLocalAddress(na *wire.NetAddress) bool { + key := NetAddressKey(na) + a.lamtx.Lock() + _, ok := a.localAddresses[key] + a.lamtx.Unlock() + return ok +} + +const ( + // Unreachable represents a publicly unreachable connection state + // between two addresses. + Unreachable = 0 + + // Default represents the default connection state between + // two addresses. + Default = iota + + // Teredo represents a connection state between two RFC4380 addresses. + Teredo + + // Ipv6Weak represents a weak IPV6 connection state between two + // addresses. + Ipv6Weak + + // Ipv4 represents an IPV4 connection state between two addreses. + Ipv4 + + // Ipv6Strong represents a connection state between two IPV6 addresses. + Ipv6Strong + + // Private reprsents a connection state connect between two Tor addresses. + Private +) + // getReachabilityFrom returns the relative reachability of the provided local // address to the provided remote address. func getReachabilityFrom(localAddr, remoteAddr *wire.NetAddress) int { - const ( - Unreachable = 0 - Default = iota - Teredo - Ipv6Weak - Ipv4 - Ipv6Strong - Private - ) - if !IsRoutable(remoteAddr) { return Unreachable } @@ -1130,6 +1155,15 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.Net return bestAddress } +// IsPeerNaValid asserts if the the provided local address is routable +// and reachable from the peer that suggested it. +func (a *AddrManager) IsPeerNaValid(localAddr, remoteAddr *wire.NetAddress) bool { + net := getNetwork(localAddr) + reach := getReachabilityFrom(localAddr, remoteAddr) + return (net == IPv4Address && reach == Ipv4) || (net == IPv6Address && + (reach == Ipv6Weak || reach == Ipv6Strong || reach == Teredo)) +} + // New returns a new Decred address manager. // Use Start to begin processing asynchronous address updates. // The address manager uses lookupFunc for necessary DNS lookups. diff --git a/addrmgr/network.go b/addrmgr/network.go index 3f9cee0a..624a8e6c 100644 --- a/addrmgr/network.go +++ b/addrmgr/network.go @@ -118,6 +118,33 @@ func isOnionCatTor(na *wire.NetAddress) bool { return onionCatNet.Contains(na.IP) } +// NetworkAddress type is used to classify a network address. +type NetworkAddress int + +const ( + LocalAddress NetworkAddress = iota + IPv4Address + IPv6Address + OnionAddress +) + +// getNetwork returns the network address type of the provided network address. +func getNetwork(na *wire.NetAddress) NetworkAddress { + switch { + case isLocal(na): + return LocalAddress + + case isIPv4(na): + return IPv4Address + + case isOnionCatTor(na): + return OnionAddress + + default: + return IPv6Address + } +} + // isRFC1918 returns whether or not the passed address is part of the IPv4 // private network address space as defined by RFC1918 (10.0.0.0/8, // 172.16.0.0/12, or 192.168.0.0/16). diff --git a/config.go b/config.go index 782da209..967272ef 100644 --- a/config.go +++ b/config.go @@ -129,6 +129,7 @@ type config struct { OnionProxyUser string `long:"onionuser" description:"Username for onion proxy server"` OnionProxyPass string `long:"onionpass" default-mask:"-" description:"Password for onion proxy server"` NoOnion bool `long:"noonion" description:"Disable connecting to tor hidden services"` + NoDiscoverIP bool `long:"nodiscoverip" description:"Disable automatic network address discovery"` TorIsolation bool `long:"torisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` TestNet bool `long:"testnet" description:"Use the test network"` SimNet bool `long:"simnet" description:"Use the simulation test network"` diff --git a/server.go b/server.go index f983dd4c..22c5e9cc 100644 --- a/server.go +++ b/server.go @@ -129,6 +129,11 @@ type peerState struct { persistentPeers map[int32]*serverPeer banned map[string]time.Time outboundGroups map[string]int + + // suggestions represents public network address suggestions from outbound + // peers. + suggestions map[addrmgr.NetworkAddress]map[string]int32 + suggestionsMtx sync.Mutex } // ConnectionsWithIP returns the number of connections with the given IP. @@ -178,6 +183,46 @@ func (ps *peerState) forAllPeers(closure func(sp *serverPeer)) { ps.forAllOutboundPeers(closure) } +// ResolveLocalAddress picks the best suggested network address from available +// options, per the network interface key provided. The best suggestion, if +// found, is added as a local address. +func (ps *peerState) ResolveLocalAddress(netKey addrmgr.NetworkAddress, addrMgr *addrmgr.AddrManager, services wire.ServiceFlag, port uint16) { + ps.suggestionsMtx.Lock() + count := len(ps.suggestions[netKey]) + if count == 0 { + ps.suggestionsMtx.Unlock() + return + } + + var bestSuggestion string + var bestTally int32 + for suggestion, tally := range ps.suggestions[netKey] { + switch { + case bestSuggestion == "", tally > bestTally: + bestSuggestion = suggestion + bestTally = tally + } + } + ps.suggestionsMtx.Unlock() + + // A valid best address suggestion must have at least two outbound peers + // concluding on the same result. + if bestTally < 2 { + return + } + + na, err := addrMgr.HostToNetAddress(bestSuggestion, port, services) + if err != nil { + amgrLog.Errorf("unable to generate network address using host %v: "+ + "%v", bestSuggestion, err) + return + } + + if !addrMgr.HasLocalAddress(na) { + addrMgr.AddLocalAddress(na, addrmgr.ManualPrio) + } +} + // server provides a Decred server for handling communications to and from // Decred peers. type server struct { @@ -254,6 +299,10 @@ type serverPeer struct { // The following chans are used to sync blockmanager and server. txProcessed chan struct{} blockProcessed chan struct{} + + // peerNa is network address of the peer connected to. + peerNa *wire.NetAddress + peerNaMtx sync.Mutex } // newServerPeer returns a new serverPeer instance. The peer needs to be set by @@ -442,6 +491,12 @@ func (sp *serverPeer) OnVersion(p *peer.Peer, msg *wire.MsgVersion) *wire.MsgRej addrManager.Good(remoteAddr) } + if !sp.Inbound() { + sp.peerNaMtx.Lock() + sp.peerNa = &msg.AddrYou + sp.peerNaMtx.Unlock() + } + // Choose whether or not to relay transactions. sp.setDisableRelayTx(msg.DisableRelayTx) @@ -1361,6 +1416,68 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool { } else { state.outboundPeers[sp.ID()] = sp } + + // Fetch the suggested public ip from the outbound peer if + // there are no prevailing conditions to disable automatic + // network address discovery. + // + // The conditions to disable automatic network address + // discovery are: + // - If there is a proxy set (--proxy, --onion). + // - If automatic network address discovery is explicitly + // disabled (--nodiscoverip). + // - If there is an external ip explicitly set (--externalip). + // - If listening has been disabled (--nolisten, listen + // disabled because of --connect, etc). + // - If Universal Plug and Play is enabled (--upnp). + // - If the active network is simnet or regnet. + if (cfg.Proxy != "" || cfg.OnionProxy != "") || + cfg.NoDiscoverIP || len(cfg.ExternalIPs) > 0 || + (cfg.DisableListen || len(cfg.Listeners) == 0) || cfg.Upnp || + activeNetParams.Name == simNetParams.Name || + activeNetParams.Name == regNetParams.Name { + return true + } + + sp.peerNaMtx.Lock() + na := sp.peerNa + sp.peerNaMtx.Unlock() + + if na == nil { + return true + } + + if !s.addrManager.IsPeerNaValid(na, sp.NA()) { + return true + } + + port, err := strconv.ParseUint(activeNetParams.DefaultPort, 10, 16) + if err != nil { + srvrLog.Errorf("unabled to parse active network port: %v", err) + return true + } + + id := na.IP.String() + switch { + case na.IP.To4() != nil: + state.suggestionsMtx.Lock() + state.suggestions[addrmgr.IPv4Address][id]++ + state.suggestionsMtx.Unlock() + + state.ResolveLocalAddress(addrmgr.IPv4Address, s.addrManager, + s.services, uint16(port)) + + case na.IP.To16() != nil: + state.suggestionsMtx.Lock() + state.suggestions[addrmgr.IPv6Address][id]++ + state.suggestionsMtx.Unlock() + + state.ResolveLocalAddress(addrmgr.IPv6Address, s.addrManager, + s.services, uint16(port)) + + default: + return true + } } return true @@ -1769,6 +1886,10 @@ func (s *server) peerHandler() { outboundPeers: make(map[int32]*serverPeer), banned: make(map[string]time.Time), outboundGroups: make(map[string]int), + suggestions: map[addrmgr.NetworkAddress]map[string]int32{ + addrmgr.IPv4Address: make(map[string]int32), + addrmgr.IPv6Address: make(map[string]int32), + }, } if !cfg.DisableDNSSeed {