mod_firehose.c revision b66245501340bb30922bd38c27365e1e6f5bb781
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Originally written @ Covalent by Jim Jagielski
* Modified to support writing to non blocking pipes @ BBC by Graham Leggett
* Modifications (C) 2011 British Broadcasting Corporation
*/
/*
* A request and response sniffer for Apache v2.x. It logs
* all filter data right before and after it goes out on the
* wire (BUT right before SSL encoded or after SSL decoded).
* It can produce a *huge* amount of data.
*/
#include "httpd.h"
#include "http_connection.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_request.h"
#include "util_ebcdic.h"
#include "apr_strings.h"
#include "apr_portable.h"
#include "apr_uuid.h"
#include "mod_proxy.h"
#include <sys/syslimits.h>
#endif
#endif
#if APR_HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <unistd.h>
#endif
typedef enum proxy_enum
{
} proxy_enum;
typedef enum request_enum
{
} request_enum;
typedef enum direction_enum
{
typedef struct firehose_conn_t
{
const char *filename;
int suppress;
typedef struct firehose_conf_t
{
typedef struct firehose_ctx_t
{
int direction;
conn_rec *c;
request_rec *r;
ap_filter_t *f;
#define HEADER_FMT "%" APR_UINT64_T_HEX_FMT " %" APR_UINT64_T_HEX_FMT " %c %s %" APR_UINT64_T_HEX_FMT CRLF
{
return APR_SUCCESS;
}
{
return APR_SUCCESS;
}
/**
* Add the terminating empty fragment to indicate end-of-connection.
*/
{
return APR_SUCCESS;
}
if (APR_SUCCESS != rv) {
/* ignore the error */
}
else if (ctx->r) {
rv,
ctx->r,
"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)",
}
else {
rv,
ctx->c,
"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)",
}
}
else {
}
return APR_SUCCESS;
}
/*
* Pump the bucket contents to the pipe.
*
* Writes smaller than PIPE_BUF are guaranteed to be atomic when written to
* pipes. As a result, we break the buckets into packets smaller than PIPE_BUF and
* send each one in turn.
*
* Each packet is marked with the UUID of the connection so that the process that
* reassembles the packets can put the right packets in the right order.
*
* Each packet is numbered with an incrementing counter. If a packet cannot be
* written we drop the packet on the floor, and the counter will enable dropped
* packets to be detected.
*/
{
if (!(APR_BUCKET_IS_METADATA(b))) {
const char *buf;
if (rv == APR_SUCCESS) {
while (nbytes > 0) {
/*
* Insert the chunk header, specifying the number of bytes in
* the chunk.
*/
if (APR_SUCCESS != rv) {
/* ignore the error */
}
else if (ctx->r) {
rv,
ctx->r,
"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)",
(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);
}
else {
rv,
ctx->c,
"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)",
(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);
}
rv = APR_SUCCESS;
}
else {
}
}
}
}
return rv;
}
{
apr_bucket *b;
/* just get out of the way of things we don't want. */
}
/* if an error was received, bail out now. If the error is
* EAGAIN and we have not yet seen an EOS, we will definitely
* be called again, at which point we will send our buffered
* data. Instead of sending EAGAIN, some filters return an
* empty brigade instead when data is not yet available. In
* this case, pass through the APR_SUCCESS and emulate the
* underlying filter.
*/
return rv;
}
= APR_BUCKET_NEXT(b)) {
if (APR_SUCCESS != rv) {
return rv;
}
}
return APR_SUCCESS;
}
{
apr_bucket *b;
b = APR_BRIGADE_FIRST(bb);
if (APR_SUCCESS != rv) {
return rv;
}
/* pass each bucket down */
/*
* If we ever see an EOS, make sure to FLUSH.
*/
if (APR_BUCKET_IS_EOS(b)) {
}
}
return rv;
}
/**
* Create a firehose for each main request.
*/
static int firehose_create_request(request_rec *r)
{
int set = 0;
ap_filter_t *f;
if (r->main) {
return DECLINED;
}
f = r->connection->input_filters;
while (f) {
if (!set) {
apr_uuid_get(&uuid);
set = 1;
}
}
}
f = f->next;
}
f = r->connection->output_filters;
while (f) {
if (!set) {
apr_uuid_get(&uuid);
set = 1;
}
}
}
f = f->next;
}
return OK;
}
/* Ideas for extension:
*
* TODO: An idea for configuration. Let the filename directives be per-directory,
* with a global hashtable of filename to filehandle mappings. As each directive
* is parsed, a file is opened at server start. By default, all input is buffered
* until the header_parser hook, at which point we check if we should be buffering
* at all. If not, we dump the buffer and remove the filter. If so, we start
* attempting to write the buffer to the file.
*
* TODO: Implement a buffer to allow firehose fragment writes to back up to some
* threshold before packets are dropped. Flush the buffer on cleanup, waiting a
* suitable amount of time for the downstream to catch up.
*
* TODO: For the request firehose, have an option to set aside request buckets
* until we decide whether we're going to record this request or not. Allows for
* targeted firehose by URL space.
*
* TODO: Potentially decide on firehose sending based on a variable in the notes
* table or subprocess_env. Use standard httpd SetEnvIf and friends to decide on
* whether to include the request or not. Using this, we can react to the value
* of a flagpole. Run this check in the header_parser hook.
*/
{
int i;
apr_uuid_get(&uuid);
}
conn++;
continue;
}
ctx->c = c;
}
}
conn++;
}
return OK;
}
{
void *data;
int i;
/* make sure we only open the files on the second pass for config */
if (!data) {
return OK;
}
while (s) {
rv, s, "mod_firehose: could not open '%s' for write, disabling firehose %s%s %s filter",
}
conn++;
}
s = s->next;
}
return OK;
}
static void firehose_register_hooks(apr_pool_t *p)
{
/*
* We know that SSL is CONNECTION + 5
*/
AP_FTYPE_CONNECTION + 3);
AP_FTYPE_CONNECTION + 3);
APR_HOOK_REALLY_LAST + 1);
}
{
return ptr;
}
void *overridesv)
{
return cconf;
}
{
*ptr =
if (arg2) {
#ifdef APR_FOPEN_NONBLOCK
#else
return "The parameter 'nonblock' is not supported by APR on this platform";
#endif
}
}
else {
"The parameter '%s' should be 'block' or 'nonblock'", arg1);
}
}
else {
}
return NULL;
}
{
| NOT_IN_LIMIT);
return err;
}
}
{
| NOT_IN_LIMIT);
return err;
}
}
{
| NOT_IN_LIMIT);
return err;
}
}
{
| NOT_IN_LIMIT);
return err;
}
}
{
| NOT_IN_LIMIT);
return err;
}
}
{
| NOT_IN_LIMIT);
return err;
}
}
static const command_rec firehose_cmds[] =
{
{ NULL }
};
{
NULL,
NULL,
};