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:
parent
62f21d3600
commit
6f5a43d6c8
10 changed files with 840 additions and 457 deletions
|
@ -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.
|
||||||
|
|
|
@ -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
17
addrmgr/cov_report.sh
Normal 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
38
addrmgr/doc.go
Normal 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
32
addrmgr/log.go
Normal 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
240
addrmgr/network.go
Normal 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
204
addrmgr/network_test.go
Normal 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
58
addrmgr/test_coverage.txt
Normal 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)
|
||||||
|
|
2
peer.go
2
peer.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
29
server.go
29
server.go
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue