Skip to content

Commit 27db053

Browse files
authored
fix: block incoming endpoints from call-me-maybe (#55)
* fix: block incoming endpoints from call-me-maybe Previously, (*magicsock.Conn).SetBlockEndpoints(true) would only prevent local endpoints from being sent to other peers. This setting is primarily used to prevent any direct connection from forming, regardless of which side initiated it, but any connections initiated locally to endpoints received from the other peer would still work. If endpoints are blocked, we will now drop any endpoints we already know of (via call-me-maybe) as well as block any future endpoints received via call-me-maybe. Endpoints received via coordination are not impacted (and should be blocked using a different mechanism). * Add debug logging to block endpoints test * Wipe all endpoints on BlockEndpoints * fixup! Wipe all endpoints on BlockEndpoints * add blockEndpoints field to endpoint
1 parent e62bfe0 commit 27db053

File tree

3 files changed

+150
-42
lines changed

3 files changed

+150
-42
lines changed

wgengine/magicsock/endpoint.go

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"net/netip"
1616
"reflect"
1717
"runtime"
18+
"strconv"
1819
"sync"
1920
"sync/atomic"
2021
"time"
@@ -62,6 +63,7 @@ type endpoint struct {
6263
lastFullPing mono.Time // last time we pinged all disco endpoints
6364
derpAddr netip.AddrPort // fallback/bootstrap path, if non-zero (non-zero for well-behaved clients)
6465

66+
blockEndpoints bool // if true, all new endpoints are discarded
6567
bestAddr addrLatency // best non-DERP path; zero if none
6668
bestAddrAt mono.Time // time best address re-confirmed
6769
trustBestAddrUntil mono.Time // time when bestAddr expires
@@ -207,6 +209,30 @@ func (de *endpoint) deleteEndpointLocked(why string, ep netip.AddrPort) {
207209
}
208210
}
209211

212+
func (de *endpoint) setBlockEndpoints(blocked bool) {
213+
de.mu.Lock()
214+
defer de.mu.Unlock()
215+
de.debugUpdates.Add(EndpointChange{
216+
When: time.Now(),
217+
What: "setBlockEndpoints-" + strconv.FormatBool(blocked),
218+
})
219+
220+
de.blockEndpoints = blocked
221+
if blocked {
222+
de.endpointState = map[netip.AddrPort]*endpointState{}
223+
if de.bestAddr.AddrPort.IsValid() {
224+
de.debugUpdates.Add(EndpointChange{
225+
When: time.Now(),
226+
What: "setBlockEndpoints-" + strconv.FormatBool(blocked) + "-bestAddr",
227+
From: de.bestAddr,
228+
})
229+
de.bestAddr = addrLatency{}
230+
}
231+
de.c.logf("magicsock: disco: node %s %s now using DERP only (all endpoints deleted)",
232+
de.publicKey.ShortString(), de.discoShort())
233+
}
234+
}
235+
210236
// initFakeUDPAddr populates fakeWGAddr with a globally unique fake UDPAddr.
211237
// The current implementation just uses the pointer value of de jammed into an IPv6
212238
// address, but it could also be, say, a counter.
@@ -764,22 +790,29 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
764790
}
765791

766792
var newIpps []netip.AddrPort
767-
for i, epStr := range n.Endpoints {
768-
if i > math.MaxInt16 {
769-
// Seems unlikely.
770-
continue
771-
}
772-
ipp, err := netip.ParseAddrPort(epStr)
773-
if err != nil {
774-
de.c.logf("magicsock: bogus netmap endpoint %q", epStr)
775-
continue
776-
}
777-
if st, ok := de.endpointState[ipp]; ok {
778-
st.index = int16(i)
779-
} else {
780-
de.endpointState[ipp] = &endpointState{index: int16(i)}
781-
newIpps = append(newIpps, ipp)
793+
if !de.blockEndpoints {
794+
for i, epStr := range n.Endpoints {
795+
if i > math.MaxInt16 {
796+
// Seems unlikely.
797+
continue
798+
}
799+
ipp, err := netip.ParseAddrPort(epStr)
800+
if err != nil {
801+
de.c.logf("magicsock: bogus netmap endpoint %q", epStr)
802+
continue
803+
}
804+
if st, ok := de.endpointState[ipp]; ok {
805+
st.index = int16(i)
806+
} else {
807+
de.endpointState[ipp] = &endpointState{index: int16(i)}
808+
newIpps = append(newIpps, ipp)
809+
}
782810
}
811+
} else {
812+
de.c.dlogf("[v1] magicsock: disco: updateFromNode: %v received %d endpoints, but endpoints blocked",
813+
de.publicKey.ShortString(),
814+
len(n.Endpoints),
815+
)
783816
}
784817
if len(newIpps) > 0 {
785818
de.debugUpdates.Add(EndpointChange{
@@ -809,6 +842,12 @@ func (de *endpoint) addCandidateEndpoint(ep netip.AddrPort, forRxPingTxID stun.T
809842
de.mu.Lock()
810843
defer de.mu.Unlock()
811844

845+
isDERP := ep.Addr() == tailcfg.DerpMagicIPAddr
846+
if isDERP && de.blockEndpoints {
847+
de.c.logf("[unexpected] attempted to add candidate endpoint %v to %v (%v) but endpoints blocked", ep, de.discoShort(), de.publicKey.ShortString())
848+
return false
849+
}
850+
812851
if st, ok := de.endpointState[ep]; ok {
813852
duplicatePing = forRxPingTxID == st.lastGotPingTxID
814853
if !duplicatePing {

wgengine/magicsock/magicsock.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ type Conn struct {
217217
// blockEndpoints is whether to avoid capturing, storing and sending
218218
// endpoints gathered from local interfaces or STUN. Only DERP endpoints
219219
// will be sent.
220+
// This will also block incoming endpoints received via call-me-maybe disco
221+
// packets.
220222
blockEndpoints bool
221223
// endpointsUpdateActive indicates that updateEndpoints is
222224
// currently running. It's used to deduplicate concurrent endpoint
@@ -855,10 +857,10 @@ func (c *Conn) DiscoPublicKey() key.DiscoPublic {
855857
return c.discoPublic
856858
}
857859

858-
// SetBlockEndpoints sets the blockEndpoints field. If changed, endpoints will
859-
// be updated to apply the new settings. Existing connections may continue to
860-
// use the old setting until they are reestablished. Disabling endpoints does
861-
// not affect the UDP socket or portmapper.
860+
// SetBlockEndpoints sets the blockEndpoints field. If enabled, all peer
861+
// endpoints will be cleared from the peer map and every connection will
862+
// immediately switch to DERP. Disabling endpoints does not affect the UDP
863+
// socket or portmapper.
862864
func (c *Conn) SetBlockEndpoints(block bool) {
863865
c.mu.Lock()
864866
defer c.mu.Unlock()
@@ -868,6 +870,7 @@ func (c *Conn) SetBlockEndpoints(block bool) {
868870
return
869871
}
870872

873+
// Re-gather local endpoints.
871874
const why = "SetBlockEndpoints"
872875
if c.endpointsUpdateActive {
873876
if c.wantEndpointsUpdate != why {
@@ -878,6 +881,11 @@ func (c *Conn) SetBlockEndpoints(block bool) {
878881
c.endpointsUpdateActive = true
879882
go c.updateEndpoints(why)
880883
}
884+
885+
// Update all endpoints to abide by the new setting.
886+
c.peerMap.forEachEndpoint(func(ep *endpoint) {
887+
ep.setBlockEndpoints(block)
888+
})
881889
}
882890

883891
// determineEndpoints returns the machine's endpoint addresses. It
@@ -1435,6 +1443,12 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
14351443
return
14361444
}
14371445

1446+
isDERP := src.Addr() == tailcfg.DerpMagicIPAddr
1447+
if !isDERP && c.blockEndpoints {
1448+
// Ignore disco messages over UDP if endpoints are blocked.
1449+
return
1450+
}
1451+
14381452
if !c.peerMap.anyEndpointForDiscoKey(sender) {
14391453
metricRecvDiscoBadPeer.Add(1)
14401454
if debugDisco() {
@@ -1490,7 +1504,6 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
14901504
return
14911505
}
14921506

1493-
isDERP := src.Addr() == tailcfg.DerpMagicIPAddr
14941507
if isDERP {
14951508
metricRecvDiscoDERP.Add(1)
14961509
} else {
@@ -1535,7 +1548,15 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netip.AddrPort, derpNodeSrc ke
15351548
c.logf("[unexpected] CallMeMaybe from peer via DERP whose netmap discokey != disco source")
15361549
return
15371550
}
1538-
c.dlogf("[v1] magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
1551+
if c.blockEndpoints {
1552+
c.dlogf("[v1] magicsock: disco: %v<-%v (%v, %v) got call-me-maybe with %d endpoints, but endpoints blocked",
1553+
c.discoShort, epDisco.short,
1554+
ep.publicKey.ShortString(), derpStr(src.String()),
1555+
len(dm.MyNumber),
1556+
)
1557+
return
1558+
}
1559+
c.dlogf("[v1] magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints",
15391560
c.discoShort, epDisco.short,
15401561
ep.publicKey.ShortString(), derpStr(src.String()),
15411562
len(dm.MyNumber))
@@ -1963,13 +1984,19 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
19631984
debugUpdates: ringbuffer.New[EndpointChange](entriesPerBuffer),
19641985
publicKey: n.Key,
19651986
publicKeyHex: n.Key.UntypedHexString(),
1987+
blockEndpoints: c.blockEndpoints,
19661988
sentPing: map[stun.TxID]sentPing{},
19671989
endpointState: map[netip.AddrPort]*endpointState{},
19681990
heartbeatDisabled: heartbeatDisabled,
19691991
isWireguardOnly: n.IsWireGuardOnly,
19701992
}
1971-
if len(n.Addresses) > 0 {
1972-
ep.nodeAddr = n.Addresses[0].Addr()
1993+
for _, addr := range n.Addresses {
1994+
// Only set nodeAddr if it's a DERP address while endpoints are
1995+
// blocked.
1996+
if !c.blockEndpoints || addr.Addr() == tailcfg.DerpMagicIPAddr {
1997+
ep.nodeAddr = addr.Addr()
1998+
break
1999+
}
19732000
}
19742001
ep.initFakeUDPAddr()
19752002
if n.DiscoKey.IsZero() {

wgengine/magicsock/magicsock_test.go

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3060,30 +3060,27 @@ func TestBlockEndpointsDERPOK(t *testing.T) {
30603060
logf, closeLogf := logger.LogfCloser(t.Logf)
30613061
defer closeLogf()
30623062

3063-
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
3064-
defer cleanup()
3063+
derpMap, cleanupDerp := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
3064+
defer cleanupDerp()
30653065

30663066
ms1 := newMagicStack(t, logger.WithPrefix(logf, "conn1: "), d.m1, derpMap)
30673067
defer ms1.Close()
3068+
ms1.conn.SetDebugLoggingEnabled(true)
30683069
ms2 := newMagicStack(t, logger.WithPrefix(logf, "conn2: "), d.m2, derpMap)
30693070
defer ms2.Close()
3071+
ms2.conn.SetDebugLoggingEnabled(true)
30703072

3071-
cleanup = meshStacks(logf, nil, ms1, ms2)
3072-
defer cleanup()
3073+
cleanupMesh := meshStacks(logf, nil, ms1, ms2)
3074+
defer cleanupMesh()
30733075

30743076
m1IP := ms1.IP()
30753077
m2IP := ms2.IP()
30763078
logf("IPs: %s %s", m1IP, m2IP)
30773079

3078-
// SetBlockEndpoints is called later since it's incompatible with the test
3079-
// meshStacks implementations.
3080-
ms1.conn.SetBlockEndpoints(true)
3081-
ms2.conn.SetBlockEndpoints(true)
3082-
waitForNoEndpoints(t, ms1.conn)
3083-
waitForNoEndpoints(t, ms2.conn)
3084-
3085-
cleanup = newPinger(t, logf, ms1, ms2)
3086-
defer cleanup()
3080+
cleanupPinger1 := newPinger(t, logf, ms1, ms2)
3081+
defer cleanupPinger1()
3082+
cleanupPinger2 := newPinger(t, logf, ms2, ms1)
3083+
defer cleanupPinger2()
30873084

30883085
// Wait for both peers to know about each other.
30893086
for {
@@ -3098,14 +3095,42 @@ func TestBlockEndpointsDERPOK(t *testing.T) {
30983095
break
30993096
}
31003097

3101-
cleanup = newPinger(t, t.Logf, ms1, ms2)
3102-
defer cleanup()
3098+
waitForEndpoints(t, ms1.conn)
3099+
waitForEndpoints(t, ms2.conn)
31033100

3104-
if len(ms1.conn.activeDerp) == 0 {
3105-
t.Errorf("unexpected DERP empty got: %v want: >0", len(ms1.conn.activeDerp))
3101+
// SetBlockEndpoints is called later since it's incompatible with the test
3102+
// meshStacks implementations.
3103+
// We only set it on ms1, since ms2's endpoints should be ignored by ms1.
3104+
ms1.conn.SetBlockEndpoints(true)
3105+
3106+
// All endpoints should've been immediately removed from ms1.
3107+
ep2, ok := ms1.conn.peerMap.endpointForNodeKey(ms2.Public())
3108+
if !ok {
3109+
t.Fatalf("endpoint not found for ms2 in ms1")
31063110
}
3107-
if len(ms2.conn.activeDerp) == 0 {
3108-
t.Errorf("unexpected DERP empty got: %v want: >0", len(ms2.conn.activeDerp))
3111+
ep2.mu.Lock()
3112+
if !ep2.blockEndpoints {
3113+
t.Fatalf("endpoints not blocked on ep2 in ms1")
3114+
}
3115+
if len(ep2.endpointState) != 0 {
3116+
ep2.mu.Unlock()
3117+
t.Fatalf("endpoints not removed on ep2 in ms1")
3118+
}
3119+
ep2.mu.Unlock()
3120+
3121+
// Wait for endpoints to finish updating.
3122+
waitForNoEndpoints(t, ms1.conn)
3123+
3124+
// Give time for another call-me-maybe packet to arrive. I couldn't think of
3125+
// a better way than sleeping without making a bunch of changes.
3126+
t.Logf("sleeping for call-me-maybe packet to be received and ignored")
3127+
time.Sleep(time.Second)
3128+
t.Logf("done sleeping")
3129+
3130+
ep2.mu.Lock()
3131+
defer ep2.mu.Unlock()
3132+
for i := range ep2.endpointState {
3133+
t.Fatalf("endpoint %q not missing", i.String())
31093134
}
31103135
}
31113136

@@ -3129,3 +3154,20 @@ func waitForNoEndpoints(t *testing.T, ms *Conn) {
31293154
}
31303155
t.Log("endpoints are blocked")
31313156
}
3157+
3158+
func waitForEndpoints(t *testing.T, ms *Conn) {
3159+
t.Helper()
3160+
for i := 0; i < 50; i++ {
3161+
time.Sleep(100 * time.Millisecond)
3162+
ms.mu.Lock()
3163+
for _, ep := range ms.lastEndpoints {
3164+
if ep.Addr.Addr() != tailcfg.DerpMagicIPAddr {
3165+
t.Log("endpoint found")
3166+
ms.mu.Unlock()
3167+
return
3168+
}
3169+
}
3170+
ms.mu.Unlock()
3171+
}
3172+
t.Fatal("endpoint was not found after 50 attempts")
3173+
}

0 commit comments

Comments
 (0)