Blame modules/http2/h2_conn_io.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
 
Packit 90a5c9
#include <assert.h>
Packit 90a5c9
#include <apr_strings.h>
Packit 90a5c9
#include <ap_mpm.h>
Packit 90a5c9
Packit 90a5c9
#include <httpd.h>
Packit 90a5c9
#include <http_core.h>
Packit 90a5c9
#include <http_log.h>
Packit 90a5c9
#include <http_connection.h>
Packit 90a5c9
#include <http_request.h>
Packit 90a5c9
Packit 90a5c9
#include "h2_private.h"
Packit 90a5c9
#include "h2_bucket_eos.h"
Packit 90a5c9
#include "h2_config.h"
Packit 90a5c9
#include "h2_conn_io.h"
Packit 90a5c9
#include "h2_h2.h"
Packit 90a5c9
#include "h2_session.h"
Packit 90a5c9
#include "h2_util.h"
Packit 90a5c9
Packit 90a5c9
#define TLS_DATA_MAX          (16*1024) 
Packit 90a5c9
Packit 90a5c9
/* Calculated like this: assuming MTU 1500 bytes
Packit 90a5c9
 * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options) 
Packit 90a5c9
 *      - TLS overhead (60-100) 
Packit 90a5c9
 * ~= 1300 bytes */
Packit 90a5c9
#define WRITE_SIZE_INITIAL    1300
Packit 90a5c9
Packit 90a5c9
/* Calculated like this: max TLS record size 16*1024
Packit 90a5c9
 *   - 40 (IP) - 20 (TCP) - 40 (TCP options) 
Packit 90a5c9
 *    - TLS overhead (60-100) 
Packit 90a5c9
 * which seems to create less TCP packets overall
Packit 90a5c9
 */
Packit 90a5c9
#define WRITE_SIZE_MAX        (TLS_DATA_MAX - 100) 
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
static void h2_conn_io_bb_log(conn_rec *c, int stream_id, int level, 
Packit 90a5c9
                              const char *tag, apr_bucket_brigade *bb)
Packit 90a5c9
{
Packit 90a5c9
    char buffer[16 * 1024];
Packit 90a5c9
    const char *line = "(null)";
Packit 90a5c9
    apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]);
Packit 90a5c9
    int off = 0;
Packit 90a5c9
    apr_bucket *b;
Packit 90a5c9
    
Packit 90a5c9
    if (bb) {
Packit 90a5c9
        memset(buffer, 0, bmax--);
Packit 90a5c9
        for (b = APR_BRIGADE_FIRST(bb); 
Packit 90a5c9
             bmax && (b != APR_BRIGADE_SENTINEL(bb));
Packit 90a5c9
             b = APR_BUCKET_NEXT(b)) {
Packit 90a5c9
            
Packit 90a5c9
            if (APR_BUCKET_IS_METADATA(b)) {
Packit 90a5c9
                if (APR_BUCKET_IS_EOS(b)) {
Packit 90a5c9
                    off += apr_snprintf(buffer+off, bmax-off, "eos ");
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_FLUSH(b)) {
Packit 90a5c9
                    off += apr_snprintf(buffer+off, bmax-off, "flush ");
Packit 90a5c9
                }
Packit 90a5c9
                else if (AP_BUCKET_IS_EOR(b)) {
Packit 90a5c9
                    off += apr_snprintf(buffer+off, bmax-off, "eor ");
Packit 90a5c9
                }
Packit 90a5c9
                else if (H2_BUCKET_IS_H2EOS(b)) {
Packit 90a5c9
                    off += apr_snprintf(buffer+off, bmax-off, "h2eos ");
Packit 90a5c9
                }
Packit 90a5c9
                else {
Packit 90a5c9
                    off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) ");
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                const char *btype = "data";
Packit 90a5c9
                if (APR_BUCKET_IS_FILE(b)) {
Packit 90a5c9
                    btype = "file";
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_PIPE(b)) {
Packit 90a5c9
                    btype = "pipe";
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_SOCKET(b)) {
Packit 90a5c9
                    btype = "socket";
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_HEAP(b)) {
Packit 90a5c9
                    btype = "heap";
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_TRANSIENT(b)) {
Packit 90a5c9
                    btype = "transient";
Packit 90a5c9
                }
Packit 90a5c9
                else if (APR_BUCKET_IS_IMMORTAL(b)) {
Packit 90a5c9
                    btype = "immortal";
Packit 90a5c9
                }
Packit 90a5c9
#if APR_HAS_MMAP
Packit 90a5c9
                else if (APR_BUCKET_IS_MMAP(b)) {
Packit 90a5c9
                    btype = "mmap";
Packit 90a5c9
                }
Packit 90a5c9
#endif
Packit 90a5c9
                else if (APR_BUCKET_IS_POOL(b)) {
Packit 90a5c9
                    btype = "pool";
Packit 90a5c9
                }
Packit 90a5c9
                
Packit 90a5c9
                off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ", 
Packit 90a5c9
                                    btype, 
Packit 90a5c9
                                    (long)(b->length == ((apr_size_t)-1)? 
Packit 90a5c9
                                           -1 : b->length));
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        line = *buffer? buffer : "(empty)";
Packit 90a5c9
    }
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s", 
Packit 90a5c9
                  c->id, tag, line);
