Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

multi: add automatic network address discovery. #1522

Merged
merged 1 commit into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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