File: stateless_reset_test.go

package info (click to toggle)
golang-github-lucas-clemente-quic-go 0.54.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 4,312 kB
  • sloc: sh: 54; makefile: 7
file content (126 lines) | stat: -rw-r--r-- 3,221 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package self_test

import (
	"context"
	"crypto/rand"
	"net"
	"sync/atomic"
	"testing"
	"time"

	"github.com/quic-go/quic-go"
	quicproxy "github.com/quic-go/quic-go/integrationtests/tools/proxy"
	"github.com/stretchr/testify/require"
)

func TestStatelessResets(t *testing.T) {
	t.Run("zero-length connection IDs", func(t *testing.T) {
		testStatelessReset(t, 0)
	})
	t.Run("10 byte connection IDs", func(t *testing.T) {
		testStatelessReset(t, 10)
	})
}

func testStatelessReset(t *testing.T, connIDLen int) {
	var statelessResetKey quic.StatelessResetKey
	rand.Read(statelessResetKey[:])

	c := newUDPConnLocalhost(t)
	tr := &quic.Transport{
		Conn:              c,
		StatelessResetKey: &statelessResetKey,
	}
	defer tr.Close()
	ln, err := tr.Listen(getTLSConfig(), getQuicConfig(nil))
	require.NoError(t, err)

	serverErr := make(chan error, 1)
	go func() {
		conn, err := ln.Accept(context.Background())
		if err != nil {
			serverErr <- err
			return
		}
		str, err := conn.OpenStream()
		if err != nil {
			serverErr <- err
			return
		}
		_, err = str.Write([]byte("foobar"))
		if err != nil {
			serverErr <- err
			return
		}
		close(serverErr)
	}()

	var drop atomic.Bool
	proxy := quicproxy.Proxy{
		Conn:       newUDPConnLocalhost(t),
		ServerAddr: ln.Addr().(*net.UDPAddr),
		DropPacket: func(quicproxy.Direction, net.Addr, net.Addr, []byte) bool { return drop.Load() },
	}
	require.NoError(t, proxy.Start())
	defer proxy.Close()

	var conn *quic.Conn
	if connIDLen > 0 {
		cl := &quic.Transport{
			Conn:               newUDPConnLocalhost(t),
			ConnectionIDLength: connIDLen,
		}
		defer cl.Close()
		var err error
		conn, err = cl.Dial(
			context.Background(),
			proxy.LocalAddr(),
			getTLSClientConfig(),
			getQuicConfig(&quic.Config{MaxIdleTimeout: 2 * time.Second}),
		)
		require.NoError(t, err)
	} else {
		conn, err = quic.Dial(
			context.Background(),
			newUDPConnLocalhost(t),
			proxy.LocalAddr(),
			getTLSClientConfig(),
			getQuicConfig(&quic.Config{MaxIdleTimeout: 2 * time.Second}),
		)
		require.NoError(t, err)
	}
	str, err := conn.AcceptStream(context.Background())
	require.NoError(t, err)
	data := make([]byte, 6)
	_, err = str.Read(data)
	require.NoError(t, err)
	require.Equal(t, []byte("foobar"), data)

	// make sure that the CONNECTION_CLOSE is dropped
	drop.Store(true)
	require.NoError(t, ln.Close())
	require.NoError(t, tr.Close())
	require.NoError(t, <-serverErr)
	time.Sleep(100 * time.Millisecond)

	// We need to create a new Transport here, since the old one is still sending out
	// CONNECTION_CLOSE packets for (recently) closed connections).
	tr2 := &quic.Transport{
		Conn:              c,
		StatelessResetKey: &statelessResetKey,
	}
	defer tr2.Close()
	ln2, err := tr2.Listen(getTLSConfig(), getQuicConfig(nil))
	require.NoError(t, err)
	drop.Store(false)

	// Trigger something (not too small) to be sent, so that we receive the stateless reset.
	// If the client already sent another packet, it might already have received a packet.
	_, serr := str.Write([]byte("Lorem ipsum dolor sit amet."))
	if serr == nil {
		_, serr = str.Read([]byte{0})
	}
	require.Error(t, serr)
	require.IsType(t, &quic.StatelessResetError{}, serr)
	require.NoError(t, ln2.Close())
}