@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 328 lines 7.69 KB
Raw
1 package integration
2
3 import (
4 "bytes"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "io"
9 "net"
10 "net/http"
11 "strings"
12 "testing"
13 "time"
14
15 "golang.org/x/net/http2"
16 "golang.org/x/net/http2/hpack"
17
18 "github.com/caddyserver/caddy/v2/caddytest"
19 )
20
21 var errExtendedConnectUnsupportedByPeer = errors.New("peer did not advertise RFC 8441 extended CONNECT support")
22
23 func TestReverseProxyExtendedConnectOverH2(t *testing.T) {
24 tester := caddytest.NewTester(t)
25 backend := newWebsocketUpgradeEchoBackend(t)
26 defer backend.Close()
27
28 tester.InitServer(fmt.Sprintf(`
29 {
30 admin localhost:2999
31 http_port 9080
32 https_port 9443
33 grace_period 1ns
34 skip_install_trust
35 servers :9443 {
36 protocols h2
37 }
38 }
39
40 https://localhost:9443 {
41 reverse_proxy %s
42 }
43 `, backend.addr), "caddyfile")
44
45 const payload = "extended-connect-echo\n"
46 if err := assertExtendedConnectH2Echo("localhost:9443", payload); err != nil {
47 if errors.Is(err, errExtendedConnectUnsupportedByPeer) {
48 t.Skipf("skipping extended CONNECT integration test: %v", err)
49 }
50 t.Fatalf("extended connect h2 echo failed: %v", err)
51 }
52 }
53
54 func assertExtendedConnectH2Echo(addr, payload string) error {
55 conn, err := tlsDialH2(addr)
56 if err != nil {
57 return fmt.Errorf("dialing h2 tls: %w", err)
58 }
59 defer conn.Close()
60
61 if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
62 return fmt.Errorf("setting deadline: %w", err)
63 }
64
65 fr := http2.NewFramer(conn, conn)
66
67 if _, err := conn.Write([]byte(http2.ClientPreface)); err != nil {
68 return fmt.Errorf("writing client preface: %w", err)
69 }
70 if err := fr.WriteSettings(http2.Setting{ID: http2.SettingEnableConnectProtocol, Val: 1}); err != nil {
71 return fmt.Errorf("writing client settings: %w", err)
72 }
73
74 supported, err := waitForServerSettings(fr)
75 if err != nil {
76 return err
77 }
78 if !supported {
79 return errExtendedConnectUnsupportedByPeer
80 }
81 if err := waitForSettingsAck(fr); err != nil {
82 return err
83 }
84
85 if err := writeExtendedConnectHeaders(fr, addr); err != nil {
86 return err
87 }
88
89 status, err := readResponseStatus(fr, 1)
90 if err != nil {
91 return err
92 }
93 if status != "200" {
94 return fmt.Errorf("unexpected extended connect status: got=%s want=200", status)
95 }
96
97 if err := fr.WriteData(1, false, []byte(payload)); err != nil {
98 return fmt.Errorf("writing stream data: %w", err)
99 }
100
101 echo, err := readStreamData(fr, 1, len(payload))
102 if err != nil {
103 return err
104 }
105 if echo != payload {
106 return fmt.Errorf("unexpected echoed payload: got=%q want=%q", echo, payload)
107 }
108
109 _ = fr.WriteRSTStream(1, http2.ErrCodeNo)
110 return nil
111 }
112
113 func tlsDialH2(addr string) (net.Conn, error) {
114 var lastErr error
115 for i := 0; i < 30; i++ {
116 dialer := &net.Dialer{Timeout: 2 * time.Second}
117 conn, err := tls.DialWithDialer(dialer, "tcp", addr, &tls.Config{
118 ServerName: "localhost",
119 InsecureSkipVerify: true,
120 NextProtos: []string{"h2"},
121 })
122 if err == nil {
123 return conn, nil
124 }
125 lastErr = err
126 time.Sleep(100 * time.Millisecond)
127 }
128 return nil, lastErr
129 }
130
131 func waitForServerSettings(fr *http2.Framer) (bool, error) {
132 for {
133 frame, err := fr.ReadFrame()
134 if err != nil {
135 return false, fmt.Errorf("reading frame before connect: %w", err)
136 }
137 settings, ok := frame.(*http2.SettingsFrame)
138 if !ok {
139 continue
140 }
141 if settings.IsAck() {
142 continue
143 }
144
145 supported := false
146 if err := settings.ForeachSetting(func(s http2.Setting) error {
147 if s.ID == http2.SettingEnableConnectProtocol && s.Val == 1 {
148 supported = true
149 }
150 return nil
151 }); err != nil {
152 return false, fmt.Errorf("reading server settings: %w", err)
153 }
154
155 if err := fr.WriteSettingsAck(); err != nil {
156 return false, fmt.Errorf("writing settings ack: %w", err)
157 }
158 return supported, nil
159 }
160 }
161
162 func waitForSettingsAck(fr *http2.Framer) error {
163 for {
164 frame, err := fr.ReadFrame()
165 if err != nil {
166 return fmt.Errorf("reading settings ack: %w", err)
167 }
168 settings, ok := frame.(*http2.SettingsFrame)
169 if ok && settings.IsAck() {
170 return nil
171 }
172 }
173 }
174
175 func writeExtendedConnectHeaders(fr *http2.Framer, addr string) error {
176 var hb bytes.Buffer
177 enc := hpack.NewEncoder(&hb)
178 for _, hf := range []hpack.HeaderField{
179 {Name: ":method", Value: "CONNECT"},
180 {Name: ":scheme", Value: "https"},
181 {Name: ":authority", Value: addr},
182 {Name: ":path", Value: "/upgrade"},
183 {Name: ":protocol", Value: "websocket"},
184 } {
185 if err := enc.WriteField(hf); err != nil {
186 return fmt.Errorf("encoding request headers: %w", err)
187 }
188 }
189
190 if err := fr.WriteHeaders(http2.HeadersFrameParam{
191 StreamID: 1,
192 BlockFragment: hb.Bytes(),
193 EndHeaders: true,
194 EndStream: false,
195 }); err != nil {
196 return fmt.Errorf("writing extended connect headers: %w", err)
197 }
198 return nil
199 }
200
201 func readResponseStatus(fr *http2.Framer, streamID uint32) (string, error) {
202 var block bytes.Buffer
203
204 for {
205 frame, err := fr.ReadFrame()
206 if err != nil {
207 return "", fmt.Errorf("reading response headers: %w", err)
208 }
209 if rst, ok := frame.(*http2.RSTStreamFrame); ok && rst.StreamID == streamID {
210 return "", fmt.Errorf("stream reset before response headers: %s", rst.ErrCode)
211 }
212
213 h, ok := frame.(*http2.HeadersFrame)
214 if !ok || h.StreamID != streamID {
215 continue
216 }
217
218 if _, err := block.Write(h.HeaderBlockFragment()); err != nil {
219 return "", fmt.Errorf("buffering response header fragment: %w", err)
220 }
221 for !h.HeadersEnded() {
222 next, err := fr.ReadFrame()
223 if err != nil {
224 return "", fmt.Errorf("reading continuation frame: %w", err)
225 }
226 c, ok := next.(*http2.ContinuationFrame)
227 if !ok || c.StreamID != streamID {
228 continue
229 }
230 if _, err := block.Write(c.HeaderBlockFragment()); err != nil {
231 return "", fmt.Errorf("buffering continuation fragment: %w", err)
232 }
233 if c.HeadersEnded() {
234 break
235 }
236 }
237 break
238 }
239
240 var status string
241 dec := hpack.NewDecoder(4096, func(f hpack.HeaderField) {
242 if f.Name == ":status" {
243 status = f.Value
244 }
245 })
246 if _, err := dec.Write(block.Bytes()); err != nil {
247 return "", fmt.Errorf("decoding response header block: %w", err)
248 }
249 if status == "" {
250 return "", fmt.Errorf("missing :status in response headers")
251 }
252 return status, nil
253 }
254
255 func readStreamData(fr *http2.Framer, streamID uint32, n int) (string, error) {
256 buf := make([]byte, 0, n)
257 for len(buf) < n {
258 frame, err := fr.ReadFrame()
259 if err != nil {
260 return "", fmt.Errorf("reading stream data: %w", err)
261 }
262 d, ok := frame.(*http2.DataFrame)
263 if !ok || d.StreamID != streamID {
264 continue
265 }
266 buf = append(buf, d.Data()...)
267 }
268 return string(buf[:n]), nil
269 }
270
271 type websocketUpgradeEchoBackend struct {
272 addr string
273 ln net.Listener
274 server *http.Server
275 }
276
277 func newWebsocketUpgradeEchoBackend(t *testing.T) *websocketUpgradeEchoBackend {
278 t.Helper()
279
280 backend := &websocketUpgradeEchoBackend{}
281 backend.server = &http.Server{
282 Handler: http.HandlerFunc(backend.serveHTTP),
283 }
284
285 ln, err := net.Listen("tcp", "127.0.0.1:0")
286 if err != nil {
287 t.Fatalf("listening for websocket backend: %v", err)
288 }
289 backend.ln = ln
290 backend.addr = ln.Addr().String()
291
292 go func() {
293 _ = backend.server.Serve(ln)
294 }()
295
296 return backend
297 }
298
299 func (b *websocketUpgradeEchoBackend) serveHTTP(w http.ResponseWriter, r *http.Request) {
300 if !strings.EqualFold(r.Header.Get("Connection"), "Upgrade") || !strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
301 http.Error(w, "upgrade required", http.StatusUpgradeRequired)
302 return
303 }
304
305 hijacker, ok := w.(http.Hijacker)
306 if !ok {
307 http.Error(w, "hijacking not supported", http.StatusInternalServerError)
308 return
309 }
310
311 conn, rw, err := hijacker.Hijack()
312 if err != nil {
313 return
314 }
315
316 _, _ = rw.WriteString("HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n")
317 _ = rw.Flush()
318
319 go func() {
320 defer conn.Close()
321 _, _ = io.Copy(conn, conn)
322 }()
323 }
324
325 func (b *websocketUpgradeEchoBackend) Close() {
326 _ = b.server.Close()
327 _ = b.ln.Close()
328 }