varnish-cache/bin/varnishd/http2/cache_http2_session.c
1
/*-
2
 * Copyright (c) 2016 Varnish Software AS
3
 * All rights reserved.
4
 *
5
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 * 1. Redistributions of source code must retain the above copyright
11
 *    notice, this list of conditions and the following disclaimer.
12
 * 2. Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
20
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26
 * SUCH DAMAGE.
27
 *
28
 */
29
30
#include "config.h"
31
32
#include "cache/cache_varnishd.h"
33
34
#include <stdio.h>
35
36
#include "cache/cache_transport.h"
37
#include "http2/cache_http2.h"
38
39
#include "vend.h"
40
#include "vtim.h"
41
42
static const char h2_resp_101[] =
43
        "HTTP/1.1 101 Switching Protocols\r\n"
44
        "Connection: Upgrade\r\n"
45
        "Upgrade: h2c\r\n"
46
        "\r\n";
47
48
static const char H2_prism[24] = {
49
        0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
50
        0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
51
        0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
52
};
53
54
static size_t
55 71
h2_enc_settings(const struct h2_settings *h2s, uint8_t *buf, size_t n)
56
{
57 71
        size_t len = 0;
58
59
#define H2_SETTING(U,l,v,d,...)                         \
60
        if (h2s->l != d) {                              \
61
                len += 6;                               \
62
                assert(len <= n);                       \
63
                vbe16enc(buf, v);                       \
64
                buf += 2;                               \
65
                vbe32enc(buf, h2s->l);                  \
66
                buf += 4;                               \
67
        }
68
#include "tbl/h2_settings.h"
69 71
        return (len);
70
}
71
72
static const struct h2_settings H2_proto_settings = {
73
#define H2_SETTING(U,l,v,d,...) . l = d,
74
#include "tbl/h2_settings.h"
75
};
76
77
static void
78 74
h2_local_settings(struct h2_settings *h2s)
79
{
80 74
        *h2s = H2_proto_settings;
81
#define H2_SETTINGS_PARAM_ONLY
82
#define H2_SETTING(U, l, ...)                   \
83
        h2s->l = cache_param->h2_##l;
84
#include "tbl/h2_settings.h"
85
#undef H2_SETTINGS_PARAM_ONLY
86 74
}
87
88
/**********************************************************************
89
 * The h2_sess struct needs many of the same things as a request,
90
 * WS, VSL, HTC &c,  but rather than implement all that stuff over, we
91
 * grab an actual struct req, and mirror the relevant fields into
92
 * struct h2_sess.
93
 */
94
95
static struct h2_sess *
96 74
h2_init_sess(const struct worker *wrk, struct sess *sp,
97
    struct h2_sess *h2s, struct req *srq, struct h2h_decode *decode)
