1 /*
2 * nghttp2 - HTTP/2 C Library
3 *
4 * Copyright (c) 2012 Tatsuhiro Tsujikawa
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 <sys/types.h>
26 #ifdef HAVE_SYS_SOCKET_H
27 # include <sys/socket.h>
28 #endif // HAVE_SYS_SOCKET_H
29 #ifdef HAVE_NETDB_H
30 # include <netdb.h>
31 #endif // HAVE_NETDB_H
32 #ifdef HAVE_UNISTD_H
33 # include <unistd.h>
34 #endif // HAVE_UNISTD_H
35 #ifdef HAVE_FCNTL_H
36 # include <fcntl.h>
37 #endif // HAVE_FCNTL_H
38 #ifdef HAVE_NETINET_IN_H
39 # include <netinet/in.h>
40 #endif // HAVE_NETINET_IN_H
41 #include <netinet/tcp.h>
42 #include <poll.h>
43
44 #include <cassert>
45 #include <cstdio>
46 #include <cerrno>
47 #include <cstdlib>
48 #include <cstring>
49 #include <string>
50 #include <iostream>
51 #include <string>
52 #include <set>
53 #include <iomanip>
54 #include <fstream>
55
56 #include "app_helper.h"
57 #include "util.h"
58 #include "http2.h"
59 #include "template.h"
60
61 namespace nghttp2 {
62
63 namespace {
strsettingsid(int32_t id)64 const char *strsettingsid(int32_t id) {
65 switch (id) {
66 case NGHTTP2_SETTINGS_HEADER_TABLE_SIZE:
67 return "SETTINGS_HEADER_TABLE_SIZE";
68 case NGHTTP2_SETTINGS_ENABLE_PUSH:
69 return "SETTINGS_ENABLE_PUSH";
70 case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
71 return "SETTINGS_MAX_CONCURRENT_STREAMS";
72 case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
73 return "SETTINGS_INITIAL_WINDOW_SIZE";
74 case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
75 return "SETTINGS_MAX_FRAME_SIZE";
76 case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
77 return "SETTINGS_MAX_HEADER_LIST_SIZE";
78 case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
79 return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
80 case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
81 return "SETTINGS_NO_RFC7540_PRIORITIES";
82 default:
83 return "UNKNOWN";
84 }
85 }
86 } // namespace
87
88 namespace {
strframetype(uint8_t type)89 std::string strframetype(uint8_t type) {
90 switch (type) {
91 case NGHTTP2_DATA:
92 return "DATA";
93 case NGHTTP2_HEADERS:
94 return "HEADERS";
95 case NGHTTP2_PRIORITY:
96 return "PRIORITY";
97 case NGHTTP2_RST_STREAM:
98 return "RST_STREAM";
99 case NGHTTP2_SETTINGS:
100 return "SETTINGS";
101 case NGHTTP2_PUSH_PROMISE:
102 return "PUSH_PROMISE";
103 case NGHTTP2_PING:
104 return "PING";
105 case NGHTTP2_GOAWAY:
106 return "GOAWAY";
107 case NGHTTP2_WINDOW_UPDATE:
108 return "WINDOW_UPDATE";
109 case NGHTTP2_ALTSVC:
110 return "ALTSVC";
111 case NGHTTP2_ORIGIN:
112 return "ORIGIN";
113 case NGHTTP2_PRIORITY_UPDATE:
114 return "PRIORITY_UPDATE";
115 }
116
117 std::string s = "extension(0x";
118 s += util::format_hex(std::span{&type, 1});
119 s += ')';
120
121 return s;
122 };
123 } // namespace
124
125 namespace {
126 bool color_output = false;
127 } // namespace
128
set_color_output(bool f)129 void set_color_output(bool f) { color_output = f; }
130
131 namespace {
132 FILE *outfile = stdout;
133 } // namespace
134
set_output(FILE * file)135 void set_output(FILE *file) { outfile = file; }
136
137 namespace {
print_frame_attr_indent()138 void print_frame_attr_indent() { fprintf(outfile, " "); }
139 } // namespace
140
141 namespace {
ansi_esc(const char * code)142 const char *ansi_esc(const char *code) { return color_output ? code : ""; }
143 } // namespace
144
145 namespace {
ansi_escend()146 const char *ansi_escend() { return color_output ? "\033[0m" : ""; }
147 } // namespace
148
149 namespace {
print_nv(nghttp2_nv * nv)150 void print_nv(nghttp2_nv *nv) {
151 fprintf(outfile, "%s%s%s: %s\n", ansi_esc("\033[1;34m"), nv->name,
152 ansi_escend(), nv->value);
153 }
154 } // namespace
155 namespace {
print_nv(nghttp2_nv * nva,size_t nvlen)156 void print_nv(nghttp2_nv *nva, size_t nvlen) {
157 auto end = nva + nvlen;
158 for (; nva != end; ++nva) {
159 print_frame_attr_indent();
160
161 print_nv(nva);
162 }
163 }
164 } // namespace
165
print_timer()166 void print_timer() {
167 auto millis = get_timer();
168 fprintf(outfile, "%s[%3ld.%03ld]%s", ansi_esc("\033[33m"),
169 (long int)(millis.count() / 1000), (long int)(millis.count() % 1000),
170 ansi_escend());
171 }
172
173 namespace {
print_frame_hd(const nghttp2_frame_hd & hd)174 void print_frame_hd(const nghttp2_frame_hd &hd) {
175 fprintf(outfile, "<length=%zu, flags=0x%02x, stream_id=%d>\n", hd.length,
176 hd.flags, hd.stream_id);
177 }
178 } // namespace
179
180 namespace {
print_flags(const nghttp2_frame_hd & hd)181 void print_flags(const nghttp2_frame_hd &hd) {
182 std::string s;
183 switch (hd.type) {
184 case NGHTTP2_DATA:
185 if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
186 s += "END_STREAM";
187 }
188 if (hd.flags & NGHTTP2_FLAG_PADDED) {
189 if (!s.empty()) {
190 s += " | ";
191 }
192 s += "PADDED";
193 }
194 break;
195 case NGHTTP2_HEADERS:
196 if (hd.flags & NGHTTP2_FLAG_END_STREAM) {
197 s += "END_STREAM";
198 }
199 if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
200 if (!s.empty()) {
201 s += " | ";
202 }
203 s += "END_HEADERS";
204 }
205 if (hd.flags & NGHTTP2_FLAG_PADDED) {
206 if (!s.empty()) {
207 s += " | ";
208 }
209 s += "PADDED";
210 }
211 if (hd.flags & NGHTTP2_FLAG_PRIORITY) {
212 if (!s.empty()) {
213 s += " | ";
214 }
215 s += "PRIORITY";
216 }
217
218 break;
219 case NGHTTP2_PRIORITY:
220 break;
221 case NGHTTP2_SETTINGS:
222 if (hd.flags & NGHTTP2_FLAG_ACK) {
223 s += "ACK";
224 }
225 break;
226 case NGHTTP2_PUSH_PROMISE:
227 if (hd.flags & NGHTTP2_FLAG_END_HEADERS) {
228 s += "END_HEADERS";
229 }
230 if (hd.flags & NGHTTP2_FLAG_PADDED) {
231 if (!s.empty()) {
232 s += " | ";
233 }
234 s += "PADDED";
235 }
236 break;
237 case NGHTTP2_PING:
238 if (hd.flags & NGHTTP2_FLAG_ACK) {
239 s += "ACK";
240 }
241 break;
242 }
243 fprintf(outfile, "; %s\n", s.c_str());
244 }
245 } // namespace
246
247 enum print_type { PRINT_SEND, PRINT_RECV };
248
249 namespace {
frame_name_ansi_esc(print_type ptype)250 const char *frame_name_ansi_esc(print_type ptype) {
251 return ansi_esc(ptype == PRINT_SEND ? "\033[1;35m" : "\033[1;36m");
252 }
253 } // namespace
254
255 namespace {
print_frame(print_type ptype,const nghttp2_frame * frame)256 void print_frame(print_type ptype, const nghttp2_frame *frame) {
257 fprintf(outfile, "%s%s%s frame ", frame_name_ansi_esc(ptype),
258 strframetype(frame->hd.type).c_str(), ansi_escend());
259 print_frame_hd(frame->hd);
260 if (frame->hd.flags) {
261 print_frame_attr_indent();
262 print_flags(frame->hd);
263 }
264 switch (frame->hd.type) {
265 case NGHTTP2_DATA:
266 if (frame->data.padlen > 0) {
267 print_frame_attr_indent();
268 fprintf(outfile, "(padlen=%zu)\n", frame->data.padlen);
269 }
270 break;
271 case NGHTTP2_HEADERS:
272 print_frame_attr_indent();
273 fprintf(outfile, "(padlen=%zu", frame->headers.padlen);
274 if (frame->hd.flags & NGHTTP2_FLAG_PRIORITY) {
275 fprintf(outfile, ", dep_stream_id=%d, weight=%u, exclusive=%d",
276 frame->headers.pri_spec.stream_id, frame->headers.pri_spec.weight,
277 frame->headers.pri_spec.exclusive);
278 }
279 fprintf(outfile, ")\n");
280 switch (frame->headers.cat) {
281 case NGHTTP2_HCAT_REQUEST:
282 print_frame_attr_indent();
283 fprintf(outfile, "; Open new stream\n");
284 break;
285 case NGHTTP2_HCAT_RESPONSE:
286 print_frame_attr_indent();
287 fprintf(outfile, "; First response header\n");
288 break;
289 case NGHTTP2_HCAT_PUSH_RESPONSE:
290 print_frame_attr_indent();
291 fprintf(outfile, "; First push response header\n");
292 break;
293 default:
294 break;
295 }
296 print_nv(frame->headers.nva, frame->headers.nvlen);
297 break;
298 case NGHTTP2_PRIORITY:
299 print_frame_attr_indent();
300
301 fprintf(outfile, "(dep_stream_id=%d, weight=%u, exclusive=%d)\n",
302 frame->priority.pri_spec.stream_id, frame->priority.pri_spec.weight,
303 frame->priority.pri_spec.exclusive);
304
305 break;
306 case NGHTTP2_RST_STREAM:
307 print_frame_attr_indent();
308 fprintf(outfile, "(error_code=%s(0x%02x))\n",
309 nghttp2_http2_strerror(frame->rst_stream.error_code),
310 frame->rst_stream.error_code);
311 break;
312 case NGHTTP2_SETTINGS:
313 print_frame_attr_indent();
314 fprintf(outfile, "(niv=%lu)\n",
315 static_cast<unsigned long>(frame->settings.niv));
316 for (size_t i = 0; i < frame->settings.niv; ++i) {
317 print_frame_attr_indent();
318 fprintf(outfile, "[%s(0x%02x):%u]\n",
319 strsettingsid(frame->settings.iv[i].settings_id),
320 frame->settings.iv[i].settings_id, frame->settings.iv[i].value);
321 }
322 break;
323 case NGHTTP2_PUSH_PROMISE:
324 print_frame_attr_indent();
325 fprintf(outfile, "(padlen=%zu, promised_stream_id=%d)\n",
326 frame->push_promise.padlen, frame->push_promise.promised_stream_id);
327 print_nv(frame->push_promise.nva, frame->push_promise.nvlen);
328 break;
329 case NGHTTP2_PING:
330 print_frame_attr_indent();
331 fprintf(outfile, "(opaque_data=%s)\n",
332 util::format_hex(frame->ping.opaque_data).c_str());
333 break;
334 case NGHTTP2_GOAWAY:
335 print_frame_attr_indent();
336 fprintf(outfile,
337 "(last_stream_id=%d, error_code=%s(0x%02x), "
338 "opaque_data(%u)=[%s])\n",
339 frame->goaway.last_stream_id,
340 nghttp2_http2_strerror(frame->goaway.error_code),
341 frame->goaway.error_code,
342 static_cast<unsigned int>(frame->goaway.opaque_data_len),
343 util::ascii_dump(frame->goaway.opaque_data,
344 frame->goaway.opaque_data_len)
345 .c_str());
346 break;
347 case NGHTTP2_WINDOW_UPDATE:
348 print_frame_attr_indent();
349 fprintf(outfile, "(window_size_increment=%d)\n",
350 frame->window_update.window_size_increment);
351 break;
352 case NGHTTP2_ALTSVC: {
353 auto altsvc = static_cast<nghttp2_ext_altsvc *>(frame->ext.payload);
354 print_frame_attr_indent();
355 fprintf(outfile, "(origin=[%.*s], altsvc_field_value=[%.*s])\n",
356 static_cast<int>(altsvc->origin_len), altsvc->origin,
357 static_cast<int>(altsvc->field_value_len), altsvc->field_value);
358 break;
359 }
360 case NGHTTP2_ORIGIN: {
361 auto origin = static_cast<nghttp2_ext_origin *>(frame->ext.payload);
362 for (size_t i = 0; i < origin->nov; ++i) {
363 auto ent = &origin->ov[i];
364 print_frame_attr_indent();
365 fprintf(outfile, "[%.*s]\n", (int)ent->origin_len, ent->origin);
366 }
367 break;
368 }
369 case NGHTTP2_PRIORITY_UPDATE: {
370 auto priority_update =
371 static_cast<nghttp2_ext_priority_update *>(frame->ext.payload);
372 print_frame_attr_indent();
373 fprintf(outfile,
374 "(prioritized_stream_id=%d, priority_field_value=[%.*s])\n",
375 priority_update->stream_id,
376 static_cast<int>(priority_update->field_value_len),
377 priority_update->field_value);
378 break;
379 }
380 default:
381 break;
382 }
383 }
384 } // namespace
385
verbose_on_header_callback(nghttp2_session * session,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t valuelen,uint8_t flags,void * user_data)386 int verbose_on_header_callback(nghttp2_session *session,
387 const nghttp2_frame *frame, const uint8_t *name,
388 size_t namelen, const uint8_t *value,
389 size_t valuelen, uint8_t flags,
390 void *user_data) {
391 nghttp2_nv nv = {const_cast<uint8_t *>(name), const_cast<uint8_t *>(value),
392 namelen, valuelen};
393
394 print_timer();
395 fprintf(outfile, " recv (stream_id=%d", frame->hd.stream_id);
396 if (flags & NGHTTP2_NV_FLAG_NO_INDEX) {
397 fprintf(outfile, ", sensitive");
398 }
399 fprintf(outfile, ") ");
400
401 print_nv(&nv);
402 fflush(outfile);
403
404 return 0;
405 }
406
verbose_on_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)407 int verbose_on_frame_recv_callback(nghttp2_session *session,
408 const nghttp2_frame *frame,
409 void *user_data) {
410 print_timer();
411 fprintf(outfile, " recv ");
412 print_frame(PRINT_RECV, frame);
413 fflush(outfile);
414 return 0;
415 }
416
verbose_on_invalid_frame_recv_callback(nghttp2_session * session,const nghttp2_frame * frame,int lib_error_code,void * user_data)417 int verbose_on_invalid_frame_recv_callback(nghttp2_session *session,
418 const nghttp2_frame *frame,
419 int lib_error_code,
420 void *user_data) {
421 print_timer();
422 fprintf(outfile, " [INVALID; error=%s] recv ",
423 nghttp2_strerror(lib_error_code));
424 print_frame(PRINT_RECV, frame);
425 fflush(outfile);
426 return 0;
427 }
428
verbose_on_frame_send_callback(nghttp2_session * session,const nghttp2_frame * frame,void * user_data)429 int verbose_on_frame_send_callback(nghttp2_session *session,
430 const nghttp2_frame *frame,
431 void *user_data) {
432 print_timer();
433 fprintf(outfile, " send ");
434 print_frame(PRINT_SEND, frame);
435 fflush(outfile);
436 return 0;
437 }
438
verbose_on_data_chunk_recv_callback(nghttp2_session * session,uint8_t flags,int32_t stream_id,const uint8_t * data,size_t len,void * user_data)439 int verbose_on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
440 int32_t stream_id, const uint8_t *data,
441 size_t len, void *user_data) {
442 print_timer();
443 auto srecv =
444 nghttp2_session_get_stream_effective_recv_data_length(session, stream_id);
445 auto crecv = nghttp2_session_get_effective_recv_data_length(session);
446
447 fprintf(outfile,
448 " recv (stream_id=%d, length=%zu, srecv=%d, crecv=%d) DATA\n",
449 stream_id, len, srecv, crecv);
450 fflush(outfile);
451
452 return 0;
453 }
454
verbose_error_callback(nghttp2_session * session,int lib_error_code,const char * msg,size_t len,void * user_data)455 int verbose_error_callback(nghttp2_session *session, int lib_error_code,
456 const char *msg, size_t len, void *user_data) {
457 print_timer();
458 fprintf(outfile, " [ERROR] %.*s\n", (int)len, msg);
459 fflush(outfile);
460
461 return 0;
462 }
463
464 namespace {
465 std::chrono::steady_clock::time_point base_tv;
466 } // namespace
467
reset_timer()468 void reset_timer() { base_tv = std::chrono::steady_clock::now(); }
469
get_timer()470 std::chrono::milliseconds get_timer() {
471 return time_delta(std::chrono::steady_clock::now(), base_tv);
472 }
473
get_time()474 std::chrono::steady_clock::time_point get_time() {
475 return std::chrono::steady_clock::now();
476 }
477
478 } // namespace nghttp2
479