mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
proxy: Support QUIC for upstream connections (#1782)
* Proxy can now use QUIC for upstream connections Add HandshakeTimeout, change h2quic syntax * Add setup and upstream test Test QUIC proxy with actual h2quic instance Use different port fo QUIC test server Add quic host to CI config Added testdata to vendor Revert "Added testdata to vendor" This reverts commit 959512282deed8623168d090e5ca5e5a7933019c. * Use local testdata
This commit is contained in:
parent
46ae4a6652
commit
22b835b9f4
9 changed files with 217 additions and 3 deletions
|
@ -1,5 +1,9 @@
|
|||
language: go
|
||||
|
||||
addons:
|
||||
hosts:
|
||||
- quic.clemente.io
|
||||
|
||||
go:
|
||||
- 1.9
|
||||
- tip
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
version: "{build}"
|
||||
|
||||
hosts:
|
||||
quic.clemente.io: 127.0.0.1
|
||||
|
||||
os: Windows Server 2012 R2
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\mholt\caddy
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
|
@ -23,6 +24,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/h2quic"
|
||||
"github.com/mholt/caddy/caddyfile"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
|
||||
|
@ -1470,3 +1472,59 @@ func TestChunkedWebSocketReverseProxy(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuic(t *testing.T) {
|
||||
upstream := "quic.clemente.io:8086"
|
||||
config := "proxy / quic://" + upstream
|
||||
content := "Hello, client"
|
||||
|
||||
// make proxy
|
||||
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(config)), "")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error. Got: %s", err.Error())
|
||||
}
|
||||
p := &Proxy{
|
||||
Next: httpserver.EmptyNext, // prevents panic in some cases when test fails
|
||||
Upstreams: upstreams,
|
||||
}
|
||||
|
||||
// start QUIC server
|
||||
go func() {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error. Got: %s", err.Error())
|
||||
return
|
||||
}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(content))
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
err = h2quic.ListenAndServeQUIC(
|
||||
upstream,
|
||||
path.Join(dir, "testdata", "fullchain.pem"),
|
||||
path.Join(dir, "testdata", "privkey.pem"),
|
||||
handler,
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error. Got: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
_, err = p.ServeHTTP(w, r)
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error. Got: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// check response
|
||||
if w.Code != 200 {
|
||||
t.Errorf("Expected response code 200, got: %d", w.Code)
|
||||
}
|
||||
responseContent := string(w.Body.Bytes())
|
||||
if responseContent != content {
|
||||
t.Errorf("Expected response body, got: %s", responseContent)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/h2quic"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
|
@ -33,6 +35,8 @@ var (
|
|||
}
|
||||
|
||||
bufferPool = sync.Pool{New: createBuffer}
|
||||
|
||||
defaultCryptoHandshakeTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func createBuffer() interface{} {
|
||||
|
@ -180,11 +184,18 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
|
|||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
}
|
||||
|
||||
rp := &ReverseProxy{Director: director, FlushInterval: 250 * time.Millisecond} // flushing good for streaming & server-sent events
|
||||
if target.Scheme == "unix" {
|
||||
rp.Transport = &http.Transport{
|
||||
Dial: socketDial(target.String()),
|
||||
}
|
||||
} else if target.Scheme == "quic" {
|
||||
rp.Transport = &h2quic.RoundTripper{
|
||||
QuicConfig: &quic.Config{
|
||||
HandshakeTimeout: defaultCryptoHandshakeTimeout,
|
||||
},
|
||||
}
|
||||
} else if keepalive != http.DefaultMaxIdleConnsPerHost {
|
||||
// if keepalive is equal to the default,
|
||||
// just use default transport, to avoid creating
|
||||
|
@ -192,7 +203,7 @@ func NewSingleHostReverseProxy(target *url.URL, without string, keepalive int) *
|
|||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: defaultDialer.Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: defaultCryptoHandshakeTimeout,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
if keepalive == 0 {
|
||||
|
@ -216,7 +227,7 @@ func (rp *ReverseProxy) UseInsecureTransport() {
|
|||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: defaultDialer.Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSHandshakeTimeout: defaultCryptoHandshakeTimeout,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
if httpserver.HTTP2 {
|
||||
|
@ -231,6 +242,11 @@ func (rp *ReverseProxy) UseInsecureTransport() {
|
|||
// No http2.ConfigureTransport() here.
|
||||
// For now this is only added in places where
|
||||
// an http.Transport is actually created.
|
||||
} else if transport, ok := rp.Transport.(*h2quic.RoundTripper); ok {
|
||||
if transport.TLSClientConfig == nil {
|
||||
transport.TLSClientConfig = &tls.Config{}
|
||||
}
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,6 +262,10 @@ func (rp *ReverseProxy) ServeHTTP(rw http.ResponseWriter, outreq *http.Request,
|
|||
|
||||
rp.Director(outreq)
|
||||
|
||||
if outreq.URL.Scheme == "quic" {
|
||||
outreq.URL.Scheme = "https" // Change scheme back to https for QUIC RoundTripper
|
||||
}
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -147,6 +147,14 @@ func TestSetup(t *testing.T) {
|
|||
"http://localhost:1984": {},
|
||||
},
|
||||
},
|
||||
// test #14 test QUIC
|
||||
{
|
||||
"proxy / quic://localhost:443",
|
||||
false,
|
||||
map[string]struct{}{
|
||||
"quic://localhost:443": {},
|
||||
},
|
||||
},
|
||||
} {
|
||||
c := caddy.NewTestController("http", test.input)
|
||||
err := setup(c)
|
||||
|
|
56
caddyhttp/proxy/testdata/fullchain.pem
vendored
Normal file
56
caddyhttp/proxy/testdata/fullchain.pem
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFAzCCA+ugAwIBAgISA7e2G9wJth5EaqaD5X0RiagYMA0GCSqGSIb3DQEBCwUA
|
||||
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
|
||||
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xNzA3MDMxODU3MDBaFw0x
|
||||
NzEwMDExODU3MDBaMBsxGTAXBgNVBAMTEHF1aWMuY2xlbWVudGUuaW8wggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7UjonSCiB0tyHsenbXZw/QF028EmH
|
||||
tvdyMTOlz2DVLqi0K6brqVIh3KSl2gPlsizLHmkoTLVINuGCnDXc4jXu6yCVHrPr
|
||||
KOf+ip8SUcqQEmLXmHw5Y+L4/6ZKUE5mFpfqmlMCEb1t86J7FI9z+QA9LGdbziYv
|
||||
qQBW8GytX16OJ4h/S1fiPCQ5GfWtkoVgYzgz8Vn4o51lLG2YXAl451BR8+XhGlYS
|
||||
OjS6x7RA0F5wqCeGgro7wKbFuyfxrkWkVzn5hNdEkBAABiub6obNBMZ6v+u84bQk
|
||||
1rH0oZB5rn3uEPycLmrQF2cYR5b+2F+BymKC0ElkFi3iWQoO98SZZQinAgMBAAGj
|
||||
ggIQMIICDDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG
|
||||
AQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNjHumfJ0g905MebRnAvNfQh
|
||||
3AvEMB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEB
|
||||
BGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0
|
||||
Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0
|
||||
Lm9yZy8wGwYDVR0RBBQwEoIQcXVpYy5jbGVtZW50ZS5pbzCB/gYDVR0gBIH2MIHz
|
||||
MAgGBmeBDAECATCB5gYLKwYBBAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6
|
||||
Ly9jcHMubGV0c2VuY3J5cHQub3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2Vy
|
||||
dGlmaWNhdGUgbWF5IG9ubHkgYmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0
|
||||
aWVzIGFuZCBvbmx5IGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUg
|
||||
UG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRv
|
||||
cnkvMA0GCSqGSIb3DQEBCwUAA4IBAQAqs3Mrr/Erqp1rOFkLwKbStWZniCvqhl58
|
||||
VnScP2CjiBsaLJUuBlWqC215FtX5CrdkIwYrMMkkOZHZI4mPxN64UVqMY5UJRonL
|
||||
GvkeHC5QYsCV09bBHjCei6JDItNH2PCec9+mV9EIQiVzd8xliE3t0eTbjNsa9zf1
|
||||
Qwp64THbiyTIXuh4xgFTxU2u58+RkIRbKGRM1X4jgIv8xjNV4P1c0jUVqaEFkCjR
|
||||
A03becsSv3wqWvPCNQRdVRdoMMghHenDEAGD621McnaXDoNz8pgn/ss1vzrO36gX
|
||||
WZ7CmbgIFdYeMgqQop/252bN2wrNjnxAjLAHo/X1MPEabjoL1C0g
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
||||
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
||||
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
||||
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
||||
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
||||
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
||||
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
||||
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
||||
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
||||
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
||||
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
||||
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
||||
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
||||
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
||||
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
||||
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
||||
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
||||
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
||||
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
||||
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
||||
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
||||
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
||||
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
||||
-----END CERTIFICATE-----
|
28
caddyhttp/proxy/testdata/privkey.pem
vendored
Normal file
28
caddyhttp/proxy/testdata/privkey.pem
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7UjonSCiB0tyH
|
||||
senbXZw/QF028EmHtvdyMTOlz2DVLqi0K6brqVIh3KSl2gPlsizLHmkoTLVINuGC
|
||||
nDXc4jXu6yCVHrPrKOf+ip8SUcqQEmLXmHw5Y+L4/6ZKUE5mFpfqmlMCEb1t86J7
|
||||
FI9z+QA9LGdbziYvqQBW8GytX16OJ4h/S1fiPCQ5GfWtkoVgYzgz8Vn4o51lLG2Y
|
||||
XAl451BR8+XhGlYSOjS6x7RA0F5wqCeGgro7wKbFuyfxrkWkVzn5hNdEkBAABiub
|
||||
6obNBMZ6v+u84bQk1rH0oZB5rn3uEPycLmrQF2cYR5b+2F+BymKC0ElkFi3iWQoO
|
||||
98SZZQinAgMBAAECggEAbO8EopNz+wuE8+Si+s8VbjMgAjL6j9H3VJEIWASha1gX
|
||||
A6/fAm0VNlv54/lFCu7y3axxut3hDn3b5viw2iMy+h4CdLXGK5s+TuiOWTj3c5E9
|
||||
qeMjWryb4fHJ4q2Q6g15ixTz8OAgKTDl7G2ofujvGqQX92uLCWxepjBrAufTNRcJ
|
||||
OZ3ngqHlKsRXX3nXkAMYrypK7ALF2kuavAGNrDQvPWUZKp3vuvd3Hx/stw0s3Th1
|
||||
XrHZnaAMZlxZg32IiVxs3vR2sACJ0YyOBpERBjjBsIaeyNXfZVrEmNzvo6iVhdhN
|
||||
ZNxrKSnPEfTdFk5pldFbTzNpvCvjbFAlE0aHXNRJAQKBgQDpAzWGkOTE+wmcWJNk
|
||||
oRi4ZJHhK/kckvNg1OZMXAqqZJOPxvwatEFgQ1GZo8rhSzdf64kB9b9I3OjEhd8r
|
||||
M90pt57BqRSq5rbytZBdR2BcbNnKkYF204AS2pkEVvkOVnWz5zSVhd8a0gMx3EdE
|
||||
LKN0r+DLKune8cnAS0BDvBjf3QKBgQDNzRJe9pI29mxUyuLQuKngaa8KmPy1EpbW
|
||||
+d21ET4MjZbH6uPOAe1Q+7aCEA7rjvFoOqGk0w1WIN0i8EaIOuwM2W0jw4VS7AVI
|
||||
rWXTYy9uSnUuLWL6gHNbehqLs6JaADEvytWdXdqiR/XWxCDn7qg2CrjxwmDB/OUm
|
||||
RopmnlkEUwKBgQCreZ4ZUmXYhDmVYiXN5zPO9svYHkkr+wS6HNMCHLYIoQ1qwG/k
|
||||
owR9d+0EGOKDm5u7rhTcaWIEl/WAMliCbZ9zRNrC/8/i2PiHcpAz5QQH4F8CUMQq
|
||||
kwjsVwxGgk60e3IRG7O52ZPPJAAP4GBdzk/X3lqaiREk7WCgb4BymGjhzQKBgEMF
|
||||
mQkCJeXuZKNMm4c7zF8AK/g4kHvrvOHv56sTHXD7H3Kl5WBusjmgb/R1hFZka+v0
|
||||
xDWoYfx9oWbCd0XgYoVgvbFa+G1j3eioR7QK5iR17SmHsGdCM89DuadrbeD/lQUq
|
||||
elzQduZIpyA1KT4/M9q9rTNWiSpD0OChMmtvADBvAoGAAXF3cARv5w0fSZGSRCOw
|
||||
U3LdFNIhBgVdROj2C4ym+uJFErKTkB5kghdUER7UsFH8fVn3JLAb35cQRYGrysYz
|
||||
XF5eK0akNhkO9GLNrK0GbSHKZm9vQxixm5W05aVoUofRHqkkKL1ceC2rhwzp3Q5P
|
||||
1jLabOA4K0DkhNga0YPKJLQ=
|
||||
-----END PRIVATE KEY-----
|
|
@ -150,7 +150,8 @@ func (u *staticUpstream) From() string {
|
|||
|
||||
func (u *staticUpstream) NewHost(host string) (*UpstreamHost, error) {
|
||||
if !strings.HasPrefix(host, "http") &&
|
||||
!strings.HasPrefix(host, "unix:") {
|
||||
!strings.HasPrefix(host, "unix:") &&
|
||||
!strings.HasPrefix(host, "quic:") {
|
||||
host = "http://" + host
|
||||
}
|
||||
uh := &UpstreamHost{
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go/h2quic"
|
||||
"github.com/mholt/caddy/caddyfile"
|
||||
)
|
||||
|
||||
|
@ -501,3 +502,38 @@ func TestHealthCheckContentString(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuicHost(t *testing.T) {
|
||||
// tests for QUIC proxy
|
||||
tests := []struct {
|
||||
config string
|
||||
flag bool
|
||||
}{
|
||||
// Test #1: without flag
|
||||
{"proxy / quic://localhost:8080", false},
|
||||
|
||||
// Test #2: with flag
|
||||
{"proxy / quic://localhost:8080 {\n insecure_skip_verify \n}", true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
upstreams, err := NewStaticUpstreams(caddyfile.NewDispenser("Testfile", strings.NewReader(test.config)), "")
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error. Got: %s", err.Error())
|
||||
}
|
||||
for _, upstream := range upstreams {
|
||||
staticUpstream, ok := upstream.(*staticUpstream)
|
||||
if !ok {
|
||||
t.Errorf("Type mismatch: %#v", upstream)
|
||||
continue
|
||||
}
|
||||
for _, host := range staticUpstream.Hosts {
|
||||
_, ok := host.ReverseProxy.Transport.(*h2quic.RoundTripper)
|
||||
if !ok {
|
||||
t.Errorf("Type mismatch: %#v", host.ReverseProxy.Transport)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue