1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2019 nghttp2 contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25 #include "h2load_http3_session.h"
26
27 #include <iostream>
28
29 #include <ngtcp2/ngtcp2.h>
30
31 #include "h2load.h"
32
33 namespace h2load {
34
Http3Session(Client * client)35 Http3Session::Http3Session(Client *client)
36 : client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
37
~Http3Session()38 Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
39
on_connect()40 void Http3Session::on_connect() {}
41
submit_request()42 int Http3Session::submit_request() {
43 if (npending_request_) {
44 ++npending_request_;
45 return 0;
46 }
47
48 auto config = client_->worker->config;
49 reqidx_ = client_->reqidx;
50
51 if (++client_->reqidx == config->nva.size()) {
52 client_->reqidx = 0;
53 }
54
55 auto stream_id = submit_request_internal();
56 if (stream_id < 0) {
57 if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
58 ++npending_request_;
59 return 0;
60 }
61 return -1;
62 }
63
64 return 0;
65 }
66
67 namespace {
read_data(nghttp3_conn * conn,int64_t stream_id,nghttp3_vec * vec,size_t veccnt,uint32_t * pflags,void * user_data,void * stream_user_data)68 nghttp3_ssize read_data(nghttp3_conn *conn, int64_t stream_id, nghttp3_vec *vec,
69 size_t veccnt, uint32_t *pflags, void *user_data,
70 void *stream_user_data) {
71 auto s = static_cast<Http3Session *>(user_data);
72
73 s->read_data(vec, veccnt, pflags);
74
75 return 1;
76 }
77 } // namespace
78
read_data(nghttp3_vec * vec,size_t veccnt,uint32_t * pflags)79 void Http3Session::read_data(nghttp3_vec *vec, size_t veccnt,
80 uint32_t *pflags) {
81 assert(veccnt > 0);
82
83 auto config = client_->worker->config;
84
85 vec[0].base = config->data;
86 vec[0].len = config->data_length;
87 *pflags |= NGHTTP3_DATA_FLAG_EOF;
88 }
89
submit_request_internal()90 int64_t Http3Session::submit_request_internal() {
91 int rv;
92 int64_t stream_id;
93
94 auto config = client_->worker->config;
95 auto &nva = config->nva[reqidx_];
96
97 rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
98 if (rv != 0) {
99 return rv;
100 }
101
102 nghttp3_data_reader dr{};
103 dr.read_data = h2load::read_data;
104
105 rv = nghttp3_conn_submit_request(
106 conn_, stream_id, reinterpret_cast<nghttp3_nv *>(nva.data()), nva.size(),
107 config->data_fd == -1 ? nullptr : &dr, nullptr);
108 if (rv != 0) {
109 return rv;
110 }
111
112 client_->on_request(stream_id);
113 auto req_stat = client_->get_req_stat(stream_id);
114 assert(req_stat);
115 client_->record_request_time(req_stat);
116
117 return stream_id;
118 }
119
on_read(const uint8_t * data,size_t len)120 int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
121
on_write()122 int Http3Session::on_write() { return -1; }
123
terminate()124 void Http3Session::terminate() {}
125
max_concurrent_streams()126 size_t Http3Session::max_concurrent_streams() {
127 return client_->worker->config->max_concurrent_streams;
128 }
129
130 namespace {
stream_close(nghttp3_conn * conn,int64_t stream_id,uint64_t app_error_code,void * user_data,void * stream_user_data)131 int stream_close(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
132 void *user_data, void *stream_user_data) {
133 auto s = static_cast<Http3Session *>(user_data);
134 if (s->stream_close(stream_id, app_error_code) != 0) {
135 return NGHTTP3_ERR_CALLBACK_FAILURE;
136 }
137 return 0;
138 }
139 } // namespace
140
stream_close(int64_t stream_id,uint64_t app_error_code)141 int Http3Session::stream_close(int64_t stream_id, uint64_t app_error_code) {
142 if (!ngtcp2_is_bidi_stream(stream_id)) {
143 assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
144 ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
145 }
146 client_->on_stream_close(stream_id, app_error_code == NGHTTP3_H3_NO_ERROR);
147 return 0;
148 }
149
150 namespace {
end_stream(nghttp3_conn * conn,int64_t stream_id,void * user_data,void * stream_user_data)151 int end_stream(nghttp3_conn *conn, int64_t stream_id, void *user_data,
152 void *stream_user_data) {
153 auto s = static_cast<Http3Session *>(user_data);
154 if (s->end_stream(stream_id) != 0) {
155 return NGHTTP3_ERR_CALLBACK_FAILURE;
156 }
157 return 0;
158 }
159 } // namespace
160
end_stream(int64_t stream_id)161 int Http3Session::end_stream(int64_t stream_id) {
162 client_->record_ttfb();
163
164 return 0;
165 }
166
167 namespace {
recv_data(nghttp3_conn * conn,int64_t stream_id,const uint8_t * data,size_t datalen,void * user_data,void * stream_user_data)168 int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
169 size_t datalen, void *user_data, void *stream_user_data) {
170 auto s = static_cast<Http3Session *>(user_data);
171 s->recv_data(stream_id, data, datalen);
172 return 0;
173 }
174 } // namespace
175
recv_data(int64_t stream_id,const uint8_t * data,size_t datalen)176 void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
177 size_t datalen) {
178 client_->record_ttfb();
179 client_->worker->stats.bytes_body += datalen;
180 consume(stream_id, datalen);
181 }
182
183 namespace {
deferred_consume(nghttp3_conn * conn,int64_t stream_id,size_t nconsumed,void * user_data,void * stream_user_data)184 int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
185 void *user_data, void *stream_user_data) {
186 auto s = static_cast<Http3Session *>(user_data);
187 s->consume(stream_id, nconsumed);
188 return 0;
189 }
190 } // namespace
191
consume(int64_t stream_id,size_t nconsumed)192 void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
193 ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
194 nconsumed);
195 ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
196 }
197
198 namespace {
begin_headers(nghttp3_conn * conn,int64_t stream_id,void * user_data,void * stream_user_data)199 int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
200 void *stream_user_data) {
201 auto s = static_cast<Http3Session *>(user_data);
202 s->begin_headers(stream_id);
203 return 0;
204 }
205 } // namespace
206
begin_headers(int64_t stream_id)207 void Http3Session::begin_headers(int64_t stream_id) {
208 auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
209 assert(payloadlen > 0);
210
211 client_->worker->stats.bytes_head += payloadlen;
212 }
213
214 namespace {
recv_header(nghttp3_conn * conn,int64_t stream_id,int32_t token,nghttp3_rcbuf * name,nghttp3_rcbuf * value,uint8_t flags,void * user_data,void * stream_user_data)215 int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
216 nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
217 void *user_data, void *stream_user_data) {
218 auto s = static_cast<Http3Session *>(user_data);
219 auto k = nghttp3_rcbuf_get_buf(name);
220 auto v = nghttp3_rcbuf_get_buf(value);
221 s->recv_header(stream_id, &k, &v);
222 return 0;
223 }
224 } // namespace
225
recv_header(int64_t stream_id,const nghttp3_vec * name,const nghttp3_vec * value)226 void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
227 const nghttp3_vec *value) {
228 client_->on_header(stream_id, name->base, name->len, value->base, value->len);
229 client_->worker->stats.bytes_head_decomp += name->len + value->len;
230 }
231
232 namespace {
stop_sending(nghttp3_conn * conn,int64_t stream_id,uint64_t app_error_code,void * user_data,void * stream_user_data)233 int stop_sending(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
234 void *user_data, void *stream_user_data) {
235 auto s = static_cast<Http3Session *>(user_data);
236 if (s->stop_sending(stream_id, app_error_code) != 0) {
237 return NGHTTP3_ERR_CALLBACK_FAILURE;
238 }
239 return 0;
240 }
241 } // namespace
242
stop_sending(int64_t stream_id,uint64_t app_error_code)243 int Http3Session::stop_sending(int64_t stream_id, uint64_t app_error_code) {
244 auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, 0, stream_id,
245 app_error_code);
246 if (rv != 0) {
247 std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
248 << std::endl;
249 return -1;
250 }
251 return 0;
252 }
253
254 namespace {
reset_stream(nghttp3_conn * conn,int64_t stream_id,uint64_t app_error_code,void * user_data,void * stream_user_data)255 int reset_stream(nghttp3_conn *conn, int64_t stream_id, uint64_t app_error_code,
256 void *user_data, void *stream_user_data) {
257 auto s = static_cast<Http3Session *>(user_data);
258 if (s->reset_stream(stream_id, app_error_code) != 0) {
259 return NGHTTP3_ERR_CALLBACK_FAILURE;
260 }
261 return 0;
262 }
263 } // namespace
264
reset_stream(int64_t stream_id,uint64_t app_error_code)265 int Http3Session::reset_stream(int64_t stream_id, uint64_t app_error_code) {
266 auto rv = ngtcp2_conn_shutdown_stream_write(client_->quic.conn, 0, stream_id,
267 app_error_code);
268 if (rv != 0) {
269 std::cerr << "ngtcp2_conn_shutdown_stream_write: " << ngtcp2_strerror(rv)
270 << std::endl;
271 return -1;
272 }
273 return 0;
274 }
275
close_stream(int64_t stream_id,uint64_t app_error_code)276 int Http3Session::close_stream(int64_t stream_id, uint64_t app_error_code) {
277 auto rv = nghttp3_conn_close_stream(conn_, stream_id, app_error_code);
278 switch (rv) {
279 case 0:
280 return 0;
281 case NGHTTP3_ERR_STREAM_NOT_FOUND:
282 if (!ngtcp2_is_bidi_stream(stream_id)) {
283 assert(!ngtcp2_conn_is_local_stream(client_->quic.conn, stream_id));
284 ngtcp2_conn_extend_max_streams_uni(client_->quic.conn, 1);
285 }
286 return 0;
287 default:
288 return -1;
289 }
290 }
291
shutdown_stream_read(int64_t stream_id)292 int Http3Session::shutdown_stream_read(int64_t stream_id) {
293 auto rv = nghttp3_conn_shutdown_stream_read(conn_, stream_id);
294 if (rv != 0) {
295 return -1;
296 }
297 return 0;
298 }
299
extend_max_local_streams()300 int Http3Session::extend_max_local_streams() {
301 auto config = client_->worker->config;
302
303 for (; npending_request_; --npending_request_) {
304 auto stream_id = submit_request_internal();
305 if (stream_id < 0) {
306 if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
307 return 0;
308 }
309 return -1;
310 }
311
312 if (++reqidx_ == config->nva.size()) {
313 reqidx_ = 0;
314 }
315 }
316
317 return 0;
318 }
319
init_conn()320 int Http3Session::init_conn() {
321 int rv;
322
323 assert(conn_ == nullptr);
324
325 if (ngtcp2_conn_get_streams_uni_left(client_->quic.conn) < 3) {
326 return -1;
327 }
328
329 nghttp3_callbacks callbacks{
330 nullptr, // acked_stream_data
331 h2load::stream_close,
332 h2load::recv_data,
333 h2load::deferred_consume,
334 h2load::begin_headers,
335 h2load::recv_header,
336 nullptr, // end_headers
337 nullptr, // begin_trailers
338 h2load::recv_header,
339 nullptr, // end_trailers
340 h2load::stop_sending,
341 h2load::end_stream,
342 h2load::reset_stream,
343 nullptr, // shutdown
344 };
345
346 auto config = client_->worker->config;
347
348 nghttp3_settings settings;
349 nghttp3_settings_default(&settings);
350 settings.qpack_max_dtable_capacity = config->header_table_size;
351 settings.qpack_blocked_streams = 100;
352
353 auto mem = nghttp3_mem_default();
354
355 rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
356 if (rv != 0) {
357 std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
358 << std::endl;
359 return -1;
360 }
361
362 int64_t ctrl_stream_id;
363
364 rv =
365 ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, nullptr);
366 if (rv != 0) {
367 std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
368 << std::endl;
369 return -1;
370 }
371
372 rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
373 if (rv != 0) {
374 std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
375 << std::endl;
376 return -1;
377 }
378
379 int64_t qpack_enc_stream_id, qpack_dec_stream_id;
380
381 rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
382 nullptr);
383 if (rv != 0) {
384 std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
385 << std::endl;
386 return -1;
387 }
388
389 rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
390 nullptr);
391 if (rv != 0) {
392 std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
393 << std::endl;
394 return -1;
395 }
396
397 rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
398 qpack_dec_stream_id);
399 if (rv != 0) {
400 std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
401 << std::endl;
402 return -1;
403 }
404
405 return 0;
406 }
407
read_stream(uint32_t flags,int64_t stream_id,const uint8_t * data,size_t datalen)408 ssize_t Http3Session::read_stream(uint32_t flags, int64_t stream_id,
409 const uint8_t *data, size_t datalen) {
410 auto nconsumed = nghttp3_conn_read_stream(
411 conn_, stream_id, data, datalen, flags & NGTCP2_STREAM_DATA_FLAG_FIN);
412 if (nconsumed < 0) {
413 std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
414 << std::endl;
415 ngtcp2_ccerr_set_application_error(
416 &client_->quic.last_error,
417 nghttp3_err_infer_quic_app_error_code(nconsumed), nullptr, 0);
418 return -1;
419 }
420 return nconsumed;
421 }
422
write_stream(int64_t & stream_id,int & fin,nghttp3_vec * vec,size_t veccnt)423 ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
424 nghttp3_vec *vec, size_t veccnt) {
425 auto sveccnt =
426 nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
427 if (sveccnt < 0) {
428 ngtcp2_ccerr_set_application_error(
429 &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(sveccnt),
430 nullptr, 0);
431 return -1;
432 }
433 return sveccnt;
434 }
435
block_stream(int64_t stream_id)436 void Http3Session::block_stream(int64_t stream_id) {
437 nghttp3_conn_block_stream(conn_, stream_id);
438 }
439
unblock_stream(int64_t stream_id)440 int Http3Session::unblock_stream(int64_t stream_id) {
441 if (nghttp3_conn_unblock_stream(conn_, stream_id) != 0) {
442 return -1;
443 }
444
445 return 0;
446 }
447
shutdown_stream_write(int64_t stream_id)448 void Http3Session::shutdown_stream_write(int64_t stream_id) {
449 nghttp3_conn_shutdown_stream_write(conn_, stream_id);
450 }
451
add_write_offset(int64_t stream_id,size_t ndatalen)452 int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
453 auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
454 if (rv != 0) {
455 ngtcp2_ccerr_set_application_error(
456 &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
457 nullptr, 0);
458 return -1;
459 }
460 return 0;
461 }
462
add_ack_offset(int64_t stream_id,size_t datalen)463 int Http3Session::add_ack_offset(int64_t stream_id, size_t datalen) {
464 auto rv = nghttp3_conn_add_ack_offset(conn_, stream_id, datalen);
465 if (rv != 0) {
466 ngtcp2_ccerr_set_application_error(
467 &client_->quic.last_error, nghttp3_err_infer_quic_app_error_code(rv),
468 nullptr, 0);
469 return -1;
470 }
471 return 0;
472 }
473
474 } // namespace h2load
475