Skip to content

Commit

Permalink
multi: Introduce addrv2 and getaddrv2 messages.
Browse files Browse the repository at this point in the history
This commit modifies the wire protocol by adding two new message
types addrv2 and getaddrv2 and bumps the wire protocol version to 10.
It does not introduce any new network address types and is intended
to implement the minimum framework necessary to support easily adding
new address types in future commits.

These two new message types are intended to eventually replace their
older counterparts, which are getaddr and addr respectively.  A peer
sending a getaddrv2 or addrv2 message with a protocol version less than
10 is in violation of the wire protocol and the peer is disconnected.
Similarly, peers advertising a protocol version greater than or equal
to 10 that send an addr or gettaddr message are in violation of the wire
protocol and are disconnected.

A getaddrv2 message is similar in structure and purpose to a getaddr
message in that it has no payload and functions as a request for a peer
to reply with an addrv2 message if it has addresses to share. An addrv2
message is similar in structure and function to an addr message
with a few key differences:

- Port is now encoded as a little endian value rather than big endian.
- Timestamp is encoded as a 64 bit value rather than a 32 bit value.
- A network address type field is now serialized with each address to
  indicate the length and type of the address it precedes.
- A message with zero addresses is no longer serializable.
- Addresses are serialized their most compact form, rather than
  always being encoded into a 16 byte structure.

The address manager's public interface has also been modified so that
addresses returned may be filtered by the maximum network address type
supported by the protocol version of a peer.  This allows the caller to
easily indicate what types of addresses should be relayed to a specific
peer while also keeping protocol versioning logic decoupled from the
address manager's internal implementation.
  • Loading branch information
sefbkn committed Mar 17, 2021
1 parent a7eb512 commit 7557e51
Show file tree
Hide file tree
Showing 25 changed files with 1,232 additions and 110 deletions.
15 changes: 12 additions & 3 deletions addrmgr/addrmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,10 @@ func (a *AddrManager) NeedMoreAddresses() bool {
}

