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 "vtim.h"
40
41
static const char h2_resp_101[] =
42
        "HTTP/1.1 101 Switching Protocols\r\n"
43
        "Connection: Upgrade\r\n"
44
        "Upgrade: h2c\r\n"
45
        "\r\n";
46
47
static const char H2_prism[24] = {
48
        0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
49
        0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
50
        0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
51
};
52
53
static const uint8_t H2_settings[] = {
54
        0x00, 0x03,
55
        0x00, 0x00, 0x00, 0x64,
56
        0x00, 0x04,
57
        0x00, 0x00, 0xff, 0xff
58
};
59
60
static const struct h2_settings H2_proto_settings = {
61
#define H2_SETTING(U,l,v,d,...) . l = d,
62
#include "tbl/h2_settings.h"
63
};
64
65
66
/**********************************************************************
67
 * The h2_sess struct needs many of the same things as a request,
68
 * WS, VSL, HTC &c,  but rather than implement all that stuff over, we
69
 * grab an actual struct req, and mirror the relevant fields into
70
 * struct h2_sess.
71
 * To make things really incestuous, we allocate the h2_sess on
72
 * the WS of that "Session ReQuest".
73
 */
74
75
static struct h2_sess *
76 114
h2_new_sess(const struct worker *wrk, struct sess *sp, struct req *srq)
77
{
78
        uintptr_t *up;
79
        struct h2_sess *h2;
80
81 114
        if (SES_Get_xport_priv(sp, &up)) {
82
                /* Already reserved if we came via H1 */
83 0
                SES_Reserve_xport_priv(sp, &up);
84 0
                *up = 0;
85
        }
86 114
        if (*up == 0) {
87 114
                if (srq == NULL)
88 10
                        srq = Req_New(wrk, sp);
89 114
                AN(srq);
90 114
                h2 = WS_Alloc(srq->ws, sizeof *h2);
91 114
                AN(h2);
92 114
                INIT_OBJ(h2, H2_SESS_MAGIC);
93 114
                h2->srq = srq;
94 114
                h2->htc = srq->htc;
95 114
                h2->ws = srq->ws;
96 114
                h2->vsl = srq->vsl;
97 114
                h2->vsl->wid = sp->vxid;
98 114
                h2->htc->rfd = &sp->fd;
99 114
                h2->sess = sp;
100 114
                h2->rxthr = pthread_self();
101 114
                VTAILQ_INIT(&h2->streams);
102 114
                VTAILQ_INIT(&h2->txqueue);
103 114
                h2->local_settings = H2_proto_settings;
104 114
                h2->remote_settings = H2_proto_settings;
105
106 114
                AZ(VHT_Init(h2->dectbl,
107
                        h2->local_settings.header_table_size));
108
109 114
                SES_Reserve_xport_priv(sp, &up);
110 114
                *up = (uintptr_t)h2;
111
        }
112 114
        AN(up);
113 114
        CAST_OBJ_NOTNULL(h2, (void*)(*up), H2_SESS_MAGIC);
114 114
        return (h2);
115
}
116
117
/**********************************************************************/
118
119
enum htc_status_e v_matchproto_(htc_complete_f)
120 10271
H2_prism_complete(struct http_conn *htc)
121
{
122
        int l;
123
124 10271
        CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
125 10271
        l = htc->rxbuf_e - htc->rxbuf_b;
126 16031
        if (l >= sizeof(H2_prism) &&
127 5760
            !memcmp(htc->rxbuf_b, H2_prism, sizeof(H2_prism)))
128 324
                return (HTC_S_COMPLETE);
129 9947
        if (l < sizeof(H2_prism) && !memcmp(htc->rxbuf_b, H2_prism, l))
130 116
                return (HTC_S_MORE);
131 9831
        return (HTC_S_JUNK);
132
}
133
134
135
/**********************************************************************
136
 * Deal with the base64url (NB: ...url!) encoded SETTINGS in the H1 req
137
 * of a H2C upgrade.
138
 */
