Skip to content

Commit e290a68

Browse files
authored
Fix relay re-establishment for handshake on Disestablised entry (#1805)
handleOutsideRelayPacket filled ViaSender.remoteIdx with relay.RemoteIndex, an index from the relay peer's index space, but the rescue in sendHandshakeResponse looks that value up in relayForByIdx, which is keyed by local index. The lookup could never hit, so a terminal relay entry left Disestablished by a one-sided teardown stayed Disestablished even after a valid handshake arrived over it. The responder's first transmit then failed to find an Established relay, deleted its only relay entry, and every subsequent send was silently dropped until dead-tunnel detection forced a re-handshake.
1 parent 6c3972f commit e290a68

4 files changed

Lines changed: 65 additions & 3 deletions

File tree

e2e/handshakes_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,70 @@ func TestReestablishRelays(t *testing.T) {
725725

726726
}
727727

728+
func TestRelayHandshakeOverDisestablishedEntry(t *testing.T) {
729+
t.Parallel()
730+
// If them tears down the tunnel while me keeps Established relay state, me's next
731+
// handshake flows through the relay with no fresh CreateRelayRequest and lands on
732+
// them's Disestablished terminal relay entry. them must re-establish that entry, or
733+
// its first transmit deletes its only relay and the tunnel is born transmit-dead:
734+
// them can receive but every send is silently dropped.
735+
ca, _, caKey, _ := cert_test.NewTestCaCert(cert.Version1, cert.Curve_CURVE25519, time.Now(), time.Now().Add(10*time.Minute), nil, nil, []string{})
736+
myControl, myVpnIpNet, _, _ := newSimpleServer(cert.Version1, ca, caKey, "me ", "10.128.0.1/24", m{"relay": m{"use_relays": true}})
737+
relayControl, relayVpnIpNet, relayUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "relay ", "10.128.0.128/24", m{"relay": m{"am_relay": true}})
738+
theirControl, theirVpnIpNet, theirUdpAddr, _ := newSimpleServer(cert.Version1, ca, caKey, "them ", "10.128.0.2/24", m{"relay": m{"use_relays": true}})
739+
740+
// Teach my how to get to the relay and that their can be reached via the relay
741+
myControl.InjectLightHouseAddr(relayVpnIpNet[0].Addr(), relayUdpAddr)
742+
myControl.InjectRelays(theirVpnIpNet[0].Addr(), []netip.Addr{relayVpnIpNet[0].Addr()})
743+
relayControl.InjectLightHouseAddr(theirVpnIpNet[0].Addr(), theirUdpAddr)
744+
745+
// Build a router so we don't have to reason who gets which packet
746+
r := router.NewR(t, myControl, relayControl, theirControl)
747+
defer r.RenderFlow()
748+
749+
// Start the servers
750+
myControl.Start()
751+
relayControl.Start()
752+
theirControl.Start()
753+
754+
t.Log("Trigger a handshake from me to them via the relay")
755+
myControl.InjectTunPacket(BuildTunUDPPacket(theirVpnIpNet[0].Addr(), 80, myVpnIpNet[0].Addr(), 80, []byte("Hi from me")))
756+
757+
p := r.RouteForAllUntilTxTun(theirControl)
758+
assertUdpPacket(t, []byte("Hi from me"), p, myVpnIpNet[0].Addr(), theirVpnIpNet[0].Addr(), 80, 80)
759+
oldIdx := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false).LocalIndex
760+
761+
t.Log("Close the tunnel on them only, marking their relay entry Disestablished")
762+
theirControl.CloseTunnel(myVpnIpNet[0].Addr(), true)
763+
764+
t.Log("Re-handshake from me, riding the still-Established relay state")
765+
myControl.ReHandshake(theirVpnIpNet[0].Addr())
766+
for {
767+
h := myControl.GetHostInfoByVpnAddr(theirVpnIpNet[0].Addr(), false)
768+
if h != nil && h.LocalIndex != oldIdx && h.RemoteIndex != 0 {
769+
break
770+
}
771+
r.RouteForAllExitFunc(func(*udp.Packet, *nebula.Control) router.ExitType {
772+
return router.RouteAndExit
773+
})
774+
}
775+
776+
hAtThem := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
777+
require.NotNil(t, hAtThem, "them should have completed the relayed handshake")
778+
require.Equal(t, []netip.Addr{relayVpnIpNet[0].Addr()}, hAtThem.CurrentRelaysToMe, "them should know a relay for the new tunnel")
779+
780+
t.Log("Send from them to me; their only relay entry must survive the transmit")
781+
theirControl.InjectTunPacket(BuildTunUDPPacket(myVpnIpNet[0].Addr(), 80, theirVpnIpNet[0].Addr(), 80, []byte("Hi from them")))
782+
require.Never(t, func() bool {
783+
h := theirControl.GetHostInfoByVpnAddr(myVpnIpNet[0].Addr(), false)
784+
return h == nil || len(h.CurrentRelaysToMe) == 0
785+
}, time.Second, 10*time.Millisecond, "them deleted its only relay entry; the tunnel is permanently transmit-dead")
786+
787+
p = r.RouteForAllUntilTxTun(myControl)
788+
assertUdpPacket(t, []byte("Hi from them"), p, theirVpnIpNet[0].Addr(), myVpnIpNet[0].Addr(), 80, 80)
789+
r.RenderHostmaps("Final hostmaps", myControl, relayControl, theirControl)
790+
}
791+
728792
func TestStage1RaceRelays(t *testing.T) {
729793
t.Parallel()
730794
//NOTE: this is a race between me and relay resulting in a full tunnel from me to them via relay

handshake_manager.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1077,7 +1077,7 @@ func (hm *HandshakeManager) sendHandshakeResponse(via ViaSender, msg []byte, hos
10771077
hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
10781078
// We received a valid handshake on this relay, so make sure the relay
10791079
// state reflects that, in case it had been marked Disestablished.
1080-
via.relayHI.relayState.UpdateRelayForByIdxState(via.remoteIdx, Established)
1080+
via.relayHI.relayState.UpdateRelayForByIdxState(via.relay.LocalIndex, Established)
10811081
f.SendVia(via.relayHI, via.relay, msg, make([]byte, 12), make([]byte, mtu), false)
10821082
f.l.Info("Handshake message sent", append(logFields, "relay", via.relayHI.vpnAddrs[0])...)
10831083
}

hostmap.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,6 @@ type HostInfo struct {
287287
type ViaSender struct {
288288
UdpAddr netip.AddrPort
289289
relayHI *HostInfo // relayHI is the host info object of the relay
290-
remoteIdx uint32 // remoteIdx is the index included in the header of the received packet
291290
relay *Relay // relay contains the rest of the relay information, including the PeerIP of the host trying to communicate with us.
292291
IsRelayed bool // IsRelayed is true if the packet was sent through a relay
293292
}

outside.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ func (f *Interface) handleOutsideRelayPacket(hostinfo *HostInfo, via ViaSender,
214214
via = ViaSender{
215215
UdpAddr: via.UdpAddr,
216216
relayHI: hostinfo,
217-
remoteIdx: relay.RemoteIndex,
218217
relay: relay,
219218
IsRelayed: true,
220219
}

0 commit comments

Comments
 (0)