Skip to content

Commit

Permalink
multi: add automatic network address discovery.
Browse files Browse the repository at this point in the history
This discovers the network address(es) of the daemon
through connected outbound peers. The address(es)
discovered are advertised to subsequent connecting peers.
  • Loading branch information
dnldd committed Aug 14, 2019
1 parent c4552a6 commit 50b0dc3
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 10 deletions.
54 changes: 44 additions & 10 deletions addrmgr/addrmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 27 additions & 0 deletions addrmgr/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
121 changes: 121 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,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.
Expand Down Expand Up @@ -177,6 +182,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 {
Expand Down Expand Up @@ -252,6 +297,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
Expand Down Expand Up @@ -440,6 +489,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)

Expand Down Expand Up @@ -1359,6 +1414,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
Expand Down Expand Up @@ -1767,6 +1884,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 {
Expand Down

0 comments on commit 50b0dc3

Please sign in to comment.