cloudflared-mirror/connection/quic_test.go

162 lines
3.8 KiB
Go

package connection
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"math/big"
"net"
"os"
"sync"
"testing"
"github.com/lucas-clemente/quic-go"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
quicpogs "github.com/cloudflare/cloudflared/quic"
)
// TestQUICServer tests if a quic server accepts and responds to a quic client with the acceptance protocol.
// It also serves as a demonstration for communication with the QUIC connection started by a cloudflared.
func TestQUICServer(t *testing.T) {
quicConfig := &quic.Config{
KeepAlive: true,
}
log := zerolog.New(os.Stdout)
udpAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0")
require.NoError(t, err)
udpListener, err := net.ListenUDP(udpAddr.Network(), udpAddr)
require.NoError(t, err)
defer udpListener.Close()
tlsConfig := generateTLSConfig()
tlsConfClient := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"argotunnel"},
}
var tests = []struct {
desc string
dest string
connectionType quicpogs.ConnectionType
metadata []quicpogs.Metadata
message []byte
expectedMessage []byte
}{
{
desc: "",
dest: "somehost.com",
connectionType: quicpogs.ConnectionTypeWebsocket,
metadata: []quicpogs.Metadata{
quicpogs.Metadata{
Key: "key",
Val: "value",
},
},
expectedMessage: []byte("OK"),
},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
go func() {
wg.Add(1)
quicServer(
t, udpListener, tlsConfig, quicConfig,
test.dest, test.connectionType, test.metadata, test.message, test.expectedMessage,
)
wg.Done()
}()
qC, err := NewQUICConnection(context.Background(), quicConfig, udpListener.LocalAddr(), tlsConfClient, log)
require.NoError(t, err)
go func() {
wg.Wait()
cancel()
}()
qC.Serve(ctx)
})
}
}
func quicServer(
t *testing.T,
conn net.PacketConn,
tlsConf *tls.Config,
config *quic.Config,
dest string,
connectionType quicpogs.ConnectionType,
metadata []quicpogs.Metadata,
message []byte,
expectedResponse []byte,
) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
earlyListener, err := quic.ListenEarly(conn, tlsConf, config)
require.NoError(t, err)
session, err := earlyListener.Accept(ctx)
require.NoError(t, err)
stream, err := session.OpenStreamSync(context.Background())
require.NoError(t, err)
// Start off ALPN
err = quicpogs.WriteConnectRequestData(stream, dest, connectionType, metadata...)
require.NoError(t, err)
_, err = quicpogs.ReadConnectResponseData(stream)
require.NoError(t, err)
if message != nil {
// ALPN successful. Write data.
_, err = stream.Write([]byte(message))
require.NoError(t, err)
}
response, err := ioutil.ReadAll(stream)
require.NoError(t, err)
// For now it is an echo server. Verify if the same data is returned.
assert.Equal(t, expectedResponse, response)
}
// Setup a bare-bones TLS config for the server
func generateTLSConfig() *tls.Config {
key, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
panic(err)
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
panic(err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
panic(err)
}
return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
NextProtos: []string{"argotunnel"},
}
}