mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 05:46:32 +00:00
Closes #1581
This fixes the error in #1581, and also documents the purpose of this line. It ensures that if a peer tells us an address we know about, whose ID is the same as our current ID, we ignore it.
This removes the previous case where the ID's matched, but the IP's did not, which could yield a potential overwrite of the IP associated with the address later on. (This then would yield an eclipse attack)
This was not a vulnerability before though, thanks to a defensive check here 95fc7e58ee/p2p/pex/addrbook.go (L522))
806 lines
22 KiB
Go
806 lines
22 KiB
Go
package pex
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"math"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
tmmath "github.com/tendermint/tendermint/libs/math"
|
|
tmrand "github.com/tendermint/tendermint/libs/rand"
|
|
"github.com/tendermint/tendermint/p2p"
|
|
)
|
|
|
|
// FIXME These tests should not rely on .(*addrBook) assertions
|
|
|
|
func TestAddrBookPickAddress(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
// 0 addresses
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
assert.Zero(t, book.Size())
|
|
|
|
addr := book.PickAddress(50)
|
|
assert.Nil(t, addr, "expected no address")
|
|
|
|
randAddrs := randNetAddressPairs(t, 1)
|
|
addrSrc := randAddrs[0]
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
|
|
// pick an address when we only have new address
|
|
addr = book.PickAddress(0)
|
|
assert.NotNil(t, addr, "expected an address")
|
|
addr = book.PickAddress(50)
|
|
assert.NotNil(t, addr, "expected an address")
|
|
addr = book.PickAddress(100)
|
|
assert.NotNil(t, addr, "expected an address")
|
|
|
|
// pick an address when we only have old address
|
|
book.MarkGood(addrSrc.addr.ID)
|
|
addr = book.PickAddress(0)
|
|
assert.NotNil(t, addr, "expected an address")
|
|
addr = book.PickAddress(50)
|
|
assert.NotNil(t, addr, "expected an address")
|
|
|
|
// in this case, nNew==0 but we biased 100% to new, so we return nil
|
|
addr = book.PickAddress(100)
|
|
assert.Nil(t, addr, "did not expected an address")
|
|
}
|
|
|
|
func TestAddrBookSaveLoad(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
// 0 addresses
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
book.Save()
|
|
|
|
book = NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
err := book.Start()
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, book.Empty())
|
|
|
|
// 100 addresses
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
|
|
for _, addrSrc := range randAddrs {
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, 100, book.Size())
|
|
book.Save()
|
|
|
|
book = NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
err = book.Start()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 100, book.Size())
|
|
}
|
|
|
|
func TestAddrBookLookup(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
for _, addrSrc := range randAddrs {
|
|
addr := addrSrc.addr
|
|
src := addrSrc.src
|
|
err := book.AddAddress(addr, src)
|
|
require.NoError(t, err)
|
|
|
|
ka := book.HasAddress(addr)
|
|
assert.True(t, ka, "Expected to find KnownAddress %v but wasn't there.", addr)
|
|
}
|
|
}
|
|
|
|
func TestAddrBookPromoteToOld(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
for _, addrSrc := range randAddrs {
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Attempt all addresses.
|
|
for _, addrSrc := range randAddrs {
|
|
book.MarkAttempt(addrSrc.addr)
|
|
}
|
|
|
|
// Promote half of them
|
|
for i, addrSrc := range randAddrs {
|
|
if i%2 == 0 {
|
|
book.MarkGood(addrSrc.addr.ID)
|
|
}
|
|
}
|
|
|
|
// TODO: do more testing :)
|
|
|
|
selection := book.GetSelection()
|
|
t.Logf("selection: %v", selection)
|
|
|
|
if len(selection) > book.Size() {
|
|
t.Errorf("selection could not be bigger than the book")
|
|
}
|
|
|
|
selection = book.GetSelectionWithBias(30)
|
|
t.Logf("selection: %v", selection)
|
|
|
|
if len(selection) > book.Size() {
|
|
t.Errorf("selection with bias could not be bigger than the book")
|
|
}
|
|
|
|
assert.Equal(t, book.Size(), 100, "expecting book size to be 100")
|
|
}
|
|
|
|
func TestAddrBookHandlesDuplicates(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
|
|
differentSrc := randIPv4Address(t)
|
|
for _, addrSrc := range randAddrs {
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
err = book.AddAddress(addrSrc.addr, addrSrc.src) // duplicate
|
|
require.NoError(t, err)
|
|
err = book.AddAddress(addrSrc.addr, differentSrc) // different src
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
assert.Equal(t, 100, book.Size())
|
|
}
|
|
|
|
type netAddressPair struct {
|
|
addr *p2p.NetAddress
|
|
src *p2p.NetAddress
|
|
}
|
|
|
|
func randNetAddressPairs(t *testing.T, n int) []netAddressPair {
|
|
randAddrs := make([]netAddressPair, n)
|
|
for i := 0; i < n; i++ {
|
|
randAddrs[i] = netAddressPair{addr: randIPv4Address(t), src: randIPv4Address(t)}
|
|
}
|
|
return randAddrs
|
|
}
|
|
|
|
func randIPv4Address(t *testing.T) *p2p.NetAddress {
|
|
for {
|
|
ip := fmt.Sprintf("%v.%v.%v.%v",
|
|
tmrand.Intn(254)+1,
|
|
tmrand.Intn(255),
|
|
tmrand.Intn(255),
|
|
tmrand.Intn(255),
|
|
)
|
|
port := tmrand.Intn(65535-1) + 1
|
|
id := p2p.ID(hex.EncodeToString(tmrand.Bytes(p2p.IDByteLength)))
|
|
idAddr := p2p.IDAddressString(id, fmt.Sprintf("%v:%v", ip, port))
|
|
addr, err := p2p.NewNetAddressString(idAddr)
|
|
assert.Nil(t, err, "error generating rand network address")
|
|
if addr.Routable() {
|
|
return addr
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddrBookRemoveAddress(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
addr := randIPv4Address(t)
|
|
err := book.AddAddress(addr, addr)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, book.Size())
|
|
|
|
book.RemoveAddress(addr)
|
|
assert.Equal(t, 0, book.Size())
|
|
|
|
nonExistingAddr := randIPv4Address(t)
|
|
book.RemoveAddress(nonExistingAddr)
|
|
assert.Equal(t, 0, book.Size())
|
|
}
|
|
|
|
func TestAddrBookGetSelectionWithOneMarkedGood(t *testing.T) {
|
|
// create a book with 10 addresses, 1 good/old and 9 new
|
|
book, fname := createAddrBookWithMOldAndNNewAddrs(t, 1, 9)
|
|
defer deleteTempFile(fname)
|
|
|
|
addrs := book.GetSelectionWithBias(biasToSelectNewPeers)
|
|
assert.NotNil(t, addrs)
|
|
assertMOldAndNNewAddrsInSelection(t, 1, 9, addrs, book)
|
|
}
|
|
|
|
func TestAddrBookGetSelectionWithOneNotMarkedGood(t *testing.T) {
|
|
// create a book with 10 addresses, 9 good/old and 1 new
|
|
book, fname := createAddrBookWithMOldAndNNewAddrs(t, 9, 1)
|
|
defer deleteTempFile(fname)
|
|
|
|
addrs := book.GetSelectionWithBias(biasToSelectNewPeers)
|
|
assert.NotNil(t, addrs)
|
|
assertMOldAndNNewAddrsInSelection(t, 9, 1, addrs, book)
|
|
}
|
|
|
|
func TestAddrBookGetSelectionReturnsNilWhenAddrBookIsEmpty(t *testing.T) {
|
|
book, fname := createAddrBookWithMOldAndNNewAddrs(t, 0, 0)
|
|
defer deleteTempFile(fname)
|
|
|
|
addrs := book.GetSelectionWithBias(biasToSelectNewPeers)
|
|
assert.Nil(t, addrs)
|
|
}
|
|
|
|
func TestAddrBookGetSelection(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
// 1) empty book
|
|
assert.Empty(t, book.GetSelection())
|
|
|
|
// 2) add one address
|
|
addr := randIPv4Address(t)
|
|
err := book.AddAddress(addr, addr)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 1, len(book.GetSelection()))
|
|
assert.Equal(t, addr, book.GetSelection()[0])
|
|
|
|
// 3) add a bunch of addresses
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
for _, addrSrc := range randAddrs {
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// check there is no duplicates
|
|
addrs := make(map[string]*p2p.NetAddress)
|
|
selection := book.GetSelection()
|
|
for _, addr := range selection {
|
|
if dup, ok := addrs[addr.String()]; ok {
|
|
t.Fatalf("selection %v contains duplicates %v", selection, dup)
|
|
}
|
|
addrs[addr.String()] = addr
|
|
}
|
|
|
|
if len(selection) > book.Size() {
|
|
t.Errorf("selection %v could not be bigger than the book", selection)
|
|
}
|
|
}
|
|
|
|
func TestAddrBookGetSelectionWithBias(t *testing.T) {
|
|
const biasTowardsNewAddrs = 30
|
|
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
// 1) empty book
|
|
selection := book.GetSelectionWithBias(biasTowardsNewAddrs)
|
|
assert.Empty(t, selection)
|
|
|
|
// 2) add one address
|
|
addr := randIPv4Address(t)
|
|
err := book.AddAddress(addr, addr)
|
|
require.NoError(t, err)
|
|
|
|
selection = book.GetSelectionWithBias(biasTowardsNewAddrs)
|
|
assert.Equal(t, 1, len(selection))
|
|
assert.Equal(t, addr, selection[0])
|
|
|
|
// 3) add a bunch of addresses
|
|
randAddrs := randNetAddressPairs(t, 100)
|
|
for _, addrSrc := range randAddrs {
|
|
err := book.AddAddress(addrSrc.addr, addrSrc.src)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// check there is no duplicates
|
|
addrs := make(map[string]*p2p.NetAddress)
|
|
selection = book.GetSelectionWithBias(biasTowardsNewAddrs)
|
|
for _, addr := range selection {
|
|
if dup, ok := addrs[addr.String()]; ok {
|
|
t.Fatalf("selection %v contains duplicates %v", selection, dup)
|
|
}
|
|
addrs[addr.String()] = addr
|
|
}
|
|
|
|
if len(selection) > book.Size() {
|
|
t.Fatalf("selection %v could not be bigger than the book", selection)
|
|
}
|
|
|
|
// 4) mark 80% of the addresses as good
|
|
randAddrsLen := len(randAddrs)
|
|
for i, addrSrc := range randAddrs {
|
|
if int((float64(i)/float64(randAddrsLen))*100) >= 20 {
|
|
book.MarkGood(addrSrc.addr.ID)
|
|
}
|
|
}
|
|
|
|
selection = book.GetSelectionWithBias(biasTowardsNewAddrs)
|
|
|
|
// check that ~70% of addresses returned are good
|
|
good := 0
|
|
for _, addr := range selection {
|
|
if book.IsGood(addr) {
|
|
good++
|
|
}
|
|
}
|
|
|
|
got, expected := int((float64(good)/float64(len(selection)))*100), 100-biasTowardsNewAddrs
|
|
|
|
// compute some slack to protect against small differences due to rounding:
|
|
slack := int(math.Round(float64(100) / float64(len(selection))))
|
|
if got > expected+slack {
|
|
t.Fatalf(
|
|
"got more good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)",
|
|
got,
|
|
expected,
|
|
good,
|
|
len(selection),
|
|
)
|
|
}
|
|
if got < expected-slack {
|
|
t.Fatalf(
|
|
"got fewer good peers (%% got: %d, %% expected: %d, number of good addrs: %d, total: %d)",
|
|
got,
|
|
expected,
|
|
good,
|
|
len(selection),
|
|
)
|
|
}
|
|
}
|
|
|
|
func TestAddrBookHasAddress(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
addr := randIPv4Address(t)
|
|
err := book.AddAddress(addr, addr)
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, book.HasAddress(addr))
|
|
|
|
book.RemoveAddress(addr)
|
|
|
|
assert.False(t, book.HasAddress(addr))
|
|
}
|
|
|
|
func testCreatePrivateAddrs(t *testing.T, numAddrs int) ([]*p2p.NetAddress, []string) {
|
|
addrs := make([]*p2p.NetAddress, numAddrs)
|
|
for i := 0; i < numAddrs; i++ {
|
|
addrs[i] = randIPv4Address(t)
|
|
}
|
|
|
|
private := make([]string, numAddrs)
|
|
for i, addr := range addrs {
|
|
private[i] = string(addr.ID)
|
|
}
|
|
return addrs, private
|
|
}
|
|
|
|
func TestBanBadPeers(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
addr := randIPv4Address(t)
|
|
_ = book.AddAddress(addr, addr)
|
|
|
|
book.MarkBad(addr, 1*time.Second)
|
|
// addr should not reachable
|
|
assert.False(t, book.HasAddress(addr))
|
|
assert.True(t, book.IsBanned(addr))
|
|
|
|
err := book.AddAddress(addr, addr)
|
|
// book should not add address from the blacklist
|
|
assert.Error(t, err)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
book.ReinstateBadPeers()
|
|
// address should be reinstated in the new bucket
|
|
assert.EqualValues(t, 1, book.Size())
|
|
assert.True(t, book.HasAddress(addr))
|
|
assert.False(t, book.IsGood(addr))
|
|
}
|
|
|
|
func TestAddrBookEmpty(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
// Check that empty book is empty
|
|
require.True(t, book.Empty())
|
|
// Check that book with our address is empty
|
|
book.AddOurAddress(randIPv4Address(t))
|
|
require.True(t, book.Empty())
|
|
// Check that book with private addrs is empty
|
|
_, privateIds := testCreatePrivateAddrs(t, 5)
|
|
book.AddPrivateIDs(privateIds)
|
|
require.True(t, book.Empty())
|
|
|
|
// Check that book with address is not empty
|
|
err := book.AddAddress(randIPv4Address(t), randIPv4Address(t))
|
|
require.NoError(t, err)
|
|
require.False(t, book.Empty())
|
|
}
|
|
|
|
func TestPrivatePeers(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
|
|
addrs, private := testCreatePrivateAddrs(t, 10)
|
|
book.AddPrivateIDs(private)
|
|
|
|
// private addrs must not be added
|
|
for _, addr := range addrs {
|
|
err := book.AddAddress(addr, addr)
|
|
if assert.Error(t, err) {
|
|
_, ok := err.(ErrAddrBookPrivate)
|
|
assert.True(t, ok)
|
|
}
|
|
}
|
|
|
|
// addrs coming from private peers must not be added
|
|
err := book.AddAddress(randIPv4Address(t), addrs[0])
|
|
if assert.Error(t, err) {
|
|
_, ok := err.(ErrAddrBookPrivateSrc)
|
|
assert.True(t, ok)
|
|
}
|
|
}
|
|
|
|
func testAddrBookAddressSelection(t *testing.T, bookSize int) {
|
|
// generate all combinations of old (m) and new addresses
|
|
for nBookOld := 0; nBookOld <= bookSize; nBookOld++ {
|
|
nBookNew := bookSize - nBookOld
|
|
dbgStr := fmt.Sprintf("book of size %d (new %d, old %d)", bookSize, nBookNew, nBookOld)
|
|
|
|
// create book and get selection
|
|
book, fname := createAddrBookWithMOldAndNNewAddrs(t, nBookOld, nBookNew)
|
|
defer deleteTempFile(fname)
|
|
addrs := book.GetSelectionWithBias(biasToSelectNewPeers)
|
|
assert.NotNil(t, addrs, "%s - expected a non-nil selection", dbgStr)
|
|
nAddrs := len(addrs)
|
|
assert.NotZero(t, nAddrs, "%s - expected at least one address in selection", dbgStr)
|
|
|
|
// check there's no nil addresses
|
|
for _, addr := range addrs {
|
|
if addr == nil {
|
|
t.Fatalf("%s - got nil address in selection %v", dbgStr, addrs)
|
|
}
|
|
}
|
|
|
|
// XXX: shadowing
|
|
nOld, nNew := countOldAndNewAddrsInSelection(addrs, book)
|
|
|
|
// Given:
|
|
// n - num new addrs, m - num old addrs
|
|
// k - num new addrs expected in the beginning (based on bias %)
|
|
// i=min(n, max(k,r-m)), aka expNew
|
|
// j=min(m, r-i), aka expOld
|
|
//
|
|
// We expect this layout:
|
|
// indices: 0...i-1 i...i+j-1
|
|
// addresses: N0..Ni-1 O0..Oj-1
|
|
//
|
|
// There is at least one partition and at most three.
|
|
var (
|
|
k = percentageOfNum(biasToSelectNewPeers, nAddrs)
|
|
expNew = tmmath.MinInt(nNew, tmmath.MaxInt(k, nAddrs-nBookOld))
|
|
expOld = tmmath.MinInt(nOld, nAddrs-expNew)
|
|
)
|
|
|
|
// Verify that the number of old and new addresses are as expected
|
|
if nNew != expNew {
|
|
t.Fatalf("%s - expected new addrs %d, got %d", dbgStr, expNew, nNew)
|
|
}
|
|
if nOld != expOld {
|
|
t.Fatalf("%s - expected old addrs %d, got %d", dbgStr, expOld, nOld)
|
|
}
|
|
|
|
// Verify that the order of addresses is as expected
|
|
// Get the sequence types and lengths of the selection
|
|
seqLens, seqTypes, err := analyseSelectionLayout(book, addrs)
|
|
assert.NoError(t, err, "%s", dbgStr)
|
|
|
|
// Build a list with the expected lengths of partitions and another with the expected types, e.g.:
|
|
// expSeqLens = [10, 22], expSeqTypes = [1, 2]
|
|
// means we expect 10 new (type 1) addresses followed by 22 old (type 2) addresses.
|
|
var expSeqLens []int
|
|
var expSeqTypes []int
|
|
|
|
switch {
|
|
case expOld == 0: // all new addresses
|
|
expSeqLens = []int{nAddrs}
|
|
expSeqTypes = []int{1}
|
|
case expNew == 0: // all old addresses
|
|
expSeqLens = []int{nAddrs}
|
|
expSeqTypes = []int{2}
|
|
case nAddrs-expNew-expOld == 0: // new addresses, old addresses
|
|
expSeqLens = []int{expNew, expOld}
|
|
expSeqTypes = []int{1, 2}
|
|
}
|
|
|
|
assert.Equal(t, expSeqLens, seqLens,
|
|
"%s - expected sequence lengths of old/new %v, got %v",
|
|
dbgStr, expSeqLens, seqLens)
|
|
assert.Equal(t, expSeqTypes, seqTypes,
|
|
"%s - expected sequence types (1-new, 2-old) was %v, got %v",
|
|
dbgStr, expSeqTypes, seqTypes)
|
|
}
|
|
}
|
|
|
|
func TestMultipleAddrBookAddressSelection(t *testing.T) {
|
|
// test books with smaller size, < N
|
|
const N = 32
|
|
for bookSize := 1; bookSize < N; bookSize++ {
|
|
testAddrBookAddressSelection(t, bookSize)
|
|
}
|
|
|
|
// Test for two books with sizes from following ranges
|
|
ranges := [...][]int{{33, 100}, {100, 175}}
|
|
bookSizes := make([]int, 0, len(ranges))
|
|
for _, r := range ranges {
|
|
bookSizes = append(bookSizes, tmrand.Intn(r[1]-r[0])+r[0])
|
|
}
|
|
t.Logf("Testing address selection for the following book sizes %v\n", bookSizes)
|
|
for _, bookSize := range bookSizes {
|
|
testAddrBookAddressSelection(t, bookSize)
|
|
}
|
|
}
|
|
|
|
func TestAddrBookAddDoesNotOverwriteOldIP(t *testing.T) {
|
|
fname := createTempFileName("addrbook_test")
|
|
defer deleteTempFile(fname)
|
|
|
|
// This test creates adds a peer to the address book and marks it good
|
|
// It then attempts to override the peer's IP, by adding a peer with the same ID
|
|
// but different IP. We distinguish the IP's by "RealIP" and "OverrideAttemptIP"
|
|
peerID := "678503e6c8f50db7279c7da3cb9b072aac4bc0d5"
|
|
peerRealIP := "1.1.1.1:26656"
|
|
peerOverrideAttemptIP := "2.2.2.2:26656"
|
|
SrcAddr := "b0dd378c3fbc4c156cd6d302a799f0d2e4227201@159.89.121.174:26656"
|
|
|
|
// There is a chance that AddAddress will ignore the new peer its given.
|
|
// So we repeat trying to override the peer several times,
|
|
// to ensure we aren't in a case that got probabilistically ignored
|
|
numOverrideAttempts := 10
|
|
|
|
peerRealAddr, err := p2p.NewNetAddressString(peerID + "@" + peerRealIP)
|
|
require.Nil(t, err)
|
|
|
|
peerOverrideAttemptAddr, err := p2p.NewNetAddressString(peerID + "@" + peerOverrideAttemptIP)
|
|
require.Nil(t, err)
|
|
|
|
src, err := p2p.NewNetAddressString(SrcAddr)
|
|
require.Nil(t, err)
|
|
|
|
book := NewAddrBook(fname, true)
|
|
book.SetLogger(log.TestingLogger())
|
|
err = book.AddAddress(peerRealAddr, src)
|
|
require.Nil(t, err)
|
|
book.MarkAttempt(peerRealAddr)
|
|
book.MarkGood(peerRealAddr.ID)
|
|
|
|
// Double check that adding a peer again doesn't error
|
|
err = book.AddAddress(peerRealAddr, src)
|
|
require.Nil(t, err)
|
|
|
|
// Try changing ip but keeping the same node id. (change 1.1.1.1 to 2.2.2.2)
|
|
// This should just be ignored, and not error.
|
|
for i := 0; i < numOverrideAttempts; i++ {
|
|
err = book.AddAddress(peerOverrideAttemptAddr, src)
|
|
require.Nil(t, err)
|
|
}
|
|
// Now check that the IP was not overridden.
|
|
// This is done by sampling several peers from addr book
|
|
// and ensuring they all have the correct IP.
|
|
// In the expected functionality, this test should only have 1 Peer, hence will pass.
|
|
for i := 0; i < numOverrideAttempts; i++ {
|
|
selection := book.GetSelection()
|
|
for _, addr := range selection {
|
|
require.Equal(t, addr.IP, peerRealAddr.IP)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAddrBookGroupKey(t *testing.T) {
|
|
// non-strict routability
|
|
testCases := []struct {
|
|
name string
|
|
ip string
|
|
expKey string
|
|
}{
|
|
// IPv4 normal.
|
|
{"ipv4 normal class a", "12.1.2.3", "12.1.0.0"},
|
|
{"ipv4 normal class b", "173.1.2.3", "173.1.0.0"},
|
|
{"ipv4 normal class c", "196.1.2.3", "196.1.0.0"},
|
|
|
|
// IPv6/IPv4 translations.
|
|
{"ipv6 rfc3964 with ipv4 encap", "2002:0c01:0203::", "12.1.0.0"},
|
|
{"ipv6 rfc4380 toredo ipv4", "2001:0:1234::f3fe:fdfc", "12.1.0.0"},
|
|
{"ipv6 rfc6052 well-known prefix with ipv4", "64:ff9b::0c01:0203", "12.1.0.0"},
|
|
{"ipv6 rfc6145 translated ipv4", "::ffff:0:0c01:0203", "12.1.0.0"},
|
|
|
|
// Tor.
|
|
{"ipv6 tor onioncat", "fd87:d87e:eb43:1234::5678", "tor:2"},
|
|
{"ipv6 tor onioncat 2", "fd87:d87e:eb43:1245::6789", "tor:2"},
|
|
{"ipv6 tor onioncat 3", "fd87:d87e:eb43:1345::6789", "tor:3"},
|
|
|
|
// IPv6 normal.
|
|
{"ipv6 normal", "2602:100::1", "2602:100::"},
|
|
{"ipv6 normal 2", "2602:0100::1234", "2602:100::"},
|
|
{"ipv6 hurricane electric", "2001:470:1f10:a1::2", "2001:470:1000::"},
|
|
{"ipv6 hurricane electric 2", "2001:0470:1f10:a1::2", "2001:470:1000::"},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
nip := net.ParseIP(tc.ip)
|
|
key := groupKeyFor(p2p.NewNetAddressIPPort(nip, 26656), false)
|
|
assert.Equal(t, tc.expKey, key, "#%d", i)
|
|
}
|
|
|
|
// strict routability
|
|
testCases = []struct {
|
|
name string
|
|
ip string
|
|
expKey string
|
|
}{
|
|
// Local addresses.
|
|
{"ipv4 localhost", "127.0.0.1", "local"},
|
|
{"ipv6 localhost", "::1", "local"},
|
|
{"ipv4 zero", "0.0.0.0", "local"},
|
|
{"ipv4 first octet zero", "0.1.2.3", "local"},
|
|
|
|
// Unroutable addresses.
|
|
{"ipv4 invalid bcast", "255.255.255.255", "unroutable"},
|
|
{"ipv4 rfc1918 10/8", "10.1.2.3", "unroutable"},
|
|
{"ipv4 rfc1918 172.16/12", "172.16.1.2", "unroutable"},
|
|
{"ipv4 rfc1918 192.168/16", "192.168.1.2", "unroutable"},
|
|
{"ipv6 rfc3849 2001:db8::/32", "2001:db8::1234", "unroutable"},
|
|
{"ipv4 rfc3927 169.254/16", "169.254.1.2", "unroutable"},
|
|
{"ipv6 rfc4193 fc00::/7", "fc00::1234", "unroutable"},
|
|
{"ipv6 rfc4843 2001:10::/28", "2001:10::1234", "unroutable"},
|
|
{"ipv6 rfc4862 fe80::/64", "fe80::1234", "unroutable"},
|
|
}
|
|
|
|
for i, tc := range testCases {
|
|
nip := net.ParseIP(tc.ip)
|
|
key := groupKeyFor(p2p.NewNetAddressIPPort(nip, 26656), true)
|
|
assert.Equal(t, tc.expKey, key, "#%d", i)
|
|
}
|
|
}
|
|
|
|
func assertMOldAndNNewAddrsInSelection(t *testing.T, m, n int, addrs []*p2p.NetAddress, book *addrBook) {
|
|
nOld, nNew := countOldAndNewAddrsInSelection(addrs, book)
|
|
assert.Equal(t, m, nOld, "old addresses")
|
|
assert.Equal(t, n, nNew, "new addresses")
|
|
}
|
|
|
|
func createTempFileName(prefix string) string {
|
|
f, err := ioutil.TempFile("", prefix)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fname := f.Name()
|
|
err = f.Close()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return fname
|
|
}
|
|
|
|
func deleteTempFile(fname string) {
|
|
err := os.Remove(fname)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func createAddrBookWithMOldAndNNewAddrs(t *testing.T, nOld, nNew int) (book *addrBook, fname string) {
|
|
fname = createTempFileName("addrbook_test")
|
|
|
|
book = NewAddrBook(fname, true).(*addrBook)
|
|
book.SetLogger(log.TestingLogger())
|
|
assert.Zero(t, book.Size())
|
|
|
|
randAddrs := randNetAddressPairs(t, nOld)
|
|
for _, addr := range randAddrs {
|
|
err := book.AddAddress(addr.addr, addr.src)
|
|
require.NoError(t, err)
|
|
book.MarkGood(addr.addr.ID)
|
|
}
|
|
|
|
randAddrs = randNetAddressPairs(t, nNew)
|
|
for _, addr := range randAddrs {
|
|
err := book.AddAddress(addr.addr, addr.src)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func countOldAndNewAddrsInSelection(addrs []*p2p.NetAddress, book *addrBook) (nOld, nNew int) {
|
|
for _, addr := range addrs {
|
|
if book.IsGood(addr) {
|
|
nOld++
|
|
} else {
|
|
nNew++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Analyse the layout of the selection specified by 'addrs'
|
|
// Returns:
|
|
// - seqLens - the lengths of the sequences of addresses of same type
|
|
// - seqTypes - the types of sequences in selection
|
|
func analyseSelectionLayout(book *addrBook, addrs []*p2p.NetAddress) (seqLens, seqTypes []int, err error) {
|
|
// address types are: 0 - nil, 1 - new, 2 - old
|
|
var (
|
|
prevType = 0
|
|
currentSeqLen = 0
|
|
)
|
|
|
|
for _, addr := range addrs {
|
|
addrType := 0
|
|
if book.IsGood(addr) {
|
|
addrType = 2
|
|
} else {
|
|
addrType = 1
|
|
}
|
|
if addrType != prevType && prevType != 0 {
|
|
seqLens = append(seqLens, currentSeqLen)
|
|
seqTypes = append(seqTypes, prevType)
|
|
currentSeqLen = 0
|
|
}
|
|
currentSeqLen++
|
|
prevType = addrType
|
|
}
|
|
|
|
seqLens = append(seqLens, currentSeqLen)
|
|
seqTypes = append(seqTypes, prevType)
|
|
|
|
return
|
|
}
|