p2p/netutil: fix PredictFullConeNAT

The discovery protocol sends pings to previously-unseen nodes after
receiving a ping from them. This means there will always be a contact
entry and PredictFullConeNAT will always return false. Fix it by
checking that the contact occurred after the IP statement.
This commit is contained in:
Felix Lange 2018-10-01 15:00:55 +02:00
parent 6c81ee8815
commit c9813cdb6e
2 changed files with 32 additions and 6 deletions

View file

@ -31,7 +31,6 @@ type IPTracker struct {
clock mclock.Clock clock mclock.Clock
statements map[string]ipStatement statements map[string]ipStatement
contact map[string]mclock.AbsTime contact map[string]mclock.AbsTime
// TODO add DistinctNetSet for additional protection
} }
type ipStatement struct { type ipStatement struct {
@ -57,13 +56,15 @@ func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTra
} }
} }
// PredictFullConeNAT checks whether the local host is behind full cone NAT. // PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by
// checking whether any statement has been received from a node we didn't contact before
// the statement was made.
func (it *IPTracker) PredictFullConeNAT() bool { func (it *IPTracker) PredictFullConeNAT() bool {
now := it.clock.Now() now := it.clock.Now()
it.gcContact(now) it.gcContact(now)
it.gcStatements(now) it.gcStatements(now)
for host := range it.statements { for host, st := range it.statements {
if _, ok := it.contact[host]; !ok { if c, ok := it.contact[host]; !ok || c > st.time {
return true return true
} }
} }
@ -92,7 +93,8 @@ func (it *IPTracker) AddStatement(host, endpoint string) {
it.statements[host] = ipStatement{endpoint, it.clock.Now()} it.statements[host] = ipStatement{endpoint, it.clock.Now()}
} }
// AddContact records that a packet containing endpoint information has been sent to a certain host. // AddContact records that a packet containing our endpoint information has been sent to a
// certain host.
func (it *IPTracker) AddContact(host string) { func (it *IPTracker) AddContact(host string) {
it.contact[host] = it.clock.Now() it.contact[host] = it.clock.Now()
} }

View file

@ -17,6 +17,7 @@
package netutil package netutil
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -27,6 +28,7 @@ const (
opStatement = iota opStatement = iota
opContact opContact
opPredict opPredict
opCheckFullCone
) )
type iptrackTestEvent struct { type iptrackTestEvent struct {
@ -55,8 +57,25 @@ func TestIPTracker(t *testing.T) {
{opStatement, 10100, "127.0.0.1", "127.0.0.2"}, {opStatement, 10100, "127.0.0.1", "127.0.0.2"},
{opPredict, 10200, "127.0.0.1", ""}, {opPredict, 10200, "127.0.0.1", ""},
}, },
"fullcone": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opContact, 3000, "", "127.0.0.4"},
{opStatement, 3010, "127.0.0.1", "127.0.0.4"},
{opCheckFullCone, 3500, "false", ""},
},
"fullcone_2": {
{opContact, 0, "", "127.0.0.2"},
{opStatement, 10, "127.0.0.1", "127.0.0.2"},
{opContact, 2000, "", "127.0.0.3"},
{opStatement, 2010, "127.0.0.1", "127.0.0.3"},
{opStatement, 3000, "127.0.0.1", "127.0.0.4"},
{opContact, 3010, "", "127.0.0.4"},
{opCheckFullCone, 3500, "true", ""},
},
} }
for name, test := range tests { for name, test := range tests {
t.Run(name, func(t *testing.T) { runIPTrackerTest(t, test) }) t.Run(name, func(t *testing.T) { runIPTrackerTest(t, test) })
} }
@ -80,6 +99,11 @@ func runIPTrackerTest(t *testing.T, evs []iptrackTestEvent) {
if pred := it.PredictEndpoint(); pred != ev.ip { if pred := it.PredictEndpoint(); pred != ev.ip {
t.Errorf("op %d: wrong prediction %q, want %q", i, pred, ev.ip) t.Errorf("op %d: wrong prediction %q, want %q", i, pred, ev.ip)
} }
case opCheckFullCone:
pred := fmt.Sprintf("%t", it.PredictFullConeNAT())
if pred != ev.ip {
t.Errorf("op %d: wrong prediction %s, want %s", i, pred, ev.ip)
}
} }
} }
} }