Coverage Report

Created: 2025-07-18 06:42

/src/h2o/lib/handler/configurator/proxy.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2014 DeNA Co., Ltd.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
#include <inttypes.h>
23
#include <errno.h>
24
#include <stdio.h>
25
#include <openssl/crypto.h>
26
#include <openssl/err.h>
27
#include <openssl/ssl.h>
28
#include "h2o.h"
29
#include "h2o/configurator.h"
30
#include "h2o/balancer.h"
31
#include "h2o/socket.h"
32
33
struct proxy_config_vars_t {
34
    h2o_proxy_config_vars_t conf;
35
    SSL_CTX *ssl_ctx;
36
};
37
38
struct proxy_configurator_t {
39
    h2o_configurator_t super;
40
    unsigned connect_timeout_set : 1;
41
    unsigned first_byte_timeout_set : 1;
42
    struct proxy_config_vars_t *vars;
43
    struct proxy_config_vars_t _vars_stack[H2O_CONFIGURATOR_NUM_LEVELS + 1];
44
};
45
46
static void warn_deprecation(h2o_configurator_command_t *cmd, yoml_t *node, const char *new_cmd, const char *extra)
47
0
{
48
0
    if (strcasecmp(cmd->name, new_cmd) != 0)
49
0
        h2o_configurator_errprintf(cmd, node, "the command is deprecated; use %s%s", new_cmd, extra);
50
0
}
51
52
static int on_config_timeout_io(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
53
0
{
54
0
    int ret;
55
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
56
0
    ret = h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.io_timeout);
57
0
    if (ret < 0)
58
0
        return ret;
59
0
    if (!self->connect_timeout_set)
60
0
        self->vars->conf.connect_timeout = self->vars->conf.io_timeout;
61
0
    if (!self->first_byte_timeout_set)
62
0
        self->vars->conf.first_byte_timeout = self->vars->conf.io_timeout;
63
0
    return ret;
64
0
}
65
66
static int on_config_timeout_connect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
67
0
{
68
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
69
0
    self->connect_timeout_set = 1;
70
0
    return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.connect_timeout);
71
0
}
72
73
static int on_config_timeout_first_byte(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
74
0
{
75
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
76
0
    self->first_byte_timeout_set = 1;
77
0
    return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.first_byte_timeout);
78
0
}
79
80
static int on_config_timeout_keepalive(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
81
0
{
82
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
83
0
    return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.keepalive_timeout);
84
0
}
85
86
static int on_config_happy_eyeballs_name_resolution_delay(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx,
87
                                                          yoml_t *node)
88
0
{
89
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
90
0
    return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.happy_eyeballs.name_resolution_delay);
91
0
}
92
93
static int on_config_happy_eyeballs_connection_attempt_delay(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx,
94
                                                             yoml_t *node)
