Skip to content

Commit 7a55208

Browse files
committed
TUN-5590: QUIC datagram max user payload is 1217 bytes
1 parent 581cfb8 commit 7a55208

File tree

6 files changed

+115
-10
lines changed

6 files changed

+115
-10
lines changed

connection/quic.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ const (
2929
// HTTPMethodKey is used to get or set http method in QUIC ALPN if the underlying proxy connection type is HTTP.
3030
HTTPMethodKey = "HttpMethod"
3131
// HTTPHostKey is used to get or set http Method in QUIC ALPN if the underlying proxy connection type is HTTP.
32-
HTTPHostKey = "HttpHost"
33-
MaxDatagramFrameSize = 1220
32+
HTTPHostKey = "HttpHost"
3433
)
3534

3635
// QUICConnection represents the type that facilitates Proxying via QUIC streams.

datagramsession/session.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (clos
5454
go func() {
5555
// QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
5656
// This makes it safe to share readBuffer between iterations
57-
readBuffer := make([]byte, s.transport.MTU())
57+
readBuffer := make([]byte, s.transport.ReceiveMTU())
5858
for {
5959
if err := s.dstToTransport(readBuffer); err != nil {
6060
s.closeChan <- err

datagramsession/transport.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ type transport interface {
88
SendTo(sessionID uuid.UUID, payload []byte) error
99
// ReceiveFrom reads the next datagram from the transport
1010
ReceiveFrom() (uuid.UUID, []byte, error)
11-
// Max transmission unit of the transport
12-
MTU() uint
11+
// Max transmission unit to receive from the transport
12+
ReceiveMTU() uint
1313
}

datagramsession/transport_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ func (mt *mockQUICTransport) ReceiveFrom() (uuid.UUID, []byte, error) {
2222
return mt.reqChan.Receive(context.Background())
2323
}
2424

25-
func (mt *mockQUICTransport) MTU() uint {
26-
return 1220
25+
func (mt *mockQUICTransport) ReceiveMTU() uint {
26+
return 1217
2727
}
2828

2929
func (mt *mockQUICTransport) newRequest(ctx context.Context, sessionID uuid.UUID, payload []byte) error {

quic/datagram.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
)
1010

1111
const (
12-
MaxDatagramFrameSize = 1220
12+
// Max datagram frame size is limited to 1220 https://github.com/lucas-clemente/quic-go/blob/v0.24.0/internal/protocol/params.go#L138
13+
// However, 3 more bytes are reserved https://github.com/lucas-clemente/quic-go/blob/v0.24.0/internal/wire/datagram_frame.go#L61
14+
MaxDatagramFrameSize = 1217
1315
sessionIDLen = len(uuid.UUID{})
1416
)
1517

@@ -34,7 +36,7 @@ func NewDatagramMuxer(quicSession quic.Session) (*DatagramMuxer, error) {
3436
func (dm *DatagramMuxer) SendTo(sessionID uuid.UUID, payload []byte) error {
3537
if len(payload) > MaxDatagramFrameSize-sessionIDLen {
3638
// TODO: TUN-5302 return ICMP packet too big message
37-
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(payload), MaxDatagramFrameSize-sessionIDLen)
39+
return fmt.Errorf("origin UDP payload has %d bytes, which exceeds transport MTU %d", len(payload), dm.SendMTU())
3840
}
3941
msgWithID, err := SuffixSessionID(sessionID, payload)
4042
if err != nil {
@@ -57,7 +59,13 @@ func (dm *DatagramMuxer) ReceiveFrom() (uuid.UUID, []byte, error) {
5759
return ExtractSessionID(msg)
5860
}
5961

60-
func (dm *DatagramMuxer) MTU() uint {
62+
// Maximum application payload to send through QUIC datagram frame
63+
func (dm *DatagramMuxer) SendMTU() uint {
64+
return uint(MaxDatagramFrameSize - sessionIDLen)
65+
}
66+
67+
// Maximum expected bytes to read from QUIC datagram frame
68+
func (dm *DatagramMuxer) ReceiveMTU() uint {
6169
return MaxDatagramFrameSize
6270
}
6371

quic/datagram_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package quic
22

33
import (
4+
"bytes"
5+
"context"
6+
"crypto/rand"
7+
"crypto/rsa"
8+
"crypto/tls"
9+
"crypto/x509"
10+
"encoding/pem"
11+
"math/big"
412
"testing"
513

614
"github.com/google/uuid"
15+
"github.com/lucas-clemente/quic-go"
716
"github.com/stretchr/testify/require"
17+
"golang.org/x/sync/errgroup"
818
)
919

1020
var (
@@ -39,3 +49,91 @@ func TestSuffixSessionIDError(t *testing.T) {
3949
_, err = SuffixSessionID(testSessionID, msg)
4050
require.Error(t, err)
4151
}
52+
53+
func TestMaxDatagramPayload(t *testing.T) {
54+
payload := make([]byte, MaxDatagramFrameSize-sessionIDLen)
55+
56+
quicConfig := &quic.Config{
57+
KeepAlive: true,
58+
EnableDatagrams: true,
59+
}
60+
quicListener := newQUICListener(t, quicConfig)
61+
defer quicListener.Close()
62+
63+
errGroup, ctx := errgroup.WithContext(context.Background())
64+
// Run edge side of datagram muxer
65+
errGroup.Go(func() error {
66+
// Accept quic connection
67+
quicSession, err := quicListener.Accept(ctx)
68+
require.NoError(t, err)
69+
70+
muxer, err := NewDatagramMuxer(quicSession)
71+
require.NoError(t, err)
72+
73+
sessionID, receivedPayload, err := muxer.ReceiveFrom()
74+
require.NoError(t, err)
75+
require.Equal(t, testSessionID, sessionID)
76+
require.True(t, bytes.Equal(payload, receivedPayload))
77+
78+
return nil
79+
})
80+
81+
// Run cloudflared side of datagram muxer
82+
errGroup.Go(func() error {
83+
tlsClientConfig := &tls.Config{
84+
InsecureSkipVerify: true,
85+
NextProtos: []string{"argotunnel"},
86+
}
87+
// Establish quic connection
88+
quicSession, err := quic.DialAddrEarly(quicListener.Addr().String(), tlsClientConfig, quicConfig)
89+
require.NoError(t, err)
90+
91+
muxer, err := NewDatagramMuxer(quicSession)
92+
require.NoError(t, err)
93+
94+
err = muxer.SendTo(testSessionID, payload)
95+
require.NoError(t, err)
96+
97+
// Payload larger than transport MTU, should return an error
98+
largePayload := append(payload, byte(1))
99+
err = muxer.SendTo(testSessionID, largePayload)
100+
require.Error(t, err)
101+
102+
return nil
103+
})
104+
105+
require.NoError(t, errGroup.Wait())
106+
}
107+
108+
func newQUICListener(t *testing.T, config *quic.Config) quic.Listener {
109+
// Create a simple tls config.
110+
tlsConfig := generateTLSConfig()
111+
112+
listener, err := quic.ListenAddr("127.0.0.1:0", tlsConfig, config)
113+
require.NoError(t, err)
114+
115+
return listener
116+
}
117+
118+
func generateTLSConfig() *tls.Config {
119+
key, err := rsa.GenerateKey(rand.Reader, 1024)
120+
if err != nil {
121+
panic(err)
122+
}
123+
template := x509.Certificate{SerialNumber: big.NewInt(1)}
124+
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
125+
if err != nil {
126+
panic(err)
127+
}
128+
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
129+
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
130+
131+
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
132+
if err != nil {
133+
panic(err)
134+
}
135+
return &tls.Config{
136+
Certificates: []tls.Certificate{tlsCert},
137+
NextProtos: []string{"argotunnel"},
138+
}
139+
}

0 commit comments

Comments
 (0)