diff --git a/addrmanager.go b/addrmanager.go index bfc19fbb..f1fb031f 100644 --- a/addrmanager.go +++ b/addrmanager.go @@ -322,18 +322,20 @@ type knownAddress struct { // AddrManager provides a concurrency safe address manager for caching potential // peers on the bitcoin network. type AddrManager struct { - mtx sync.Mutex - rand *rand.Rand - key [32]byte - addrIndex map[string]*knownAddress // address key to ka for all addrs. - addrNew [newBucketCount]map[string]*knownAddress - addrTried [triedBucketCount]*list.List - started int32 - shutdown int32 - wg sync.WaitGroup - quit chan bool - nTried int - nNew int + mtx sync.Mutex + rand *rand.Rand + key [32]byte + addrIndex map[string]*knownAddress // address key to ka for all addrs. + addrNew [newBucketCount]map[string]*knownAddress + addrTried [triedBucketCount]*list.List + started int32 + shutdown int32 + wg sync.WaitGroup + quit chan bool + nTried int + nNew int + lamtx sync.Mutex + localAddresses map[string]*localAddress } func (a *AddrManager) getNewBucket(netAddr, srcAddr *btcwire.NetAddress) int { @@ -748,8 +750,9 @@ func (a *AddrManager) reset() { // Use Start to begin processing asynchronous address updates. func NewAddrManager() *AddrManager { am := AddrManager{ - rand: rand.New(rand.NewSource(time.Now().UnixNano())), - quit: make(chan bool), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), + quit: make(chan bool), + localAddresses: make(map[string]*localAddress), } am.reset() return &am @@ -1152,6 +1155,7 @@ func Valid(na *btcwire.NetAddress) bool { // not. This is true as long as the address is valid and is not in any reserved // ranges. func Routable(na *btcwire.NetAddress) bool { + // TODO(oga) bitcoind doesn't include RFC3849 here, but should we? return Valid(na) && !(RFC1918(na) || RFC3927(na) || RFC4862(na) || (RFC4193(na) && !Tor(na)) || RFC4843(na) || Local(na)) } @@ -1211,3 +1215,142 @@ func GroupKey(na *btcwire.NetAddress) string { return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String() } + +// addressPrio is an enum type used to describe the heirarchy of local address +// discovery methods. +type addressPrio int + +const ( + InterfacePrio addressPrio = iota // address of local interface. + BoundPrio // Address explicitly bound to. + UpnpPrio // External IP discovered from UPnP + HttpPrio // Obtained from internet service. + ManualPrio // provided by --externalip. +) + +type localAddress struct { + na *btcwire.NetAddress + score addressPrio +} + +// addLocalAddress adds na to the list of known local addresses to advertise +// with the given priority. +func (a *AddrManager) addLocalAddress(na *btcwire.NetAddress, + priority addressPrio) { + // sanity check. + if !Routable(na) { + amgrLog.Debugf("rejecting address %s:%d due to routability", + na.IP, na.Port) + return + } + amgrLog.Debugf("adding address %s:%d", + na.IP, na.Port) + + a.lamtx.Lock() + defer a.lamtx.Unlock() + + key := NetAddressKey(na) + la, ok := a.localAddresses[key] + if !ok || la.score < priority { + if ok { + la.score = priority + 1 + } else { + a.localAddresses[key] = &localAddress{ + na: na, + score: priority, + } + } + } +} + +// getReachabilityFrom returns the relative reachability of na from fromna. +func getReachabilityFrom(na, fromna *btcwire.NetAddress) int { + const ( + Unreachable = 0 + Default = iota + Teredo + Ipv6_weak + Ipv4 + Ipv6_strong + Private + ) + if !Routable(fromna) { + return Unreachable + } else if Tor(fromna) { + if Tor(na) { + return Private + } else if Routable(na) && na.IP.To4() != nil { + return Ipv4 + } else { + return Default + } + } else if RFC4380(fromna) { + if !Routable(na) { + return Default + } else if RFC4380(na) { + return Teredo + } else if na.IP.To4() != nil { + return Ipv4 + } else { // ipv6 + return Ipv6_weak + } + } else if fromna.IP.To4() != nil { + if Routable(na) && na.IP.To4() != nil { + return Ipv4 + } + return Default + } else /* ipv6 */ { + var tunnelled bool + // Is our v6 is tunnelled? + if RFC3964(na) || RFC6052(na) || RFC6145(na) { + tunnelled = true + } + if !Routable(na) { + return Default + } else if RFC4380(na) { + return Teredo + } else if na.IP.To4() != nil { + return Ipv4 + } else if tunnelled { + // only prioritise ipv6 if we aren't tunnelling it. + return Ipv6_weak + } + return Ipv6_strong + } +} + +// getBestLocalAddress returns the most appropriate local address that we know +// of to be contacted by rna +func (a *AddrManager) getBestLocalAddress(rna *btcwire.NetAddress) *btcwire.NetAddress { + a.lamtx.Lock() + defer a.lamtx.Unlock() + + bestreach := 0 + var bestscore addressPrio + var bestna *btcwire.NetAddress + for _, la := range a.localAddresses { + reach := getReachabilityFrom(la.na, rna) + if reach > bestreach || + (reach == bestreach && la.score > bestscore) { + bestreach = reach + bestscore = la.score + bestna = la.na + } + } + if bestna != nil { + amgrLog.Debugf("Suggesting address %s:%d for %s:%d", + bestna.IP, bestna.Port, rna.IP, rna.Port) + } else { + amgrLog.Debugf("No worthy address for %s:%d", + rna.IP, rna.Port) + // Send something unroutable if nothing suitable. + bestna = &btcwire.NetAddress{ + Timestamp: time.Now(), + Services: 0, + IP: net.IP([]byte{0, 0, 0, 0}), + Port: 0, + } + } + + return bestna +} diff --git a/peer.go b/peer.go index 45f072a0..b935f8a8 100644 --- a/peer.go +++ b/peer.go @@ -281,17 +281,13 @@ func (p *peer) handleVersionMsg(msg *btcwire.MsgVersion) { if !p.inbound { // TODO(davec): Only do this if not doing the initial block // download and the local address is routable. - if !cfg.DisableListen { - // Advertise the local address. - na, err := newNetAddress(p.conn.LocalAddr(), p.services) - if err != nil { - p.logError("Can't advertise local "+ - "address: %v", err) - p.Disconnect() - return + if !cfg.DisableListen /* && isCurrent? */ { + // get address that best matches. p.na + lna := p.server.addrManager.getBestLocalAddress(p.na) + if Routable(lna) { + addresses := []*btcwire.NetAddress{lna} + p.pushAddrMsg(addresses) } - addresses := []*btcwire.NetAddress{na} - p.pushAddrMsg(addresses) } // Request known addresses if the server address manager needs diff --git a/rpcserver.go b/rpcserver.go index 326077c5..45e8baff 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -472,7 +472,7 @@ func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) { // TODO(oga) this code is similar to that in server, should be // factored into something shared. - ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs) + ipv4ListenAddrs, ipv6ListenAddrs, _, err := parseListeners(listenAddrs) if err != nil { return nil, err } diff --git a/server.go b/server.go index 453760d5..161f0968 100644 --- a/server.go +++ b/server.go @@ -771,28 +771,31 @@ func (s *server) ScheduleShutdown(duration time.Duration) { // listeners on the correct interface "tcp4" and "tcp6". It also properly // detects addresses which apply to "all interfaces" and adds the address to // both slices. -func parseListeners(addrs []string) ([]string, []string, error) { +func parseListeners(addrs []string) ([]string, []string, bool, error) { ipv4ListenAddrs := make([]string, 0, len(addrs)*2) ipv6ListenAddrs := make([]string, 0, len(addrs)*2) + haveWildcard := false + for _, addr := range addrs { host, _, err := net.SplitHostPort(addr) if err != nil { // Shouldn't happen due to already being normalized. - return nil, nil, err + return nil, nil, false, err } // Empty host or host of * on plan9 is both IPv4 and IPv6. if host == "" || (host == "*" && runtime.GOOS == "plan9") { ipv4ListenAddrs = append(ipv4ListenAddrs, addr) ipv6ListenAddrs = append(ipv6ListenAddrs, addr) + haveWildcard = true continue } // Parse the IP. ip := net.ParseIP(host) if ip == nil { - return nil, nil, fmt.Errorf("'%s' is not a valid IP "+ - "address", host) + return nil, nil, false, fmt.Errorf("'%s' is not a "+ + "valid IP address", host) } // To4 returns nil when the IP is not an IPv4 address, so use @@ -803,7 +806,7 @@ func parseListeners(addrs []string) ([]string, []string, error) { ipv4ListenAddrs = append(ipv4ListenAddrs, addr) } } - return ipv4ListenAddrs, ipv6ListenAddrs, nil + return ipv4ListenAddrs, ipv6ListenAddrs, haveWildcard, nil } // newServer returns a new btcd server configured to listen on addr for the @@ -815,14 +818,39 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s return nil, err } + amgr := NewAddrManager() + var listeners []net.Listener if !cfg.DisableListen { - ipv4Addrs, ipv6Addrs, err := parseListeners(listenAddrs) + ipv4Addrs, ipv6Addrs, wildcard, err := + parseListeners(listenAddrs) if err != nil { return nil, err } listeners = make([]net.Listener, 0, len(ipv4Addrs)+len(ipv6Addrs)) + // TODO(oga) nonstandard port... + if wildcard { + port, err := + strconv.ParseUint(activeNetParams.listenPort, + 10, 16) + if err != nil { + // I can't think of a cleaner way to do this... + goto nowc + } + addrs, err := net.InterfaceAddrs() + for _, a := range addrs { + ip, _, err := net.ParseCIDR(a.String()) + if err != nil { + continue + } + na := btcwire.NewNetAddressIPPort(ip, + uint16(port), btcwire.SFNodeNetwork) + amgr.addLocalAddress(na, InterfacePrio) + } + } + nowc: + for _, addr := range ipv4Addrs { listener, err := net.Listen("tcp4", addr) if err != nil { @@ -831,6 +859,10 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s continue } listeners = append(listeners, listener) + + if na, err := deserialiseNetAddress(addr); err == nil { + amgr.addLocalAddress(na, BoundPrio) + } } for _, addr := range ipv6Addrs { @@ -841,6 +873,9 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s continue } listeners = append(listeners, listener) + if na, err := deserialiseNetAddress(addr); err == nil { + amgr.addLocalAddress(na, BoundPrio) + } } if len(listeners) == 0 { @@ -852,7 +887,7 @@ func newServer(listenAddrs []string, db btcdb.Db, btcnet btcwire.BitcoinNet) (*s nonce: nonce, listeners: listeners, btcnet: btcnet, - addrManager: NewAddrManager(), + addrManager: amgr, newPeers: make(chan *peer, cfg.MaxPeers), donePeers: make(chan *peer, cfg.MaxPeers), banPeers: make(chan *peer, cfg.MaxPeers),