// AddressCache returns a randomized subset of all addresses known to the
// address manager.
// address manager with a network address type not newer than the provided type.
//
// This function is safe for concurrent access.
func (a *AddrManager) AddressCache() []*NetAddress {
func (a *AddrManager) AddressCache(maxAddressType NetAddressType) []*NetAddress {
a.mtx.Lock()
defer a.mtx.Unlock()

Expand All @@ -691,6 +691,10 @@ func (a *AddrManager) AddressCache() []*NetAddress {
allAddr := make([]*NetAddress, 0, addrLen)
// Iteration order is undefined here, but we randomize it anyway.
for _, v := range a.addrIndex {
// Skip address types newer than what is requested by the caller.
if v.na.Type > maxAddressType {
continue
}
// Skip low quality addresses.
if v.isBad() {
continue
Expand Down Expand Up @@ -1153,14 +1157,19 @@ func getReachabilityFrom(localAddr, remoteAddr *NetAddress) NetAddressReach {
// for the given remote address.
//
// This function is safe for concurrent access.
func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress) *NetAddress {
func (a *AddrManager) GetBestLocalAddress(remoteAddr *NetAddress, maxAddressType NetAddressType) *NetAddress {
a.lamtx.Lock()
defer a.lamtx.Unlock()

bestreach := Unreachable
var bestscore AddressPriority
var bestAddress *NetAddress
for _, la := range a.localAddresses {
// Do not return address types newer than what was requested by the
// caller.
if la.na.Type > maxAddressType {
continue
}
reach := getReachabilityFrom(la.na, remoteAddr)
if reach > bestreach ||
(reach == bestreach && la.score > bestscore) {
Expand Down
8 changes: 4 additions & 4 deletions addrmgr/addrmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ func TestGood(t *testing.T) {
t.Errorf("Number of addresses is too many: %d vs %d", numAddrs, addrsToAdd)
}

numCache := len(n.AddressCache())
numCache := len(n.AddressCache(TORv2Address))
if numCache >= numAddrs/4 {
t.Errorf("Number of addresses in cache: got %d, want %d", numCache, numAddrs/4)
}
Expand Down Expand Up @@ -522,7 +522,7 @@ func TestGetBestLocalAddress(t *testing.T) {

// Test against default when there's no address
for x, test := range tests {
got := amgr.GetBestLocalAddress(test.remoteAddr)
got := amgr.GetBestLocalAddress(test.remoteAddr, IPv6Address)
if !reflect.DeepEqual(test.want0.IP, 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)
Expand All @@ -536,7 +536,7 @@ func TestGetBestLocalAddress(t *testing.T) {

// Test against want1
for x, test := range tests {
got := amgr.GetBestLocalAddress(test.remoteAddr)
got := amgr.GetBestLocalAddress(test.remoteAddr, IPv6Address)
if !reflect.DeepEqual(test.want1.IP, 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)
Expand All @@ -550,7 +550,7 @@ func TestGetBestLocalAddress(t *testing.T) {

// Test against want2
for x, test := range tests {
got := amgr.GetBestLocalAddress(test.remoteAddr)
got := amgr.GetBestLocalAddress(test.remoteAddr, IPv6Address)
if !reflect.DeepEqual(test.want2.IP, 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)
Expand Down
2 changes: 1 addition & 1 deletion addrmgr/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func isOnionCatTor(netIP net.IP) bool {

// NetAddressType is used to indicate which network a network address belongs
// to.
type NetAddressType int
type NetAddressType uint8

const (
UnknownAddressType NetAddressType = iota
Expand Down
20 changes: 10 additions & 10 deletions peer/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,16 @@ of a most-recently used algorithm.
Message Sending Helper Functions
In addition to the bare QueueMessage function previously described, the
PushAddrMsg, PushGetBlocksMsg, and PushGetHeadersMsg functions are provided as a
convenience. While it is of course possible to create and send these messages
manually via QueueMessage, these helper functions provided additional useful
functionality that is typically desired.
For example, the PushAddrMsg function automatically limits the addresses to the
maximum number allowed by the message and randomizes the chosen addresses when
there are too many. This allows the caller to simply provide a slice of known
addresses, such as that returned by the addrmgr package, without having to worry
about the details.
PushAddrMsg, PushAddrMsgV2, PushGetBlocksMsg, and PushGetHeadersMsg functions
are provided as a convenience. While it is of course possible to create and
send these messages manually via QueueMessage, these helper functions provide
additional useful functionality that is typically desired.
For example, the PushAddrMsg and PushAddrMsgV2 functions automatically limit the
addresses to the maximum number allowed by the message and randomizes the chosen
addresses when there are too many. This allows the caller to simply provide a
slice of known addresses, such as that returned by the addrmgr package, without
having to worry about the details.
Finally, the PushGetBlocksMsg and PushGetHeadersMsg functions will construct
proper messages using a block locator and ignore back to back duplicate
Expand Down
3 changes: 3 additions & 0 deletions peer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.11

require (
github.com/davecgh/go-spew v1.1.1
github.com/decred/dcrd/addrmgr/v2 v2.0.0
github.com/decred/dcrd/chaincfg/chainhash v1.0.2
github.com/decred/dcrd/lru v1.1.0
github.com/decred/dcrd/txscript/v4 v4.0.0-20210129190127-4ebd135a82f1
Expand All @@ -13,7 +14,9 @@ require (
)

replace (
github.com/decred/dcrd/addrmgr/v2 => ../addrmgr
github.com/decred/dcrd/dcrec/secp256k1/v4 => ../dcrec/secp256k1
github.com/decred/dcrd/dcrutil/v4 => ../dcrutil
github.com/decred/dcrd/txscript/v4 => ../txscript
github.com/decred/dcrd/wire => ../wire
)
2 changes: 0 additions & 2 deletions peer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1 h1:V6eqU1crZzuoFT4KG2LhaU5xDSdkHu
github.com/decred/dcrd/dcrec/edwards/v2 v2.0.1/go.mod h1:d0H8xGMWbiIQP7gN3v2rByWUcuZPm9YsgmnfoxgbINc=
github.com/decred/dcrd/lru v1.1.0 h1:QwT6v8LFKOL3xQ3qtucgRk4pdiawrxIfCbUXWpm+JL4=
github.com/decred/dcrd/lru v1.1.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
github.com/decred/dcrd/wire v1.4.0 h1:KmSo6eTQIvhXS0fLBQ/l7hG7QLcSJQKSwSyzSqJYDk0=
github.com/decred/dcrd/wire v1.4.0/go.mod h1:WxC/0K+cCAnBh+SKsRjIX9YPgvrjhmE+6pZlel1G7Ro=
github.com/decred/go-socks v1.1.0 h1:dnENcc0KIqQo3HSXdgboXAHgqsCIutkqq6ntQjYtm2U=
github.com/decred/go-socks v1.1.0/go.mod h1:sDhHqkZH0X4JjSa02oYOGhcGHYp12FsY1jQ/meV8md0=
github.com/decred/slog v1.1.0 h1:uz5ZFfmaexj1rEDgZvzQ7wjGkoSPjw2LCh8K+K1VrW4=
Expand Down
8 changes: 7 additions & 1 deletion peer/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,14 @@ func messageSummary(msg wire.Message) string {
case *wire.MsgGetAddr:
// No summary.

case *wire.MsgGetAddrV2:
// No summary.

case *wire.MsgAddr:
return fmt.Sprintf("%d addr", len(msg.AddrList))
return fmt.Sprintf("%d addrs", len(msg.AddrList))

case *wire.MsgAddrV2:
return fmt.Sprintf("%d addrs", len(msg.AddrList))

case *wire.MsgPing:
// No summary - perhaps add nonce.
Expand Down
54 changes: 53 additions & 1 deletion peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"github.com/davecgh/go-spew/spew"
"github.com/decred/dcrd/addrmgr/v2"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/lru"
"github.com/decred/dcrd/wire"
Expand All @@ -27,7 +28,7 @@ import (

const (
// MaxProtocolVersion is the max protocol version the peer supports.
MaxProtocolVersion = wire.InitStateVersion
MaxProtocolVersion = wire.AddrV2Version

// outputBufferSize is the number of elements the output channels use.
outputBufferSize = 5000
Expand Down Expand Up @@ -96,9 +97,15 @@ type MessageListeners struct {
// OnGetAddr is invoked when a peer receives a getaddr wire message.
OnGetAddr func(p *Peer, msg *wire.MsgGetAddr)

// OnGetAddrV2 is invoked when a peer receives a getaddrV2 wire message.
OnGetAddrV2 func(p *Peer, msg *wire.MsgGetAddrV2)

// OnAddr is invoked when a peer receives an addr wire message.
OnAddr func(p *Peer, msg *wire.MsgAddr)

// OnAddrV2 is invoked when a peer receives an addrV2 wire message.
OnAddrV2 func(p *Peer, msg *wire.MsgAddrV2)

// OnPing is invoked when a peer receives a ping wire message.
OnPing func(p *Peer, msg *wire.MsgPing)

Expand Down Expand Up @@ -835,6 +842,41 @@ func (p *Peer) PushAddrMsg(addresses []*wire.NetAddress) ([]*wire.NetAddress, er
return msg.AddrList, nil
}

// PushAddrV2Msg sends an addrv2 message to the connected peer using the
// provided addresses. This function is useful over manually sending the
// message via QueueMessage since it automatically limits the addresses to the
// maximum number allowed by the message and randomizes the chosen addresses
// when there are too many. It returns the addresses that were actually sent
// and no message will be sent if there are no entries in the provided addresses
// slice.
//
// This function is safe for concurrent access.
func (p *Peer) PushAddrV2Msg(addresses []*addrmgr.NetAddress) ([]*addrmgr.NetAddress, error) {
// Nothing to send.
if len(addresses) == 0 {
return nil, nil
}

msg := wire.NewMsgAddrV2()
msg.AddrList = make([]*addrmgr.NetAddress, len(addresses))
copy(msg.AddrList, addresses)

// Randomize the addresses sent if there are more than the maximum allowed.
if len(msg.AddrList) > wire.MaxAddrPerMsg {
// Shuffle the address list.
for i := range msg.AddrList {
j := rand.Intn(i + 1)
msg.AddrList[i], msg.AddrList[j] = msg.AddrList[j], msg.AddrList[i]
}

// Truncate it to the maximum size.
msg.AddrList = msg.AddrList[:wire.MaxAddrPerMsg]
}

p.QueueMessage(msg, nil)
return msg.AddrList, nil
}

// PushGetBlocksMsg sends a getblocks message for the provided block locator
// and stop hash. It will ignore back-to-back duplicate requests.
//
Expand Down Expand Up @@ -1302,6 +1344,16 @@ out:
p.cfg.Listeners.OnVerAck(p, msg)
}

case *wire.MsgGetAddrV2:
if p.cfg.Listeners.OnGetAddrV2 != nil {
p.cfg.Listeners.OnGetAddrV2(p, msg)
}

case *wire.MsgAddrV2:
if p.cfg.Listeners.OnAddrV2 != nil {
p.cfg.Listeners.OnAddrV2(p, msg)
}

case *wire.MsgGetAddr:
if p.cfg.Listeners.OnGetAddr != nil {
p.cfg.Listeners.OnGetAddr(p, msg)
Expand Down

0 comments on commit 7557e51

Please sign in to comment.