summaryrefslogtreecommitdiffstats
path: root/include/haproxy/h2.h
blob: 4082b38a80f9904d518bbd9bd9f852712a3d3d9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/*
 * include/haproxy/h2.h
 * This file contains types and macros used for the HTTP/2 protocol
 *
 * Copyright (C) 2000-2017 Willy Tarreau - w@1wt.eu
 * Copyright (C) 2017 HAProxy Technologies
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef _HAPROXY_H2_H
#define _HAPROXY_H2_H

#include <import/ist.h>
#include <haproxy/api.h>
#include <haproxy/http-hdr-t.h>
#include <haproxy/htx-t.h>

/* indexes of most important pseudo headers can be simplified to an almost
 * linear array by dividing the index by 2 for all values from 1 to 9, and
 * caping to 4 for values up to 14 ; thus it fits in a single 24-bit array
 * shifted by 3 times the index value/2, or a 32-bit array shifted by 4x.
 * Don't change these values, they are assumed by hpack_idx_to_phdr(). There
 * is an entry for the Host header field which is not a pseudo-header but
 * needs to be tracked as we should only use :authority if it's absent.
 */
enum {
	H2_PHDR_IDX_NONE = 0,
	H2_PHDR_IDX_AUTH = 1, /* :authority = 1     */
	H2_PHDR_IDX_METH = 2, /* :method    = 2..3  */
	H2_PHDR_IDX_PATH = 3, /* :path      = 4..5  */
	H2_PHDR_IDX_SCHM = 4, /* :scheme    = 6..7  */
	H2_PHDR_IDX_STAT = 5, /* :status    = 8..14 */
	H2_PHDR_IDX_HOST = 6, /* Host, never returned, just a place-holder */
	H2_PHDR_IDX_PROT = 7, /* :protocol from rfc 8441 Extended Connect */
	H2_PHDR_NUM_ENTRIES   /* must be last */
};

/* bit fields indicating the pseudo-headers found. It also covers the HOST
 * header field as well as any non-pseudo-header field (NONE).
 */
enum {
	H2_PHDR_FND_NONE = 1 << H2_PHDR_IDX_NONE, /* found a regular header */
	H2_PHDR_FND_AUTH = 1 << H2_PHDR_IDX_AUTH,
	H2_PHDR_FND_METH = 1 << H2_PHDR_IDX_METH,
	H2_PHDR_FND_PATH = 1 << H2_PHDR_IDX_PATH,
	H2_PHDR_FND_SCHM = 1 << H2_PHDR_IDX_SCHM,
	H2_PHDR_FND_STAT = 1 << H2_PHDR_IDX_STAT,
	H2_PHDR_FND_HOST = 1 << H2_PHDR_IDX_HOST,
	H2_PHDR_FND_PROT = 1 << H2_PHDR_IDX_PROT,
};

/* frame types, from the standard */
enum h2_ft {
	H2_FT_DATA            = 0x00,     // RFC7540 #6.1
	H2_FT_HEADERS         = 0x01,     // RFC7540 #6.2
	H2_FT_PRIORITY        = 0x02,     // RFC7540 #6.3
	H2_FT_RST_STREAM      = 0x03,     // RFC7540 #6.4
	H2_FT_SETTINGS        = 0x04,     // RFC7540 #6.5
	H2_FT_PUSH_PROMISE    = 0x05,     // RFC7540 #6.6
	H2_FT_PING            = 0x06,     // RFC7540 #6.7
	H2_FT_GOAWAY          = 0x07,     // RFC7540 #6.8
	H2_FT_WINDOW_UPDATE   = 0x08,     // RFC7540 #6.9
	H2_FT_CONTINUATION    = 0x09,     // RFC7540 #6.10
	H2_FT_ENTRIES /* must be last */
} __attribute__((packed));

