From f93203b91e66848f6e2c5a1750f96cf3aa15e9ad Mon Sep 17 00:00:00 2001 From: "Owain G. Ainsworth" Date: Tue, 26 Nov 2013 00:40:16 +0000 Subject: [PATCH] Initial basic support for selection of external ip address. This implements only the bare bones of external ip address selection using very similar algorithms and selection methods to bitcoind. Every address we bind to, and if we bind to the wildcard, every listening address is recorded, and one for the appropriate address type of the peer is selected. Support for fetching addresses via upnp, external services, or via the command line are not yet implemented. Closes #35 --- addrmanager.go | 171 +++++++++++++++++++++++++++++++++++++++++++++---- peer.go | 16 ++--- rpcserver.go | 2 +- server.go | 49 ++++++++++++-- 4 files changed, 206 insertions(+), 32 deletions(-) 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),