Skip to content
This repository has been archived by the owner on May 11, 2022. It is now read-only.

Correlate dial back responses with actual inbound dials #48

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 181 additions & 24 deletions autonat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ import (
"testing"
"time"

pb "github.com/libp2p/go-libp2p-autonat/pb"
"github.com/libp2p/go-libp2p-core/crypto"
cryptopb "github.com/libp2p/go-libp2p-core/crypto/pb"
"github.com/libp2p/go-libp2p-core/event"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"

ggio "github.com/gogo/protobuf/io"
pb "github.com/libp2p/go-libp2p-autonat/pb"
bhost "github.com/libp2p/go-libp2p-blankhost"
swarmt "github.com/libp2p/go-libp2p-swarm/testing"
tnet "github.com/libp2p/go-libp2p-testing/net"
ma "github.com/multiformats/go-multiaddr"

ggio "github.com/gogo/protobuf/io"
"github.com/multiformats/go-varint"
"github.com/stretchr/testify/require"
)

func init() {
Expand All @@ -24,50 +30,154 @@ func init() {
AutoNATIdentifyDelay = 100 * time.Millisecond
}

// these are mock service implementations for testing
func makeAutoNATServicePrivate(ctx context.Context, t *testing.T) host.Host {
h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
h.SetStreamHandler(AutoNATProto, sayAutoNATPrivate)
return h
type mockAutoNatService struct {
t *testing.T
ctx context.Context

h host.Host

dialer host.Host
dialerPk *cryptopb.PublicKey
dialerSk crypto.PrivKey
}

func makeAutoNATServicePublic(ctx context.Context, t *testing.T) host.Host {
func mkMockAutoNatService(ctx context.Context, t *testing.T) *mockAutoNatService {
h := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
h.SetStreamHandler(AutoNATProto, sayAutoNATPublic)
return h

dialer := bhost.NewBlankHost(swarmt.GenSwarm(t, ctx))
sk := dialer.Peerstore().PrivKey(dialer.ID())
pk := dialer.Peerstore().PubKey(dialer.ID())

cpk, err := crypto.PublicKeyToProto(pk)
require.NoError(t, err, "failed to get proto public key")

return &mockAutoNatService{t, ctx, h, dialer, cpk, sk}
}

func (v *mockAutoNatService) newDialerCertificate(nonce uint64) *pb.Message_DialerIdentityCertificate {
msg := &pb.Message_DialerIdentityCertificate{}
msg.PublicKey = v.dialerPk
Comment on lines +58 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
msg := &pb.Message_DialerIdentityCertificate{}
msg.PublicKey = v.dialerPk
msg := &pb.Message_DialerIdentityCertificate{PublicKey: v.dialerPk}

s, err := v.dialerSk.Sign(varint.ToUvarint(nonce))
require.NoError(v.t, err, "failed to sign nonce")
msg.Signature = s
return msg
}

func (v *mockAutoNatService) readNonce(s network.Stream) uint64 {
var req pb.Message
r := ggio.NewDelimitedReader(s, network.MessageSizeMax)
err := r.ReadMsg(&req)
require.NoError(v.t, err, "failed to read dial req")
return *req.Dial.Nonce
}

func (v *mockAutoNatService) sayPublicNoDial(s network.Stream) {
defer s.Close()
// read nonce
n := v.readNonce(s)

// send OK response without dialing
w := ggio.NewDelimitedWriter(s)
res := pb.Message{
Type: pb.Message_DIAL_RESPONSE.Enum(),
DialResponse: v.newDialResponseOK(s.Conn().RemoteMultiaddr(), n),
}
w.WriteMsg(&res)
}

func (v *mockAutoNatService) sayPublicInvalidPublicKey(s network.Stream) {
defer s.Close()
// read nonce
n := v.readNonce(s)

// dial
require.NoError(v.t, v.dialer.Connect(v.ctx, peer.AddrInfo{s.Conn().RemotePeer(), []ma.Multiaddr{s.Conn().RemoteMultiaddr()}}),
"AutoNat dialer failed to dial back to the client host")

// use a different public key
p := tnet.RandPeerNetParamsOrFatal(v.t)
cpk, err := crypto.PublicKeyToProto(p.PubKey)
require.NoError(v.t, err)
v.dialerPk = cpk
v.dialerSk = p.PrivKey

w := ggio.NewDelimitedWriter(s)
res := pb.Message{
Type: pb.Message_DIAL_RESPONSE.Enum(),
DialResponse: v.newDialResponseOK(s.Conn().RemoteMultiaddr(), n),
}

w.WriteMsg(&res)
}

func sayAutoNATPrivate(s network.Stream) {
func (v *mockAutoNatService) sayPublicInvalidSignature(s network.Stream) {
defer s.Close()
// read nonce
n := v.readNonce(s)

// dial
require.NoError(v.t, v.dialer.Connect(v.ctx, peer.AddrInfo{s.Conn().RemotePeer(), []ma.Multiaddr{s.Conn().RemoteMultiaddr()}}),
"AutoNat dialer failed to dial back to the client host")

// send OK response with signature over an invalid nonce
w := ggio.NewDelimitedWriter(s)
res := pb.Message{
Type: pb.Message_DIAL_RESPONSE.Enum(),
DialResponse: newDialResponseError(pb.Message_E_DIAL_ERROR, "no dialable addresses"),
DialResponse: v.newDialResponseOK(s.Conn().RemoteMultiaddr(), n+1),
}
w.WriteMsg(&res)
}

func sayAutoNATPublic(s network.Stream) {
func (v *mockAutoNatService) sayPrivate(s network.Stream) {
defer s.Close()

// read nonce
n := v.readNonce(s)

// dial
require.NoError(v.t, v.dialer.Connect(v.ctx, peer.AddrInfo{s.Conn().RemotePeer(), []ma.Multiaddr{s.Conn().RemoteMultiaddr()}}),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it need to do this in the private case?

"AutoNat dialer failed to dial back to the client host")

w := ggio.NewDelimitedWriter(s)
res := pb.Message{
Type: pb.Message_DIAL_RESPONSE.Enum(),
DialResponse: newDialResponseOK(s.Conn().RemoteMultiaddr()),
DialResponse: v.newDialResponseError(pb.Message_E_DIAL_ERROR, n, "no dialable addresses"),
}
w.WriteMsg(&res)
}

func newDialResponseOK(addr ma.Multiaddr) *pb.Message_DialResponse {
func (v *mockAutoNatService) sayPublic(s network.Stream) {
defer s.Close()

// read nonce
n := v.readNonce(s)

// dial first
require.NoError(v.t, v.dialer.Connect(v.ctx, peer.AddrInfo{s.Conn().RemotePeer(), []ma.Multiaddr{s.Conn().RemoteMultiaddr()}}),
"AutoNat dialer failed to dial back to the client host")

w := ggio.NewDelimitedWriter(s)
res := pb.Message{
Type: pb.Message_DIAL_RESPONSE.Enum(),
DialResponse: v.newDialResponseOK(s.Conn().RemoteMultiaddr(), n),
}
w.WriteMsg(&res)
}

func (v *mockAutoNatService) newDialResponseOK(addr ma.Multiaddr, nonce uint64) *pb.Message_DialResponse {
dr := new(pb.Message_DialResponse)
dr.Status = pb.Message_OK.Enum()
dr.Addr = addr.Bytes()
dr.DialerIdentityCertificate = v.newDialerCertificate(nonce)

return dr
}

func newDialResponseError(status pb.Message_ResponseStatus, text string) *pb.Message_DialResponse {
func (v *mockAutoNatService) newDialResponseError(status pb.Message_ResponseStatus, nonce uint64, text string) *pb.Message_DialResponse {
dr := new(pb.Message_DialResponse)
dr.Status = status.Enum()
dr.StatusText = &text
dr.DialerIdentityCertificate = v.newDialerCertificate(nonce)
return dr
}

Expand All @@ -80,24 +190,69 @@ func makeAutoNAT(ctx context.Context, t *testing.T, ash host.Host) (host.Host, A
return h, a
}

func connect(t *testing.T, a, b host.Host) {
func connect(t *testing.T, ctx context.Context, a, b host.Host) {
pinfo := peer.AddrInfo{ID: a.ID(), Addrs: a.Addrs()}
err := b.Connect(context.Background(), pinfo)
err := b.Connect(ctx, pinfo)
if err != nil {
t.Fatal(err)
}
}

func TestServerAttacks(t *testing.T) {

tsts := map[string]struct {
streamFnc func(svc *mockAutoNatService)
}{
"Signature of an incorrect nonce": {
streamFnc: func(svc *mockAutoNatService) {
svc.h.SetStreamHandler(AutoNATProto, svc.sayPublicInvalidSignature)
}},
"No Actual Dial back": {
streamFnc: func(svc *mockAutoNatService) {
svc.h.SetStreamHandler(AutoNATProto, svc.sayPublicNoDial)
},
},
"Public Key does not match dialer peerId": {
streamFnc: func(svc *mockAutoNatService) {
svc.h.SetStreamHandler(AutoNATProto, svc.sayPublicInvalidPublicKey)
},
},
}

for k, ts := range tsts {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
svc := mkMockAutoNatService(ctx, t)
ts.streamFnc(svc)
hc, an := makeAutoNAT(ctx, t, svc.h)
status := an.Status()
if status != NATStatusUnknown {
t.Fatalf("unexpected NAT status: %d, test case is %s", status, k)
}

connect(t, ctx, svc.h, hc)
time.Sleep(2 * time.Second)

status = an.Status()
if status != NATStatusUnknown {
t.Fatalf("unexpected NAT status: %d, test case is \"%s\"", status, k)
}
}
}

// tests
func TestAutoNATPrivate(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

hs := makeAutoNATServicePrivate(ctx, t)
hc, an := makeAutoNAT(ctx, t, hs)
svc := mkMockAutoNatService(ctx, t)
svc.h.SetStreamHandler(AutoNATProto, svc.sayPrivate)

hc, an := makeAutoNAT(ctx, t, svc.h)

// subscribe to AutoNat events
s, err := hc.EventBus().Subscribe(&event.EvtLocalRoutabilityPrivate{})
defer s.Close()
if err != nil {
t.Fatalf("failed to subscribe to event EvtLocalRoutabilityPrivate, err=%s", err)
}
Expand All @@ -107,7 +262,7 @@ func TestAutoNATPrivate(t *testing.T) {
t.Fatalf("unexpected NAT status: %d", status)
}

connect(t, hs, hc)
connect(t, ctx, svc.h, hc)
time.Sleep(2 * time.Second)

status = an.Status()
Expand All @@ -131,11 +286,13 @@ func TestAutoNATPublic(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

hs := makeAutoNATServicePublic(ctx, t)
hc, an := makeAutoNAT(ctx, t, hs)
svc := mkMockAutoNatService(ctx, t)
svc.h.SetStreamHandler(AutoNATProto, svc.sayPublic)
hc, an := makeAutoNAT(ctx, t, svc.h)

// subscribe to AutoNat events
s, err := hc.EventBus().Subscribe(&event.EvtLocalRoutabilityPublic{})
defer s.Close()
if err != nil {
t.Fatalf("failed to subscribe to event EvtLocalRoutabilityPublic, err=%s", err)
}
Expand All @@ -145,7 +302,7 @@ func TestAutoNATPublic(t *testing.T) {
t.Fatalf("unexpected NAT status: %d", status)
}

connect(t, hs, hc)
connect(t, ctx, svc.h, hc)
time.Sleep(2 * time.Second)

status = an.Status()
Expand Down
Loading