/* frame types, turned to bits or bit fields */
enum {
	/* one bit per frame type */
	H2_FT_DATA_BIT          = 1U << H2_FT_DATA,
	H2_FT_HEADERS_BIT       = 1U << H2_FT_HEADERS,
	H2_FT_PRIORITY_BIT      = 1U << H2_FT_PRIORITY,
	H2_FT_RST_STREAM_BIT    = 1U << H2_FT_RST_STREAM,
	H2_FT_SETTINGS_BIT      = 1U << H2_FT_SETTINGS,
	H2_FT_PUSH_PROMISE_BIT  = 1U << H2_FT_PUSH_PROMISE,
	H2_FT_PING_BIT          = 1U << H2_FT_PING,
	H2_FT_GOAWAY_BIT        = 1U << H2_FT_GOAWAY,
	H2_FT_WINDOW_UPDATE_BIT = 1U << H2_FT_WINDOW_UPDATE,
	H2_FT_CONTINUATION_BIT  = 1U << H2_FT_CONTINUATION,
	/* padded frames */
	H2_FT_PADDED_MASK       = H2_FT_DATA_BIT | H2_FT_HEADERS_BIT | H2_FT_PUSH_PROMISE_BIT,
	/* flow controlled frames */
	H2_FT_FC_MASK           = H2_FT_DATA_BIT,
	/* header frames */
	H2_FT_HDR_MASK          = H2_FT_HEADERS_BIT | H2_FT_PUSH_PROMISE_BIT | H2_FT_CONTINUATION_BIT,
	/* frames allowed to arrive late on a stream */
	H2_FT_LATE_MASK         = H2_FT_WINDOW_UPDATE_BIT | H2_FT_RST_STREAM_BIT | H2_FT_PRIORITY_BIT,
};


/* flags defined for each frame type */

// RFC7540 #6.1
#define H2_F_DATA_END_STREAM 0x01
#define H2_F_DATA_PADDED     0x08

// RFC7540 #6.2
#define H2_F_HEADERS_END_STREAM  0x01
#define H2_F_HEADERS_END_HEADERS 0x04
#define H2_F_HEADERS_PADDED      0x08
#define H2_F_HEADERS_PRIORITY    0x20

// RFC7540 #6.3 : PRIORITY defines no flags
// RFC7540 #6.4 : RST_STREAM defines no flags

// RFC7540 #6.5
#define H2_F_SETTINGS_ACK   0x01

// RFC7540 #6.6
#define H2_F_PUSH_PROMISE_END_HEADERS 0x04
#define H2_F_PUSH_PROMISE_PADDED      0x08

// RFC7540 #6.7
#define H2_F_PING_ACK   0x01

// RFC7540 #6.8 : GOAWAY defines no flags
// RFC7540 #6.9 : WINDOW_UPDATE defines no flags

// PADDED is the exact same among DATA, HEADERS and PUSH_PROMISE (8)
#define H2_F_PADDED              0x08

/* HTTP/2 error codes - RFC7540 #7 */
enum h2_err {
	H2_ERR_NO_ERROR            = 0x0,
	H2_ERR_PROTOCOL_ERROR      = 0x1,
	H2_ERR_INTERNAL_ERROR      = 0x2,
	H2_ERR_FLOW_CONTROL_ERROR  = 0x3,
	H2_ERR_SETTINGS_TIMEOUT    = 0x4,
	H2_ERR_STREAM_CLOSED       = 0x5,
	H2_ERR_FRAME_SIZE_ERROR    = 0x6,
	H2_ERR_REFUSED_STREAM      = 0x7,
	H2_ERR_CANCEL              = 0x8,
	H2_ERR_COMPRESSION_ERROR   = 0x9,
	H2_ERR_CONNECT_ERROR       = 0xa,
	H2_ERR_ENHANCE_YOUR_CALM   = 0xb,
	H2_ERR_INADEQUATE_SECURITY = 0xc,
	H2_ERR_HTTP_1_1_REQUIRED   = 0xd,
} __attribute__((packed));

// RFC7540 #11.3 : Settings Registry
#define H2_SETTINGS_HEADER_TABLE_SIZE       0x0001
#define H2_SETTINGS_ENABLE_PUSH             0x0002
#define H2_SETTINGS_MAX_CONCURRENT_STREAMS  0x0003
#define H2_SETTINGS_INITIAL_WINDOW_SIZE     0x0004
#define H2_SETTINGS_MAX_FRAME_SIZE          0x0005
#define H2_SETTINGS_MAX_HEADER_LIST_SIZE    0x0006
#define H2_SETTINGS_ENABLE_CONNECT_PROTOCOL 0x0008