98
{
99
        uintptr_t *up;
100
        struct h2_sess *h2;
101
102 74
        if (SES_Get_proto_priv(sp, &up)) {
103
                /* Already reserved if we came via H1 */
104 0
                SES_Reserve_proto_priv(sp, &up);
105 0
                *up = 0;
106
        }
107 74
        if (*up == 0) {
108 74
                if (srq == NULL)
109 5
                        srq = Req_New(wrk, sp);
110 74
                AN(srq);
111 74
                h2 = h2s;
112 74
                AN(h2);
113 74
                INIT_OBJ(h2, H2_SESS_MAGIC);
114 74
                h2->srq = srq;
115 74
                h2->htc = srq->htc;
116 74
                h2->ws = srq->ws;
117 74
                h2->vsl = srq->vsl;
118 74
                VSL_Flush(h2->vsl, 0);
119 74
                h2->vsl->wid = sp->vxid;
120 74
                h2->htc->rfd = &sp->fd;
121 74
                h2->sess = sp;
122 74
                h2->rxthr = pthread_self();
123 74
                AZ(pthread_cond_init(h2->winupd_cond, NULL));
124 74
                VTAILQ_INIT(&h2->streams);
125 74
                VTAILQ_INIT(&h2->txqueue);
126 74
                h2_local_settings(&h2->local_settings);
127 74
                h2->remote_settings = H2_proto_settings;
128 74
                h2->decode = decode;
129
130 74
                AZ(VHT_Init(h2->dectbl,
131
                        h2->local_settings.header_table_size));
132
133 74
                SES_Reserve_proto_priv(sp, &up);
134 74
                *up = (uintptr_t)h2;
135
        }
136 74
        AN(up);
137 74
        CAST_OBJ_NOTNULL(h2, (void*)(*up), H2_SESS_MAGIC);
138 74
        return (h2);
139
}
140
141
static void
142 71
h2_del_sess(struct worker *wrk, struct h2_sess *h2, enum sess_close reason)
143
{
144
        struct sess *sp;
145
        struct req *req;
146
147 71
        CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
148 71
        AZ(h2->refcnt);
149 71
        assert(VTAILQ_EMPTY(&h2->streams));
150
151 71
        VHT_Fini(h2->dectbl);
152 71
        AZ(pthread_cond_destroy(h2->winupd_cond));
153 71
        req = h2->srq;
154 71
        AZ(req->ws->r);
155 71
        sp = h2->sess;
156 71
        Req_Cleanup(sp, wrk, req);
157 71
        Req_Release(req);
158 71
        SES_Delete(sp, reason, NAN);
159 71
}
160
161
/**********************************************************************/
162
163
enum htc_status_e v_matchproto_(htc_complete_f)
164 6688
H2_prism_complete(struct http_conn *htc)
165
{
166
        int l;
167
168 6688
        CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
169 6688
        l = htc->rxbuf_e - htc->rxbuf_b;
170 13233
        if (l >= sizeof(H2_prism) &&
171 6545
            !memcmp(htc->rxbuf_b, H2_prism, sizeof(H2_prism)))
172 215
                return (HTC_S_COMPLETE);
173 6473
        if (l < sizeof(H2_prism) && !memcmp(htc->rxbuf_b, H2_prism, l))
174 76
                return (HTC_S_MORE);
175 6397
        return (HTC_S_JUNK);
176
}
177
178
179
/**********************************************************************
180
 * Deal with the base64url (NB: ...url!) encoded SETTINGS in the H1 req
181
 * of a H2C upgrade.
182
 */
183
184
static int
185 5
h2_b64url_settings(struct h2_sess *h2, struct req *req)
186
{
187
        const char *p, *q;
188
        uint8_t u[6], *up;
189
        unsigned x;
190
        int i, n;
191
        static const char s[] =
192
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
193
            "abcdefghijklmnopqrstuvwxyz"
194
            "0123456789"
195
            "-_=";
196
197
        /*
198
         * If there is trouble with this, we could reject the upgrade
199
         * but putting this on the H1 side is just plain wrong...
200
         */
201 5
        if (!http_GetHdr(req->http, H_HTTP2_Settings, &p))
202 1
                return (-1);
203 4
        AN(p);
204 4
        VSLb(req->vsl, SLT_Debug, "H2CS %s", p);
205
206 4
        n = 0;
207 4
        x = 0;
208 4
        up = u;
209 52
        for (;*p; p++) {
210 49
                q = strchr(s, *p);
211 49
                if (q == NULL)
212 1
                        return (-1);
213 48
                i = q - s;
214 48
                assert(i >= 0 && i <= 64);
215 48
                x <<= 6;
216 48
                x |= i;
217 48
                n += 6;
218 48
                if (n < 8)
219 12
                        continue;
220 36
                *up++ = (uint8_t)(x >> (n - 8));
221 36
                n -= 8;
222 36
                if (up == u + sizeof u) {
223 6
                        AZ(n);
224 6
                        if (h2_set_setting(h2, (void*)u))
225 0
                                return (-1);
226 6
                        up = u;
227
                }
228
        }
229 3
        if (up != u)
230 0
                return (-1);
231 3
        return (0);
232
}
233
234
235
/**********************************************************************/
236
237
static int
238 2
h2_ou_rel(struct worker *wrk, struct req *req)
239
{
240 2
        CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
241 2
        CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
242 2
        AZ(req->vcl);
243 2
        Req_Cleanup(req->sp, wrk, req);
244 2
        Req_Release(req);
245 2
        return (0);
246
}
247
248
static int
249 5
h2_ou_session(struct worker *wrk, struct h2_sess *h2,
250
    struct req *req)
