diff --git a/addrmgr/addrmanager.go b/addrmgr/addrmanager.go index ec2835bd..ca4c5915 100644 --- a/addrmgr/addrmanager.go +++ b/addrmgr/addrmanager.go @@ -35,7 +35,7 @@ const ( // dumpAddressInterval is the interval used to dump the address // cache to disk for future use. - dumpAddressInterval = time.Minute * 2 + dumpAddressInterval = time.Minute * 10 // triedBucketSize is the maximum number of addresses in each // tried address bucket. @@ -155,46 +155,30 @@ func bad(ka *knownAddress) bool { } // chance returns the selection probability for a known address. The priority -// depends upon how recent the address has been seen, how recent it was last +// depends upon how recently the address has been seen, how recently it was last // attempted and how often attempts to connect to it have failed. func chance(ka *knownAddress) float64 { - c := 1.0 - now := time.Now() - var lastSeen float64 - var lastTry float64 - if !ka.na.Timestamp.After(now) { - var dur time.Duration - if ka.na.Timestamp.IsZero() { - // use unix epoch to match bitcoind. - dur = now.Sub(time.Unix(0, 0)) + lastSeen := now.Sub(ka.na.Timestamp) + lastAttempt := now.Sub(ka.lastattempt) - } else { - dur = now.Sub(ka.na.Timestamp) - } - lastSeen = dur.Seconds() + if lastSeen < 0 { + lastSeen = 0 } - if !ka.lastattempt.After(now) { - var dur time.Duration - if ka.lastattempt.IsZero() { - // use unix epoch to match bitcoind. - dur = now.Sub(time.Unix(0, 0)) - } else { - dur = now.Sub(ka.lastattempt) - } - lastTry = dur.Seconds() + if lastAttempt < 0 { + lastAttempt = 0 } - c = 600.0 / (600.0 + lastSeen) + c := 600.0 / (600.0 + lastSeen.Seconds()) // Very recent attempts are less likely to be retried. - if lastTry > 60.0*10.0 { + if lastAttempt > 10*time.Minute { c *= 0.01 } // Failed attempts deprioritise. - if ka.attempts > 0 { - c /= float64(ka.attempts) * 1.5 + for i := ka.attempts; i < 0; i++ { + c /= 1.5 } return c @@ -226,7 +210,7 @@ type AddrManager struct { func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) { // Filter out non-routable addresses. Note that non-routable // also includes invalid and local addresses. - if !Routable(netAddr) { + if !IsRoutable(netAddr) { return } @@ -420,7 +404,7 @@ out: log.Trace("Address handler done") } -type serialisedKnownAddress struct { +type serializedKnownAddress struct { Addr string Src string Attempts int @@ -430,10 +414,10 @@ type serialisedKnownAddress struct { // no refcount or tried, that is available from context. } -type serialisedAddrManager struct { +type serializedAddrManager struct { Version int Key [32]byte - Addresses []*serialisedKnownAddress + Addresses []*serializedKnownAddress NewBuckets [newBucketCount][]string // string is NetAddressKey TriedBuckets [triedBucketCount][]string } @@ -446,14 +430,14 @@ func (a *AddrManager) savePeers() { // First we make a serialisable datastructure so we can encode it to // json. - sam := new(serialisedAddrManager) + sam := new(serializedAddrManager) sam.Version = serialisationVersion copy(sam.Key[:], a.key[:]) - sam.Addresses = make([]*serialisedKnownAddress, len(a.addrIndex)) + sam.Addresses = make([]*serializedKnownAddress, len(a.addrIndex)) i := 0 for k, v := range a.addrIndex { - ska := new(serialisedKnownAddress) + ska := new(serializedKnownAddress) ska.Addr = k ska.TimeStamp = v.na.Timestamp.Unix() ska.Src = NetAddressKey(v.srcAddr) @@ -510,7 +494,7 @@ func (a *AddrManager) loadPeers() { filename := "peers.json" filePath := filepath.Join(a.dataDir, filename) - err := a.deserialisePeers(filePath) + err := a.deserializePeers(filePath) if err != nil { log.Errorf("Failed to parse %s: %v", filePath, err) // if it is invalid we nuke the old one unconditionally. @@ -524,7 +508,7 @@ func (a *AddrManager) loadPeers() { log.Infof("Loaded %d addresses from '%s'", a.nNew+a.nTried, filePath) } -func (a *AddrManager) deserialisePeers(filePath string) error { +func (a *AddrManager) deserializePeers(filePath string) error { _, err := os.Stat(filePath) if os.IsNotExist(err) { @@ -536,7 +520,7 @@ func (a *AddrManager) deserialisePeers(filePath string) error { } defer r.Close() - var sam serialisedAddrManager + var sam serializedAddrManager dec := json.NewDecoder(r) err = dec.Decode(&sam) if err != nil { @@ -544,21 +528,21 @@ func (a *AddrManager) deserialisePeers(filePath string) error { } if sam.Version != serialisationVersion { - return fmt.Errorf("unknown version %v in serialised "+ + return fmt.Errorf("unknown version %v in serialized "+ "addrmanager", sam.Version) } copy(a.key[:], sam.Key[:]) for _, v := range sam.Addresses { ka := new(knownAddress) - ka.na, err = a.DeserialiseNetAddress(v.Addr) + ka.na, err = a.DeserializeNetAddress(v.Addr) if err != nil { - return fmt.Errorf("failed to deserialise netaddress "+ + return fmt.Errorf("failed to deserialize netaddress "+ "%s: %v", v.Addr, err) } - ka.srcAddr, err = a.DeserialiseNetAddress(v.Src) + ka.srcAddr, err = a.DeserializeNetAddress(v.Src) if err != nil { - return fmt.Errorf("failed to deserialise netaddress "+ + return fmt.Errorf("failed to deserialize netaddress "+ "%s: %v", v.Src, err) } ka.attempts = v.Attempts @@ -612,7 +596,8 @@ func (a *AddrManager) deserialisePeers(filePath string) error { return nil } -func (a *AddrManager) DeserialiseNetAddress(addr string) (*btcwire.NetAddress, error) { +// DeserializeNetAddress converts a given address string to a *btcwire.NetAddress +func (a *AddrManager) DeserializeNetAddress(addr string) (*btcwire.NetAddress, error) { host, portStr, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -803,7 +788,7 @@ func (a *AddrManager) HostToNetAddress(host string, port uint16, services btcwir // ip is in the range used for tor addresses then it will be transformed into // the relavent .onion address. func ipString(na *btcwire.NetAddress) string { - if Tor(na) { + if IsOnionCatTor(na) { // We know now that na.IP is long enogh. base32 := base32.StdEncoding.EncodeToString(na.IP[6:]) return strings.ToLower(base32) + ".onion" @@ -844,7 +829,7 @@ func (a *AddrManager) GetAddress(class string, newBias int) *knownAddress { (100.0 - float64(newBias)) newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias) - if (newCorrelation+triedCorrelation)*a.rand.Float64() < + if ((newCorrelation + triedCorrelation) * a.rand.Float64()) < triedCorrelation { // Tried entry. large := 1 << 30 @@ -906,12 +891,8 @@ func (a *AddrManager) find(addr *btcwire.NetAddress) *knownAddress { return a.addrIndex[NetAddressKey(addr)] } -/* - * Connected - updates the last seen time but only every 20 minutes. - * Good - last tried = last success = last seen = now. attmempts = 0. - * - move address to tried. - * Attempted - set last tried to time. nattempts++ - */ +// Attempt increases the given address' attempt counter and updates +// the last attempt time. func (a *AddrManager) Attempt(addr *btcwire.NetAddress) { a.mtx.Lock() defer a.mtx.Unlock() @@ -1039,217 +1020,38 @@ func (a *AddrManager) Good(addr *btcwire.NetAddress) { a.addrNew[newBucket][rmkey] = rmka } -// RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) -var rfc1918ten = net.IPNet{IP: net.ParseIP("10.0.0.0"), - Mask: net.CIDRMask(8, 32)} -var rfc1918oneninetwo = net.IPNet{IP: net.ParseIP("192.168.0.0"), - Mask: net.CIDRMask(16, 32)} -var rfc1918oneseventwo = net.IPNet{IP: net.ParseIP("172.16.0.0"), - Mask: net.CIDRMask(12, 32)} - -func RFC1918(na *btcwire.NetAddress) bool { - return rfc1918ten.Contains(na.IP) || - rfc1918oneninetwo.Contains(na.IP) || - rfc1918oneseventwo.Contains(na.IP) -} - -// RFC3849 IPv6 Documentation address (2001:0DB8::/32) -var rfc3849 = net.IPNet{IP: net.ParseIP("2001:0DB8::"), - Mask: net.CIDRMask(32, 128)} - -func RFC3849(na *btcwire.NetAddress) bool { - return rfc3849.Contains(na.IP) -} - -// RFC3927 IPv4 Autoconfig (169.254.0.0/16) -var rfc3927 = net.IPNet{IP: net.ParseIP("169.254.0.0"), Mask: net.CIDRMask(16, 32)} - -func RFC3927(na *btcwire.NetAddress) bool { - return rfc3927.Contains(na.IP) -} - -// RFC3964 IPv6 6to4 (2002::/16) -var rfc3964 = net.IPNet{IP: net.ParseIP("2002::"), - Mask: net.CIDRMask(16, 128)} - -func RFC3964(na *btcwire.NetAddress) bool { - return rfc3964.Contains(na.IP) -} - -// RFC4193 IPv6 unique local (FC00::/7) -var rfc4193 = net.IPNet{IP: net.ParseIP("FC00::"), - Mask: net.CIDRMask(7, 128)} - -func RFC4193(na *btcwire.NetAddress) bool { - return rfc4193.Contains(na.IP) -} - -// RFC4380 IPv6 Teredo tunneling (2001::/32) -var rfc4380 = net.IPNet{IP: net.ParseIP("2001::"), - Mask: net.CIDRMask(32, 128)} - -func RFC4380(na *btcwire.NetAddress) bool { - return rfc4380.Contains(na.IP) -} - -// RFC4843 IPv6 ORCHID: (2001:10::/28) -var rfc4843 = net.IPNet{IP: net.ParseIP("2001:10::"), - Mask: net.CIDRMask(28, 128)} - -func RFC4843(na *btcwire.NetAddress) bool { - return rfc4843.Contains(na.IP) -} - -// RFC4862 IPv6 Autoconfig (FE80::/64) -var rfc4862 = net.IPNet{IP: net.ParseIP("FE80::"), - Mask: net.CIDRMask(64, 128)} - -func RFC4862(na *btcwire.NetAddress) bool { - return rfc4862.Contains(na.IP) -} - -// RFC6052: IPv6 well known prefix (64:FF9B::/96) -var rfc6052 = net.IPNet{IP: net.ParseIP("64:FF9B::"), - Mask: net.CIDRMask(96, 128)} - -func RFC6052(na *btcwire.NetAddress) bool { - return rfc6052.Contains(na.IP) -} - -// RFC6145: IPv6 IPv4 translated address ::FFFF:0:0:0/96 -var rfc6145 = net.IPNet{IP: net.ParseIP("::FFFF:0:0:0"), - Mask: net.CIDRMask(96, 128)} - -func RFC6145(na *btcwire.NetAddress) bool { - return rfc6145.Contains(na.IP) -} - -var onioncatrange = net.IPNet{IP: net.ParseIP("FD87:d87e:eb43::"), - Mask: net.CIDRMask(48, 128)} - -func Tor(na *btcwire.NetAddress) bool { - // bitcoind encodes a .onion address as a 16 byte number by decoding the - // address prior to the .onion (i.e. the key hash) base32 into a ten - // byte number. it then stores the first 6 bytes of the address as - // 0xfD, 0x87, 0xD8, 0x7e, 0xeb, 0x43 - // this is the same range used by onioncat, part of the - // RFC4193 Unique local IPv6 range. - // In summary the format is: - // { magic 6 bytes, 10 bytes base32 decode of key hash } - return onioncatrange.Contains(na.IP) -} - -var zero4 = net.IPNet{IP: net.ParseIP("0.0.0.0"), - Mask: net.CIDRMask(8, 32)} - -func Local(na *btcwire.NetAddress) bool { - return na.IP.IsLoopback() || zero4.Contains(na.IP) -} - -// Valid returns true if an address is not one of the invalid formats. -// For IPv4 these are either a 0 or all bits set address. For IPv6 a zero -// address or one that matches the RFC3849 documentation address format. -func Valid(na *btcwire.NetAddress) bool { - // IsUnspecified returns if address is 0, so only all bits set, and - // RFC3849 need to be explicitly checked. bitcoind here also checks for - // invalid protocol addresses from earlier versions of bitcoind (before - // 0.2.9), however, since protocol versions before 70001 are - // disconnected by the bitcoin network now we have elided it. - return na.IP != nil && !(na.IP.IsUnspecified() || RFC3849(na) || - na.IP.Equal(net.IPv4bcast)) -} - -// Routable returns whether a netaddress is routable on the public internet or -// 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)) -} - -// GroupKey returns a string representing the network group an address -// is part of. -// This is the /16 for IPv6, the /32 (/36 for he.net) for IPv6, the string -// "local" for a local address and the string "unroutable for an unroutable -// address. -func GroupKey(na *btcwire.NetAddress) string { - if Local(na) { - return "local" - } - if !Routable(na) { - return "unroutable" - } - - if ipv4 := na.IP.To4(); ipv4 != nil { - return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(16, 32)}).String() - } - if RFC6145(na) || RFC6052(na) { - // last four bytes are the ip address - ip := net.IP(na.IP[12:16]) - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() - } - - if RFC3964(na) { - ip := net.IP(na.IP[2:7]) - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() - - } - if RFC4380(na) { - // teredo tunnels have the last 4 bytes as the v4 address XOR - // 0xff. - ip := net.IP(make([]byte, 4)) - for i, byte := range na.IP[12:16] { - ip[i] = byte ^ 0xff - } - return (&net.IPNet{IP: ip, Mask: net.CIDRMask(16, 32)}).String() - } - if Tor(na) { - // group is keyed off the first 4 bits of the actual onion key. - return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) - } - - // OK, so now we know ourselves to be a IPv6 address. - // bitcoind uses /32 for everything, except for Hurricane Electric's - // (he.net) IP range, which it uses /36 for. - bits := 32 - heNet := &net.IPNet{IP: net.ParseIP("2001:470::"), - Mask: net.CIDRMask(32, 128)} - if heNet.Contains(na.IP) { - bits = 36 - } - - 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 +// AddressPriority type is used to describe the heirarchy of local address // discovery methods. -type addressPrio int +type AddressPriority 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. + // InterfacePrio signifies the address is on a local interface + InterfacePrio AddressPriority = iota + + // BoundPrio signifies the address has been explicity bounded to. + BoundPrio + + // UpnpPrio signifies the address was obtained from UPnP. + UpnpPrio + + // HTTPPrio signifies the address was obtained from an external HTTP service. + HTTPPrio + + // ManualPrio signifies the address was provided by --externalip. + ManualPrio ) type localAddress struct { na *btcwire.NetAddress - score addressPrio + score AddressPriority } // 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) { - log.Debugf("rejecting address %s:%d due to routability", - na.IP, na.Port) - return +func (a *AddrManager) AddLocalAddress(na *btcwire.NetAddress, priority AddressPriority) error { + if !IsRoutable(na) { + return fmt.Errorf("address %s is not routable", na.IP) } - log.Debugf("adding address %s:%d", na.IP, na.Port) a.lamtx.Lock() defer a.lamtx.Unlock() @@ -1266,10 +1068,12 @@ func (a *AddrManager) AddLocalAddress(na *btcwire.NetAddress, } } } + return nil } -// getReachabilityFrom returns the relative reachability of na from fromna. -func getReachabilityFrom(na, fromna *btcwire.NetAddress) int { +// getReachabilityFrom returns the relative reachability of the provided local +// address to the provided remote address. +func getReachabilityFrom(localAddr, remoteAddr *btcwire.NetAddress) int { const ( Unreachable = 0 Default = iota @@ -1280,61 +1084,61 @@ func getReachabilityFrom(na, fromna *btcwire.NetAddress) int { Private ) - if !Routable(fromna) { + if !IsRoutable(remoteAddr) { return Unreachable } - if Tor(fromna) { - if Tor(na) { + if IsOnionCatTor(remoteAddr) { + if IsOnionCatTor(localAddr) { return Private } - if Routable(na) && na.IP.To4() != nil { + if IsRoutable(localAddr) && IsIPv4(localAddr) { return Ipv4 } return Default } - if RFC4380(fromna) { - if !Routable(na) { + if IsRFC4380(remoteAddr) { + if !IsRoutable(localAddr) { return Default } - if RFC4380(na) { + if IsRFC4380(localAddr) { return Teredo } - if na.IP.To4() != nil { + if IsIPv4(localAddr) { return Ipv4 } return Ipv6Weak } - if fromna.IP.To4() != nil { - if Routable(na) && na.IP.To4() != nil { + if IsIPv4(remoteAddr) { + if IsRoutable(localAddr) && IsIPv4(localAddr) { return Ipv4 } - return Default + return Unreachable } /* ipv6 */ var tunnelled bool // Is our v6 is tunnelled? - if RFC3964(na) || RFC6052(na) || RFC6145(na) { + if IsRFC3964(localAddr) || IsRFC6052(localAddr) || IsRFC6145(localAddr) { tunnelled = true } - if !Routable(na) { + if !IsRoutable(localAddr) { return Default } - if RFC4380(na) { + if IsRFC4380(localAddr) { return Teredo } - if na.IP.To4() != nil { + if IsIPv4(localAddr) { return Ipv4 } @@ -1346,39 +1150,45 @@ func getReachabilityFrom(na, fromna *btcwire.NetAddress) int { return Ipv6Strong } -// 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 { +// GetBestLocalAddress returns the most appropriate local address to use +// for the given remote address. +func (a *AddrManager) GetBestLocalAddress(remoteAddr *btcwire.NetAddress) *btcwire.NetAddress { a.lamtx.Lock() defer a.lamtx.Unlock() bestreach := 0 - var bestscore addressPrio - var bestna *btcwire.NetAddress + var bestscore AddressPriority + var bestAddress *btcwire.NetAddress for _, la := range a.localAddresses { - reach := getReachabilityFrom(la.na, rna) + reach := getReachabilityFrom(la.na, remoteAddr) if reach > bestreach || (reach == bestreach && la.score > bestscore) { bestreach = reach bestscore = la.score - bestna = la.na + bestAddress = la.na } } - if bestna != nil { - log.Debugf("Suggesting address %s:%d for %s:%d", bestna.IP, - bestna.Port, rna.IP, rna.Port) + if bestAddress != nil { + log.Debugf("Suggesting address %s:%d for %s:%d", bestAddress.IP, + bestAddress.Port, remoteAddr.IP, remoteAddr.Port) } else { - log.Debugf("No worthy address for %s:%d", rna.IP, rna.Port) + log.Debugf("No worthy address for %s:%d", remoteAddr.IP, + remoteAddr.Port) + // Send something unroutable if nothing suitable. - bestna = &btcwire.NetAddress{ + bestAddress = &btcwire.NetAddress{ Timestamp: time.Now(), - Services: 0, - IP: net.IP([]byte{0, 0, 0, 0}), + Services: btcwire.SFNodeNetwork, Port: 0, } + if !IsIPv4(remoteAddr) && !IsOnionCatTor(remoteAddr) { + bestAddress.IP = net.IPv6zero + } else { + bestAddress.IP = net.IPv4zero + } } - return bestna + return bestAddress } // New returns a new bitcoin address manager. diff --git a/addrmgr/addrmanager_test.go b/addrmgr/addrmanager_test.go index 814c5021..6c572599 100644 --- a/addrmgr/addrmanager_test.go +++ b/addrmgr/addrmanager_test.go @@ -21,88 +21,9 @@ type naTest struct { want string } -type ipTest struct { - in btcwire.NetAddress - rfc1918 bool - rfc3849 bool - rfc3927 bool - rfc3964 bool - rfc4193 bool - rfc4380 bool - rfc4843 bool - rfc4862 bool - rfc6052 bool - rfc6145 bool - local bool - valid bool - routable bool -} - // naTests houses all of the tests to be performed against the NetAddressKey // method. var naTests = make([]naTest, 0) -var ipTests = make([]ipTest, 0) - -func addIPTest(ip string, rfc1918, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380, - rfc4843, rfc4862, rfc6052, rfc6145, local, valid, routable bool) { - nip := net.ParseIP(ip) - na := btcwire.NetAddress{ - Timestamp: time.Now(), - Services: btcwire.SFNodeNetwork, - IP: nip, - Port: 8333, - } - test := ipTest{na, rfc1918, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380, - rfc4843, rfc4862, rfc6052, rfc6145, local, valid, routable} - ipTests = append(ipTests, test) -} - -func addIPTests() { - addIPTest("10.255.255.255", true, false, false, false, false, false, - false, false, false, false, false, true, false) - addIPTest("192.168.0.1", true, false, false, false, false, false, - false, false, false, false, false, true, false) - addIPTest("172.31.255.1", true, false, false, false, false, false, - false, false, false, false, false, true, false) - addIPTest("172.32.1.1", false, false, false, false, false, false, - false, false, false, false, false, true, true) - addIPTest("169.254.250.120", false, false, true, false, false, false, - false, false, false, false, false, true, false) - addIPTest("0.0.0.0", false, false, false, false, false, false, - false, false, false, false, true, false, false) - addIPTest("255.255.255.255", false, false, false, false, false, false, - false, false, false, false, false, false, false) - addIPTest("127.0.0.1", false, false, false, false, false, false, - false, false, false, false, true, true, false) - addIPTest("fd00:dead::1", false, false, false, false, true, false, - false, false, false, false, false, true, false) - addIPTest("2001::1", false, false, false, false, false, true, - false, false, false, false, false, true, true) - addIPTest("2001:10:abcd::1:1", false, false, false, false, false, false, - true, false, false, false, false, true, false) - addIPTest("fe80::1", false, false, false, false, false, false, - false, true, false, false, false, true, false) - addIPTest("fe80:1::1", false, false, false, false, false, false, - false, false, false, false, false, true, true) - addIPTest("64:ff9b::1", false, false, false, false, false, false, - false, false, true, false, false, true, true) - addIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false, - false, false, false, false, false, true, true) - addIPTest("::1", false, false, false, false, false, false, - false, false, false, false, true, true, false) -} - -func addNaTest(ip string, port uint16, want string) { - nip := net.ParseIP(ip) - na := btcwire.NetAddress{ - Timestamp: time.Now(), - Services: btcwire.SFNodeNetwork, - IP: nip, - Port: port, - } - test := naTest{na, want} - naTests = append(naTests, test) -} // addNaTests func addNaTests() { @@ -165,10 +86,64 @@ func addNaTests() { addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336") } +func addNaTest(ip string, port uint16, want string) { + nip := net.ParseIP(ip) + na := btcwire.NetAddress{ + Timestamp: time.Now(), + Services: btcwire.SFNodeNetwork, + IP: nip, + Port: port, + } + test := naTest{na, want} + naTests = append(naTests, test) +} + func lookupFunc(host string) ([]net.IP, error) { return nil, errors.New("not implemented") } +func TestAddLocalAddress(t *testing.T) { + var tests = []struct { + address btcwire.NetAddress + valid bool + }{ + { + btcwire.NetAddress{IP: net.ParseIP("192.168.0.100")}, + false, + }, + { + btcwire.NetAddress{IP: net.ParseIP("204.124.1.1")}, + true, + }, + { + btcwire.NetAddress{IP: net.ParseIP("::1")}, + false, + }, + { + btcwire.NetAddress{IP: net.ParseIP("fe80::1")}, + false, + }, + { + btcwire.NetAddress{IP: net.ParseIP("2620:100::1")}, + true, + }, + } + amgr := addrmgr.New("", nil) + for x, test := range tests { + result := amgr.AddLocalAddress(&test.address, addrmgr.InterfacePrio) + if result == nil && !test.valid { + t.Errorf("TestAddLocalAddress test #%d failed: %s should have "+ + "been accepted", x, test.address.IP) + continue + } + if result != nil && test.valid { + t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+ + "been accepted", test.address.IP) + continue + } + } +} + func TestGetAddress(t *testing.T) { n := addrmgr.New("testdir", lookupFunc) if rv := n.GetAddress("any", 10); rv != nil { @@ -176,101 +151,95 @@ func TestGetAddress(t *testing.T) { } } -func TestIPTypes(t *testing.T) { - addIPTests() +func TestGetBestLocalAddress(t *testing.T) { + localAddrs := []btcwire.NetAddress{ + {IP: net.ParseIP("192.168.0.100")}, + {IP: net.ParseIP("::1")}, + {IP: net.ParseIP("fe80::1")}, + {IP: net.ParseIP("2001:470::1")}, + } - t.Logf("Running %d tests", len(ipTests)) - for _, test := range ipTests { - rv := addrmgr.RFC1918(&test.in) - if rv != test.rfc1918 { - t.Errorf("RFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918) + var tests = []struct { + remoteAddr btcwire.NetAddress + want1 btcwire.NetAddress + want2 btcwire.NetAddress + want3 btcwire.NetAddress + }{ + { + // Remote connection from public IPv4 + btcwire.NetAddress{IP: net.ParseIP("204.124.8.1")}, + btcwire.NetAddress{IP: net.IPv4zero}, + btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")}, + btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, + }, + { + // Remote connection from private IPv4 + btcwire.NetAddress{IP: net.ParseIP("172.16.0.254")}, + btcwire.NetAddress{IP: net.IPv4zero}, + btcwire.NetAddress{IP: net.IPv4zero}, + btcwire.NetAddress{IP: net.IPv4zero}, + }, + { + // Remote connection from public IPv6 + btcwire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")}, + btcwire.NetAddress{IP: net.ParseIP("2001:470::1")}, + btcwire.NetAddress{IP: net.ParseIP("2001:470::1")}, + btcwire.NetAddress{IP: net.ParseIP("2001:470::1")}, + }, + /* XXX + { + // Remote connection from Tor + btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")}, + btcwire.NetAddress{IP: net.IPv4zero}, + btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")}, + btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}, + }, + */ + } + + amgr := addrmgr.New("", nil) + for _, localAddr := range localAddrs { + amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) + } + + // Test against want1 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want1.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want1.IP, got.IP) continue } } - for _, test := range ipTests { - rv := addrmgr.RFC3849(&test.in) - if rv != test.rfc3849 { - t.Errorf("RFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849) + + // Add a public IP to the list of local addresses. + localAddr := btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")} + amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio) + + // Test against want2 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want2.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want2.IP, got.IP) continue } } - for _, test := range ipTests { - rv := addrmgr.RFC3927(&test.in) - if rv != test.rfc3927 { - t.Errorf("RFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927) - continue + /* + // Add a tor generated IP address + localAddr = btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")} + amgr.AddLocalAddress(&localAddr, addrmgr.ManualPrio) + + // Test against want3 + for x, test := range tests { + got := amgr.GetBestLocalAddress(&test.remoteAddr) + if !test.want3.IP.Equal(got.IP) { + t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s", + x, test.remoteAddr.IP, test.want3.IP, got.IP) + continue + } } - } - for _, test := range ipTests { - rv := addrmgr.RFC3964(&test.in) - if rv != test.rfc3964 { - t.Errorf("RFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC4193(&test.in) - if rv != test.rfc4193 { - t.Errorf("RFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC4380(&test.in) - if rv != test.rfc4380 { - t.Errorf("RFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC4843(&test.in) - if rv != test.rfc4843 { - t.Errorf("RFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC4862(&test.in) - if rv != test.rfc4862 { - t.Errorf("RFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC6052(&test.in) - if rv != test.rfc6052 { - t.Errorf("RFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.RFC6145(&test.in) - if rv != test.rfc6145 { - t.Errorf("RFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.Local(&test.in) - if rv != test.local { - t.Errorf("Local %s\n got: %v want: %v", test.in.IP, rv, test.local) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.Valid(&test.in) - if rv != test.valid { - t.Errorf("Valid %s\n got: %v want: %v", test.in.IP, rv, test.valid) - continue - } - } - for _, test := range ipTests { - rv := addrmgr.Routable(&test.in) - if rv != test.routable { - t.Errorf("Routable %s\n got: %v want: %v", test.in.IP, rv, test.routable) - continue - } - } + */ } func TestNetAddressKey(t *testing.T) { diff --git a/addrmgr/cov_report.sh b/addrmgr/cov_report.sh new file mode 100644 index 00000000..307f05b7 --- /dev/null +++ b/addrmgr/cov_report.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script uses gocov to generate a test coverage report. +# The gocov tool my be obtained with the following command: +# go get github.com/axw/gocov/gocov +# +# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH. + +# Check for gocov. +type gocov >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo >&2 "This script requires the gocov tool." + echo >&2 "You may obtain it with the following command:" + echo >&2 "go get github.com/axw/gocov/gocov" + exit 1 +fi +gocov test | gocov report diff --git a/addrmgr/doc.go b/addrmgr/doc.go new file mode 100644 index 00000000..528dfe9b --- /dev/null +++ b/addrmgr/doc.go @@ -0,0 +1,38 @@ +// Copyright (c) 2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +/* +Package addrmgr implements concurrency safe Bitcoin address manager. + +Address Manager Overview + +In order maintain the peer-to-peer Bitcoin network, there needs to be a source +of addresses to connect to as nodes come and go. The Bitcoin protocol provides +a the getaddr and addr messages to allow peers to communicate known addresses +with each other. However, there needs to a mechanism to store those results and +select peers from them. It is also important to note that remote peers can't +be trusted to send valid peers nor attempt to provide you with only peers they +control with malicious intent. + +With that in mind, this package provides a concurrency safe address manager for +caching and selecting peers in a non-determinstic manner. The general idea is +the caller adds addresses to the address manager and notifies it when addresses +are connected, known good, and attempted. The caller also requests addresses as +it needs them. + +The address manager internally segregates the addresses into groups and +non-deterministically selects groups in a cryptographically random manner. This +reduce the chances multiple addresses from the same nets are selected which +generally helps provide greater peer diversity, and perhaps more importantly, +drastically reduces the chances an attacker is able to coerce your peer into +only connecting to nodes they control. + +The address manager also understands routability and tor addresses and tries +hard to only return routable addresses. In addition, it uses the information +provided by the caller about connected, known good, and attempted addresses to +periodically purge peers which no longer appear to be good peers as well as +bias the selection toward known good peers. The general idea is to make a best +effort at only providing usuable addresses. +*/ +package addrmgr diff --git a/addrmgr/log.go b/addrmgr/log.go new file mode 100644 index 00000000..9cb49973 --- /dev/null +++ b/addrmgr/log.go @@ -0,0 +1,32 @@ +// Copyright (c) 2013-2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "github.com/conformal/btclog" +) + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log btclog.Logger + +// The default amount of logging is none. +func init() { + DisableLog() +} + +// DisableLog disables all library log output. Logging output is disabled +// by default until either UseLogger or SetLogWriter are called. +func DisableLog() { + log = btclog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using btclog. +func UseLogger(logger btclog.Logger) { + log = logger +} diff --git a/addrmgr/network.go b/addrmgr/network.go new file mode 100644 index 00000000..ec281d13 --- /dev/null +++ b/addrmgr/network.go @@ -0,0 +1,240 @@ +// Copyright (c) 2013-2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr + +import ( + "fmt" + "net" + + "github.com/conformal/btcwire" +) + +var ( + // rfc1918Nets specifies the IPv4 private adddress blocks as defined by + // by RFC1918 (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16). + rfc1918Nets = []net.IPNet{ + ipNet("10.0.0.0", 8, 32), + ipNet("172.16.0.0", 12, 32), + ipNet("192.168.0.0", 16, 32), + } + + // rfc3849Net specifies the IPv6 documentation address block as defined + // by RFC3849 (2001:DB8::/32). + rfc3849Net = ipNet("2001:DB8::", 32, 128) + + // rfc3927Net specifies the IPv4 auto configuration address block as + // defined by RFC3927 (169.254.0.0/16). + rfc3927Net = ipNet("169.254.0.0", 16, 32) + + // rfc3964Net specifies the IPv6 to IPv4 encapsulation address block as + // defined by RFC3964 (2002::/16). + rfc3964Net = ipNet("2002::", 16, 128) + + // rfc4193Net specifies the IPv6 unique local address block as defined + // by RFC4193 (FC00::/7). + rfc4193Net = ipNet("FC00::", 7, 128) + + // rfc4380Net specifies the IPv6 teredo tunneling over UDP address block + // as defined by RFC4380 (2001::/32). + rfc4380Net = ipNet("2001::", 32, 128) + + // rfc4843Net specifies the IPv6 ORCHID address block as defined by + // RFC4843 (2001:10::/28). + rfc4843Net = ipNet("2001:10::", 28, 128) + + // rfc4862Net specifies the IPv6 stateless address autoconfiguration + // address block as defined by RFC4862 (FE80::/64). + rfc4862Net = ipNet("FE80::", 64, 128) + + // rfc6052Net specifies the IPv6 well-known prefix address block as + // defined by RFC6052 (64:FF9B::/96). + rfc6052Net = ipNet("64:FF9B::", 96, 128) + + // rfc6145Net specifies the IPv6 to IPv4 translated address range as + // defined by RFC6145 (::FFFF:0:0:0/96). + rfc6145Net = ipNet("::FFFF:0:0:0", 96, 128) + + // onionCatNet defines the IPv6 address block used to support Tor. + // bitcoind encodes a .onion address as a 16 byte number by decoding the + // address prior to the .onion (i.e. the key hash) base32 into a ten + // byte number. It then stores the first 6 bytes of the address as + // 0xfd, 0x87, 0xd8, 0x7e, 0xeb, 0x43. + // + // This is the same range used by OnionCat, which is part part of the + // RFC4193 unique local IPv6 range. + // + // In summary the format is: + // { magic 6 bytes, 10 bytes base32 decode of key hash } + onionCatNet = ipNet("fd87:d87e:eb43::", 48, 128) + + // zero4Net defines the IPv4 address block for address staring with 0 + // (0.0.0.0/8). + zero4Net = ipNet("0.0.0.0", 8, 32) + + // heNet defines the Hurricane Electric IPv6 address block. + heNet = ipNet("2001:470::", 32, 128) +) + +// ipNet returns a net.IPNet struct given the passed IP address string, number +// of one bits to include at the start of the mask, and the total number of bits +// for the mask. +func ipNet(ip string, ones, bits int) net.IPNet { + return net.IPNet{IP: net.ParseIP(ip), Mask: net.CIDRMask(ones, bits)} +} + +// IsIPv4 returns whether or not the given address is an IPv4 address. +func IsIPv4(na *btcwire.NetAddress) bool { + return na.IP.To4() != nil +} + +// IsLocal returns whether or not the given address is a local address. +func IsLocal(na *btcwire.NetAddress) bool { + return na.IP.IsLoopback() || zero4Net.Contains(na.IP) +} + +// IsOnionCatTor returns whether or not the passed address is in the IPv6 range +// used by bitcoin to support Tor (fd87:d87e:eb43::/48). Note that this range +// is the same range used by OnionCat, which is part of the RFC4193 unique local +// IPv6 range. +func IsOnionCatTor(na *btcwire.NetAddress) bool { + return onionCatNet.Contains(na.IP) +} + +// 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). +func IsRFC1918(na *btcwire.NetAddress) bool { + for _, rfc := range rfc1918Nets { + if rfc.Contains(na.IP) { + return true + } + } + return false +} + +// IsRFC3849 returns whether or not the passed address is part of the IPv6 +// documentation range as defined by RFC3849 (2001:DB8::/32). +func IsRFC3849(na *btcwire.NetAddress) bool { + return rfc3849Net.Contains(na.IP) +} + +// IsRFC3927 returns whether or not the passed address is part of the IPv4 +// autoconfiguration range as defined by RFC3927 (169.254.0.0/16). +func IsRFC3927(na *btcwire.NetAddress) bool { + return rfc3927Net.Contains(na.IP) +} + +// IsRFC3964 returns whether or not the passed address is part of the IPv6 to +// IPv4 encapsulation range as defined by RFC3964 (2002::/16). +func IsRFC3964(na *btcwire.NetAddress) bool { + return rfc3964Net.Contains(na.IP) +} + +// IsRFC4193 returns whether or not the passed address is part of the IPv6 +// unique local range as defined by RFC4193 (FC00::/7). +func IsRFC4193(na *btcwire.NetAddress) bool { + return rfc4193Net.Contains(na.IP) +} + +// IsRFC4380 returns whether or not the passed address is part of the IPv6 +// teredo tunneling over UDP range as defined by RFC4380 (2001::/32). +func IsRFC4380(na *btcwire.NetAddress) bool { + return rfc4380Net.Contains(na.IP) +} + +// IsRFC4843 returns whether or not the passed address is part of the IPv6 +// ORCHID range as defined by RFC4843 (2001:10::/28). +func IsRFC4843(na *btcwire.NetAddress) bool { + return rfc4843Net.Contains(na.IP) +} + +// IsRFC4862 returns whether or not the passed address is part of the IPv6 +// stateless address autoconfiguration range as defined by RFC4862 (FE80::/64). +func IsRFC4862(na *btcwire.NetAddress) bool { + return rfc4862Net.Contains(na.IP) +} + +// IsRFC6052 returns whether or not the passed address is part of the IPv6 +// well-known prefix range as defined by RFC6052 (64:FF9B::/96). +func IsRFC6052(na *btcwire.NetAddress) bool { + return rfc6052Net.Contains(na.IP) +} + +// IsRFC6145 returns whether or not the passed address is part of the IPv6 to +// IPv4 translated address range as defined by RFC6145 (::FFFF:0:0:0/96). +func IsRFC6145(na *btcwire.NetAddress) bool { + return rfc6145Net.Contains(na.IP) +} + +// IsValid returns whether or not the passed address is valid. The address is +// considered invalid under the following circumstances: +// IPv4: It is either a zero or all bits set address. +// IPv6: It is either a zero or RFC3849 documentation address. +func IsValid(na *btcwire.NetAddress) bool { + // IsUnspecified returns if address is 0, so only all bits set, and + // RFC3849 need to be explicitly checked. + return na.IP != nil && !(na.IP.IsUnspecified() || + na.IP.Equal(net.IPv4bcast)) +} + +// IsRoutable returns whether or not the passed address is routable over +// the public internet. This is true as long as the address is valid and is not +// in any reserved ranges. +func IsRoutable(na *btcwire.NetAddress) bool { + return IsValid(na) && !(IsRFC1918(na) || IsRFC3927(na) || + IsRFC4862(na) || IsRFC3849(na) || IsRFC4843(na) || + IsLocal(na) || (IsRFC4193(na) && !IsOnionCatTor(na))) +} + +// GroupKey returns a string representing the network group an address is part +// of. This is the /16 for IPv4, the /32 (/36 for he.net) for IPv6, the string +// "local" for a local address, the string "tor:key" where key is the /4 of the +// onion address for tor address, and the string "unroutable" for an unroutable +// address. +func GroupKey(na *btcwire.NetAddress) string { + if IsLocal(na) { + return "local" + } + if !IsRoutable(na) { + return "unroutable" + } + if IsIPv4(na) { + return na.IP.Mask(net.CIDRMask(16, 32)).String() + } + if IsRFC6145(na) || IsRFC6052(na) { + // last four bytes are the ip address + ip := net.IP(na.IP[12:16]) + return ip.Mask(net.CIDRMask(16, 32)).String() + } + + if IsRFC3964(na) { + ip := net.IP(na.IP[2:6]) + return ip.Mask(net.CIDRMask(16, 32)).String() + + } + if IsRFC4380(na) { + // teredo tunnels have the last 4 bytes as the v4 address XOR + // 0xff. + ip := net.IP(make([]byte, 4)) + for i, byte := range na.IP[12:16] { + ip[i] = byte ^ 0xff + } + return ip.Mask(net.CIDRMask(16, 32)).String() + } + if IsOnionCatTor(na) { + // group is keyed off the first 4 bits of the actual onion key. + return fmt.Sprintf("tor:%d", na.IP[6]&((1<<4)-1)) + } + + // OK, so now we know ourselves to be a IPv6 address. + // bitcoind uses /32 for everything, except for Hurricane Electric's + // (he.net) IP range, which it uses /36 for. + bits := 32 + if heNet.Contains(na.IP) { + bits = 36 + } + + return na.IP.Mask(net.CIDRMask(bits, 128)).String() +} diff --git a/addrmgr/network_test.go b/addrmgr/network_test.go new file mode 100644 index 00000000..73308398 --- /dev/null +++ b/addrmgr/network_test.go @@ -0,0 +1,204 @@ +// Copyright (c) 2013-2014 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package addrmgr_test + +import ( + "net" + "testing" + "time" + + "github.com/conformal/btcd/addrmgr" + "github.com/conformal/btcwire" +) + +// TestIPTypes ensures the various functions which determine the type of an IP +// address based on RFCs work as intended. +func TestIPTypes(t *testing.T) { + type ipTest struct { + in btcwire.NetAddress + rfc1918 bool + rfc3849 bool + rfc3927 bool + rfc3964 bool + rfc4193 bool + rfc4380 bool + rfc4843 bool + rfc4862 bool + rfc6052 bool + rfc6145 bool + local bool + valid bool + routable bool + } + + newIPTest := func(ip string, rfc1918, rfc3849, rfc3927, rfc3964, + rfc4193, rfc4380, rfc4843, rfc4862, rfc6052, rfc6145, local, + valid, routable bool) ipTest { + nip := net.ParseIP(ip) + na := btcwire.NetAddress{ + Timestamp: time.Now(), + Services: btcwire.SFNodeNetwork, + IP: nip, + Port: 8333, + } + test := ipTest{na, rfc1918, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380, + rfc4843, rfc4862, rfc6052, rfc6145, local, valid, routable} + return test + } + + tests := []ipTest{ + newIPTest("10.255.255.255", true, false, false, false, false, + false, false, false, false, false, false, true, false), + newIPTest("192.168.0.1", true, false, false, false, false, + false, false, false, false, false, false, true, false), + newIPTest("172.31.255.1", true, false, false, false, false, + false, false, false, false, false, false, true, false), + newIPTest("172.32.1.1", false, false, false, false, false, + false, false, false, false, false, false, true, true), + newIPTest("169.254.250.120", false, false, true, false, false, + false, false, false, false, false, false, true, false), + newIPTest("0.0.0.0", false, false, false, false, false, false, + false, false, false, false, true, false, false), + newIPTest("255.255.255.255", false, false, false, false, false, + false, false, false, false, false, false, false, false), + newIPTest("127.0.0.1", false, false, false, false, false, + false, false, false, false, false, true, true, false), + newIPTest("fd00:dead::1", false, false, false, false, true, + false, false, false, false, false, false, true, false), + newIPTest("2001::1", false, false, false, false, false, + true, false, false, false, false, false, true, true), + newIPTest("2001:10:abcd::1:1", false, false, false, false, false, + false, true, false, false, false, false, true, false), + newIPTest("fe80::1", false, false, false, false, false, + false, false, true, false, false, false, true, false), + newIPTest("fe80:1::1", false, false, false, false, false, + false, false, false, false, false, false, true, true), + newIPTest("64:ff9b::1", false, false, false, false, false, + false, false, false, true, false, false, true, true), + newIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, + false, false, false, false, false, false, true, true), + newIPTest("::1", false, false, false, false, false, + false, false, false, false, false, true, true, false), + } + + t.Logf("Running %d tests", len(tests)) + for _, test := range tests { + if rv := addrmgr.IsRFC1918(&test.in); rv != test.rfc1918 { + t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918) + } + + if rv := addrmgr.IsRFC3849(&test.in); rv != test.rfc3849 { + t.Errorf("IsRFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849) + } + + if rv := addrmgr.IsRFC3927(&test.in); rv != test.rfc3927 { + t.Errorf("IsRFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927) + } + + if rv := addrmgr.IsRFC3964(&test.in); rv != test.rfc3964 { + t.Errorf("IsRFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964) + } + + if rv := addrmgr.IsRFC4193(&test.in); rv != test.rfc4193 { + t.Errorf("IsRFC4193 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4193) + } + + if rv := addrmgr.IsRFC4380(&test.in); rv != test.rfc4380 { + t.Errorf("IsRFC4380 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4380) + } + + if rv := addrmgr.IsRFC4843(&test.in); rv != test.rfc4843 { + t.Errorf("IsRFC4843 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4843) + } + + if rv := addrmgr.IsRFC4862(&test.in); rv != test.rfc4862 { + t.Errorf("IsRFC4862 %s\n got: %v want: %v", test.in.IP, rv, test.rfc4862) + } + + if rv := addrmgr.IsRFC6052(&test.in); rv != test.rfc6052 { + t.Errorf("isRFC6052 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6052) + } + + if rv := addrmgr.IsRFC6145(&test.in); rv != test.rfc6145 { + t.Errorf("IsRFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc6145) + } + + if rv := addrmgr.IsLocal(&test.in); rv != test.local { + t.Errorf("IsLocal %s\n got: %v want: %v", test.in.IP, rv, test.local) + } + + if rv := addrmgr.IsValid(&test.in); rv != test.valid { + t.Errorf("IsValid %s\n got: %v want: %v", test.in.IP, rv, test.valid) + } + + if rv := addrmgr.IsRoutable(&test.in); rv != test.routable { + t.Errorf("IsRoutable %s\n got: %v want: %v", test.in.IP, rv, test.routable) + } + } +} + +// TestGroupKey tests the GroupKey function to ensure it properly groups various +// IP addresses. +func TestGroupKey(t *testing.T) { + tests := []struct { + name string + ip string + expected string + }{ + // Local addresses. + {name: "ipv4 localhost", ip: "127.0.0.1", expected: "local"}, + {name: "ipv6 localhost", ip: "::1", expected: "local"}, + {name: "ipv4 zero", ip: "0.0.0.0", expected: "local"}, + {name: "ipv4 first octet zero", ip: "0.1.2.3", expected: "local"}, + + // Unroutable addresses. + {name: "ipv4 invalid bcast", ip: "255.255.255.255", expected: "unroutable"}, + {name: "ipv4 rfc1918 10/8", ip: "10.1.2.3", expected: "unroutable"}, + {name: "ipv4 rfc1918 172.16/12", ip: "172.16.1.2", expected: "unroutable"}, + {name: "ipv4 rfc1918 192.168/16", ip: "192.168.1.2", expected: "unroutable"}, + {name: "ipv6 rfc3849 2001:db8::/32", ip: "2001:db8::1234", expected: "unroutable"}, + {name: "ipv4 rfc3927 169.254/16", ip: "169.254.1.2", expected: "unroutable"}, + {name: "ipv6 rfc4193 fc00::/7", ip: "fc00::1234", expected: "unroutable"}, + {name: "ipv6 rfc4843 2001:10::/28", ip: "2001:10::1234", expected: "unroutable"}, + {name: "ipv6 rfc4862 fe80::/64", ip: "fe80::1234", expected: "unroutable"}, + + // IPv4 normal. + {name: "ipv4 normal class a", ip: "12.1.2.3", expected: "12.1.0.0"}, + {name: "ipv4 normal class b", ip: "173.1.2.3", expected: "173.1.0.0"}, + {name: "ipv4 normal class c", ip: "196.1.2.3", expected: "196.1.0.0"}, + + // IPv6/IPv4 translations. + {name: "ipv6 rfc3964 with ipv4 encap", ip: "2002:0c01:0203::", expected: "12.1.0.0"}, + {name: "ipv6 rfc4380 toredo ipv4", ip: "2001:0:1234::f3fe:fdfc", expected: "12.1.0.0"}, + {name: "ipv6 rfc6052 well-known prefix with ipv4", ip: "64:ff9b::0c01:0203", expected: "12.1.0.0"}, + {name: "ipv6 rfc6145 translated ipv4", ip: "::ffff:0:0c01:0203", expected: "12.1.0.0"}, + + // Tor. + {name: "ipv6 tor onioncat", ip: "fd87:d87e:eb43:1234::5678", expected: "tor:2"}, + {name: "ipv6 tor onioncat 2", ip: "fd87:d87e:eb43:1245::6789", expected: "tor:2"}, + {name: "ipv6 tor onioncat 3", ip: "fd87:d87e:eb43:1345::6789", expected: "tor:3"}, + + // IPv6 normal. + {name: "ipv6 normal", ip: "2602:100::1", expected: "2602:100::"}, + {name: "ipv6 normal 2", ip: "2602:0100::1234", expected: "2602:100::"}, + {name: "ipv6 hurricane electric", ip: "2001:470:1f10:a1::2", expected: "2001:470:1000::"}, + {name: "ipv6 hurricane electric 2", ip: "2001:0470:1f10:a1::2", expected: "2001:470:1000::"}, + } + + for i, test := range tests { + nip := net.ParseIP(test.ip) + na := btcwire.NetAddress{ + Timestamp: time.Now(), + Services: btcwire.SFNodeNetwork, + IP: nip, + Port: 8333, + } + if key := addrmgr.GroupKey(&na); key != test.expected { + t.Errorf("TestGroupKey #%d (%s): unexpected group key "+ + "- got '%s', want '%s'", i, test.name, + key, test.expected) + } + } +} diff --git a/addrmgr/test_coverage.txt b/addrmgr/test_coverage.txt new file mode 100644 index 00000000..93057019 --- /dev/null +++ b/addrmgr/test_coverage.txt @@ -0,0 +1,58 @@ + +github.com/conformal/btcd/addrmgr/network.go GroupKey 100.00% (23/23) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.reset 100.00% (6/6) +github.com/conformal/btcd/addrmgr/network.go IsRFC1918 100.00% (4/4) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.NumAddresses 100.00% (3/3) +github.com/conformal/btcd/addrmgr/addrmanager.go NetAddressKey 100.00% (3/3) +github.com/conformal/btcd/addrmgr/addrmanager.go New 100.00% (3/3) +github.com/conformal/btcd/addrmgr/network.go IsRFC4193 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC3964 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC3927 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC3849 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC4843 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsOnionCatTor 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRoutable 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsLocal 100.00% (1/1) +github.com/conformal/btcd/addrmgr/log.go init 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsValid 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC6145 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC6052 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC4862 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsRFC4380 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go IsIPv4 100.00% (1/1) +github.com/conformal/btcd/addrmgr/network.go ipNet 100.00% (1/1) +github.com/conformal/btcd/addrmgr/log.go DisableLog 100.00% (1/1) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.GetBestLocalAddress 94.74% (18/19) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddLocalAddress 90.91% (10/11) +github.com/conformal/btcd/addrmgr/addrmanager.go getReachabilityFrom 51.52% (17/33) +github.com/conformal/btcd/addrmgr/addrmanager.go ipString 50.00% (2/4) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.GetAddress 4.65% (2/43) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.deserializePeers 0.00% (0/50) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Good 0.00% (0/44) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.savePeers 0.00% (0/41) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.updateAddress 0.00% (0/32) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.expireNew 0.00% (0/21) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddressCache 0.00% (0/16) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddressByIP 0.00% (0/16) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.getNewBucket 0.00% (0/15) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.getTriedBucket 0.00% (0/14) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.HostToNetAddress 0.00% (0/14) +github.com/conformal/btcd/addrmgr/addrmanager.go chance 0.00% (0/13) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.loadPeers 0.00% (0/13) +github.com/conformal/btcd/addrmgr/addrmanager.go bad 0.00% (0/11) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Connected 0.00% (0/10) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.addressHandler 0.00% (0/9) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.pickTried 0.00% (0/8) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.DeserializeNetAddress 0.00% (0/7) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Attempt 0.00% (0/7) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Stop 0.00% (0/7) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.Start 0.00% (0/6) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddresses 0.00% (0/2) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.NeedMoreAddresses 0.00% (0/1) +github.com/conformal/btcd/addrmgr/log.go UseLogger 0.00% (0/1) +github.com/conformal/btcd/addrmgr/addrmanager.go knownAddress.LastAttempt 0.00% (0/1) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.find 0.00% (0/1) +github.com/conformal/btcd/addrmgr/addrmanager.go knownAddress.NetAddress 0.00% (0/1) +github.com/conformal/btcd/addrmgr/addrmanager.go AddrManager.AddAddress 0.00% (0/1) +github.com/conformal/btcd/addrmgr --------------------------------- 20.34% (108/531) + diff --git a/peer.go b/peer.go index ee782123..26e15e02 100644 --- a/peer.go +++ b/peer.go @@ -298,7 +298,7 @@ func (p *peer) updateAddresses(msg *btcwire.MsgVersion) { if !cfg.DisableListen /* && isCurrent? */ { // Get address that best matches. lna := p.server.addrManager.GetBestLocalAddress(p.na) - if addrmgr.Routable(lna) { + if addrmgr.IsRoutable(lna) { addresses := []*btcwire.NetAddress{lna} p.pushAddrMsg(addresses) } diff --git a/server.go b/server.go index c6e51d87..d638cee0 100644 --- a/server.go +++ b/server.go @@ -1030,7 +1030,10 @@ out: } na := btcwire.NewNetAddressIPPort(externalip, uint16(listenPort), btcwire.SFNodeNetwork) - s.addrManager.AddLocalAddress(na, addrmgr.UpnpPrio) + err = s.addrManager.AddLocalAddress(na, addrmgr.UpnpPrio) + if err != nil { + // XXX DeletePortMapping? + } srvrLog.Warnf("Successfully bound via UPnP to %s", addrmgr.NetAddressKey(na)) first = false } @@ -1104,7 +1107,10 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se continue } - amgr.AddLocalAddress(na, addrmgr.ManualPrio) + err = amgr.AddLocalAddress(na, addrmgr.ManualPrio) + if err != nil { + amgrLog.Warnf("Skipping specified external IP: %v", err) + } } } else if discover && cfg.Upnp { nat, err = Discover() @@ -1132,7 +1138,10 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se na := btcwire.NewNetAddressIPPort(ip, uint16(port), btcwire.SFNodeNetwork) if discover { - amgr.AddLocalAddress(na, addrmgr.InterfacePrio) + err = amgr.AddLocalAddress(na, addrmgr.InterfacePrio) + if err != nil { + amgrLog.Debugf("Skipping local address: %v", err) + } } } } @@ -1148,8 +1157,11 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se listeners = append(listeners, listener) if discover { - if na, err := amgr.DeserialiseNetAddress(addr); err == nil { - amgr.AddLocalAddress(na, addrmgr.BoundPrio) + if na, err := amgr.DeserializeNetAddress(addr); err == nil { + err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) + if err != nil { + amgrLog.Warnf("Skipping bound address: %v", err) + } } } } @@ -1163,8 +1175,11 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se } listeners = append(listeners, listener) if discover { - if na, err := amgr.DeserialiseNetAddress(addr); err == nil { - amgr.AddLocalAddress(na, addrmgr.BoundPrio) + if na, err := amgr.DeserializeNetAddress(addr); err == nil { + err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) + if err != nil { + amgrLog.Debugf("Skipping bound address: %v", err) + } } } }