Packit 90a5c9
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, 
Packit 90a5c9
                             const h2_config *cfg)
Packit 90a5c9
{
Packit 90a5c9
    io->c              = c;
Packit 90a5c9
    io->output         = apr_brigade_create(c->pool, c->bucket_alloc);
Packit 90a5c9
    io->is_tls         = h2_h2_is_tls(c);
Packit 90a5c9
    io->buffer_output  = io->is_tls;
Packit 90a5c9
    io->flush_threshold = (apr_size_t)h2_config_geti64(cfg, H2_CONF_STREAM_MAX_MEM);
Packit 90a5c9
Packit 90a5c9
    if (io->is_tls) {
Packit 90a5c9
        /* This is what we start with, 
Packit 90a5c9
         * see https://issues.apache.org/jira/browse/TS-2503 
Packit 90a5c9
         */
Packit 90a5c9
        io->warmup_size    = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE);
Packit 90a5c9
        io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS) 
Packit 90a5c9
                              * APR_USEC_PER_SEC);
Packit 90a5c9
        io->write_size     = (io->cooldown_usecs > 0? 
Packit 90a5c9
                              WRITE_SIZE_INITIAL : WRITE_SIZE_MAX); 
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        io->warmup_size    = 0;
Packit 90a5c9
        io->cooldown_usecs = 0;