251
{
252
        ssize_t sz;
253
        enum htc_status_e hs;
254
        struct h2_req *r2;
255
256 5
        if (h2_b64url_settings(h2, req)) {
257 2
                VSLb(h2->vsl, SLT_Debug, "H2: Bad HTTP-Settings");
258 2
                return (h2_ou_rel(wrk, req));
259
        }
260
261 3
        sz = write(h2->sess->fd, h2_resp_101, strlen(h2_resp_101));
262 3
        if (sz != strlen(h2_resp_101)) {
263 0
                VSLb(h2->vsl, SLT_Debug, "H2: Upgrade: Error writing 101"
264 0
                    " response: %s\n", vstrerror(errno));
265 0
                return (h2_ou_rel(wrk, req));
266
        }
267
268 3
        http_Unset(req->http, H_Upgrade);
269 3
        http_Unset(req->http, H_HTTP2_Settings);
270
271
        /* Steal pipelined read-ahead, if any */
272 3
        h2->htc->pipeline_b = req->htc->pipeline_b;
273 3
        h2->htc->pipeline_e = req->htc->pipeline_e;
274 3
        req->htc->pipeline_b = NULL;
275 3
        req->htc->pipeline_e = NULL;
276
        /* XXX: This call may assert on buffer overflow if the pipelined
277
           data exceeds the available space in the ws workspace. What to
278
           do about the overflowing data is an open issue. */
279 3
        HTC_RxInit(h2->htc, h2->ws);
280
281
        /* Start req thread */
282 3
        r2 = h2_new_req(wrk, h2, 1, req);
283 3
        req->transport = &H2_transport;
284 3
        req->req_step = R_STP_TRANSPORT;
285 3
        req->task.func = h2_do_req;
286 3
        req->task.priv = req;
287 3
        r2->scheduled = 1;
288 3
        r2->state = H2_S_CLOS_REM; // rfc7540,l,489,491
289 3
        req->err_code = 0;
290 3
        http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");
291
292
        /* Wait for PRISM response */
293 3
        hs = HTC_RxStuff(h2->htc, H2_prism_complete,
294 3
            NULL, NULL, NAN, h2->sess->t_idle + cache_param->timeout_idle,
295
            sizeof H2_prism);
296 3
        if (hs != HTC_S_COMPLETE) {
297 1
                VSLb(h2->vsl, SLT_Debug, "H2: No/Bad OU PRISM (hs=%d)", hs);
298 1
                r2->scheduled = 0;
299 1
                h2_del_req(wrk, r2);
300 1
                return (0);
301
        }
302 2
        XXXAZ(Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ));
303 2
        return (1);
304
}
305
306
/**********************************************************************
307
 */
