Skip to content

Commit

Permalink
First round of address manager package refactor
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dajohi authored and davecgh committed Jul 7, 2014
1 parent 62f21d3 commit 6f5a43d
Show file tree
Hide file tree
Showing 10 changed files with 836 additions and 453 deletions.
372 changes: 91 additions & 281 deletions addrmgr/addrmanager.go

Large diffs are not rendered by default.

297 changes: 133 additions & 164 deletions addrmgr/addrmanager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,88 +21,9 @@ type naTest struct {
want string
}

type ipTest struct {
in btcwire.NetAddress
rfc1918 bool
rfc3849 bool
rfc3927 bool
rfc3964 bool
rfc4193 bool
rfc4380 bool
rfc4843 bool
rfc4862 bool
rfc6052 bool
rfc6145 bool
local bool
valid bool
routable bool
}

// naTests houses all of the tests to be performed against the NetAddressKey
// method.
var naTests = make([]naTest, 0)
var ipTests = make([]ipTest, 0)

func addIPTest(ip string, rfc1918, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
rfc4843, rfc4862, rfc6052, rfc6145, local, valid, routable bool) {
nip := net.ParseIP(ip)
na := btcwire.NetAddress{
Timestamp: time.Now(),
Services: btcwire.SFNodeNetwork,
IP: nip,
Port: 8333,
}
test := ipTest{na, rfc1918, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
rfc4843, rfc4862, rfc6052, rfc6145, local, valid, routable}
ipTests = append(ipTests, test)
}

func addIPTests() {
addIPTest("10.255.255.255", true, false, false, false, false, false,
false, false, false, false, false, true, false)
addIPTest("192.168.0.1", true, false, false, false, false, false,
false, false, false, false, false, true, false)
addIPTest("172.31.255.1", true, false, false, false, false, false,
false, false, false, false, false, true, false)
addIPTest("172.32.1.1", false, false, false, false, false, false,
false, false, false, false, false, true, true)
addIPTest("169.254.250.120", false, false, true, false, false, false,
false, false, false, false, false, true, false)
addIPTest("0.0.0.0", false, false, false, false, false, false,
false, false, false, false, true, false, false)
addIPTest("255.255.255.255", false, false, false, false, false, false,
false, false, false, false, false, false, false)
addIPTest("127.0.0.1", false, false, false, false, false, false,
false, false, false, false, true, true, false)
addIPTest("fd00:dead::1", false, false, false, false, true, false,
false, false, false, false, false, true, false)
addIPTest("2001::1", false, false, false, false, false, true,
false, false, false, false, false, true, true)
addIPTest("2001:10:abcd::1:1", false, false, false, false, false, false,
true, false, false, false, false, true, false)
addIPTest("fe80::1", false, false, false, false, false, false,
false, true, false, false, false, true, false)
addIPTest("fe80:1::1", false, false, false, false, false, false,
false, false, false, false, false, true, true)
addIPTest("64:ff9b::1", false, false, false, false, false, false,
false, false, true, false, false, true, true)
addIPTest("::ffff:abcd:ef12:1", false, false, false, false, false, false,
false, false, false, false, false, true, true)
addIPTest("::1", false, false, false, false, false, false,
false, false, false, false, true, true, false)
}

func addNaTest(ip string, port uint16, want string) {
nip := net.ParseIP(ip)
na := btcwire.NetAddress{
Timestamp: time.Now(),
Services: btcwire.SFNodeNetwork,
IP: nip,
Port: port,
}
test := naTest{na, want}
naTests = append(naTests, test)
}

// addNaTests
func addNaTests() {
Expand Down Expand Up @@ -165,112 +86,160 @@ func addNaTests() {
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
}

func lookupFunc(host string) ([]net.IP, error) {
return nil, errors.New("not implemented")
}

func TestGetAddress(t *testing.T) {
n := addrmgr.New("testdir", lookupFunc)
if rv := n.GetAddress("any", 10); rv != nil {
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
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 TestIPTypes(t *testing.T) {
addIPTests()
func lookupFunc(host string) ([]net.IP, error) {
return nil, errors.New("not implemented")
}

t.Logf("Running %d tests", len(ipTests))
for _, test := range ipTests {
rv := addrmgr.RFC1918(&test.in)
if rv != test.rfc1918 {
t.Errorf("RFC1918 %s\n got: %v want: %v", test.in.IP, rv, test.rfc1918)
continue
}
}
for _, test := range ipTests {
rv := addrmgr.RFC3849(&test.in)
if rv != test.rfc3849 {
t.Errorf("RFC3849 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3849)
continue
}
}
for _, test := range ipTests {
rv := addrmgr.RFC3927(&test.in)
if rv != test.rfc3927 {
t.Errorf("RFC3927 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3927)
continue
}
}
for _, test := range ipTests {
rv := addrmgr.RFC3964(&test.in)
if rv != test.rfc3964 {
t.Errorf("RFC3964 %s\n got: %v want: %v", test.in.IP, rv, test.rfc3964)
continue
}
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,
},
}
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)
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
}
}
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)
if result != nil && test.valid {
t.Errorf("TestAddLocalAddress test #%d failed: %s should not have "+
"been accepted", test.address.IP)
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
}
}

