1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Originally written @ Covalent by Jim Jagielski
19 * Modified to support writing to non blocking pipes @ BBC by Graham Leggett
20 * Modifications (C) 2011 British Broadcasting Corporation
25 * A request and response sniffer for Apache v2.x. It logs
26 * all filter data right before and after it goes out on the
27 * wire (BUT right before SSL encoded or after SSL decoded).
28 * It can produce a *huge* amount of data.
32 #include "http_connection.h"
33 #include "http_config.h"
34 #include "http_core.h"
36 #include "http_request.h"
37 #include "util_ebcdic.h"
38 #include "apr_strings.h"
39 #include "apr_portable.h"
41 #include "mod_proxy.h"
43 #if APR_HAVE_SYS_SYSLIMITS_H
44 #include <sys/syslimits.h>
46 #if APR_HAVE_LINUX_LIMITS_H
47 #include <linux/limits.h>
56 module AP_MODULE_DECLARE_DATA firehose_module;
58 typedef enum proxy_enum
60 FIREHOSE_PROXY, FIREHOSE_NORMAL
63 typedef enum request_enum
65 FIREHOSE_CONNECTION, FIREHOSE_REQUEST
68 typedef enum direction_enum
70 FIREHOSE_IN = '<', FIREHOSE_OUT = '>'
73 typedef struct firehose_conn_t
78 direction_enum direction;
84 typedef struct firehose_conf_t
86 apr_array_header_t *firehoses;
89 typedef struct firehose_ctx_t
91 firehose_conf_t *conf;
92 firehose_conn_t *conn;
93 apr_bucket_brigade *bb;
94 apr_bucket_brigade *tmp;
95 char uuid[APR_UUID_FORMATTED_LENGTH + 1];
103 #define HEADER_LEN (sizeof(apr_uint64_t)*6 + APR_UUID_FORMATTED_LENGTH + 7)
104 #define BODY_LEN (PIPE_BUF - HEADER_LEN - 2)
105 #define HEADER_FMT "%" APR_UINT64_T_HEX_FMT " %" APR_UINT64_T_HEX_FMT " %c %s %" APR_UINT64_T_HEX_FMT CRLF
107 static apr_status_t filter_output_cleanup(void *dummy)
109 ap_filter_t *f = (ap_filter_t *) dummy;
110 ap_remove_output_filter(f);
114 static apr_status_t filter_input_cleanup(void *dummy)
116 ap_filter_t *f = (ap_filter_t *) dummy;
117 ap_remove_input_filter(f);
122 * Add the terminating empty fragment to indicate end-of-connection.
124 static apr_status_t pumpit_cleanup(void *dummy)
126 firehose_ctx_t *ctx = (firehose_ctx_t *) dummy;
129 char header[HEADER_LEN + 1];
135 hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
136 (apr_uint64_t) 0, (apr_uint64_t) apr_time_now(), ctx->direction,
137 ctx->uuid, ctx->count);
138 ap_xlate_proto_to_ascii(header, hdr_len);
140 rv = apr_file_write_full(ctx->conn->file, header, hdr_len, NULL);
141 if (APR_SUCCESS != rv) {
142 if (ctx->conn->suppress) {
143 /* ignore the error */
151 "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
152 (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
160 "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
161 (apr_uint64_t)(hdr_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
163 ctx->conn->suppress = 1;
166 ctx->conn->suppress = 0;
175 * Pump the bucket contents to the pipe.
177 * Writes smaller than PIPE_BUF are guaranteed to be atomic when written to
178 * pipes. As a result, we break the buckets into packets smaller than PIPE_BUF and
179 * send each one in turn.
181 * Each packet is marked with the UUID of the connection so that the process that
182 * reassembles the packets can put the right packets in the right order.
184 * Each packet is numbered with an incrementing counter. If a packet cannot be
185 * written we drop the packet on the floor, and the counter will enable dropped
186 * packets to be detected.
188 static apr_status_t pumpit(ap_filter_t *f, apr_bucket *b, firehose_ctx_t *ctx)
190 apr_status_t rv = APR_SUCCESS;
192 if (!(APR_BUCKET_IS_METADATA(b))) {
194 apr_size_t nbytes, offset = 0;
196 rv = apr_bucket_read(b, &buf, &nbytes, APR_BLOCK_READ);
198 if (rv == APR_SUCCESS) {
200 char header[HEADER_LEN + 1];
202 apr_size_t body_len = nbytes < BODY_LEN ? nbytes : BODY_LEN;
207 * Insert the chunk header, specifying the number of bytes in
210 hdr_len = apr_snprintf(header, sizeof(header), HEADER_FMT,
211 (apr_uint64_t) body_len, (apr_uint64_t) apr_time_now(),
212 ctx->direction, ctx->uuid, ctx->count);
213 ap_xlate_proto_to_ascii(header, hdr_len);
215 vec[0].iov_base = header;
216 vec[0].iov_len = hdr_len;
217 vec[1].iov_base = (void *) (buf + offset);
218 vec[1].iov_len = body_len;
219 vec[2].iov_base = CRLF;
222 rv = apr_file_writev_full(ctx->conn->file, vec, 3, &bytes);
223 if (APR_SUCCESS != rv) {
224 if (ctx->conn->suppress) {
225 /* ignore the error */
233 "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
234 (apr_uint64_t)(vec[0].iov_len + vec[1].iov_len + vec[2].iov_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
242 "mod_firehose: could not write %" APR_UINT64_T_FMT " bytes to '%s' for '%c' connection '%s' and count '%0" APR_UINT64_T_HEX_FMT "', bytes dropped (further errors will be suppressed)",
243 (apr_uint64_t)(vec[0].iov_len + vec[1].iov_len + vec[2].iov_len), ctx->conn->filename, ctx->conn->direction, ctx->uuid, ctx->count);
245 ctx->conn->suppress = 1;
249 ctx->conn->suppress = 0;
253 nbytes -= vec[1].iov_len;
254 offset += vec[1].iov_len;
262 static apr_status_t firehose_input_filter(ap_filter_t *f,
263 apr_bucket_brigade *bb,
264 ap_input_mode_t mode,
265 apr_read_type_e block,
270 firehose_ctx_t *ctx = f->ctx;
272 /* just get out of the way of things we don't want. */
273 if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
274 return ap_get_brigade(f->next, bb, mode, block, readbytes);
277 rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
279 /* if an error was received, bail out now. If the error is
280 * EAGAIN and we have not yet seen an EOS, we will definitely
281 * be called again, at which point we will send our buffered
282 * data. Instead of sending EAGAIN, some filters return an
283 * empty brigade instead when data is not yet available. In
284 * this case, pass through the APR_SUCCESS and emulate the
287 if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(bb)) {
291 for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb); b
292 = APR_BUCKET_NEXT(b)) {
293 rv = pumpit(f, b, ctx);
294 if (APR_SUCCESS != rv) {
302 static apr_status_t firehose_output_filter(ap_filter_t *f,
303 apr_bucket_brigade *bb)
306 apr_status_t rv = APR_SUCCESS;
307 firehose_ctx_t *ctx = f->ctx;
309 while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
311 b = APR_BRIGADE_FIRST(bb);
313 rv = pumpit(f, b, ctx);
314 if (APR_SUCCESS != rv) {
318 /* pass each bucket down */
319 APR_BUCKET_REMOVE(b);
320 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
323 * If we ever see an EOS, make sure to FLUSH.
325 if (APR_BUCKET_IS_EOS(b)) {
326 apr_bucket *flush = apr_bucket_flush_create(f->c->bucket_alloc);
327 APR_BUCKET_INSERT_BEFORE(b, flush);
330 rv = ap_pass_brigade(f->next, ctx->bb);
338 * Create a firehose for each main request.
340 static int firehose_create_request(request_rec *r)
351 f = r->connection->input_filters;
353 if (f->frec->filter_func.in_func == &firehose_input_filter) {
354 ctx = (firehose_ctx_t *) f->ctx;
355 if (ctx->conn->request == FIREHOSE_REQUEST) {
361 apr_uuid_format(ctx->uuid, &uuid);
367 f = r->connection->output_filters;
369 if (f->frec->filter_func.out_func == &firehose_output_filter) {
370 ctx = (firehose_ctx_t *) f->ctx;
371 if (ctx->conn->request == FIREHOSE_REQUEST) {
377 apr_uuid_format(ctx->uuid, &uuid);
386 /* Ideas for extension:
388 * TODO: An idea for configuration. Let the filename directives be per-directory,
389 * with a global hashtable of filename to filehandle mappings. As each directive
390 * is parsed, a file is opened at server start. By default, all input is buffered
391 * until the header_parser hook, at which point we check if we should be buffering
392 * at all. If not, we dump the buffer and remove the filter. If so, we start
393 * attempting to write the buffer to the file.
395 * TODO: Implement a buffer to allow firehose fragment writes to back up to some
396 * threshold before packets are dropped. Flush the buffer on cleanup, waiting a
397 * suitable amount of time for the downstream to catch up.
399 * TODO: For the request firehose, have an option to set aside request buckets
400 * until we decide whether we're going to record this request or not. Allows for
401 * targeted firehose by URL space.
403 * TODO: Potentially decide on firehose sending based on a variable in the notes
404 * table or subprocess_env. Use standard httpd SetEnvIf and friends to decide on
405 * whether to include the request or not. Using this, we can react to the value
406 * of a flagpole. Run this check in the header_parser hook.
409 static int firehose_pre_conn(conn_rec *c, void *csd)
411 firehose_conf_t *conf;
415 firehose_conn_t *conn;
417 conf = ap_get_module_config(c->base_server->module_config,
420 if (conf->firehoses->nelts) {
424 conn = (firehose_conn_t *) conf->firehoses->elts;
425 for (i = 0; i < conf->firehoses->nelts; i++) {
427 if (!conn->file || (conn->proxy == FIREHOSE_NORMAL
428 && !c->sbh) || (conn->proxy == FIREHOSE_PROXY && c->sbh)) {
433 ctx = apr_pcalloc(c->pool, sizeof(firehose_ctx_t));
434 apr_uuid_format(ctx->uuid, &uuid);
437 ctx->bb = apr_brigade_create(c->pool, c->bucket_alloc);
439 apr_pool_cleanup_register(c->pool, ctx, pumpit_cleanup, pumpit_cleanup);
440 if (conn->direction == FIREHOSE_IN) {
441 ctx->direction = conn->proxy == FIREHOSE_PROXY ? '>' : '<';
442 ctx->f = ap_add_input_filter("FIREHOSE_IN", ctx, NULL, c);
443 apr_pool_cleanup_register(c->pool, ctx->f, filter_input_cleanup,
444 filter_input_cleanup);
446 if (conn->direction == FIREHOSE_OUT) {
447 ctx->direction = conn->proxy == FIREHOSE_PROXY ? '<' : '>';
448 ctx->f = ap_add_output_filter("FIREHOSE_OUT", ctx, NULL, c);
449 apr_pool_cleanup_register(c->pool, ctx->f, filter_output_cleanup,
450 filter_output_cleanup);
459 static int firehose_open_logs(apr_pool_t *p, apr_pool_t *plog,
460 apr_pool_t *ptemp, server_rec *s)
462 firehose_conf_t *conf;
466 firehose_conn_t *conn;
468 /* make sure we only open the files on the second pass for config */
469 apr_pool_userdata_get(&data, "mod_firehose", s->process->pool);
471 apr_pool_userdata_set((const void *) 1, "mod_firehose",
472 apr_pool_cleanup_null, s->process->pool);
478 conf = ap_get_module_config(s->module_config,
481 conn = (firehose_conn_t *) conf->firehoses->elts;
482 for (i = 0; i < conf->firehoses->nelts; i++) {
483 if (APR_SUCCESS != (rv = apr_file_open(&conn->file, conn->filename,
484 APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_APPEND
485 | conn->nonblock, APR_OS_DEFAULT, plog))) {
486 ap_log_error(APLOG_MARK,
488 rv, s, "mod_firehose: could not open '%s' for write, disabling firehose %s%s %s filter",
489 conn->filename, conn->proxy == FIREHOSE_PROXY ? "proxy " : "",
490 conn->request == FIREHOSE_REQUEST ? " request" : "connection",
491 conn->direction == FIREHOSE_IN ? "input" : "output");
502 static void firehose_register_hooks(apr_pool_t *p)
505 * We know that SSL is CONNECTION + 5
507 ap_register_output_filter("FIREHOSE_OUT", firehose_output_filter, NULL,
508 AP_FTYPE_CONNECTION + 3);
510 ap_register_input_filter("FIREHOSE_IN", firehose_input_filter, NULL,
511 AP_FTYPE_CONNECTION + 3);
513 ap_hook_open_logs(firehose_open_logs, NULL, NULL, APR_HOOK_LAST);
514 ap_hook_pre_connection(firehose_pre_conn, NULL, NULL, APR_HOOK_MIDDLE);
515 ap_hook_create_request(firehose_create_request, NULL, NULL,
516 APR_HOOK_REALLY_LAST + 1);
519 static void *firehose_create_sconfig(apr_pool_t *p, server_rec *s)
521 firehose_conf_t *ptr = apr_pcalloc(p, sizeof(firehose_conf_t));
523 ptr->firehoses = apr_array_make(p, 2, sizeof(firehose_conn_t));
528 static void *firehose_merge_sconfig(apr_pool_t *p, void *basev,
531 firehose_conf_t *cconf = apr_pcalloc(p, sizeof(firehose_conf_t));
532 firehose_conf_t *base = (firehose_conf_t *) basev;
533 firehose_conf_t *overrides = (firehose_conf_t *) overridesv;
535 cconf->firehoses = apr_array_append(p, overrides->firehoses,
541 static const char *firehose_enable_connection(cmd_parms *cmd, const char *arg1,
542 const char *arg2, proxy_enum proxy, direction_enum direction,
543 request_enum request)
545 const char *name = arg2 ? arg2 : arg1;
547 firehose_conn_t *firehose;
550 (firehose_conf_t *) ap_get_module_config(cmd->server->module_config,
553 firehose = apr_array_push(ptr->firehoses);
555 firehose->filename = name;
556 firehose->proxy = proxy;
557 firehose->direction = direction;
558 firehose->request = request;
561 if (!strcmp(arg1, "nonblock")) {
562 #ifdef APR_FOPEN_NONBLOCK
563 firehose->nonblock = APR_FOPEN_NONBLOCK;
565 return "The parameter 'nonblock' is not supported by APR on this platform";
568 else if (!strcmp(arg1, "block")) {
569 firehose->nonblock = 0;
572 return apr_psprintf(cmd->pool,
573 "The parameter '%s' should be 'block' or 'nonblock'", arg1);
577 firehose->nonblock = 0;
583 static const char *firehose_enable_connection_input(cmd_parms *cmd,
584 void *dummy, const char *arg1, const char *arg2)
587 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
593 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
594 FIREHOSE_IN, FIREHOSE_CONNECTION);
598 static const char *firehose_enable_connection_output(cmd_parms *cmd,
599 void *dummy, const char *arg1, const char *arg2)
602 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
608 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
609 FIREHOSE_OUT, FIREHOSE_CONNECTION);
613 static const char *firehose_enable_request_input(cmd_parms *cmd, void *dummy,
614 const char *arg1, const char *arg2)
617 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
623 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
624 FIREHOSE_IN, FIREHOSE_REQUEST);
628 static const char *firehose_enable_request_output(cmd_parms *cmd, void *dummy,
629 const char *arg1, const char *arg2)
632 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
638 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_NORMAL,
639 FIREHOSE_OUT, FIREHOSE_REQUEST);
643 static const char *firehose_enable_proxy_connection_input(cmd_parms *cmd,
644 void *dummy, const char *arg1, const char *arg2)
647 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
653 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
654 FIREHOSE_IN, FIREHOSE_CONNECTION);
658 static const char *firehose_enable_proxy_connection_output(cmd_parms *cmd,
659 void *dummy, const char *arg1, const char *arg2)
662 const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY
668 return firehose_enable_connection(cmd, arg1, arg2, FIREHOSE_PROXY,
669 FIREHOSE_OUT, FIREHOSE_CONNECTION);
673 static const command_rec firehose_cmds[] =
675 AP_INIT_TAKE12("FirehoseConnectionInput", firehose_enable_connection_input, NULL,
676 RSRC_CONF, "Enable firehose on connection input data written to the given file/pipe"),
677 AP_INIT_TAKE12("FirehoseConnectionOutput", firehose_enable_connection_output, NULL,
678 RSRC_CONF, "Enable firehose on connection output data written to the given file/pipe"),
679 AP_INIT_TAKE12("FirehoseRequestInput", firehose_enable_request_input, NULL,
680 RSRC_CONF, "Enable firehose on request input data written to the given file/pipe"),
681 AP_INIT_TAKE12("FirehoseRequestOutput", firehose_enable_request_output, NULL,
682 RSRC_CONF, "Enable firehose on request output data written to the given file/pipe"),
683 AP_INIT_TAKE12("FirehoseProxyConnectionInput", firehose_enable_proxy_connection_input, NULL,
684 RSRC_CONF, "Enable firehose on proxied connection input data written to the given file/pipe"),
685 AP_INIT_TAKE12("FirehoseProxyConnectionOutput", firehose_enable_proxy_connection_output, NULL,
686 RSRC_CONF, "Enable firehose on proxied connection output data written to the given file/pipe"),
690 AP_DECLARE_MODULE(firehose) =
692 STANDARD20_MODULE_STUFF,
695 firehose_create_sconfig,
696 firehose_merge_sconfig,
698 firehose_register_hooks