95
0
{
96
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
97
0
    return h2o_configurator_scanf(cmd, node, "%" SCNu64, &self->vars->conf.happy_eyeballs.connection_attempt_delay);
98
0
}
99
100
static int on_config_preserve_host(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
101
0
{
102
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
103
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
104
0
    if (ret == -1)
105
0
        return -1;
106
0
    self->vars->conf.preserve_host = (int)ret;
107
0
    return 0;
108
0
}
109
110
static int on_config_proxy_protocol(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
111
0
{
112
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
113
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
114
0
    if (ret == -1)
115
0
        return -1;
116
0
    self->vars->conf.use_proxy_protocol = (int)ret;
117
0
    return 0;
118
0
}
119
120
static int on_config_connect_proxy_status(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
121
0
{
122
0
    warn_deprecation(cmd, node, "proxy.connect.emit-proxy-status", "");
123
124
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
125
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
126
0
    if (ret == -1)
127
0
        return -1;
128
0
    self->vars->conf.connect_proxy_status_enabled = (int)ret;
129
0
    return 0;
130
0
}
131
132
static int on_config_connect_proxy_masque_draft_03(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
133
0
{
134
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
135
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
136
0
    if (ret == -1)
137
0
        return -1;
138
0
    self->vars->conf.support_masque_draft_03 = (int)ret;
139
0
    return 0;
140
0
}
141
142
static int on_config_proxy_status_identity(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
143
0
{
144
0
    warn_deprecation(cmd, node, "proxy.proxy-status.identity", "");
145
146
    /* https://tools.ietf.org/html/rfc8941#section-3.3.4 */
147
0
    static const char *tfirst = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*";
148
0
    static const char *tchars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&'*+-.^_`|~:/";
149
150
0
    const char *s = node->data.scalar;
151
0
    size_t slen = strlen(s);
152
0
    for (size_t i = 0; i < slen; ++i) {
153
0
        unsigned char b = s[i];
154
0
        if (b < 0x20 || b > 0x7E) {
155
0
            h2o_configurator_errprintf(cmd, node, "the identity must only consist of printable ASCII characters");
156
0
            return -1;
157
0
        }
158
0
    }
159
0
    if (s[0] != '\0' && strchr(tfirst, s[0]) != NULL && strspn(s, tchars) == slen) {
160
        /* sf-token */
161
0
        ctx->globalconf->proxy_status_identity = h2o_strdup(NULL, s, slen);
162
0
    } else {
163
        /* sf-string */
164
0
        ctx->globalconf->proxy_status_identity = h2o_encode_sf_string(NULL, s, slen);
165
0
    }
166
167
0
    return 0;
168
0
}
169
170
static int on_config_tunnel(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
171
0
{
172
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
173
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
174
0
    if (ret == -1)
175
0
        return -1;
176
0
    self->vars->conf.tunnel_enabled = (int)ret;
177
0
    return 0;
178
0
}
179
180
static SSL_CTX *create_ssl_ctx(void)
181
0
{
182
0
    long options;
183
0
    SSL_CTX *ctx = SSL_CTX_new(SSLv23_client_method());
184
0
    options = SSL_CTX_get_options(ctx) | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
185
0
#ifdef SSL_OP_NO_RENEGOTIATION
186
    /* introduced in openssl 1.1.0h */
187
0
    options |= SSL_OP_NO_RENEGOTIATION;
188
0
#endif
189
0
    SSL_CTX_set_options(ctx, options);
190
0
    SSL_CTX_set_session_id_context(ctx, H2O_SESSID_CTX, H2O_SESSID_CTX_LEN);
191
0
    SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE);
192
0
    SSL_CTX_sess_set_new_cb(ctx, h2o_socket_ssl_new_session_cb);
193
0
    return ctx;
194
0
}
195
196
static h2o_cache_t *create_ssl_session_cache(size_t capacity, uint64_t duration)
197
0
{
198
0
    return h2o_cache_create(H2O_CACHE_FLAG_MULTITHREADED, capacity, duration, h2o_socket_ssl_destroy_session_cache_entry);
199
0
}
200
201
static void update_ssl_ctx(SSL_CTX **ctx, X509_STORE *cert_store, int verify_mode, h2o_cache_t **session_cache)
202
0
{
203
0
    assert(*ctx != NULL);
204
205
    /* inherit the properties that weren't specified */
206
0
    if (cert_store == NULL)
207
0
        cert_store = SSL_CTX_get_cert_store(*ctx);
208
0
    X509_STORE_up_ref(cert_store);
209
0
    if (verify_mode == -1)
210
0
        verify_mode = SSL_CTX_get_verify_mode(*ctx);
211
0
    h2o_cache_t *new_session_cache;
212
0
    if (session_cache == NULL) {
213
0
        h2o_cache_t *current = h2o_socket_ssl_get_session_cache(*ctx);
214
0
        new_session_cache =
215
0
            current == NULL ? NULL : create_ssl_session_cache(h2o_cache_get_capacity(current), h2o_cache_get_duration(current));
216
0
    } else {
217
0
        new_session_cache = *session_cache;
218
0
    }
219
220
    /* free the existing context */
221
0
    if (*ctx != NULL)
222
0
        SSL_CTX_free(*ctx);
223
224
    /* create new ctx */
225
0
    *ctx = create_ssl_ctx();
226
0
    SSL_CTX_set_session_id_context(*ctx, H2O_SESSID_CTX, H2O_SESSID_CTX_LEN);
227
0
    SSL_CTX_set_cert_store(*ctx, cert_store);
228
0
    SSL_CTX_set_verify(*ctx, verify_mode, NULL);
229
0
    if (new_session_cache != NULL)
230
0
        h2o_socket_ssl_set_session_cache(*ctx, new_session_cache);
231
0
}
232
233
static int on_config_ssl_verify_peer(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
234
0
{
235
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
236
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
237
0
    if (ret == -1)
238
0
        return -1;
239
240
0
    update_ssl_ctx(&self->vars->ssl_ctx, NULL, ret != 0 ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE,
241
0
                   NULL);
242
243
0
    return 0;
244
0
}
245
246
static int on_config_ssl_cafile(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
247
0
{
248
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
249
0
    X509_STORE *store = X509_STORE_new();
250
0
    int ret = -1;
251
252
0
    if (X509_STORE_load_locations(store, node->data.scalar, NULL) == 1) {
253
0
        update_ssl_ctx(&self->vars->ssl_ctx, store, -1, NULL);
254
0
        ret = 0;
255
0
    } else {
256
0
        h2o_configurator_errprintf(cmd, node, "failed to load certificates file:%s", node->data.scalar);
257
0
        ERR_print_errors_fp(stderr);
258
0
    }
259
260
0
    X509_STORE_free(store);
261
0
    return ret;
262
0
}
263
264
static int on_config_ssl_session_cache(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
265
0
{
266
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
267
0
    size_t capacity = 0;
268
0
    uint64_t duration = 0;
269
0
    h2o_cache_t *current_cache = h2o_socket_ssl_get_session_cache(self->vars->ssl_ctx);
270
271
0
    switch (node->type) {
272
0
    case YOML_TYPE_SCALAR:
273
0
        if (strcasecmp(node->data.scalar, "OFF") == 0) {
274
0
            if (current_cache != NULL) {
275
                /* set the cache NULL */
276
0
                h2o_cache_t *empty_cache = NULL;
277
0
                update_ssl_ctx(&self->vars->ssl_ctx, NULL, -1, &empty_cache);
278
0
            }
279
0
            return 0;
280
0
        } else if (strcasecmp(node->data.scalar, "ON") == 0) {
281
            /* use default values */
282
0
            capacity = H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_CAPACITY;
283
0
            duration = H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_DURATION;
284
0
        } else {
285
0
            h2o_configurator_errprintf(cmd, node, "scalar argument must be either of: `OFF`, `ON`");
286
0
            return -1;
287
0
        }
288
0
        break;
289
0
    case YOML_TYPE_MAPPING: {
290
0
        yoml_t **capacity_node, **lifetime_node;
291
0
        if (h2o_configurator_parse_mapping(cmd, node, "capacity:*,lifetime:*", NULL, &capacity_node, &lifetime_node) != 0)
292
0
            return -1;
293
0
        if (h2o_configurator_scanf(cmd, *capacity_node, "%zu", &capacity) != 0)
294
0
            return -1;
295
0
        if (capacity == 0) {
296
0
            h2o_configurator_errprintf(cmd, *capacity_node, "capacity must be greater than zero");
297
0
            return -1;
298
0
        }
299
0
        unsigned lifetime = 0;
300
0
        if (h2o_configurator_scanf(cmd, *lifetime_node, "%u", &lifetime) != 0)
301
0
            return -1;
302
0
        if (lifetime == 0) {
303
0
            h2o_configurator_errprintf(cmd, *lifetime_node, "lifetime must be greater than zero");
304
0
            return -1;
305
0
        }
306
0
        duration = (uint64_t)lifetime * 1000;
307
0
    } break;
308
0
    default:
309
0
        h2o_configurator_errprintf(cmd, node, "node must be a scalar or a mapping");
310
0
        return -1;
311
0
    }
312
313
0
    if (current_cache != NULL) {
314
0
        size_t current_capacity = h2o_cache_get_capacity(current_cache);
315
0
        uint64_t current_duration = h2o_cache_get_duration(current_cache);
316
0
        if (capacity == current_capacity && duration == current_duration) {
317
            /* parameters aren't changed, so reuse it */
318
0
            return 0;
319
0
        }
320
0
    }
321
322
0
    h2o_cache_t *new_cache = create_ssl_session_cache(capacity, duration);
323
0
    update_ssl_ctx(&self->vars->ssl_ctx, NULL, -1, &new_cache);
324
0
    return 0;
325
0
}
326
327
static h2o_socketpool_target_t *parse_backend(h2o_configurator_command_t *cmd, yoml_t *backend)
328
0
{
329
0
    h2o_mem_pool_t pool;
330
0
    yoml_t **url_node;
331
0
    h2o_socketpool_target_t *result = NULL;
332
0
    h2o_socketpool_target_conf_t lb_per_target_conf = {0}; /* default weight of each target */
333
334
0
    h2o_mem_init_pool(&pool);
335
336
0
    switch (backend->type) {
337
0
    case YOML_TYPE_SCALAR:
338
0
        url_node = &backend;
339
0
        break;
340
0
    case YOML_TYPE_MAPPING: {
341
0
        yoml_t **weight_node;
342
0
        if (h2o_configurator_parse_mapping(cmd, backend, "url:s", "weight:*", &url_node, &weight_node) != 0)
343
0
            goto Exit;
344
0
        if (weight_node != NULL) {
345
0
            unsigned weight;
346
0
            if (h2o_configurator_scanf(cmd, *weight_node, "%u", &weight) != 0)
347
0
                goto Exit;
348
0
            if (!(1 <= weight && weight <= H2O_SOCKETPOOL_TARGET_MAX_WEIGHT)) {
349
0
                h2o_configurator_errprintf(cmd, *weight_node, "weight must be an integer in range 1 - 256");
350
0
                goto Exit;
351
0
            }
352
0
            lb_per_target_conf.weight_m1 = weight - 1;
353
0
        }
354
0
    } break;
355
0
    default:
356
0
        h2o_configurator_errprintf(cmd, backend,
357
0
                                   "items of arguments passed to proxy.reverse.url must be either a scalar or a mapping");
358
0
        goto Exit;
359
0
    }
360
361
0
    h2o_url_t url;
362
0
    if (h2o_url_parse(&pool, (*url_node)->data.scalar, SIZE_MAX, &url) != 0) {
363
0
        h2o_configurator_errprintf(cmd, *url_node, "failed to parse URL: %s\n", (*url_node)->data.scalar);
364
0
        goto Exit;
365
0
    }
366
367
0
    result = h2o_socketpool_create_target(&url, &lb_per_target_conf);
368
369
0
Exit:
370
0
    h2o_mem_clear_pool(&pool);
371
0
    return result;
372
0
}
373
374
static int on_config_reverse_url(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
375
0
{
376
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
377
378
0
    yoml_t **backends, **balancer_conf = NULL;
379
0
    size_t i, num_backends = 0;
380
0
    h2o_balancer_t *balancer = NULL;
381
382
    /* collect the nodes */
383
0
    switch (node->type) {
384
0
    case YOML_TYPE_SCALAR:
385
0
        backends = &node;
386
0
        num_backends = 1;
387
0
        break;
388
0
    case YOML_TYPE_SEQUENCE:
389
0
        backends = node->data.sequence.elements;
390
0
        num_backends = node->data.sequence.size;
391
0
        break;
392
0
    case YOML_TYPE_MAPPING:
393
0
        if (h2o_configurator_parse_mapping(cmd, node, "backends:*", "balancer:s", &backends, &balancer_conf) != 0)
394
0
            return -1;
395
0
        switch ((*backends)->type) {
396
0
        case YOML_TYPE_SCALAR:
397
0
            num_backends = 1;
398
0
            break;
399
0
        case YOML_TYPE_SEQUENCE:
400
0
            num_backends = (*backends)->data.sequence.size;
401
0
            backends = (*backends)->data.sequence.elements;
402
0
            break;
403
0
        default:
404
0
            h2o_configurator_errprintf(cmd, *backends, "value for the `backends` property must be either a scalar or a sequence");
405
0
            return -1;
406
0
        }
407
0
        break;
408
0
    default:
409
0
        h2o_fatal("unexpected node type");
410
0
        return -1;
411
0
    }
412
0
    if (num_backends == 0) {
413
0
        h2o_configurator_errprintf(cmd, node, "at least one backend url must be set");
414
0
        return -1;
415
0
    }
416
417
    /* determine the balancer */
418
0
    if (balancer_conf != NULL) {
419
0
        if (strcmp((*balancer_conf)->data.scalar, "round-robin") == 0) {
420
0
            balancer = h2o_balancer_create_rr();
421
0
        } else if (strcmp((*balancer_conf)->data.scalar, "least-conn") == 0) {
422
0
            balancer = h2o_balancer_create_lc();
423
0
        } else {
424
0
            h2o_configurator_errprintf(
425
0
                cmd, node, "specified balancer is not supported. Currently supported ones are: round-robin, least-conn");
426
0
            return -1;
427
0
        }
428
0
    }
429
430
    /* parse the backends */
431
0
    h2o_socketpool_target_t **targets = alloca(sizeof(*targets) * num_backends);
432
0
    for (i = 0; i != num_backends; ++i)
433
0
        if ((targets[i] = parse_backend(cmd, backends[i])) == NULL)
434
0
            return -1;
435
436
    /* check consistency */
437
0
    if (self->vars->conf.keepalive_timeout != 0 && self->vars->conf.use_proxy_protocol) {
438
0
        h2o_configurator_errprintf(cmd, node,
439
0
                                   "please either set `proxy.use-proxy-protocol` to `OFF` or disable keep-alive by "
440
0
                                   "setting `proxy.timeout.keepalive` to zero; the features are mutually exclusive");
441
0
        return -1;
442
0
    }
443
0
    if (self->vars->conf.protocol_ratio.http2 + self->vars->conf.protocol_ratio.http3 > 100) {
444
0
        h2o_configurator_errprintf(cmd, node, "sum of http2.ratio and http3.ratio cannot be greater than 100");
445
0
        return -1;
446
0
    }
447
0
    if (self->vars->conf.http2.force_cleartext && self->vars->conf.protocol_ratio.http2 != 100) {
448
0
        h2o_configurator_errprintf(
449
0
            cmd, node, "when `proxy.http2.force-cleartext` is `ON`, `proxy.http2.ratio` must be set to `100` (percent)");
450
0
        return -1;
451
0
    }
452
453
0
    if (self->vars->conf.headers_cmds != NULL)
454
0
        h2o_mem_addref_shared(self->vars->conf.headers_cmds);
455
456
0
    h2o_socketpool_t *sockpool = h2o_mem_alloc(sizeof(*sockpool));
457
0
    memset(sockpool, 0, sizeof(*sockpool));
458
    /* init socket pool */
459
0
    h2o_socketpool_init_specific(sockpool, SIZE_MAX /* FIXME */, targets, num_backends, balancer);
460
0
    h2o_socketpool_set_timeout(sockpool, self->vars->conf.keepalive_timeout);
461
0
    h2o_socketpool_set_ssl_ctx(sockpool, self->vars->ssl_ctx);
462
0
    h2o_proxy_register_reverse_proxy(ctx->pathconf, &self->vars->conf, sockpool);
463
0
    return 0;
464
0
}
465
466
static int config_connect_core(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node,
467
                               void (*registrar)(h2o_pathconf_t *, h2o_proxy_config_vars_t *, h2o_connect_acl_entry_t *, size_t))
468
0
{
469
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
470
471
    /* Convert list of ACLs to internal representation; input is a sequence of: [+-]address(?::port|) */
472
0
    h2o_connect_acl_entry_t acl_entries[node->data.sequence.size];
473
0
    for (size_t i = 0; i < node->data.sequence.size; ++i) {
474
0
        if (node->data.sequence.elements[i]->type != YOML_TYPE_SCALAR) {
475
0
            h2o_configurator_errprintf(cmd, node->data.sequence.elements[i], "ACL entry must be a scalar");
476
0
            return -1;
477
0
        }
478
0
        const char *err = h2o_connect_parse_acl(acl_entries + i, node->data.sequence.elements[i]->data.scalar);
479
0
        if (err != NULL) {
480
0
            h2o_configurator_errprintf(cmd, node->data.sequence.elements[i], "%s", err);
481
0
            return -1;
482
0
        }
483
0
    }
484
485
0
    registrar(ctx->pathconf, &self->vars->conf, acl_entries, node->data.sequence.size);
486
0
    return 0;
487
0
}
488
489
static int on_config_classic_connect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
490
0
{
491
0
    return config_connect_core(cmd, ctx, node, h2o_connect_register);
492
0
}
493
494
static int on_config_connect_udp(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
495
0
{
496
0
    return config_connect_core(cmd, ctx, node, h2o_connect_udp_register);
497
0
}
498
499
static int on_config_emit_x_forwarded_headers(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
500
0
{
501
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
502
0
    if (ret == -1)
503
0
        return -1;
504
0
    ctx->globalconf->proxy.emit_x_forwarded_headers = (int)ret;
505
0
    return 0;
506
0
}
507
508
static int on_config_emit_via_header(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
509
0
{
510
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
511
0
    if (ret == -1)
512
0
        return -1;
513
0
    ctx->globalconf->proxy.emit_via_header = (int)ret;
514
0
    return 0;
515
0
}
516
517
static int on_config_emit_missing_date_header(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
518
0
{
519
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
520
0
    if (ret == -1)
521
0
        return -1;
522
0
    ctx->globalconf->proxy.emit_missing_date_header = (int)ret;
523
0
    return 0;
524
0
}
525
526
static int on_config_zerocopy(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
527
0
{
528
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON,ALWAYS");
529
0
    switch (ret) {
530
0
    case 0:
531
0
        ctx->globalconf->proxy.zerocopy = H2O_PROXY_ZEROCOPY_DISABLED;
532
0
        break;
533
0
    case 1:
534
0
        ctx->globalconf->proxy.zerocopy = H2O_PROXY_ZEROCOPY_ENABLED;
535
0
        break;
536
0
    case 2:
537
0
        ctx->globalconf->proxy.zerocopy = H2O_PROXY_ZEROCOPY_ALWAYS;
538
0
        break;
539
0
    default:
540
0
        return -1;
541
0
    }
542
0
    return 0;
543
0
}
544
545
static int on_config_preserve_x_forwarded_proto(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
546
0
{
547
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
548
0
    if (ret == -1)
549
0
        return -1;
550
0
    ctx->globalconf->proxy.preserve_x_forwarded_proto = (int)ret;
551
0
    return 0;
552
0
}
553
554
static int on_config_max_buffer_size(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
555
0
{
556
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
557
0
    if (h2o_configurator_scanf(cmd, node, "%zu", &self->vars->conf.max_buffer_size) != 0)
558
0
        return -1;
559
0
    if (self->vars->conf.max_buffer_size == 0) {
560
0
        h2o_configurator_errprintf(cmd, node, "proxy.buffer_size must be a positive value");
561
0
        return -1;
562
0
    }
563
0
    return 0;
564
0
}
565
566
static int on_config_http2_max_concurrent_streams(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
567
0
{
568
0
    warn_deprecation(cmd, node, "proxy.http2.max-concurrent-streams", " (notice `-` and `_`)");
569
570
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
571
0
    return h2o_configurator_scanf(cmd, node, "%u", &self->vars->conf.http2.max_concurrent_streams);
572
0
}
573
574
static int on_config_http2_force_cleartext(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
575
0
{
576
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
577
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
578
0
    if (ret < 0)
579
0
        return -1;
580
0
    self->vars->conf.http2.force_cleartext = (unsigned)ret;
581
0
    return 0;
582
0
}
583
584
static int on_config_http2_ratio(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
585
0
{
586
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
587
0
    int ret = h2o_configurator_scanf(cmd, node, "%" SCNd8, &self->vars->conf.protocol_ratio.http2);
588
0
    if (ret < 0)
589
0
        return ret;
590
0
    if (self->vars->conf.protocol_ratio.http2 < 0 || 100 < self->vars->conf.protocol_ratio.http2) {
591
0
        h2o_configurator_errprintf(cmd, node, "proxy.http2.ratio must be between 0 and 100");
592
0
        return -1;
593
0
    }
594
0
    return 0;
595
0
}
596
597
static int on_config_http3_ratio(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
598
0
{
599
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
600
0
    int ret = h2o_configurator_scanf(cmd, node, "%" SCNd8, &self->vars->conf.protocol_ratio.http3);
601
0
    if (ret < 0)
602
0
        return ret;
603
0
    if (self->vars->conf.protocol_ratio.http3 < 0 || 100 < self->vars->conf.protocol_ratio.http3) {
604
0
        h2o_configurator_errprintf(cmd, node, "proxy.http3.ratio must be between 0 and 100");
605
0
        return -1;
606
0
    }
607
0
    return 0;
608
0
}
609
610
static int on_config_expect(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
611
0
{
612
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
613
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON,FORWARD");
614
0
    switch (ret) {
615
0
    case 0:
616
0
        self->vars->conf.expect_mode = H2O_PROXY_EXPECT_OFF;
617
0
        break;
618
0
    case 1:
619
0
        self->vars->conf.expect_mode = H2O_PROXY_EXPECT_ON;
620
0
        break;
621
0
    case 2:
622
0
        self->vars->conf.expect_mode = H2O_PROXY_EXPECT_FORWARD;
623
0
        break;
624
0
    default:
625
0
        return -1;
626
0
    }
627
628
0
    return 0;
629
0
}
630
631
static int on_config_forward_close_connection(h2o_configurator_command_t *cmd, h2o_configurator_context_t *ctx, yoml_t *node)
632
0
{
633
0
    struct proxy_configurator_t *self = (void *)cmd->configurator;
634
0
    ssize_t ret = h2o_configurator_get_one_of(cmd, node, "OFF,ON");
635
0
    if (ret == -1)
636
0
        return -1;
637
0
    self->vars->conf.forward_close_connection = (int)ret;
638
0
    return 0;
639
0
}
640
641
static int on_config_enter(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
642
0
{
643
0
    struct proxy_configurator_t *self = (void *)_self;
644
645
0
    memcpy(self->vars + 1, self->vars, sizeof(*self->vars));
646
0
    if (self->vars[1].conf.headers_cmds != NULL)
647
0
        h2o_mem_addref_shared(self->vars[1].conf.headers_cmds);
648
0
    ++self->vars;
649
0
    self->connect_timeout_set = 0;
650
0
    self->first_byte_timeout_set = 0;
651
652
0
    if (ctx->pathconf == NULL && ctx->hostconf == NULL) {
653
        /* is global conf, setup the default SSL context */
654
0
        self->vars->ssl_ctx = create_ssl_ctx();
655
0
        char *ca_bundle = h2o_configurator_get_cmd_path("share/h2o/ca-bundle.crt");
656
0
        if (SSL_CTX_load_verify_locations(self->vars->ssl_ctx, ca_bundle, NULL) != 1)
657
0
            h2o_error_printf("Warning: failed to load the default certificates file at %s. Proxying to HTTPS servers may fail.\n",
658
0
                             ca_bundle);
659
0
        free(ca_bundle);
660
0
        SSL_CTX_set_verify(self->vars->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
661
0
        h2o_cache_t *ssl_session_cache =
662
0
            create_ssl_session_cache(H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_CAPACITY, H2O_DEFAULT_PROXY_SSL_SESSION_CACHE_DURATION);
663
0
        h2o_socket_ssl_set_session_cache(self->vars->ssl_ctx, ssl_session_cache);
664
0
    } else {
665
0
        SSL_CTX_up_ref(self->vars->ssl_ctx);
666
0
    }
667
668
0
    return 0;
669
0
}
670
671
static int on_config_exit(h2o_configurator_t *_self, h2o_configurator_context_t *ctx, yoml_t *node)
672
0
{
673
0
    struct proxy_configurator_t *self = (void *)_self;
674
675
0
    if (ctx->pathconf == NULL && ctx->hostconf == NULL) {
676
        /* is global conf */
677
0
        ctx->globalconf->proxy.io_timeout = self->vars->conf.io_timeout;
678
0
        ctx->globalconf->proxy.connect_timeout = self->vars->conf.connect_timeout;
679
0
        ctx->globalconf->proxy.first_byte_timeout = self->vars->conf.first_byte_timeout;
680
0
        ctx->globalconf->proxy.keepalive_timeout = self->vars->conf.keepalive_timeout;
681
0
        ctx->globalconf->proxy.max_buffer_size = self->vars->conf.max_buffer_size;
682
0
        ctx->globalconf->proxy.http2.max_concurrent_streams = self->vars->conf.http2.max_concurrent_streams;
683
0
        ctx->globalconf->proxy.protocol_ratio.http2 = self->vars->conf.protocol_ratio.http2;
684
0
        ctx->globalconf->proxy.protocol_ratio.http3 = self->vars->conf.protocol_ratio.http3;
685
0
        h2o_socketpool_set_ssl_ctx(&ctx->globalconf->proxy.global_socketpool, self->vars->ssl_ctx);
686
0
        h2o_socketpool_set_timeout(&ctx->globalconf->proxy.global_socketpool, self->vars->conf.keepalive_timeout);
687
0
    }
688
0
    SSL_CTX_free(self->vars->ssl_ctx);
689
690
0
    if (self->vars->conf.headers_cmds != NULL)
691
0
        h2o_mem_release_shared(self->vars->conf.headers_cmds);
692
693
0
    --self->vars;
694
0
    return 0;
695
0
}
696
697
static h2o_headers_command_t **get_headers_commands(h2o_configurator_t *_self)
698
0
{
699
0
    struct proxy_configurator_t *self = (void *)_self;
700
0
    return &self->vars->conf.headers_cmds;
701
0
}
702
703
void h2o_proxy_register_configurator(h2o_globalconf_t *conf)
704
0
{
705
0
    struct proxy_configurator_t *c = (void *)h2o_configurator_create(conf, sizeof(*c));
706
707
    /* set default vars */
708
0
    c->vars = c->_vars_stack;
709
0
    c->vars->conf = (h2o_proxy_config_vars_t){
710
0
        .io_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT,
711
0
        .connect_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT,
712
0
        .first_byte_timeout = H2O_DEFAULT_PROXY_IO_TIMEOUT,
713
0
        .happy_eyeballs =
714
0
            {
715
0
                .name_resolution_delay = H2O_DEFAULT_HAPPY_EYEBALLS_NAME_RESOLUTION_DELAY,
716
0
                .connection_attempt_delay = H2O_DEFAULT_HAPPY_EYEBALLS_CONNECTION_ATTEMPT_DELAY,
717
0
            },
718
0
        .tunnel_enabled = 0, /* experimental support for tunneling (e.g., CONNECT, websocket) is disabled by default */
719
0
        .max_buffer_size = SIZE_MAX,
720
0
        .http2.max_concurrent_streams = H2O_DEFAULT_PROXY_HTTP2_MAX_CONCURRENT_STREAMS,
721
0
        .protocol_ratio.http2 = -1,
722
0
        .keepalive_timeout = h2o_socketpool_get_timeout(&conf->proxy.global_socketpool),
723
0
    };
724
725
    /* setup handlers */
726
0
    c->super.enter = on_config_enter;
727
0
    c->super.exit = on_config_exit;
728
0
    h2o_configurator_define_command(&c->super, "proxy.reverse.url",
729
0
                                    H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR |
730
0
                                        H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE | H2O_CONFIGURATOR_FLAG_EXPECT_MAPPING |
731
0
                                        H2O_CONFIGURATOR_FLAG_DEFERRED,
732
0
                                    on_config_reverse_url);
733
0
    h2o_configurator_define_command(&c->super, "proxy.connect",
734
0
                                    H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE |
735
0
                                        H2O_CONFIGURATOR_FLAG_DEFERRED,
736
0
                                    on_config_classic_connect);
737
0
    h2o_configurator_define_command(
738
0
        &c->super, "proxy.connect-udp",
739
0
        H2O_CONFIGURATOR_FLAG_PATH | H2O_CONFIGURATOR_FLAG_EXPECT_SEQUENCE | H2O_CONFIGURATOR_FLAG_DEFERRED, on_config_connect_udp);
740
0
    h2o_configurator_define_command(&c->super, "proxy.connect.emit-proxy-status",
741
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
742
0
                                    on_config_connect_proxy_status);
743
0
    h2o_configurator_define_command(&c->super, "proxy.connect.proxy-status",
744
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
745
0
                                    on_config_connect_proxy_status);
746
0
    h2o_configurator_define_command(&c->super, "proxy.connect.masque-draft-03",
747
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
748
0
                                    on_config_connect_proxy_masque_draft_03);
749
0
    h2o_configurator_define_command(&c->super, "proxy.proxy-status.identity",
750
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
751
0
                                    on_config_proxy_status_identity);
752
0
    h2o_configurator_define_command(&c->super, "proxy-status.identity",
753
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
754
0
                                    on_config_proxy_status_identity);
755
0
    h2o_configurator_define_command(&c->super, "proxy.preserve-host",
756
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
757
0
                                    on_config_preserve_host);
758
0
    h2o_configurator_define_command(&c->super, "proxy.proxy-protocol",
759
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
760
0
                                    on_config_proxy_protocol);
761
0
    h2o_configurator_define_command(&c->super, "proxy.timeout.io",
762
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_timeout_io);
763
0
    h2o_configurator_define_command(&c->super, "proxy.timeout.connect",
764
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
765
0
                                    on_config_timeout_connect);
766
0
    h2o_configurator_define_command(&c->super, "proxy.timeout.first_byte",
767
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
768
0
                                    on_config_timeout_first_byte);
769
0
    h2o_configurator_define_command(&c->super, "proxy.timeout.keepalive",
770
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
771
0
                                    on_config_timeout_keepalive);
772
0
    h2o_configurator_define_command(&c->super, "proxy.happy-eyeballs.name-resolution-delay",
773
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
774
0
                                    on_config_happy_eyeballs_name_resolution_delay);
775
0
    h2o_configurator_define_command(&c->super, "proxy.happy-eyeballs.connection-attempt-delay",
776
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
777
0
                                    on_config_happy_eyeballs_connection_attempt_delay);
778
0
    h2o_configurator_define_command(&c->super, "proxy.tunnel",
779
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_tunnel);
780
0
    h2o_configurator_define_command(&c->super, "proxy.ssl.verify-peer",
781
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
782
0
                                    on_config_ssl_verify_peer);
783
0
    h2o_configurator_define_command(&c->super, "proxy.ssl.cafile",
784
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_ssl_cafile);
785
0
    h2o_configurator_define_command(&c->super, "proxy.ssl.session-cache", H2O_CONFIGURATOR_FLAG_ALL_LEVELS,
786
0
                                    on_config_ssl_session_cache);
787
0
    h2o_configurator_define_command(&c->super, "proxy.preserve-x-forwarded-proto",
788
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
789
0
                                    on_config_preserve_x_forwarded_proto);
790
0
    h2o_configurator_define_command(&c->super, "proxy.emit-x-forwarded-headers",
791
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
792
0
                                    on_config_emit_x_forwarded_headers);
793
0
    h2o_configurator_define_command(&c->super, "proxy.emit-via-header",
794
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_emit_via_header);
795
0
    h2o_configurator_define_command(&c->super, "proxy.emit-missing-date-header",
796
0
                                    H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
797
0
                                    on_config_emit_missing_date_header);
798
0
    h2o_configurator_define_command(&c->super, "proxy.zerocopy", H2O_CONFIGURATOR_FLAG_GLOBAL | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
799
0
                                    on_config_zerocopy);
800
0
    h2o_configurator_define_headers_commands(conf, &c->super, "proxy.header", get_headers_commands);
801
0
    h2o_configurator_define_command(&c->super, "proxy.max-buffer-size",
802
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
803
0
                                    on_config_max_buffer_size);
804
0
    h2o_configurator_define_command(&c->super, "proxy.http2.max-concurrent-streams",
805
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
806
0
                                    on_config_http2_max_concurrent_streams);
807
0
    h2o_configurator_define_command(&c->super, "proxy.http2.max-concurrent_streams",
808
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
809
0
                                    on_config_http2_max_concurrent_streams);
810
0
    h2o_configurator_define_command(&c->super, "proxy.http2.force-cleartext",
811
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
812
0
                                    on_config_http2_force_cleartext);
813
0
    h2o_configurator_define_command(&c->super, "proxy.http2.ratio",
814
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_http2_ratio);
815
0
    h2o_configurator_define_command(&c->super, "proxy.http3.ratio",
816
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_http3_ratio);
817
0
    h2o_configurator_define_command(&c->super, "proxy.expect",
818
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR, on_config_expect);
819
0
    h2o_configurator_define_command(&c->super, "proxy.forward.close-connection",
820
0
                                    H2O_CONFIGURATOR_FLAG_ALL_LEVELS | H2O_CONFIGURATOR_FLAG_EXPECT_SCALAR,
821
0
                                    on_config_forward_close_connection);
822
0
}