139
140
static int
141 10
h2_b64url_settings(struct h2_sess *h2, struct req *req)
142
{
143
        const char *p, *q;
144
        uint8_t u[6], *up;
145
        unsigned x;
146
        int i, n;
147
        static const char s[] =
148
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
149
            "abcdefghijklmnopqrstuvwxyz"
150
            "0123456789"
151
            "-_=";
152
153
        /*
154
         * If there is trouble with this, we could reject the upgrade
155
         * but putting this on the H1 side is just plain wrong...
156
         */
157 10
        if (!http_GetHdr(req->http, H_HTTP2_Settings, &p))
158 2
                return (-1);
159 8
        AN(p);
160 8
        VSLb(req->vsl, SLT_Debug, "H2CS %s", p);
161
162 8
        n = 0;
163 8
        x = 0;
164 8
        up = u;
165 104
        for (;*p; p++) {
166 98
                q = strchr(s, *p);
167 98
                if (q == NULL)
168 2
                        return (-1);
169 96
                i = q - s;
170 96
                assert(i >= 0 && i <= 63);
171 96
                x <<= 6;
172 96
                x |= i;
173 96
                n += 6;
174 96
                if (n < 8)
175 24
                        continue;
176 72
                *up++ = (uint8_t)(x >> (n - 8));
177 72
                n -= 8;
178 72
                if (up == u + sizeof u) {
179 12
                        AZ(n);
180 12
                        if (h2_set_setting(h2, (void*)u))
181 0
                                return (-1);
182 12
                        up = u;
183
                }
184
        }
185 6
        if (up != u)
186 0
                return (-1);
187 6
        return (0);
188
}
189
190
191
/**********************************************************************/
192
193
static int
194 10
h2_ou_session(struct worker *wrk, struct h2_sess *h2,
195
    struct req *req)
196
{
197
        ssize_t sz;
198
        enum htc_status_e hs;
199
        struct h2_req *r2;
200
201 10
        if (h2_b64url_settings(h2, req)) {
202 4
                VSLb(h2->vsl, SLT_Debug, "H2: Bad HTTP-Settings");
203 4
                AN (req->vcl);
204 4
                VCL_Rel(&req->vcl);
205 4
                Req_AcctLogCharge(wrk->stats, req);
206 4
                Req_Release(req);
207 4
                return (0);
208
        }
209
210 6
        sz = write(h2->sess->fd, h2_resp_101, strlen(h2_resp_101));
211 6
        assert(sz == strlen(h2_resp_101));
212
213 6
        http_Unset(req->http, H_Upgrade);
214 6
        http_Unset(req->http, H_HTTP2_Settings);
215
216
        /* Steal pipelined read-ahead, if any */
217 6
        h2->htc->pipeline_b = req->htc->pipeline_b;
218 6
        h2->htc->pipeline_e = req->htc->pipeline_e;
219 6
        req->htc->pipeline_b = NULL;
220 6
        req->htc->pipeline_e = NULL;
221
        /* XXX: This call may assert on buffer overflow if the pipelined
222
           data exceeds the available space in the ws workspace. What to
223
           do about the overflowing data is an open issue. */
224 6
        HTC_RxInit(h2->htc, h2->ws);
225
226
        /* Start req thread */
227 6
        r2 = h2_new_req(wrk, h2, 1, req);
228 6
        req->req_step = R_STP_RECV;
229 6
        req->transport = &H2_transport;
230 6
        req->req_step = R_STP_TRANSPORT;
231 6
        req->task.func = h2_do_req;
232 6
        req->task.priv = req;
233 6
        r2->scheduled = 1;
234 6
        req->err_code = 0;
235 6
        http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");
236
237
        /* Wait for PRISM response */
238 6
        hs = HTC_RxStuff(h2->htc, H2_prism_complete,
239 6
            NULL, NULL, NAN, h2->sess->t_idle + cache_param->timeout_idle,
240
            sizeof H2_prism);
241 6
        if (hs != HTC_S_COMPLETE) {
242 2
                VSLb(h2->vsl, SLT_Debug, "H2: No/Bad OU PRISM (hs=%d)", hs);
243 2
                r2->scheduled = 0;
244 2
                h2_del_req(wrk, r2);
245 2
                return (0);
246
        }
247 4
        XXXAZ(Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ));
248 4
        return (1);
249
}
250
251
/**********************************************************************
252
 */
