Skip to content

Commit ed024d0

Browse files
TUN-4597: Add a QUIC server skeleton
- Added a QUIC server to accept streams - Unit test for this server also tests ALPN - Temporary echo capability for HTTP ConnectionType
1 parent fd40001 commit ed024d0

File tree

768 files changed

+84848
-15639
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

768 files changed

+84848
-15639
lines changed

connection/quic.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package connection
2+
3+
import (
4+
"context"
5+
"crypto/tls"
6+
"net"
7+
8+
"github.com/lucas-clemente/quic-go"
9+
"github.com/pkg/errors"
10+
"github.com/rs/zerolog"
11+
12+
quicpogs "github.com/cloudflare/cloudflared/quic"
13+
)
14+
15+
// QUICConnection represents the type that facilitates Proxying via QUIC streams.
16+
type QUICConnection struct {
17+
session quic.Session
18+
logger zerolog.Logger
19+
}
20+
21+
// NewQUICConnection returns a new instance of QUICConnection.
22+
func NewQUICConnection(
23+
ctx context.Context,
24+
quicConfig *quic.Config,
25+
edgeAddr net.Addr,
26+
tlsConfig *tls.Config,
27+
logger zerolog.Logger,
28+
) (*QUICConnection, error) {
29+
session, err := quic.DialAddr(edgeAddr.String(), tlsConfig, quicConfig)
30+
if err != nil {
31+
return nil, errors.Wrap(err, "failed to dial to edge")
32+
}
33+
34+
//TODO: RegisterConnectionRPC here.
35+
36+
return &QUICConnection{
37+
session: session,
38+
logger: logger,
39+
}, nil
40+
}
41+
42+
// Serve starts a QUIC session that begins accepting streams.
43+
func (q *QUICConnection) Serve(ctx context.Context) error {
44+
ctx, cancel := context.WithCancel(ctx)
45+
defer cancel()
46+
47+
for {
48+
stream, err := q.session.AcceptStream(ctx)
49+
if err != nil {
50+
return errors.Wrap(err, "failed to accept QUIC stream")
51+
}
52+
go func() {
53+
defer stream.Close()
54+
if err = q.handleStream(stream); err != nil {
55+
q.logger.Err(err).Msg("Failed to handle QUIC stream")
56+
}
57+
}()
58+
}
59+
}
60+
61+
// Close calls this to close the QuicConnection stream.
62+
func (q *QUICConnection) Close() {
63+
q.session.CloseWithError(0, "")
64+
}
65+
66+
func (q *QUICConnection) handleStream(stream quic.Stream) error {
67+
connectRequest, err := quicpogs.ReadConnectRequestData(stream)
68+
if err != nil {
69+
return err
70+
}
71+
72+
switch connectRequest.Type {
73+
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
74+
// Temporary dummy code for the unit test.
75+
if err := quicpogs.WriteConnectResponseData(stream, nil, quicpogs.Metadata{Key: "HTTPStatus", Val: "200"}); err != nil {
76+
return err
77+
}
78+
79+
stream.Write([]byte("OK"))
80+
case quicpogs.ConnectionTypeTCP:
81+
82+
}
83+
return nil
84+
}

