lnd.xprv/netann/host_ann_test.go
Olaoluwa Osuntokun ba3688c3b9
netann: add new HostAnnouncer to support dynamic IPs via domains
In this commit, we add a new sub-system, then `HostAnnouncer` which
allows a users without a static IP address to ensure that lnd always
announces the most up to date address based on a domain name. A new
command line flag `--external-hosts` has been added which allows a user
to specify one or most hosts that should be periodically resolved to
update any advertised IPs the node has.

Fixes #1624.
2020-07-06 17:30:08 -07:00

277 lines
6.3 KiB
Go

package netann
import (
"net"
"testing"
"time"
"github.com/lightningnetwork/lnd/ticker"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestHostAnnouncerUpdates tests that the HostAnnouncer will properly announce
// a new set of addresses each time a target host changes and will noop if not
// change happens during an interval.
func TestHostAnnouncerUpdates(t *testing.T) {
t.Parallel()
hosts := []string{"test.com", "example.com"}
startingAddrs := []net.Addr{
&net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
&net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
}
ticker := ticker.NewForce(time.Hour * 24)
testTimeout := time.Millisecond * 200
type annReq struct {
newAddrs []net.Addr
removedAddrs map[string]struct{}
}
testCases := []struct {
preAdvertisedIPs map[string]struct{}
startingAddrs []net.Addr
preTickHosts map[string]net.Addr
postTickHosts map[string]net.Addr
updateTriggered bool
newAddrs []net.Addr
removedAddrs map[string]struct{}
}{
// The set of addresses are the same before and after a tick we
// expect no change.
{
preTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
startingAddrs: startingAddrs,
postTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
updateTriggered: false,
},
// Half of the addresses are changed out, the new one should be
// added with the old one forgotten.
{
preTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
startingAddrs: startingAddrs,
postTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("9.9.9.9"),
},
},
updateTriggered: true,
newAddrs: []net.Addr{
&net.TCPAddr{
IP: net.ParseIP("9.9.9.9"),
},
},
removedAddrs: map[string]struct{}{
"8.8.8.8:0": {},
},
},
// All addresses change, they should all be refreshed.
{
preTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
startingAddrs: startingAddrs,
postTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("2.2.2.2"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("9.9.9.9"),
},
},
updateTriggered: true,
newAddrs: []net.Addr{
&net.TCPAddr{
IP: net.ParseIP("2.2.2.2"),
},
&net.TCPAddr{
IP: net.ParseIP("9.9.9.9"),
},
},
removedAddrs: map[string]struct{}{
"8.8.8.8:0": {},
"1.1.1.1:0": {},
},
},
// Two addresses, one has already been advertised on start up,
// so we only expect one of them to be announced again. After
// the tick we don't expect an update trigger since nothing.
// changed.
{
preAdvertisedIPs: map[string]struct{}{
"1.1.1.1:0": {},
},
startingAddrs: []net.Addr{
&net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
preTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
postTickHosts: map[string]net.Addr{
"test.com": &net.TCPAddr{
IP: net.ParseIP("1.1.1.1"),
},
"example.com": &net.TCPAddr{
IP: net.ParseIP("8.8.8.8"),
},
},
updateTriggered: false,
},
}
for idx, testCase := range testCases {
hostResps := make(chan net.Addr)
annReqs := make(chan annReq)
hostAnncer := NewHostAnnouncer(HostAnnouncerConfig{
Hosts: hosts,
AdvertisedIPs: testCase.preAdvertisedIPs,
RefreshTicker: ticker,
LookupHost: func(str string) (net.Addr, error) {
return <-hostResps, nil
},
AnnounceNewIPs: func(newAddrs []net.Addr,
removeAddrs map[string]struct{}) error {
annReqs <- annReq{
newAddrs: newAddrs,
removedAddrs: removeAddrs,
}
return nil
},
})
if err := hostAnncer.Start(); err != nil {
t.Fatalf("unable to start announcer: %v", err)
}
// As soon as the announcer starts, it'll try to query for the
// state of the hosts. We'll return the preTick state for all
// hosts.
for i := 0; i < len(hosts); i++ {
hostResps <- testCase.preTickHosts[hosts[i]]
}
// Since this is the first time the announcer is starting up,
// we expect it to advertise the hosts as they exist before any
// updates.
select {
case addrUpdate := <-annReqs:
assert.Equal(
t, testCase.startingAddrs, addrUpdate.newAddrs,
"addresses should match",
)
assert.Empty(
t, addrUpdate.removedAddrs,
"removed addrs should match",
)
case <-time.After(testTimeout):
t.Fatalf("#%v: no addr update sent", idx)
}
// We'll now force a tick which'll force another query. This
// time we'll respond with the set of the hosts as they should
// be post-tick.
ticker.Force <- time.Time{}
for i := 0; i < len(hosts); i++ {
hostResps <- testCase.postTickHosts[hosts[i]]
}
// If we expect an update, then we'll assert that we received
// the proper set of modified addresses.
if testCase.updateTriggered {
select {
// The receive update should match exactly what the
// test case dictates.
case addrUpdate := <-annReqs:
require.Equal(
t, testCase.newAddrs, addrUpdate.newAddrs,
"addresses should match",
)
require.Equal(
t, testCase.removedAddrs, addrUpdate.removedAddrs,
"removed addrs should match",
)
case <-time.After(testTimeout):
t.Fatalf("#%v: no addr update set", idx)
}
if err := hostAnncer.Stop(); err != nil {
t.Fatalf("unable to stop announcer: %v", err)
}
continue
}
// Otherwise, no updates should be sent since nothing changed.
select {
case <-annReqs:
t.Fatalf("#%v: expected no call to AnnounceNewIPs", idx)
case <-time.After(testTimeout):
}
if err := hostAnncer.Stop(); err != nil {
t.Fatalf("unable to stop announcer: %v", err)
}
}
}