253
254
#define H2_PU_MARKER    1
255
#define H2_OU_MARKER    2
256
257
void
258 104
H2_PU_Sess(struct worker *wrk, struct sess *sp, struct req *req)
259
{
260 104
        VSLb(req->vsl, SLT_Debug, "H2 Prior Knowledge Upgrade");
261 104
        req->err_code = H2_PU_MARKER;
262 104
        SES_SetTransport(wrk, sp, req, &H2_transport);
263 104
}
264
265
void
266 10
H2_OU_Sess(struct worker *wrk, struct sess *sp, struct req *req)
267
{
268 10
        VSLb(req->vsl, SLT_Debug, "H2 Optimistic Upgrade");
269 10
        req->err_code = H2_OU_MARKER;
270 10
        SES_SetTransport(wrk, sp, req, &H2_transport);
271 10
}
272
273
static void v_matchproto_(task_func_t)
274 114
h2_new_session(struct worker *wrk, void *arg)
275
{
276
        struct req *req;
277
        struct sess *sp;
278
        struct h2_sess *h2;
279
        struct h2_req *r2, *r22;
280
        uintptr_t wsp;
281
        int again;
282
283 114
        CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
284 114
        CAST_OBJ_NOTNULL(req, arg, REQ_MAGIC);
285 114
        sp = req->sp;
286 114
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
287
288 114
        assert(req->transport == &H2_transport);
289
290 114
        assert (req->err_code == H2_PU_MARKER || req->err_code == H2_OU_MARKER);
291
292 114
        h2 = h2_new_sess(wrk, sp, req->err_code == H2_PU_MARKER ? req : NULL);
293 114
        wsp = WS_Snapshot(h2->ws);
294 114
        h2->req0 = h2_new_req(wrk, h2, 0, NULL);
295
296 114
        if (req->err_code == H2_OU_MARKER && !h2_ou_session(wrk, h2, req)) {
297 6
                h2_del_req(wrk, h2->req0);
298 118
                return;
299
        }
300 108
        assert(HTC_S_COMPLETE == H2_prism_complete(h2->htc));
301 108
        HTC_RxPipeline(h2->htc, h2->htc->rxbuf_b + sizeof(H2_prism));
302 108
        HTC_RxInit(h2->htc, h2->ws);
303 108
        VSLb(h2->vsl, SLT_Debug, "H2: Got pu PRISM");
304
305 108
        THR_SetRequest(h2->srq);
306
307 108
        H2_Send_Get(wrk, h2, h2->req0);
308 108
        H2_Send_Frame(wrk, h2,
309
            H2_F_SETTINGS, H2FF_NONE, sizeof H2_settings, 0, H2_settings);
310 108
        H2_Send_Rel(h2, h2->req0);
311
312
        /* and off we go... */
313 108
        h2->cond = &wrk->cond;
314
315 578
        while (h2_rxframe(wrk, h2)) {
316 362
                WS_Reset(h2->ws, wsp);
317 362
                HTC_RxInit(h2->htc, h2->ws);
318
        }
319
320 106
        AN(h2->error);
321
322
        /* Delete all idle streams */
323 106
        VSLb(h2->vsl, SLT_Debug, "H2 CLEANUP %s", h2->error->name);
324 106
        Lck_Lock(&h2->sess->mtx);
325 294
        VTAILQ_FOREACH(r2, &h2->streams, list) {
326 188
                if (r2->error == 0)
327 188
                        r2->error = h2->error;
328 188
                if (r2->cond != NULL)
329 2
                        AZ(pthread_cond_signal(r2->cond));
330
        }
331 106
        AZ(pthread_cond_broadcast(h2->cond));
332 106
        Lck_Unlock(&h2->sess->mtx);
333
        while (1) {
334 178
                again = 0;
335 446
                VTAILQ_FOREACH_SAFE(r2, &h2->streams, list, r22) {
336 268
                        if (r2 != h2->req0) {
337 90
                                h2_kill_req(wrk, h2, r2, h2->error);
338 90
                                again++;
339
                        }
340
                }
341 178
                if (!again)
342 106
                        break;
343 72
                Lck_Lock(&h2->sess->mtx);
344 152
                VTAILQ_FOREACH(r2, &h2->streams, list)
345 80
                        VSLb(h2->vsl, SLT_Debug, "ST %u %d",
346 80
                            r2->stream, r2->state);
347 72
                (void)Lck_CondWait(h2->cond, &h2->sess->mtx, VTIM_real() + .1);
348 72
                Lck_Unlock(&h2->sess->mtx);
349 72
        }
350 106
        h2->cond = NULL;
351 106
        h2_del_req(wrk, h2->req0);
352
}
353
354
static void v_matchproto_(vtr_reembark_f)
355 6
h2_reembark(struct worker *wrk, struct req *req)
356
{
357
        struct sess *sp;
358
359 6
        sp = req->sp;
360 6
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
361 6
        assert(req->transport == &H2_transport);
362
363 6
        if (!SES_Reschedule_Req(req, TASK_QUEUE_STR))
364 12
                return;
365
366
        /* Couldn't schedule, ditch */
367 0
        wrk->stats->busy_wakeup--;
368 0
        wrk->stats->busy_killed++;
369 0
        AN (req->vcl);
370 0
        VCL_Rel(&req->vcl);
371 0
        Req_AcctLogCharge(wrk->stats, req);
372 0
        Req_Release(req);
373 0
        DSL(DBG_WAITINGLIST, req->vsl->wid, "kill from waiting list");
374 0
        usleep(10000);
375
}
376
377
378
struct transport H2_transport = {
379
        .name =                 "H2",
380
        .magic =                TRANSPORT_MAGIC,
381
        .deliver =              h2_deliver,
382
        .minimal_response =     h2_minimal_response,
383
        .new_session =          h2_new_session,
384
        .reembark =             h2_reembark,
385
        .req_body =             h2_req_body,
386
        .req_fail =             h2_req_fail,
387
        .sess_panic =           h2_sess_panic,
388
};