connection/quic_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package connection
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"crypto/rsa"
7+
"crypto/tls"
8+
"crypto/x509"
9+
"encoding/pem"
10+
"io/ioutil"
11+
"math/big"
12+
"net"
13+
"os"
14+
"sync"
15+
"testing"
16+
17+
"github.com/lucas-clemente/quic-go"
18+
"github.com/rs/zerolog"
19+
"github.com/stretchr/testify/assert"
20+
"github.com/stretchr/testify/require"
21+
22+
quicpogs "github.com/cloudflare/cloudflared/quic"
23+
)
24+
25+
// TestQUICServer tests if a quic server accepts and responds to a quic client with the acceptance protocol.
26+
// It also serves as a demonstration for communication with the QUIC connection started by a cloudflared.
27+
func TestQUICServer(t *testing.T) {
28+
quicConfig := &quic.Config{
29+
KeepAlive: true,
30+
}
31+
32+
log := zerolog.New(os.Stdout)
33+
34+
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
35+
36+
require.NoError(t, err)
37+
udpListener, err := net.ListenUDP(udpAddr.Network(), udpAddr)
38+
require.NoError(t, err)
39+
defer udpListener.Close()
40+
tlsConfig := generateTLSConfig()
41+
tlsConfClient := &tls.Config{
42+
InsecureSkipVerify: true,
43+
NextProtos: []string{"argotunnel"},
44+
}
45+
var tests = []struct {
46+
desc string
47+
dest string
48+
connectionType quicpogs.ConnectionType
49+
metadata []quicpogs.Metadata
50+
message []byte
51+
expectedMessage []byte
52+
}{
53+
{
54+
desc: "",
55+
dest: "somehost.com",
56+
connectionType: quicpogs.ConnectionTypeWebsocket,
57+
metadata: []quicpogs.Metadata{
58+
quicpogs.Metadata{
59+
Key: "key",
60+
Val: "value",
61+
},
62+
},
63+
expectedMessage: []byte("OK"),
64+
},
65+
}
66+
67+
for _, test := range tests {
68+
t.Run(test.desc, func(t *testing.T) {
69+
ctx, cancel := context.WithCancel(context.Background())
70+
71+
var wg sync.WaitGroup
72+
go func() {
73+
wg.Add(1)
74+
quicServer(
75+
t, udpListener, tlsConfig, quicConfig,
76+
test.dest, test.connectionType, test.metadata, test.message, test.expectedMessage,
77+
)
78+
wg.Done()
79+
}()
80+
81+
qC, err := NewQUICConnection(context.Background(), quicConfig, udpListener.LocalAddr(), tlsConfClient, log)
82+
require.NoError(t, err)
83+
84+
go func() {
85+
wg.Wait()
86+
cancel()
87+
}()
88+
89+
qC.Serve(ctx)
90+
91+
})
92+
}
93+
94+
}
95+
96+
func quicServer(
97+
t *testing.T,
98+
conn net.PacketConn,
99+
tlsConf *tls.Config,
100+
config *quic.Config,
101+
dest string,
102+
connectionType quicpogs.ConnectionType,
103+
metadata []quicpogs.Metadata,
104+
message []byte,
105+
expectedResponse []byte,
106+
) {
107+
ctx, cancel := context.WithCancel(context.Background())
108+
defer cancel()
109+
110+
earlyListener, err := quic.ListenEarly(conn, tlsConf, config)
111+
require.NoError(t, err)
112+
113+
session, err := earlyListener.Accept(ctx)
114+
require.NoError(t, err)
115+
116+
stream, err := session.OpenStreamSync(context.Background())
117+
require.NoError(t, err)
118+
119+
// Start off ALPN
120+
err = quicpogs.WriteConnectRequestData(stream, dest, connectionType, metadata...)
121+
require.NoError(t, err)
122+
123+
_, err = quicpogs.ReadConnectResponseData(stream)
124+
require.NoError(t, err)
125+
126+
if message != nil {
127+
// ALPN successful. Write data.
128+
_, err = stream.Write([]byte(message))
129+
require.NoError(t, err)
130+
}
131+
132+
response, err := ioutil.ReadAll(stream)
133+
require.NoError(t, err)
134+
135+
// For now it is an echo server. Verify if the same data is returned.
136+
assert.Equal(t, expectedResponse, response)
137+
}
138+
139+
// Setup a bare-bones TLS config for the server
140+
func generateTLSConfig() *tls.Config {
141+
key, err := rsa.GenerateKey(rand.Reader, 1024)
142+
if err != nil {
143+
panic(err)
144+
}
145+
template := x509.Certificate{SerialNumber: big.NewInt(1)}
146+
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
147+
if err != nil {
148+
panic(err)
149+
}
150+
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
151+
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
152+
153+
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
154+
if err != nil {
155+
panic(err)
156+
}
157+
return &tls.Config{
158+
Certificates: []tls.Certificate{tlsCert},
159+
NextProtos: []string{"argotunnel"},
160+
}
161+
}

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ require (
2929
github.com/json-iterator/go v1.1.10
3030
github.com/kr/text v0.2.0 // indirect
3131
github.com/kylelemons/godebug v1.1.0 // indirect
32+
github.com/lucas-clemente/quic-go v0.21.1
3233
github.com/mattn/go-colorable v0.1.8
3334
github.com/miekg/dns v1.1.31
3435
github.com/mitchellh/go-homedir v1.1.0
@@ -45,10 +46,10 @@ require (
4546
github.com/urfave/cli/v2 v2.2.0
4647
go.uber.org/automaxprocs v1.4.0
4748
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
48-
golang.org/x/net v0.0.0-20200904194848-62affa334b73
49+
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
4950
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
50-
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
51-
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
51+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
52+
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
5253
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
5354
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect
5455
google.golang.org/grpc v1.32.0 // indirect

0 commit comments

Comments
 (0)