func TestGetAddress(t *testing.T) {
n := addrmgr.New("testdir", lookupFunc)
if rv := n.GetAddress("any", 10); rv != nil {
t.Errorf("GetAddress failed: got: %v want: %v\n", rv, nil)
}
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
}
}

func TestGetBestLocalAddress(t *testing.T) {
localAddrs := []btcwire.NetAddress{
{IP: net.ParseIP("192.168.0.100")},
{IP: net.ParseIP("::1")},
{IP: net.ParseIP("fe80::1")},
{IP: net.ParseIP("2001:470::1")},
}
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
}

var tests = []struct {
remoteAddr btcwire.NetAddress
want1 btcwire.NetAddress
want2 btcwire.NetAddress
want3 btcwire.NetAddress
}{
{
// Remote connection from public IPv4
btcwire.NetAddress{IP: net.ParseIP("204.124.8.1")},
btcwire.NetAddress{IP: net.IPv4zero},
btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")},
btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
},
{
// Remote connection from private IPv4
btcwire.NetAddress{IP: net.ParseIP("172.16.0.254")},
btcwire.NetAddress{IP: net.IPv4zero},
btcwire.NetAddress{IP: net.IPv4zero},
btcwire.NetAddress{IP: net.IPv4zero},
},
{
// Remote connection from public IPv6
btcwire.NetAddress{IP: net.ParseIP("2602:100:abcd::102")},
btcwire.NetAddress{IP: net.ParseIP("2001:470::1")},
btcwire.NetAddress{IP: net.ParseIP("2001:470::1")},
btcwire.NetAddress{IP: net.ParseIP("2001:470::1")},
},
/* XXX
{
// Remote connection from Tor
btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43::100")},
btcwire.NetAddress{IP: net.IPv4zero},
btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")},
btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")},
},
*/
}
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
}

amgr := addrmgr.New("", nil)
for _, localAddr := range localAddrs {
amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio)
}
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)

// Test against want1
for x, test := range tests {
got := amgr.GetBestLocalAddress(&test.remoteAddr)
if !test.want1.IP.Equal(got.IP) {
t.Errorf("TestGetBestLocalAddress test1 #%d failed for remote address %s: want %s got %s",
x, test.remoteAddr.IP, test.want1.IP, got.IP)
continue
}
}
for _, test := range ipTests {
rv := addrmgr.Valid(&test.in)
if rv != test.valid {
t.Errorf("Valid %s\n got: %v want: %v", test.in.IP, rv, test.valid)

// Add a public IP to the list of local addresses.
localAddr := btcwire.NetAddress{IP: net.ParseIP("204.124.8.100")}
amgr.AddLocalAddress(&localAddr, addrmgr.InterfacePrio)

// Test against want2
for x, test := range tests {
got := amgr.GetBestLocalAddress(&test.remoteAddr)
if !test.want2.IP.Equal(got.IP) {
t.Errorf("TestGetBestLocalAddress test2 #%d failed for remote address %s: want %s got %s",
x, test.remoteAddr.IP, test.want2.IP, got.IP)
continue
}
}
for _, test := range ipTests {
rv := addrmgr.Routable(&test.in)
if rv != test.routable {
t.Errorf("Routable %s\n got: %v want: %v", test.in.IP, rv, test.routable)
continue
/*
// Add a tor generated IP address
localAddr = btcwire.NetAddress{IP: net.ParseIP("fd87:d87e:eb43:25::1")}
amgr.AddLocalAddress(&localAddr, addrmgr.ManualPrio)
// Test against want3
for x, test := range tests {
got := amgr.GetBestLocalAddress(&test.remoteAddr)
if !test.want3.IP.Equal(got.IP) {
t.Errorf("TestGetBestLocalAddress test3 #%d failed for remote address %s: want %s got %s",
x, test.remoteAddr.IP, test.want3.IP, got.IP)
continue
}
}
}
*/
}

func TestNetAddressKey(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions addrmgr/cov_report.sh
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions addrmgr/doc.go
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 6f5a43d

Please sign in to comment.