308
309
#define H2_PU_MARKER    1
310
#define H2_OU_MARKER    2
311
312
void
313 69
H2_PU_Sess(struct worker *wrk, struct sess *sp, struct req *req)
314
{
315 69
        VSLb(req->vsl, SLT_Debug, "H2 Prior Knowledge Upgrade");
316 69
        req->err_code = H2_PU_MARKER;
317 69
        SES_SetTransport(wrk, sp, req, &H2_transport);
318 69
}
319
320
void
321 5
H2_OU_Sess(struct worker *wrk, struct sess *sp, struct req *req)
322
{
323 5
        VSLb(req->vsl, SLT_Debug, "H2 Optimistic Upgrade");
324 5
        req->err_code = H2_OU_MARKER;
325 5
        SES_SetTransport(wrk, sp, req, &H2_transport);
326 5
}
327
328
static void v_matchproto_(task_func_t)
329 73
h2_new_session(struct worker *wrk, void *arg)
330
{
331
        struct req *req;
332
        struct sess *sp;
333
        struct h2_sess h2s;
334
        struct h2_sess *h2;
335
        struct h2_req *r2, *r22;
336
        int again;
337
        uint8_t settings[48];
338
        struct h2h_decode decode;
339
        size_t l;
340
341 73
        CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
342 73
        CAST_OBJ_NOTNULL(req, arg, REQ_MAGIC);
343 73
        sp = req->sp;
344 73
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
345
346 73
        if (wrk->vcl)
347 11
                VCL_Rel(&wrk->vcl);
348
349 74
        assert(req->transport == &H2_transport);
350
351 74
        assert (req->err_code == H2_PU_MARKER || req->err_code == H2_OU_MARKER);
352
353 74
        h2 = h2_init_sess(wrk, sp, &h2s,
354 74
            req->err_code == H2_PU_MARKER ? req : NULL, &decode);
355 74
        h2->req0 = h2_new_req(wrk, h2, 0, NULL);
356 74
        AZ(h2->htc->priv);
357 74
        h2->htc->priv = h2;
358
359 74
        if (req->err_code == H2_OU_MARKER && !h2_ou_session(wrk, h2, req)) {
360 3
                assert(h2->refcnt == 1);
361 3
                h2_del_req(wrk, h2->req0);
362 3
                h2_del_sess(wrk, h2, SC_RX_JUNK);
363 3
                return;
364
        }
365 71
        assert(HTC_S_COMPLETE == H2_prism_complete(h2->htc));
366 71
        HTC_RxPipeline(h2->htc, h2->htc->rxbuf_b + sizeof(H2_prism));
367 71
        WS_Reset(h2->ws, 0);
368 71
        HTC_RxInit(h2->htc, h2->ws);
369 71
        AN(h2->ws->r);
370 71
        VSLb(h2->vsl, SLT_Debug, "H2: Got pu PRISM");
371
372 71
        THR_SetRequest(h2->srq);
373 71
        AN(h2->ws->r);
374
375 71
        l = h2_enc_settings(&h2->local_settings, settings, sizeof (settings));
376 71
        AN(h2->ws->r);
377 71
        H2_Send_Get(wrk, h2, h2->req0);
378 71
        AN(h2->ws->r);
379 71
        H2_Send_Frame(wrk, h2,
380
            H2_F_SETTINGS, H2FF_NONE, l, 0, settings);
381 71
        AN(h2->ws->r);
382 71
        H2_Send_Rel(h2, h2->req0);
383 71
        AN(h2->ws->r);
384
385
        /* and off we go... */
386 71
        h2->cond = &wrk->cond;
387
388 407
        while (h2_rxframe(wrk, h2)) {
389 265
                WS_Reset(h2->ws, 0);
390 265
                HTC_RxInit(h2->htc, h2->ws);
391 265
                if (WS_Overflowed(h2->ws)) {
392 0
                        VSLb(h2->vsl, SLT_Debug, "H2: Empty Rx Workspace");
393 0
                        h2->error = H2CE_INTERNAL_ERROR;
394 0
                        break;
395
                }
396 265
                AN(h2->ws->r);
397
        }
398
399 70
        AN(h2->error);
400
401
        /* Delete all idle streams */
402 70
        VSLb(h2->vsl, SLT_Debug, "H2 CLEANUP %s", h2->error->name);
403 70
        Lck_Lock(&h2->sess->mtx);
404 190
        VTAILQ_FOREACH(r2, &h2->streams, list) {
405 120
                if (r2->error == 0)
406 119
                        r2->error = h2->error;
407 120
                if (r2->cond != NULL)
408 4
                        AZ(pthread_cond_signal(r2->cond));
409
        }
410 70
        AZ(pthread_cond_broadcast(h2->winupd_cond));
411 70
        Lck_Unlock(&h2->sess->mtx);
412
        while (1) {
413 366
                again = 0;
414 596
                VTAILQ_FOREACH_SAFE(r2, &h2->streams, list, r22) {
415 377
                        if (r2 != h2->req0) {
416 158
                                h2_kill_req(wrk, h2, r2, h2->error);
417 160
                                again++;
418
                        }
419
                }
420 219
                if (!again)
421 68
                        break;
422 151
                Lck_Lock(&h2->sess->mtx);
423 412
                VTAILQ_FOREACH(r2, &h2->streams, list)
424 261
                        VSLb(h2->vsl, SLT_Debug, "ST %u %d",
425 261
                            r2->stream, r2->state);
426 151
                (void)Lck_CondWait(h2->cond, &h2->sess->mtx, VTIM_real() + .1);
427 149
                Lck_Unlock(&h2->sess->mtx);
428
        }
429 68
        h2->cond = NULL;
430 68
        assert(h2->refcnt == 1);
431 68
        h2_del_req(wrk, h2->req0);
432
        /* TODO: proper sess close reason */
433 68
        h2_del_sess(wrk, h2, SC_RX_JUNK);
434
}
435
436
struct transport H2_transport = {
437
        .name =                 "H2",
438
        .magic =                TRANSPORT_MAGIC,
439
        .deliver =              h2_deliver,
440
        .minimal_response =     h2_minimal_response,
441
        .new_session =          h2_new_session,
442
        .req_body =             h2_req_body,
443
        .req_fail =             h2_req_fail,
444
        .sess_panic =           h2_sess_panic,
445
};