/* some protocol constants */

// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
#define H2_CONN_PREFACE                     \
	"\x50\x52\x49\x20\x2a\x20\x48\x54"  \
	"\x54\x50\x2f\x32\x2e\x30\x0d\x0a"  \
	"\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a"


/* some flags related to protocol parsing */
#define H2_MSGF_BODY           0x0001    // a body is present
#define H2_MSGF_BODY_CL        0x0002    // content-length is present
#define H2_MSGF_BODY_TUNNEL    0x0004    // a tunnel is in use (CONNECT)
#define H2_MSGF_RSP_1XX        0x0010    // a 1xx ( != 101) HEADERS frame was received
#define H2_MSGF_BODYLESS_RSP   0x0020    // response message is known to have no body
                                         // (response to HEAD request or 204/304 response)
#define H2_MSGF_EXT_CONNECT    0x0040    // Extended CONNECT method from rfc 8441

#define H2_MAX_STREAM_ID       ((1U << 31) - 1)
#define H2_MAX_FRAME_LEN       ((1U << 24) - 1)
#define H2_DIR_REQ             1
#define H2_DIR_RES             2
#define H2_DIR_BOTH            3

/* constraints imposed by the protocol on each frame type, in terms of stream
 * ID values, frame sizes, and direction so that most connection-level checks
 * can be centralized regardless of the frame's acceptance.
 */
struct h2_frame_definition {
	int32_t dir;     /* 0=none, 1=request, 2=response, 3=both */
	int32_t min_id;  /* minimum allowed stream ID */
	int32_t max_id;  /* maximum allowed stream ID */
	int32_t min_len; /* minimum frame length */
	int32_t max_len; /* maximum frame length */
};

extern struct h2_frame_definition h2_frame_definition[H2_FT_ENTRIES];

/* various protocol processing functions */

int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len);
int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, int relaxed);
int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len, char *upgrade_protocol);
int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx);

/*
 * Some helpful debugging functions.
 */

/* returns a bit corresponding to the frame type */
static inline unsigned int h2_ft_bit(enum h2_ft ft)
{
	if (ft >= H2_FT_ENTRIES)
		return 0;
	return 1U << ft;
}

/* returns the frame type as a string */
static inline const char *h2_ft_str(int type)
{
	switch (type) {
	case H2_FT_DATA          : return "DATA";
	case H2_FT_HEADERS       : return "HEADERS";
	case H2_FT_PRIORITY      : return "PRIORITY";
	case H2_FT_RST_STREAM    : return "RST_STREAM";
	case H2_FT_SETTINGS      : return "SETTINGS";
	case H2_FT_PUSH_PROMISE  : return "PUSH_PROMISE";
	case H2_FT_PING          : return "PING";
	case H2_FT_GOAWAY        : return "GOAWAY";
	case H2_FT_WINDOW_UPDATE : return "WINDOW_UPDATE";
	default                  : return "_UNKNOWN_";
	}
}

/* returns the error code as a string */
static inline const char *h2_err_str(enum h2_err err)
{
	switch (err) {
	case H2_ERR_NO_ERROR            : return "NO_ERROR";
	case H2_ERR_PROTOCOL_ERROR      : return "PROTOCOL_ERROR";
	case H2_ERR_INTERNAL_ERROR      : return "INTERNAL_ERROR";
	case H2_ERR_FLOW_CONTROL_ERROR  : return "FLOW_CONTROL_ERROR";
	case H2_ERR_SETTINGS_TIMEOUT    : return "SETTINGS_TIMEOUT";
	case H2_ERR_STREAM_CLOSED       : return "STREAM_CLOSED";
	case H2_ERR_FRAME_SIZE_ERROR    : return "FRAME_SIZE_ERROR";
	case H2_ERR_REFUSED_STREAM      : return "REFUSED_STREAM";
	case H2_ERR_CANCEL              : return "CANCEL";
	case H2_ERR_COMPRESSION_ERROR   : return "COMPRESSION_ERROR";
	case H2_ERR_CONNECT_ERROR       : return "CONNECT_ERROR";
	case H2_ERR_ENHANCE_YOUR_CALM   : return "ENHANCE_YOUR_CALM";
	case H2_ERR_INADEQUATE_SECURITY : return "INADEQUATE_SECURITY";
	case H2_ERR_HTTP_1_1_REQUIRED   : return "HTTP_1_1_REQUIRED";
	default                         : return "_UNKNOWN_";
	}
}

