1
0
Эх сурвалжийг харах

e2e: Back to back hybrid handshakes, and a classical attempt crossing a hybrid one

JackDoan 6 өдөр өмнө
parent
commit
5edcaefa33
1 өөрчлөгдсөн 104 нэмэгдсэн , 0 устгасан
  1. 104 0
      e2e/pq_hybrid_test.go

+ 104 - 0
e2e/pq_hybrid_test.go

@@ -397,3 +397,107 @@ func TestHybridRelays(t *testing.T) {
 	relayControl.Stop()
 	relayControl.Stop()
 	theirControl.Stop()
 	theirControl.Stop()
 }
 }
+
+// TestHybridBackToBack rebuilds the tunnel more times than the responder's
+// per-source quota allows messages in progress, faster than its table expires
+// entries. The repair fragments trailing each completed msg1 must not count
+// against that quota, or later handshakes fall back to classical.
+func TestHybridBackToBack(t *testing.T) {
+	t.Parallel()
+	myControl, theirControl, myVpn, theirVpn, _, theirUdp, r := newHybridPair(t, pqConfig(true, true, false), pqConfig(false, true, false))
+	defer r.RenderFlow()
+
+	for i := range 6 {
+		// Closing the last tunnel forgets the peer's address.
+		myControl.InjectLightHouseAddr(theirVpn[0].Addr(), theirUdp)
+		myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
+		require.True(t, routeUntilData(t, r, theirControl, 5*time.Second, nil), "tunnel %d never came up", i)
+		theirControl.GetFromTun(true)
+		// Let the trailing repair fragments land before tearing down.
+		r.RouteForAllExitFuncOrTimeout(50*time.Millisecond, func(*udp.Packet, *nebula.Control) router.ExitType { return router.KeepRouting })
+
+		mine := myControl.GetHostInfoByVpnAddr(theirVpn[0].Addr(), false)
+		theirs := theirControl.GetHostInfoByVpnAddr(myVpn[0].Addr(), false)
+		require.NotNil(t, mine)
+		require.NotNil(t, theirs)
+		assert.Equal(t, cert.Version3, mine.Cert.Version(), "tunnel %d is hybrid", i)
+		assert.Equal(t, cert.Version3, theirs.Cert.Version(), "tunnel %d is hybrid", i)
+
+		myControl.CloseTunnel(theirVpn[0].Addr(), true)
+		theirControl.CloseTunnel(myVpn[0].Addr(), true)
+	}
+
+	myControl.Stop()
+	theirControl.Stop()
+}
+
+// TestHybridRaceWithClassical has the initiator's classical attempt reach the
+// responder, which completes it at once, while the hybrid handshake is still
+// waiting for msg3. The initiator gets the hybrid msg2 first and commits to it,
+// so the responder must take the hybrid tunnel when msg3 arrives even though the
+// classical one is newer.
+func TestHybridRaceWithClassical(t *testing.T) {
+	t.Parallel()
+	// Both trust the v2 form of the CA too, or the classical attempt could not complete.
+	ca := newHybridCA(t)
+	ca2, _ := ca.v2()
+	myControl, theirControl, myVpn, theirVpn, _, _, r := newHybridPairWithCA(t, ca,
+		withCA(pqConfig(true, true, false), ca.ca, ca2),
+		withCA(pqConfig(false, true, false), ca.ca, ca2),
+	)
+	defer r.RenderFlow()
+
+	var heldMsg2 []*udp.Packet
+	var sawClassical, recvErrors int
+	myControl.InjectTunPacket(BuildTunUDPPacket(theirVpn[0].Addr(), 80, myVpn[0].Addr(), 80, []byte("Hi from me")))
+	require.True(t, r.RouteForAllExitFuncOrTimeout(3*time.Second, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
+		h, fh := handshakeOf(p)
+		switch {
+		case h != nil && fh != nil && h.MessageCounter == 2:
+			// Hold the hybrid msg2 back until the classical attempt has gone through.
+			heldMsg2 = append(heldMsg2, p.Copy())
+			return router.Drop
+		case h != nil && fh == nil && h.MessageCounter == 1:
+			sawClassical++
+			return router.RouteAndExit
+		}
+		return router.KeepRouting
+	}), "no classical attempt")
+	require.Positive(t, sawClassical)
+
+	// Lose the classical msg2, so the initiator only ever completes the hybrid handshake.
+	r.RouteForAllExitFuncOrTimeout(50*time.Millisecond, func(p *udp.Packet, receiver *nebula.Control) router.ExitType {
+		if h, fh := handshakeOf(p); h != nil && fh == nil && h.MessageCounter == 2 {
+			return router.Drop
+		}
+		if h, fh := handshakeOf(p); h != nil && fh != nil && h.MessageCounter == 2 {
+			heldMsg2 = append(heldMsg2, p.Copy())
+			return router.Drop
+		}
+		return router.KeepRouting
+	})
+	require.NotNil(t, theirControl.GetHostInfoByVpnAddr(myVpn[0].Addr(), false), "the responder completed the classical handshake")
+
+	for _, p := range heldMsg2 {
+		r.InjectUDPPacket(theirControl, myControl, p)
+	}
+	require.True(t, routeUntilData(t, r, theirControl, 3*time.Second, func(p *udp.Packet, _ *nebula.Control) router.ExitType {
+		if isType(p, header.RecvError) {
+			recvErrors++
+		}
+		return router.KeepRouting
+	}), "data never arrived")
+	assertUdpPacket(t, []byte("Hi from me"), theirControl.GetFromTun(true), myVpn[0].Addr(), theirVpn[0].Addr(), 80, 80)
+
+	assert.Zero(t, recvErrors)
+	mine := myControl.GetHostInfoByVpnAddr(theirVpn[0].Addr(), false)
+	theirs := theirControl.GetHostInfoByVpnAddr(myVpn[0].Addr(), false)
+	require.NotNil(t, mine)
+	require.NotNil(t, theirs)
+	assert.Equal(t, cert.Version3, mine.Cert.Version())
+	assert.Equal(t, cert.Version3, theirs.Cert.Version(), "the responder's primary tunnel is the hybrid one")
+	assert.Equal(t, mine.LocalIndex, theirs.RemoteIndex)
+
+	myControl.Stop()
+	theirControl.Stop()
+}