Packit 90a5c9
        io->write_size     = 0;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    if (APLOGctrace1(c)) {
Packit 90a5c9
        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
Packit 90a5c9
                      "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, "
Packit 90a5c9
                      "cd_secs=%f", io->c->id, io->buffer_output, 
Packit 90a5c9
                      (long)io->warmup_size,
Packit 90a5c9
                      ((float)io->cooldown_usecs/APR_USEC_PER_SEC));
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void append_scratch(h2_conn_io *io) 
Packit 90a5c9
{
Packit 90a5c9
    if (io->scratch && io->slen > 0) {
Packit 90a5c9
        apr_bucket *b = apr_bucket_heap_create(io->scratch, io->slen,
Packit 90a5c9
                                               apr_bucket_free,
Packit 90a5c9
                                               io->c->bucket_alloc);
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(io->output, b);
Packit 90a5c9
        io->scratch = NULL;
Packit 90a5c9
        io->slen = io->ssize = 0;
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_size_t assure_scratch_space(h2_conn_io *io) {
Packit 90a5c9
    apr_size_t remain = io->ssize - io->slen; 
Packit 90a5c9
    if (io->scratch && remain == 0) {
Packit 90a5c9
        append_scratch(io);
Packit 90a5c9
    }
Packit 90a5c9
    if (!io->scratch) {
Packit 90a5c9
        /* we control the size and it is larger than what buckets usually
Packit 90a5c9
         * allocate. */
Packit 90a5c9
        io->scratch = apr_bucket_alloc(io->write_size, io->c->bucket_alloc);
Packit 90a5c9
        io->ssize = io->write_size;
Packit 90a5c9
        io->slen = 0;
Packit 90a5c9
        remain = io->ssize;
Packit 90a5c9
    }
Packit 90a5c9
    return remain;
Packit 90a5c9
}
Packit 90a5c9
    
Packit 90a5c9
static apr_status_t read_to_scratch(h2_conn_io *io, apr_bucket *b)
Packit 90a5c9
{
Packit 90a5c9
    apr_status_t status;
Packit 90a5c9
    const char *data;
Packit 90a5c9
    apr_size_t len;
Packit 90a5c9
    
Packit 90a5c9
    if (!b->length) {
Packit 90a5c9
        return APR_SUCCESS;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    ap_assert(b->length <= (io->ssize - io->slen));
Packit 90a5c9
    if (APR_BUCKET_IS_FILE(b)) {
Packit 90a5c9
        apr_bucket_file *f = (apr_bucket_file *)b->data;
Packit 90a5c9
        apr_file_t *fd = f->fd;
Packit 90a5c9
        apr_off_t offset = b->start;
Packit 90a5c9
        apr_size_t len = b->length;
Packit 90a5c9
        
Packit 90a5c9
        /* file buckets will either mmap (which we do not want) or
Packit 90a5c9
         * read 8000 byte chunks and split themself. However, we do
Packit 90a5c9
         * know *exactly* how many bytes we need where.
Packit 90a5c9
         */
Packit 90a5c9
        status = apr_file_seek(fd, APR_SET, &offset);
Packit 90a5c9
        if (status != APR_SUCCESS) {
Packit 90a5c9
            return status;
Packit 90a5c9
        }
Packit 90a5c9
        status = apr_file_read(fd, io->scratch + io->slen, &len;;
Packit 90a5c9
        if (status != APR_SUCCESS && status != APR_EOF) {
Packit 90a5c9
            return status;
Packit 90a5c9
        }
Packit 90a5c9
        io->slen += len;
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
Packit 90a5c9
        if (status == APR_SUCCESS) {
Packit 90a5c9
            memcpy(io->scratch+io->slen, data, len);
Packit 90a5c9
            io->slen += len;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void check_write_size(h2_conn_io *io) 
Packit 90a5c9
{
Packit 90a5c9
    if (io->write_size > WRITE_SIZE_INITIAL 
Packit 90a5c9
        && (io->cooldown_usecs > 0)
Packit 90a5c9
        && (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
Packit 90a5c9
        /* long time not written, reset write size */
Packit 90a5c9
        io->write_size = WRITE_SIZE_INITIAL;
Packit 90a5c9
        io->bytes_written = 0;
Packit 90a5c9
    }
Packit 90a5c9
    else if (io->write_size < WRITE_SIZE_MAX 
Packit 90a5c9
             && io->bytes_written >= io->warmup_size) {
Packit 90a5c9
        /* connection is hot, use max size */
Packit 90a5c9
        io->write_size = WRITE_SIZE_MAX;
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_status_t pass_output(h2_conn_io *io, int flush)
Packit 90a5c9
{
Packit 90a5c9
    conn_rec *c = io->c;
Packit 90a5c9
    apr_bucket_brigade *bb = io->output;
Packit 90a5c9
    apr_bucket *b;
Packit 90a5c9
    apr_off_t bblen;
Packit 90a5c9
    apr_status_t status;
Packit 90a5c9
    
Packit 90a5c9
    append_scratch(io);
Packit 90a5c9
    if (flush && !io->is_flushed) {
Packit 90a5c9
        b = apr_bucket_flush_create(c->bucket_alloc);
Packit 90a5c9
        APR_BRIGADE_INSERT_TAIL(bb, b);
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    if (APR_BRIGADE_EMPTY(bb)) {
Packit 90a5c9
        return APR_SUCCESS;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
Packit 90a5c9
    apr_brigade_length(bb, 0, &bblen);
Packit 90a5c9
    h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "out", bb);
Packit 90a5c9
    
Packit 90a5c9
    status = ap_pass_brigade(c->output_filters, bb);
Packit 90a5c9
    if (status == APR_SUCCESS) {
Packit 90a5c9
        io->bytes_written += (apr_size_t)bblen;
Packit 90a5c9
        io->last_write = apr_time_now();
Packit 90a5c9
        if (flush) {
Packit 90a5c9
            io->is_flushed = 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    apr_brigade_cleanup(bb);
Packit 90a5c9
Packit 90a5c9
    if (status != APR_SUCCESS) {
Packit 90a5c9
        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03044)
Packit 90a5c9
                      "h2_conn_io(%ld): pass_out brigade %ld bytes",
Packit 90a5c9
                      c->id, (long)bblen);
Packit 90a5c9
    }
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
int h2_conn_io_needs_flush(h2_conn_io *io)
Packit 90a5c9
{
Packit 90a5c9
    if (!io->is_flushed) {
Packit 90a5c9
        apr_off_t len = h2_brigade_mem_size(io->output);
Packit 90a5c9
        if (len > io->flush_threshold) {
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
        /* if we do not exceed flush length due to memory limits,
Packit 90a5c9
         * we want at least flush when we have that amount of data. */
Packit 90a5c9
        apr_brigade_length(io->output, 0, &len;;
Packit 90a5c9
        return len > (4 * io->flush_threshold);
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_status_t h2_conn_io_flush(h2_conn_io *io)
Packit 90a5c9
{
Packit 90a5c9
    apr_status_t status;
Packit 90a5c9
    status = pass_output(io, 1);
Packit 90a5c9
    check_write_size(io);
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
Packit 90a5c9
{
Packit 90a5c9
    apr_status_t status = APR_SUCCESS;
Packit 90a5c9
    apr_size_t remain;
Packit 90a5c9
    
Packit 90a5c9
    if (length > 0) {
Packit 90a5c9
        io->is_flushed = 0;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    if (io->buffer_output) {
Packit 90a5c9
        while (length > 0) {
Packit 90a5c9
            remain = assure_scratch_space(io);
Packit 90a5c9
            if (remain >= length) {
Packit 90a5c9
                memcpy(io->scratch + io->slen, data, length);
Packit 90a5c9
                io->slen += length;
Packit 90a5c9
                length = 0;
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                memcpy(io->scratch + io->slen, data, remain);
Packit 90a5c9
                io->slen += remain;
Packit 90a5c9
                data += remain;
Packit 90a5c9
                length -= remain;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        status = apr_brigade_write(io->output, NULL, NULL, data, length);
Packit 90a5c9
    }
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb)
Packit 90a5c9
{
Packit 90a5c9
    apr_bucket *b;
Packit 90a5c9
    apr_status_t status = APR_SUCCESS;
Packit 90a5c9
    
Packit 90a5c9
    if (!APR_BRIGADE_EMPTY(bb)) {
Packit 90a5c9
        io->is_flushed = 0;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
Packit 90a5c9
        b = APR_BRIGADE_FIRST(bb);
Packit 90a5c9
        
Packit 90a5c9
        if (APR_BUCKET_IS_METADATA(b)) {
Packit 90a5c9
            /* need to finish any open scratch bucket, as meta data 
Packit 90a5c9
             * needs to be forward "in order". */
Packit 90a5c9
            append_scratch(io);
Packit 90a5c9
            APR_BUCKET_REMOVE(b);
Packit 90a5c9
            APR_BRIGADE_INSERT_TAIL(io->output, b);
Packit 90a5c9
        }
Packit 90a5c9
        else if (io->buffer_output) {
Packit 90a5c9
            apr_size_t remain = assure_scratch_space(io);
Packit 90a5c9
            if (b->length > remain) {
Packit 90a5c9
                apr_bucket_split(b, remain);
Packit 90a5c9
                if (io->slen == 0) {
Packit 90a5c9
                    /* complete write_size bucket, append unchanged */
Packit 90a5c9
                    APR_BUCKET_REMOVE(b);
Packit 90a5c9
                    APR_BRIGADE_INSERT_TAIL(io->output, b);
Packit 90a5c9
                    continue;
Packit 90a5c9
                }
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                /* bucket fits in remain, copy to scratch */
Packit 90a5c9
                status = read_to_scratch(io, b);
Packit 90a5c9
                apr_bucket_delete(b);
Packit 90a5c9
                continue;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        else {
Packit 90a5c9
            /* no buffering, forward buckets setaside on flush */
Packit 90a5c9
            if (APR_BUCKET_IS_TRANSIENT(b)) {
Packit 90a5c9
                apr_bucket_setaside(b, io->c->pool);
Packit 90a5c9
            }
Packit 90a5c9
            APR_BUCKET_REMOVE(b);
Packit 90a5c9
            APR_BRIGADE_INSERT_TAIL(io->output, b);
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9