/* Returns an error code if the frame is valid protocol-wise, otherwise 0. <ft>
 * is the frame type (H2_FT_*), <dir> is the direction (1=req, 2=res), <id> is
 * the stream ID from the frame header, <len> is the frame length from the
 * header. The purpose is to be able to quickly return a PROTOCOL_ERROR or
 * FRAME_SIZE_ERROR connection error even for situations where the frame will
 * be ignored. <mfs> must be the max frame size currently in place for the
 * protocol.
 */
static inline int h2_frame_check(enum h2_ft ft, int dir, int32_t id, int32_t len, int32_t mfs)
{
	struct h2_frame_definition *fd;

	if (ft >= H2_FT_ENTRIES)
		return H2_ERR_NO_ERROR; // ignore unhandled frame types

	fd = &h2_frame_definition[ft];

	if (!(dir & fd->dir))
		return H2_ERR_PROTOCOL_ERROR;

	if (id < fd->min_id || id > fd->max_id)
		return H2_ERR_PROTOCOL_ERROR;

	if (len < fd->min_len || len > fd->max_len)
		return H2_ERR_FRAME_SIZE_ERROR;

	if (len > mfs)
		return H2_ERR_FRAME_SIZE_ERROR;

	if (ft == H2_FT_SETTINGS && (len % 6) != 0)
		return H2_ERR_FRAME_SIZE_ERROR; // RFC7540#6.5

	return H2_ERR_NO_ERROR;
}

/* returns the pseudo-header <str> corresponds to among H2_PHDR_IDX_*, 0 if not a
 * pseudo-header, or -1 if not a valid pseudo-header.
 */
static inline int h2_str_to_phdr(const struct ist str)
{
	if (*str.ptr == ':') {
		if (isteq(str, ist(":path")))           return H2_PHDR_IDX_PATH;
		else if (isteq(str, ist(":method")))    return H2_PHDR_IDX_METH;
		else if (isteq(str, ist(":scheme")))    return H2_PHDR_IDX_SCHM;
		else if (isteq(str, ist(":status")))    return H2_PHDR_IDX_STAT;
		else if (isteq(str, ist(":authority"))) return H2_PHDR_IDX_AUTH;
		else if (isteq(str, ist(":protocol")))  return H2_PHDR_IDX_PROT;

		/* all other names starting with ':' */
		return -1;
	}

	/* not a pseudo header */
	return 0;
}

/* returns the pseudo-header name <num> as an ist, or ":UNKNOWN" if unknown.
 * Note that all strings are zero-terminated constants.
 */
static inline struct ist h2_phdr_to_ist(int phdr)
{
	switch (phdr) {
	case H2_PHDR_IDX_NONE: return ist(":NONE");
	case H2_PHDR_IDX_AUTH: return ist(":authority");
	case H2_PHDR_IDX_METH: return ist(":method");
	case H2_PHDR_IDX_PATH: return ist(":path");
	case H2_PHDR_IDX_SCHM: return ist(":scheme");
	case H2_PHDR_IDX_STAT: return ist(":status");
	case H2_PHDR_IDX_HOST: return ist("Host");
	default:               return ist(":UNKNOWN");
	}
}

/* returns the pseudo-header name <num> as a string, or ":UNKNOWN" if unknown */
static inline const char *h2_phdr_to_str(int phdr)
{
	return h2_phdr_to_ist(phdr).ptr;
}

#endif /* _HAPROXY_H2_H */

/*
 * Local variables:
 *  c-indent-level: 8
 *  c-basic-offset: 8
 * End:
 */