blob: 251c3f05d2fb56819a69b4e8de385b8e411c2789 [file] [log] [blame]
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed 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
*
* http://www.apache.org/licenses/LICENSE-2.0
* 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.
*/
#include <assert.h>
#include <apr_hash.h>
#include <apr_lib.h>
#include <httpd.h>
#include <http_core.h>
#include <http_config.h>
#include <http_log.h>
#include <http_vhost.h>
#include <ap_mpm.h>
#include <apr_strings.h>
#include "h2.h"
#include "h2_alt_svc.h"
#include "h2_ctx.h"
#include "h2_conn.h"
#include "h2_config.h"
#include "h2_h2.h"
#include "h2_private.h"
#define DEF_VAL (-1)
#define H2_CONFIG_GET(a, b, n) \
(((a)->n == DEF_VAL)? (b) : (a))->n
static h2_config defconf = {
"default",
100, /* max_streams */
H2_INITIAL_WINDOW_SIZE, /* window_size */
-1, /* min workers */
-1, /* max workers */
10 * 60, /* max workers idle secs */
64 * 1024, /* stream max mem size */
NULL, /* no alt-svcs */
-1, /* alt-svc max age */
0, /* serialize headers */
-1, /* h2 direct mode */
-1, /* # session extra files */
1, /* modern TLS only */
-1, /* HTTP/1 Upgrade support */
1024*1024, /* TLS warmup size */
1, /* TLS cooldown secs */
1, /* HTTP/2 server push enabled */
NULL, /* map of content-type to priorities */
256, /* push diary size */
0, /* copy files across threads */
};
void h2_config_init(apr_pool_t *pool)
{
(void)pool;
}
static void *h2_config_create(apr_pool_t *pool,
const char *prefix, const char *x)
{
h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
const char *s = x? x : "unknown";
char *name = apr_pstrcat(pool, prefix, "[", s, "]", NULL);
conf->name = name;
conf->h2_max_streams = DEF_VAL;
conf->h2_window_size = DEF_VAL;
conf->min_workers = DEF_VAL;
conf->max_workers = DEF_VAL;
conf->max_worker_idle_secs = DEF_VAL;
conf->stream_max_mem_size = DEF_VAL;
conf->alt_svc_max_age = DEF_VAL;
conf->serialize_headers = DEF_VAL;
conf->h2_direct = DEF_VAL;
conf->session_extra_files = DEF_VAL;
conf->modern_tls_only = DEF_VAL;
conf->h2_upgrade = DEF_VAL;
conf->tls_warmup_size = DEF_VAL;
conf->tls_cooldown_secs = DEF_VAL;
conf->h2_push = DEF_VAL;
conf->priorities = NULL;
conf->push_diary_size = DEF_VAL;
conf->copy_files = DEF_VAL;
return conf;
}
void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
{
return h2_config_create(pool, "srv", s->defn_name);
}
void *h2_config_create_dir(apr_pool_t *pool, char *x)
{
return h2_config_create(pool, "dir", x);
}
void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
{
h2_config *base = (h2_config *)basev;
h2_config *add = (h2_config *)addv;
h2_config *n = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
char *name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
n->name = name;
n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
n->min_workers = H2_CONFIG_GET(add, base, min_workers);
n->max_workers = H2_CONFIG_GET(add, base, max_workers);
n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs);
n->h2_push = H2_CONFIG_GET(add, base, h2_push);
if (add->priorities && base->priorities) {
n->priorities = apr_hash_overlay(pool, add->priorities, base->priorities);
}
else {
n->priorities = add->priorities? add->priorities : base->priorities;
}
n->push_diary_size = H2_CONFIG_GET(add, base, push_diary_size);
n->copy_files = H2_CONFIG_GET(add, base, copy_files);
return n;
}
int h2_config_geti(const h2_config *conf, h2_config_var_t var)
{
return (int)h2_config_geti64(conf, var);
}
apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var)
{
switch(var) {
case H2_CONF_MAX_STREAMS:
return H2_CONFIG_GET(conf, &defconf, h2_max_streams);
case H2_CONF_WIN_SIZE:
return H2_CONFIG_GET(conf, &defconf, h2_window_size);
case H2_CONF_MIN_WORKERS:
return H2_CONFIG_GET(conf, &defconf, min_workers);
case H2_CONF_MAX_WORKERS:
return H2_CONFIG_GET(conf, &defconf, max_workers);
case H2_CONF_MAX_WORKER_IDLE_SECS:
return H2_CONFIG_GET(conf, &defconf, max_worker_idle_secs);
case H2_CONF_STREAM_MAX_MEM:
return H2_CONFIG_GET(conf, &defconf, stream_max_mem_size);
case H2_CONF_ALT_SVC_MAX_AGE:
return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
case H2_CONF_SER_HEADERS:
return H2_CONFIG_GET(conf, &defconf, serialize_headers);
case H2_CONF_MODERN_TLS_ONLY:
return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
case H2_CONF_UPGRADE:
return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
case H2_CONF_DIRECT:
return H2_CONFIG_GET(conf, &defconf, h2_direct);
case H2_CONF_SESSION_FILES:
return H2_CONFIG_GET(conf, &defconf, session_extra_files);
case H2_CONF_TLS_WARMUP_SIZE:
return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
case H2_CONF_TLS_COOLDOWN_SECS:
return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
case H2_CONF_PUSH:
return H2_CONFIG_GET(conf, &defconf, h2_push);
case H2_CONF_PUSH_DIARY_SIZE:
return H2_CONFIG_GET(conf, &defconf, push_diary_size);
case H2_CONF_COPY_FILES:
return H2_CONFIG_GET(conf, &defconf, copy_files);
default:
return DEF_VAL;
}
}
const h2_config *h2_config_sget(server_rec *s)
{
h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config,
&http2_module);
AP_DEBUG_ASSERT(cfg);
return cfg;
}
const struct h2_priority *h2_config_get_priority(const h2_config *conf,
const char *content_type)
{
if (content_type && conf->priorities) {
size_t len = strcspn(content_type, "; \t");
h2_priority *prio = apr_hash_get(conf->priorities, content_type, len);
return prio? prio : apr_hash_get(conf->priorities, "*", 1);
}
return NULL;
}
static const char *h2_conf_set_max_streams(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->h2_max_streams = (int)apr_atoi64(value);
(void)arg;
if (cfg->h2_max_streams < 1) {
return "value must be > 0";
}
return NULL;
}
static const char *h2_conf_set_window_size(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->h2_window_size = (int)apr_atoi64(value);
(void)arg;
if (cfg->h2_window_size < 1024) {
return "value must be >= 1024";
}
return NULL;
}
static const char *h2_conf_set_min_workers(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->min_workers = (int)apr_atoi64(value);
(void)arg;
if (cfg->min_workers < 1) {
return "value must be > 0";
}
return NULL;
}
static const char *h2_conf_set_max_workers(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->max_workers = (int)apr_atoi64(value);
(void)arg;
if (cfg->max_workers < 1) {
return "value must be > 0";
}
return NULL;
}
static const char *h2_conf_set_max_worker_idle_secs(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->max_worker_idle_secs = (int)apr_atoi64(value);
(void)arg;
if (cfg->max_worker_idle_secs < 1) {
return "value must be > 0";
}
return NULL;
}
static const char *h2_conf_set_stream_max_mem_size(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->stream_max_mem_size = (int)apr_atoi64(value);
(void)arg;
if (cfg->stream_max_mem_size < 1024) {
return "value must be >= 1024";
}
return NULL;
}
static const char *h2_add_alt_svc(cmd_parms *parms,
void *arg, const char *value)
{
if (value && strlen(value)) {
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool);
if (!as) {
return "unable to parse alt-svc specifier";
}
if (!cfg->alt_svcs) {
cfg->alt_svcs = apr_array_make(parms->pool, 5, sizeof(h2_alt_svc*));
}
APR_ARRAY_PUSH(cfg->alt_svcs, h2_alt_svc*) = as;
}
(void)arg;
return NULL;
}
static const char *h2_conf_set_alt_svc_max_age(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->alt_svc_max_age = (int)apr_atoi64(value);
(void)arg;
return NULL;
}
static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
apr_int64_t max = (int)apr_atoi64(value);
if (max < 0) {
return "value must be a non-negative number";
}
cfg->session_extra_files = (int)max;
(void)arg;
return NULL;
}
static const char *h2_conf_set_serialize_headers(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->serialize_headers = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->serialize_headers = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
static const char *h2_conf_set_direct(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_direct = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->h2_direct = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
static const char *h2_conf_set_push(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_push = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->h2_push = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
static const char *h2_conf_add_push_priority(cmd_parms *cmd, void *_cfg,
const char *ctype, const char *sdependency,
const char *sweight)
{
h2_config *cfg = (h2_config *)h2_config_sget(cmd->server);
const char *sdefweight = "16"; /* default AFTER weight */
h2_dependency dependency;
h2_priority *priority;
int weight;
if (!strlen(ctype)) {
return "1st argument must be a mime-type, like 'text/css' or '*'";
}
if (!sweight) {
/* 2 args only, but which one? */
if (apr_isdigit(sdependency[0])) {
sweight = sdependency;
sdependency = "AFTER"; /* default dependency */
}
}
if (!strcasecmp("AFTER", sdependency)) {
dependency = H2_DEPENDANT_AFTER;
}
else if (!strcasecmp("BEFORE", sdependency)) {
dependency = H2_DEPENDANT_BEFORE;
if (sweight) {
return "dependecy 'Before' does not allow a weight";
}
}
else if (!strcasecmp("INTERLEAVED", sdependency)) {
dependency = H2_DEPENDANT_INTERLEAVED;
sdefweight = "256"; /* default INTERLEAVED weight */
}
else {
return "dependency must be one of 'After', 'Before' or 'Interleaved'";
}
weight = (int)apr_atoi64(sweight? sweight : sdefweight);
if (weight < NGHTTP2_MIN_WEIGHT) {
return apr_psprintf(cmd->pool, "weight must be a number >= %d",
NGHTTP2_MIN_WEIGHT);
}
priority = apr_pcalloc(cmd->pool, sizeof(*priority));
priority->dependency = dependency;
priority->weight = weight;
if (!cfg->priorities) {
cfg->priorities = apr_hash_make(cmd->pool);
}
apr_hash_set(cfg->priorities, ctype, strlen(ctype), priority);
return NULL;
}
static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->modern_tls_only = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->modern_tls_only = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
static const char *h2_conf_set_upgrade(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
if (!strcasecmp(value, "On")) {
cfg->h2_upgrade = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->h2_upgrade = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->tls_warmup_size = apr_atoi64(value);
(void)arg;
return NULL;
}
static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
cfg->tls_cooldown_secs = (int)apr_atoi64(value);
(void)arg;
return NULL;
}
static const char *h2_conf_set_push_diary_size(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
(void)arg;
cfg->push_diary_size = (int)apr_atoi64(value);
if (cfg->push_diary_size < 0) {
return "value must be >= 0";
}
if (cfg->push_diary_size > 0 && (cfg->push_diary_size & (cfg->push_diary_size-1))) {
return "value must a power of 2";
}
if (cfg->push_diary_size > (1 << 15)) {
return "value must <= 65536";
}
return NULL;
}
static const char *h2_conf_set_copy_files(cmd_parms *parms,
void *arg, const char *value)
{
h2_config *cfg = (h2_config *)arg;
if (!strcasecmp(value, "On")) {
cfg->copy_files = 1;
return NULL;
}
else if (!strcasecmp(value, "Off")) {
cfg->copy_files = 0;
return NULL;
}
(void)arg;
return "value must be On or Off";
}
#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
const command_rec h2_cmds[] = {
AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
RSRC_CONF, "maximum number of open streams per session"),
AP_INIT_TAKE1("H2WindowSize", h2_conf_set_window_size, NULL,
RSRC_CONF, "window size on client DATA"),
AP_INIT_TAKE1("H2MinWorkers", h2_conf_set_min_workers, NULL,
RSRC_CONF, "minimum number of worker threads per child"),
AP_INIT_TAKE1("H2MaxWorkers", h2_conf_set_max_workers, NULL,
RSRC_CONF, "maximum number of worker threads per child"),
AP_INIT_TAKE1("H2MaxWorkerIdleSeconds", h2_conf_set_max_worker_idle_secs, NULL,
RSRC_CONF, "maximum number of idle seconds before a worker shuts down"),
AP_INIT_TAKE1("H2StreamMaxMemSize", h2_conf_set_stream_max_mem_size, NULL,
RSRC_CONF, "maximum number of bytes buffered in memory for a stream"),
AP_INIT_TAKE1("H2AltSvc", h2_add_alt_svc, NULL,
RSRC_CONF, "adds an Alt-Svc for this server"),
AP_INIT_TAKE1("H2AltSvcMaxAge", h2_conf_set_alt_svc_max_age, NULL,
RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
RSRC_CONF, "on to enable header serialization for compatibility"),
AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
RSRC_CONF, "on to allow HTTP/1 Upgrades to h2/h2c"),
AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
RSRC_CONF, "on to enable direct HTTP/2 mode"),
AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
RSRC_CONF, "number of extra file a session might keep open"),
AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
AP_INIT_TAKE1("H2Push", h2_conf_set_push, NULL,
RSRC_CONF, "off to disable HTTP/2 server push"),
AP_INIT_TAKE23("H2PushPriority", h2_conf_add_push_priority, NULL,
RSRC_CONF, "define priority of PUSHed resources per content type"),
AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL,
RSRC_CONF, "size of push diary"),
AP_INIT_TAKE1("H2CopyFiles", h2_conf_set_copy_files, NULL,
OR_ALL, "on to perform copy of file data"),
AP_END_CMD
};
const h2_config *h2_config_rget(request_rec *r)
{
h2_config *cfg = (h2_config *)ap_get_module_config(r->per_dir_config,
&http2_module);
return cfg? cfg : h2_config_sget(r->server);
}
const h2_config *h2_config_get(conn_rec *c)
{
h2_ctx *ctx = h2_ctx_get(c, 0);
if (ctx) {
if (ctx->config) {
return ctx->config;
}
else if (ctx->server) {
ctx->config = h2_config_sget(ctx->server);
return ctx->config;
}
}
return h2_config_sget(c->base_server);
}