First round of address manager package refactor

These changes are a joint effort between myself and @dajohi.

- Separate IP address range/network code into its own file
- Group all of the RFC range declarations together
- Introduces a new unexported function to simplify the range declarations
- Add comments for all exported functions
- Use consistent variable casing in refactored code
- Add initial doc.go package overview
- Bump serialize interval to 10 minutes
- Correct GroupKey to perform as intended
- Make AddLocalAddress return error instead of just a debug message
- Add tests for AddLocalAddress
- Add tests for GroupKey
- Add tests for GetBestLocalAddress
- Use time.Time to improve readability
- Make address manager code golint clean
- Misc cleanup
- Add test coverage reporting
This commit is contained in:
David Hill 2014-07-06 09:47:42 -04:00 committed by Dave Collins
parent 62f21d3600
commit 6f5a43d6c8
10 changed files with 840 additions and 457 deletions

View file

@ -35,7 +35,7 @@ const (
// dumpAddressInterval is the interval used to dump the address // dumpAddressInterval is the interval used to dump the address
// cache to disk for future use. // cache to disk for future use.
dumpAddressInterval = time.Minute * 2 dumpAddressInterval = time.Minute * 10
// triedBucketSize is the maximum number of addresses in each // triedBucketSize is the maximum number of addresses in each
// tried address bucket. // tried address bucket.
@ -155,46 +155,30 @@ func bad(ka *knownAddress) bool {
} }
// chance returns the selection probability for a known address. The priority // 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. // attempted and how often attempts to connect to it have failed.
func chance(ka *knownAddress) float64 { func chance(ka *knownAddress) float64 {
c := 1.0
now := time.Now() now := time.Now()
var lastSeen float64 lastSeen := now.Sub(ka.na.Timestamp)
var lastTry float64 lastAttempt := now.Sub(ka.lastattempt)
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))
} else { if lastSeen < 0 {
dur = now.Sub(ka.na.Timestamp) lastSeen = 0
} }
lastSeen = dur.Seconds() if lastAttempt < 0 {
} lastAttempt = 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()
} }
c = 600.0 / (600.0 + lastSeen) c := 600.0 / (600.0 + lastSeen.Seconds())
// Very recent attempts are less likely to be retried. // Very recent attempts are less likely to be retried.
if lastTry > 60.0*10.0 { if lastAttempt > 10*time.Minute {
c *= 0.01 c *= 0.01
} }
// Failed attempts deprioritise. // Failed attempts deprioritise.
if ka.attempts > 0 { for i := ka.attempts; i < 0; i++ {
c /= float64(ka.attempts) * 1.5 c /= 1.5
} }
return c return c
@ -226,7 +210,7 @@ type AddrManager struct {
func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) { func (a *AddrManager) updateAddress(netAddr, srcAddr *btcwire.NetAddress) {
// Filter out non-routable addresses. Note that non-routable // Filter out non-routable addresses. Note that non-routable
// also includes invalid and local addresses. // also includes invalid and local addresses.
if !Routable(netAddr) { if !IsRoutable(netAddr) {
return return
} }
@ -420,7 +404,7 @@ out:
log.Trace("Address handler done") log.Trace("Address handler done")
} }
type serialisedKnownAddress struct { type serializedKnownAddress struct {
Addr string Addr string
Src string Src string
Attempts int Attempts int
@ -430,10 +414,10 @@ type serialisedKnownAddress struct {
// no refcount or tried, that is available from context. // no refcount or tried, that is available from context.
} }
type serialisedAddrManager struct { type serializedAddrManager struct {
Version int Version int
Key [32]byte Key [32]byte
Addresses []*serialisedKnownAddress Addresses []*serializedKnownAddress
NewBuckets [newBucketCount][]string // string is NetAddressKey NewBuckets [newBucketCount][]string // string is NetAddressKey
TriedBuckets [triedBucketCount][]string TriedBuckets [triedBucketCount][]string
} }
@ -446,14 +430,14 @@ func (a *AddrManager) savePeers() {
// First we make a serialisable datastructure so we can encode it to // First we make a serialisable datastructure so we can encode it to
// json. // json.
sam := new(serialisedAddrManager) sam := new(serializedAddrManager)
sam.Version = serialisationVersion sam.Version = serialisationVersion
copy(sam.Key[:], a.key[:]) copy(sam.Key[:], a.key[:])
sam.Addresses = make([]*serialisedKnownAddress, len(a.addrIndex)) sam.Addresses = make([]*serializedKnownAddress, len(a.addrIndex))
i := 0 i := 0
for k, v := range a.addrIndex { for k, v := range a.addrIndex {
ska := new(serialisedKnownAddress) ska := new(serializedKnownAddress)
ska.Addr = k ska.Addr = k
ska.TimeStamp = v.na.Timestamp.Unix() ska.TimeStamp = v.na.Timestamp.Unix()
ska.Src = NetAddressKey(v.srcAddr) ska.Src = NetAddressKey(v.srcAddr)
@ -510,7 +494,7 @@ func (a *AddrManager) loadPeers() {
filename := "peers.json" filename := "peers.json"
filePath := filepath.Join(a.dataDir, filename) filePath := filepath.Join(a.dataDir, filename)
err := a.deserialisePeers(filePath) err := a.deserializePeers(filePath)
if err != nil { if err != nil {
log.Errorf("Failed to parse %s: %v", filePath, err) log.Errorf("Failed to parse %s: %v", filePath, err)
// if it is invalid we nuke the old one unconditionally. // 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) 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) _, err := os.Stat(filePath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -536,7 +520,7 @@ func (a *AddrManager) deserialisePeers(filePath string) error {
} }
defer r.Close() defer r.Close()
var sam serialisedAddrManager var sam serializedAddrManager
dec := json.NewDecoder(r) dec := json.NewDecoder(r)
err = dec.Decode(&sam) err = dec.Decode(&sam)
if err != nil { if err != nil {
@ -544,21 +528,21 @@ func (a *AddrManager) deserialisePeers(filePath string) error {
} }
if sam.Version != serialisationVersion { if sam.Version != serialisationVersion {
return fmt.Errorf("unknown version %v in serialised "+ return fmt.Errorf("unknown version %v in serialized "+
"addrmanager", sam.Version) "addrmanager", sam.Version)
} }
copy(a.key[:], sam.Key[:]) copy(a.key[:], sam.Key[:])
for _, v := range sam.Addresses { for _, v := range sam.Addresses {
ka := new(knownAddress) ka := new(knownAddress)
ka.na, err = a.DeserialiseNetAddress(v.Addr) ka.na, err = a.DeserializeNetAddress(v.Addr)
if err != nil { if err != nil {
return fmt.Errorf("failed to deserialise netaddress "+ return fmt.Errorf("failed to deserialize netaddress "+
"%s: %v", v.Addr, err) "%s: %v", v.Addr, err)
} }
ka.srcAddr, err = a.DeserialiseNetAddress(v.Src) ka.srcAddr, err = a.DeserializeNetAddress(v.Src)
if err != nil { if err != nil {
return fmt.Errorf("failed to deserialise netaddress "+ return fmt.Errorf("failed to deserialize netaddress "+
"%s: %v", v.Src, err) "%s: %v", v.Src, err)
} }
ka.attempts = v.Attempts ka.attempts = v.Attempts
@ -612,7 +596,8 @@ func (a *AddrManager) deserialisePeers(filePath string) error {
return nil 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) host, portStr, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
return nil, err 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 // ip is in the range used for tor addresses then it will be transformed into
// the relavent .onion address. // the relavent .onion address.
func ipString(na *btcwire.NetAddress) string { func ipString(na *btcwire.NetAddress) string {
if Tor(na) { if IsOnionCatTor(na) {
// We know now that na.IP is long enogh. // We know now that na.IP is long enogh.
base32 := base32.StdEncoding.EncodeToString(na.IP[6:]) base32 := base32.StdEncoding.EncodeToString(na.IP[6:])
return strings.ToLower(base32) + ".onion" return strings.ToLower(base32) + ".onion"
@ -844,7 +829,7 @@ func (a *AddrManager) GetAddress(class string, newBias int) *knownAddress {
(100.0 - float64(newBias)) (100.0 - float64(newBias))
newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias) newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias)
if (newCorrelation+triedCorrelation)*a.rand.Float64() < if ((newCorrelation + triedCorrelation) * a.rand.Float64()) <
triedCorrelation { triedCorrelation {
// Tried entry. // Tried entry.
large := 1 << 30 large := 1 << 30
@ -906,12 +891,8 @@ func (a *AddrManager) find(addr *btcwire.NetAddress) *knownAddress {
return a.addrIndex[NetAddressKey(addr)] return a.addrIndex[NetAddressKey(addr)]
} }
/* // Attempt increases the given address' attempt counter and updates
* Connected - updates the last seen time but only every 20 minutes. // the last attempt time.
* Good - last tried = last success = last seen = now. attmempts = 0.
* - move address to tried.
* Attempted - set last tried to time. nattempts++
*/
func (a *AddrManager) Attempt(addr *btcwire.NetAddress) { func (a *AddrManager) Attempt(addr *btcwire.NetAddress) {
a.mtx.Lock() a.mtx.Lock()
defer a.mtx.Unlock() defer a.mtx.Unlock()
@ -1039,217 +1020,38 @@ func (a *AddrManager) Good(addr *btcwire.NetAddress) {
a.addrNew[newBucket][rmkey] = rmka a.addrNew[newBucket][rmkey] = rmka
} }
// RFC1918: IPv4 Private networks (10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12) // AddressPriority type is used to describe the heirarchy of local address
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
// discovery methods. // discovery methods.
type addressPrio int type AddressPriority int
const ( const (
InterfacePrio addressPrio = iota // address of local interface. // InterfacePrio signifies the address is on a local interface
BoundPrio // Address explicitly bound to. InterfacePrio AddressPriority = iota
UpnpPrio // External IP discovered from UPnP
HTTPPrio // Obtained from internet service. // BoundPrio signifies the address has been explicity bounded to.
ManualPrio // provided by --externalip. 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 { type localAddress struct {
na *btcwire.NetAddress na *btcwire.NetAddress
score addressPrio score AddressPriority
} }
// AddLocalAddress adds na to the list of known local addresses to advertise // AddLocalAddress adds na to the list of known local addresses to advertise
// with the given priority. // with the given priority.
func (a *AddrManager) AddLocalAddress(na *btcwire.NetAddress, func (a *AddrManager) AddLocalAddress(na *btcwire.NetAddress, priority AddressPriority) error {
priority addressPrio) { if !IsRoutable(na) {
// sanity check. return fmt.Errorf("address %s is not routable", na.IP)
if !Routable(na) {
log.Debugf("rejecting address %s:%d due to routability",
na.IP, na.Port)
return
} }
log.Debugf("adding address %s:%d", na.IP, na.Port)
a.lamtx.Lock() a.lamtx.Lock()
defer a.lamtx.Unlock() 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. // getReachabilityFrom returns the relative reachability of the provided local
func getReachabilityFrom(na, fromna *btcwire.NetAddress) int { // address to the provided remote address.
func getReachabilityFrom(localAddr, remoteAddr *btcwire.NetAddress) int {
const ( const (
Unreachable = 0 Unreachable = 0
Default = iota Default = iota
@ -1280,61 +1084,61 @@ func getReachabilityFrom(na, fromna *btcwire.NetAddress) int {
Private Private
) )
if !Routable(fromna) { if !IsRoutable(remoteAddr) {
return Unreachable return Unreachable
} }
if Tor(fromna) { if IsOnionCatTor(remoteAddr) {
if Tor(na) { if IsOnionCatTor(localAddr) {
return Private return Private
} }
if Routable(na) && na.IP.To4() != nil { if IsRoutable(localAddr) && IsIPv4(localAddr) {
return Ipv4 return Ipv4
} }
return Default return Default
} }
if RFC4380(fromna) { if IsRFC4380(remoteAddr) {
if !Routable(na) { if !IsRoutable(localAddr) {
return Default return Default
} }
if RFC4380(na) { if IsRFC4380(localAddr) {
return Teredo return Teredo
} }
if na.IP.To4() != nil { if IsIPv4(localAddr) {
return Ipv4 return Ipv4
} }
return Ipv6Weak return Ipv6Weak
} }
if fromna.IP.To4() != nil { if IsIPv4(remoteAddr) {
if Routable(na) && na.IP.To4() != nil { if IsRoutable(localAddr) && IsIPv4(localAddr) {
return Ipv4 return Ipv4
} }
return Default return Unreachable
} }
/* ipv6 */ /* ipv6 */
var tunnelled bool var tunnelled bool
// Is our v6 is tunnelled? // Is our v6 is tunnelled?
if RFC3964(na) || RFC6052(na) || RFC6145(na) { if IsRFC3964(localAddr) || IsRFC6052(localAddr) || IsRFC6145(localAddr) {
tunnelled = true tunnelled = true
} }
if !Routable(na) { if !IsRoutable(localAddr) {
return Default return Default
} }
if RFC4380(na) { if IsRFC4380(localAddr) {
return Teredo return Teredo
} }
if na.IP.To4() != nil { if IsIPv4(localAddr) {
return Ipv4 return Ipv4
} }
@ -1346,39 +1150,45 @@ func getReachabilityFrom(na, fromna *btcwire.NetAddress) int {
return Ipv6Strong return Ipv6Strong
} }
// getBestLocalAddress returns the most appropriate local address that we know // GetBestLocalAddress returns the most appropriate local address to use
// of to be contacted by rna. // for the given remote address.
func (a *AddrManager) GetBestLocalAddress(rna *btcwire.NetAddress) *btcwire.NetAddress { func (a *AddrManager) GetBestLocalAddress(remoteAddr *btcwire.NetAddress) *btcwire.NetAddress {
a.lamtx.Lock() a.lamtx.Lock()
defer a.lamtx.Unlock() defer a.lamtx.Unlock()
bestreach := 0 bestreach := 0
var bestscore addressPrio var bestscore AddressPriority
var bestna *btcwire.NetAddress var bestAddress *btcwire.NetAddress
for _, la := range a.localAddresses { for _, la := range a.localAddresses {
reach := getReachabilityFrom(la.na, rna) reach := getReachabilityFrom(la.na, remoteAddr)
if reach > bestreach || if reach > bestreach ||
(reach == bestreach && la.score > bestscore) { (reach == bestreach && la.score > bestscore) {
bestreach = reach bestreach = reach
bestscore = la.score bestscore = la.score
bestna = la.na bestAddress = la.na
} }
} }
if bestna != nil { if bestAddress != nil {
log.Debugf("Suggesting address %s:%d for %s:%d", bestna.IP, log.Debugf("Suggesting address %s:%d for %s:%d", bestAddress.IP,
bestna.Port, rna.IP, rna.Port) bestAddress.Port, remoteAddr.IP, remoteAddr.Port)
} else { } 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. // Send something unroutable if nothing suitable.
bestna = &btcwire.NetAddress{ bestAddress = &btcwire.NetAddress{
Timestamp: time.Now(), Timestamp: time.Now(),
Services: 0, Services: btcwire.SFNodeNetwork,
IP: net.IP([]byte{0, 0, 0, 0}),
Port: 0, 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. // New returns a new bitcoin address manager.

View file

@ -21,88 +21,9 @@ type naTest struct {
want string 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 // naTests houses all of the tests to be performed against the NetAddressKey
// method. // method.
var naTests = make([]naTest, 0) 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 // addNaTests
func addNaTests() { func addNaTests() {
@ -165,10 +86,64 @@ func addNaTests() {
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336") 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) { func lookupFunc(host string) ([]net.IP, error) {
return nil, errors.New("not implemented") 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) { func TestGetAddress(t *testing.T) {
n := addrmgr.New("testdir", lookupFunc) n := addrmgr.New("testdir", lookupFunc)
if rv := n.GetAddress("any", 10); rv != nil { if rv := n.GetAddress("any", 10); rv != nil {
@ -176,101 +151,95 @@ func TestGetAddress(t *testing.T) {
} }
} }
func TestIPTypes(t *testing.T) { func TestGetBestLocalAddress(t *testing.T) {
addIPTests() 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)) var tests = []struct {
for _, test := range ipTests { remoteAddr btcwire.NetAddress
rv := addrmgr.RFC1918(&test.in) want1 btcwire.NetAddress
if rv != test.rfc1918 { want2 btcwire.NetAddress
t.Errorf("RFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918) 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 continue
} }
} }
for _, test := range ipTests {
rv := addrmgr.RFC3849(&test.in) // Add a public IP to the list of local addresses.
if rv != test.rfc3849 { localAddr := btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")}
t.Errorf("RFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849) 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 continue
} }
} }
for _, test := range ipTests { /*
rv := addrmgr.RFC3927(&test.in) // Add a tor generated IP address
if rv != test.rfc3927 { localAddr = btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
t.Errorf("RFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927) amgr.AddLocalAddress(&localAddr, addrmgr.ManualPrio)
continue
} // Test against want3
} for x, test := range tests {
for _, test := range ipTests { got := amgr.GetBestLocalAddress(&test.remoteAddr)
rv := addrmgr.RFC3964(&test.in) if !test.want3.IP.Equal(got.IP) {
if rv != test.rfc3964 { t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s",
t.Errorf("RFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964) x, test.remoteAddr.IP, test.want3.IP, got.IP)
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 continue
} }
} }
*/
} }
func TestNetAddressKey(t *testing.T) { func TestNetAddressKey(t *testing.T) {

17
addrmgr/cov_report.sh Normal file
View file

@ -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

38
addrmgr/doc.go Normal file
View file

@ -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

32
addrmgr/log.go Normal file
View file

@ -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
}

240
addrmgr/network.go Normal file
View file

@ -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()
}

204
addrmgr/network_test.go Normal file
View file

@ -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)
}
}
}

58
addrmgr/test_coverage.txt Normal file
View file

@ -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)

View file

@ -298,7 +298,7 @@ func (p *peer) updateAddresses(msg *btcwire.MsgVersion) {
if !cfg.DisableListen /* && isCurrent? */ { if !cfg.DisableListen /* && isCurrent? */ {
// Get address that best matches. // Get address that best matches.
lna := p.server.addrManager.GetBestLocalAddress(p.na) lna := p.server.addrManager.GetBestLocalAddress(p.na)
if addrmgr.Routable(lna) { if addrmgr.IsRoutable(lna) {
addresses := []*btcwire.NetAddress{lna} addresses := []*btcwire.NetAddress{lna}
p.pushAddrMsg(addresses) p.pushAddrMsg(addresses)
} }

View file

@ -1030,7 +1030,10 @@ out:
} }
na := btcwire.NewNetAddressIPPort(externalip, uint16(listenPort), na := btcwire.NewNetAddressIPPort(externalip, uint16(listenPort),
btcwire.SFNodeNetwork) 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)) srvrLog.Warnf("Successfully bound via UPnP to %s", addrmgr.NetAddressKey(na))
first = false first = false
} }
@ -1104,7 +1107,10 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se
continue 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 { } else if discover && cfg.Upnp {
nat, err = Discover() nat, err = Discover()
@ -1132,7 +1138,10 @@ func newServer(listenAddrs []string, db btcdb.Db, netParams *btcnet.Params) (*se
na := btcwire.NewNetAddressIPPort(ip, na := btcwire.NewNetAddressIPPort(ip,
uint16(port), btcwire.SFNodeNetwork) uint16(port), btcwire.SFNodeNetwork)
if discover { 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) listeners = append(listeners, listener)
if discover { if discover {
if na, err := amgr.DeserialiseNetAddress(addr); err == nil { if na, err := amgr.DeserializeNetAddress(addr); err == nil {
amgr.AddLocalAddress(na, addrmgr.BoundPrio) 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) listeners = append(listeners, listener)
if discover { if discover {
if na, err := amgr.DeserialiseNetAddress(addr); err == nil { if na, err := amgr.DeserializeNetAddress(addr); err == nil {
amgr.AddLocalAddress(na, addrmgr.BoundPrio) err = amgr.AddLocalAddress(na, addrmgr.BoundPrio)
if err != nil {
amgrLog.Debugf("Skipping bound address: %v", err)
}
} }
} }
} }