Compare commits

..

No commits in common. "master" and "v3.4-dev13" have entirely different histories.

33 changed files with 107 additions and 221 deletions

View file

@ -3329,7 +3329,7 @@ setenv <name> <value>
the configuration file sees the new value. See also "presetenv", "resetenv", the configuration file sees the new value. See also "presetenv", "resetenv",
and "unsetenv". and "unsetenv".
shm-stats-file <name> shm-stats-file <name> [ EXPERIMENTAL ]
When this directive is set, it enables the use of shared memory for storing When this directive is set, it enables the use of shared memory for storing
stats counters. <name> is used as argument to shm_open() to open the shared stats counters. <name> is used as argument to shm_open() to open the shared
memory at a unique location. It also means that the directive is only memory at a unique location. It also means that the directive is only
@ -3345,7 +3345,7 @@ shm-stats-file <name>
See also "guid", "guid-prefix" and "shm-stats-file-max-objects" See also "guid", "guid-prefix" and "shm-stats-file-max-objects"
shm-stats-file-max-objects <number> shm-stats-file-max-objects <number> [ EXPERIMENTAL ]
This setting defines the maximum number of objects the shared memory used This setting defines the maximum number of objects the shared memory used
for shared counters will be able to store per thread group. It is directly for shared counters will be able to store per thread group. It is directly
related to the maximum memory size of the shm and is used to "premap" the related to the maximum memory size of the shm and is used to "premap" the

View file

@ -179,8 +179,6 @@ enum {
/* below we have all handshake flags grouped into one */ /* below we have all handshake flags grouped into one */
CO_FL_HANDSHAKE = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV, CO_FL_HANDSHAKE = CO_FL_SEND_PROXY | CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP | CO_FL_SOCKS4_SEND | CO_FL_SOCKS4_RECV,
CO_FL_WAIT_XPRT = CO_FL_WAIT_L4_CONN | CO_FL_HANDSHAKE | CO_FL_WAIT_L6_CONN, CO_FL_WAIT_XPRT = CO_FL_WAIT_L4_CONN | CO_FL_HANDSHAKE | CO_FL_WAIT_L6_CONN,
/* handshake running on top of a layer6 */
CO_FL_WAIT_XPRT_L6 = CO_FL_QMUX_SEND | CO_FL_QMUX_RECV,
CO_FL_SSL_WAIT_HS = 0x08000000, /* wait for an SSL handshake to complete */ CO_FL_SSL_WAIT_HS = 0x08000000, /* wait for an SSL handshake to complete */

View file

@ -114,7 +114,6 @@ int conn_reverse(struct connection *conn);
const char *conn_err_code_name(struct connection *c); const char *conn_err_code_name(struct connection *c);
const char *conn_err_code_str(struct connection *c); const char *conn_err_code_str(struct connection *c);
int xprt_add_hs(struct connection *conn); int xprt_add_hs(struct connection *conn);
int xprt_add_l6hs(struct connection *conn, int xprt);
void register_mux_proto(struct mux_proto_list *list); void register_mux_proto(struct mux_proto_list *list);
static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type); static inline void conn_report_term_evt(struct connection *conn, enum term_event_loc loc, unsigned char type);

View file

@ -121,8 +121,8 @@ static inline size_t array_size_or_fail(size_t m, size_t n)
{ {
size_t size; size_t size;
if (unlikely(mulsz_overflow(m, n, &size))) if (mulsz_overflow(m, n, &size))
return DISGUISE(~(size_t)0); return ~(size_t)0;
return size; return size;
} }

View file

@ -1,5 +1,5 @@
/* /*
* include/haproxy/resolvers-t.h * include/haproxy/dns-t.h
* This file provides structures and types for DNS. * This file provides structures and types for DNS.
* *
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com> * Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>
@ -114,7 +114,7 @@ struct resolv_answer_item {
char name[DNS_MAX_NAME_SIZE+1]; /* answer name */ char name[DNS_MAX_NAME_SIZE+1]; /* answer name */
int16_t type; /* question type */ int16_t type; /* question type */
int16_t class; /* query class */ int16_t class; /* query class */
uint32_t ttl; /* response TTL */ int32_t ttl; /* response TTL */
int16_t priority; /* SRV type priority */ int16_t priority; /* SRV type priority */
uint16_t weight; /* SRV type weight */ uint16_t weight; /* SRV type weight */
uint16_t port; /* SRV type port */ uint16_t port; /* SRV type port */
@ -281,7 +281,7 @@ enum {
* matching preference was found. * matching preference was found.
*/ */
RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found RSLV_UPD_SRVIP_NOT_FOUND, /* provided IP not found
* OR provided IP found and preference is not matched and an IP * OR provided IP found and preference is not match and an IP
* matching preference was found. * matching preference was found.
*/ */
RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */ RSLV_UPD_NO_IP_FOUND, /* no IP could be found in the response */

View file

@ -1,5 +1,5 @@
/* /*
* include/haproxy/resolvers.h * include/haproxy/dns.h
* This file provides functions related to DNS protocol * This file provides functions related to DNS protocol
* *
* Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com> * Copyright (C) 2014 Baptiste Assmann <bedis9@gmail.com>

View file

@ -50,7 +50,7 @@ struct certificate_ocsp {
int refcount_store; /* Number of ckch_store that reference this certificate_ocsp */ int refcount_store; /* Number of ckch_store that reference this certificate_ocsp */
int refcount; /* Number of actual references to this certificate_ocsp (SSL_CTXs mostly) */ int refcount; /* Number of actual references to this certificate_ocsp (SSL_CTXs mostly) */
struct buffer response; struct buffer response;
unsigned long expire; long expire;
X509 *issuer; X509 *issuer;
STACK_OF(X509) *chain; STACK_OF(X509) *chain;
struct eb64_node next_update; /* Key of items inserted in ocsp_update_tree (sorted by absolute date) */ struct eb64_node next_update; /* Key of items inserted in ocsp_update_tree (sorted by absolute date) */

View file

@ -1562,16 +1562,6 @@ int acme_res_certificate(struct task *task, struct acme_ctx *ctx, char **errmsg)
key = ctx->store->data->key; key = ctx->store->data->key;
ctx->store->data->key = NULL; ctx->store->data->key = NULL;
/* OpenSSL's BIO_new_mem_buf() expects a NUL-terminated string when
* passed -1. The httpclient buffer lacks this, so manually terminate
* it here to prevent an out-of-bounds heap read during PEM parsing.
*/
if (b_room(&hc->res.buf) < 1) {
memprintf(errmsg, "ACME certificate response has no room for NUL terminator");
goto error;
}
hc->res.buf.area[hc->res.buf.data] = '\0';
/* XXX: might need a function dedicated to this, which does not read a private key */ /* XXX: might need a function dedicated to this, which does not read a private key */
if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0) if (ssl_sock_load_pem_into_ckch(ctx->store->path, hc->res.buf.area, ctx->store->data , errmsg) != 0)
goto error; goto error;

View file

@ -539,6 +539,9 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC)) if (applet_fl_test(appctx, APPCTX_FL_OUTBLK_ALLOC))
goto end; goto end;
if (!count)
goto end;
if (!appctx_get_buf(appctx, &appctx->outbuf)) { if (!appctx_get_buf(appctx, &appctx->outbuf)) {
TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx); TRACE_STATE("waiting for appctx outbuf allocation", APPLET_EV_RECV|APPLET_EV_BLK, appctx);
goto end; goto end;
@ -547,8 +550,7 @@ size_t appctx_rcv_buf(struct stconn *sc, struct buffer *buf, size_t count, unsig
if (flags & CO_RFL_BUF_FLUSH) if (flags & CO_RFL_BUF_FLUSH)
applet_fl_set(appctx, APPCTX_FL_FASTFWD); applet_fl_set(appctx, APPCTX_FL_FASTFWD);
if (count) ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
ret = CALL_APPLET_WITH_RET(appctx->applet, rcv_buf(appctx, buf, count, flags));
if (ret) if (ret)
applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL); applet_fl_clr(appctx, APPCTX_FL_OUTBLK_FULL);

View file

@ -1818,7 +1818,7 @@ int connect_server(struct stream *s)
{ {
struct connection *cli_conn = objt_conn(strm_orig(s)); struct connection *cli_conn = objt_conn(strm_orig(s));
struct connection *srv_conn = NULL; struct connection *srv_conn = NULL;
const struct mux_proto_list *mux_proto = NULL; const struct mux_proto_list *mux_proto;
struct server *srv; struct server *srv;
struct ist name = IST_NULL; struct ist name = IST_NULL;
struct sample *name_smp; struct sample *name_smp;
@ -2139,10 +2139,12 @@ int connect_server(struct stream *s)
} }
if (may_start_mux_now) { if (may_start_mux_now) {
/* Delay MUX init if an XPRT handshake is required prior. */ /* Delay QMux MUX init to let xprt_qmux handshake runs first. */
mux_proto = conn_select_mux_be(srv_conn); mux_proto = conn_select_mux_be(srv_conn);
if (mux_proto && mux_proto->init_xprt) if (mux_proto && mux_proto->init_xprt == XPRT_QMUX) {
srv_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
may_start_mux_now = 0; may_start_mux_now = 0;
}
} }
#if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation) #if defined(USE_OPENSSL) && defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
@ -2252,13 +2254,6 @@ int connect_server(struct stream *s)
} }
} }
} }
else if (mux_proto && mux_proto->init_xprt) {
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (xprt_add_l6hs(srv_conn, mux_proto->init_xprt)) {
conn_full_close(srv_conn);
return SF_ERR_INTERNAL;
}
}
/* /*
* Now that the mux may have been created, we can start the xprt. * Now that the mux may have been created, we can start the xprt.

View file

@ -2180,17 +2180,7 @@ enum act_return http_action_req_cache_use(struct act_rule *rule, struct proxy *p
sec_entry = get_secondary_entry(cache_tree, res, sec_entry = get_secondary_entry(cache_tree, res,
s->txn.http->cache_secondary_hash, s->txn.http->cache_secondary_hash,
0); 0);
if (!sec_entry) { if (sec_entry && sec_entry != res) {
/* Secondary key miss: release the retained primary entry
* and reattach the detached row before returning.
*/
release_entry(cache_tree, res, 0);
shctx_wrlock(shctx);
if (detached)
shctx_row_reattach(shctx, entry_block);
shctx_wrunlock(shctx);
}
else if (sec_entry != res) {
/* The wrong row was added to the hot list. */ /* The wrong row was added to the hot list. */
release_entry(cache_tree, res, 0); release_entry(cache_tree, res, 0);
retain_entry(sec_entry); retain_entry(sec_entry);

View file

@ -1629,6 +1629,11 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
struct proxy *curpx, const struct proxy *defpx, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) const char *file, int line, char **err)
{ {
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (global.shm_stats_file != NULL) { if (global.shm_stats_file != NULL) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1639,6 +1644,7 @@ static int cfg_parse_global_shm_stats_file(char **args, int section_type,
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
global.shm_stats_file = strdup(args[1]); global.shm_stats_file = strdup(args[1]);
return 0; return 0;
} }
@ -1647,6 +1653,11 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
struct proxy *curpx, const struct proxy *defpx, struct proxy *curpx, const struct proxy *defpx,
const char *file, int line, char **err) const char *file, int line, char **err)
{ {
if (!experimental_directives_allowed) {
memprintf(err, "'%s' directive is experimental, must be allowed via a global 'expose-experimental-directives'", args[0]);
return -1;
}
if (shm_stats_file_max_objects != -1) { if (shm_stats_file_max_objects != -1) {
memprintf(err, "'%s' already specified.\n", args[0]); memprintf(err, "'%s' already specified.\n", args[0]);
return -1; return -1;
@ -1657,6 +1668,7 @@ static int cfg_parse_global_shm_stats_file_max_objects(char **args, int section_
return -1; return -1;
} }
mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
shm_stats_file_max_objects = atoi(args[1]); shm_stats_file_max_objects = atoi(args[1]);
return 0; return 0;
} }

View file

@ -1151,13 +1151,8 @@ int cli_parse_cmdline(struct appctx *appctx)
*/ */
if (len-1 == strlen(appctx->cli_ctx.payload_pat)) { if (len-1 == strlen(appctx->cli_ctx.payload_pat)) {
if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) { if (strncmp(str, appctx->cli_ctx.payload_pat, len-1) == 0) {
/* end of payload was reached, rewind before the previous \n, if any, and replace it by a \0 /* end of payload was reached, rewind before the previous \n and replace it by a \0 */
* Otherwise, the payload is empty, just b_sub(buf, strlen(appctx->cli_ctx.payload_pat) + 2);
*/
if (b_data(buf) > len)
b_sub(buf, len+1);
else
b_sub(buf, len);
*b_tail(buf) = '\0'; *b_tail(buf) = '\0';
appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD; appctx->st1 &= ~APPCTX_CLI_ST1_PAYLOAD;
} }

View file

@ -196,7 +196,7 @@ int conn_notify_mux(struct connection *conn, int old_flags, int forced_wake)
* information to create one, typically from the ALPN. If we're * information to create one, typically from the ALPN. If we're
* done with the handshake, attempt to create one. * done with the handshake, attempt to create one.
*/ */
if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_WAIT_XPRT_L6))) { if (unlikely(!conn->mux) && !(conn->flags & (CO_FL_WAIT_XPRT|CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) {
ret = conn_create_mux(conn, NULL); ret = conn_create_mux(conn, NULL);
if (ret < 0) if (ret < 0)
goto done; goto done;
@ -847,43 +847,6 @@ int xprt_add_hs(struct connection *conn)
return 0; return 0;
} }
/* Activates an <xprt> layer on top of <conn> connection. This handshake layer
* should be designed to work on top of the layer 6. If SSL is active and its
* handshake still in progress, this function does nothing.
*
* Returns 0 on success else a negative error code.
*/
int xprt_add_l6hs(struct connection *conn, int xprt)
{
const struct xprt_ops *ops = xprt_get(xprt);
void *ops_ctx = NULL;
/* Only QMux is supported as handshake on top of layer6 for now. */
BUG_ON(xprt != XPRT_QMUX);
if (conn->flags & CO_FL_ERROR)
return -1;
/* Do nothing if SSL is in used but handshake still in progress. In
* this case, xprt layer will be added on handshake completion.
*/
if (conn->xprt == xprt_get(XPRT_SSL) &&
(conn->flags & CO_FL_WAIT_L6_CONN)) {
return 0;
}
if (ops->init(conn, &ops_ctx))
return -1;
ops->add_xprt(conn, ops_ctx, conn->xprt_ctx, conn->xprt, NULL, NULL);
conn->xprt = ops;
conn->xprt_ctx = ops_ctx;
/* Reset XPRT READY flag before the next conn_xprt_start(). */
conn->flags &= ~CO_FL_XPRT_READY;
return 0;
}
/* returns a short name for an error, typically the same as the enum name /* returns a short name for an error, typically the same as the enum name
* without the "CO_ER_" prefix, or an empty string for no error (better eye * without the "CO_ER_" prefix, or an empty string for no error (better eye
* catching in logs). This is more compact for some debug cases. * catching in logs). This is more compact for some debug cases.

View file

@ -79,7 +79,7 @@ static struct dict_entry *__dict_lookup(struct dict *d, const char *s)
*/ */
struct dict_entry *dict_insert(struct dict *d, char *s) struct dict_entry *dict_insert(struct dict *d, char *s)
{ {
struct dict_entry *de, *tree_de; struct dict_entry *de;
struct ebpt_node *n; struct ebpt_node *n;
HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock); HA_RWLOCK_RDLOCK(DICT_LOCK, &d->rwlock);
@ -97,18 +97,13 @@ struct dict_entry *dict_insert(struct dict *d, char *s)
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock); HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
n = ebis_insert(&d->values, &de->value); n = ebis_insert(&d->values, &de->value);
tree_de = container_of(n, struct dict_entry, value); HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
if (tree_de == de) if (n != &de->value) {
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
else {
/* another entry was already there, we'll return it, kill
* ours and bump the other's refcount before returning it.
*/
HA_ATOMIC_INC(&tree_de->refcount);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
free_dict_entry(de); free_dict_entry(de);
de = container_of(n, struct dict_entry, value);
} }
return tree_de;
return de;
} }
@ -122,11 +117,10 @@ void dict_entry_unref(struct dict *d, struct dict_entry *de)
if (!de) if (!de)
return; return;
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock); if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0)
if (HA_ATOMIC_SUB_FETCH(&de->refcount, 1) != 0) {
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);
return; return;
}
HA_RWLOCK_WRLOCK(DICT_LOCK, &d->rwlock);
ebpt_delete(&de->value); ebpt_delete(&de->value);
HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock); HA_RWLOCK_WRUNLOCK(DICT_LOCK, &d->rwlock);

View file

@ -1926,37 +1926,20 @@ static void dump_registered_keywords(void)
/* Generate a random cluster-secret in case the setting is not provided in the /* Generate a random cluster-secret in case the setting is not provided in the
* configuration. This allows to use features which rely on it albeit with some * configuration. This allows to use features which rely on it albeit with some
* limitations. The function doesn't (solely) use ha_random64() because this * limitations.
* secret is permanent, and ha_random64() can easily be leaked at various
* places.
*/ */
static void generate_random_cluster_secret() static void generate_random_cluster_secret()
{ {
/* used as a default random cluster-secret if none defined. */ /* used as a default random cluster-secret if none defined. */
union { uint64_t rand;
uint64_t by64[2];
uint32_t by32[4];
uchar by8[16];
} rand;
/* The caller must not overwrite an already defined secret. */ /* The caller must not overwrite an already defined secret. */
BUG_ON(cluster_secret_isset); BUG_ON(cluster_secret_isset);
BUG_ON(sizeof(global.cluster_secret) != sizeof(rand));
#ifdef USE_OPENSSL
if (RAND_bytes(rand.by8, sizeof(rand.by8)) != 1)
#endif
{
/* no SSL or not working, fall back to other sources */
rand.by64[0] = ha_random64();
rand.by64[1] = ha_random64();
rand.by32[0] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[1] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[2] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
rand.by32[3] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
}
rand = ha_random64();
memcpy(global.cluster_secret, &rand, sizeof(rand)); memcpy(global.cluster_secret, &rand, sizeof(rand));
rand = ha_random64();
memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
cluster_secret_isset = 1; cluster_secret_isset = 1;
} }

View file

@ -788,7 +788,7 @@ static void hstream_parse_uri(struct ist uri, struct hstream *hs)
} while (*next); } while (*next);
if (use_rand) if (use_rand)
result = ((long long)statistical_prng() * result) / 0xFFFFFFFFU; result = ((long long)ha_random64() * result) / ((long long)RAND_MAX + 1);
switch (*arg) { switch (*arg) {
case 's': case 's':

View file

@ -2949,20 +2949,20 @@ __LJMP static int hlua_socket_receive_yield(struct lua_State *L, int status, lua
/* remove final \r\n. */ /* remove final \r\n. */
if (nblk == 1) { if (nblk == 1) {
if (len1 && blk1[len1-1] == '\n') { if (blk1[len1-1] == '\n') {
len1--; len1--;
skip_at_end++; skip_at_end++;
if (len1 && blk1[len1-1] == '\r') { if (blk1[len1-1] == '\r') {
len1--; len1--;
skip_at_end++; skip_at_end++;
} }
} }
} }
else { else {
if (len2 && blk2[len2-1] == '\n') { if (blk2[len2-1] == '\n') {
len2--; len2--;
skip_at_end++; skip_at_end++;
if (len2 && blk2[len2-1] == '\r') { if (blk2[len2-1] == '\r') {
len2--; len2--;
skip_at_end++; skip_at_end++;
} }

View file

@ -3319,7 +3319,7 @@ struct ist *build_log_header(struct log_header hdr, size_t *nbelem)
break; break;
} }
else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) { else if (metadata && metadata[LOG_META_TIME].len >= LOG_ISOTIME_MINLEN) {
uint month; int month;
char *timestamp = metadata[LOG_META_TIME].ptr; char *timestamp = metadata[LOG_META_TIME].ptr;
/* iso time always begins like this: '1970-01-01T00:00:00' */ /* iso time always begins like this: '1970-01-01T00:00:00' */
@ -5499,7 +5499,7 @@ void parse_log_message(char *buf, size_t buflen, int *level, int *facility,
return; return;
fac_level = 10*fac_level + (*p - '0'); fac_level = 10*fac_level + (*p - '0');
p++; p++;
if ((p - buf) >= buflen) if ((p - buf) > buflen)
return; return;
} }
@ -6743,7 +6743,6 @@ int cfg_parse_log_profile(const char *file, int linenum, char **args, int kwm)
SMP_VAL_FE_LOG_END, &errmsg)) { SMP_VAL_FE_LOG_END, &errmsg)) {
ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n", ha_alert("Parsing [%s:%d]: failed to parse logformat: %s.\n",
file, linenum, errmsg); file, linenum, errmsg);
lf_expr_deinit(target_lf);
err_code |= ERR_ALERT | ERR_FATAL; err_code |= ERR_ALERT | ERR_FATAL;
goto out; goto out;
} }

View file

@ -6236,13 +6236,6 @@ next_frame:
/* Skip StreamDep and weight for now (we don't support PRIORITY) */ /* Skip StreamDep and weight for now (we don't support PRIORITY) */
if (h2c->dff & H2_F_HEADERS_PRIORITY) { if (h2c->dff & H2_F_HEADERS_PRIORITY) {
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
if (read_n32(hdrs) == h2c->dsi) { if (read_n32(hdrs) == h2c->dsi) {
/* RFC7540#5.3.1 : stream dep may not depend on itself */ /* RFC7540#5.3.1 : stream dep may not depend on itself */
h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself"); h2c_report_glitch(h2c, 1, "PRIORITY frame referencing itself");
@ -6252,6 +6245,13 @@ next_frame:
goto fail; goto fail;
} }
if (flen < 5) {
h2c_report_glitch(h2c, 1, "too short PRIORITY frame");
TRACE_STATE("too short PRIORITY frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn);
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
goto fail;
}
hdrs += 5; // stream dep = 4, weight = 1 hdrs += 5; // stream dep = 4, weight = 1
flen -= 5; flen -= 5;
} }

View file

@ -447,8 +447,8 @@ static size_t tcp_fullhdr_find_opt(const struct sample *smp, uint8_t opt)
/* kind1 = NOP and is a single byte, others have a length field */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[next] == 1) if (smp->data.u.str.area[next] == 1)
next++; next++;
else if (next + 1 < len && smp->data.u.str.area[next + 1] > 1) else if (next + 1 < len)
next += (uchar)smp->data.u.str.area[next + 1]; next += smp->data.u.str.area[next + 1];
else else
break; break;
if (smp->data.u.str.area[curr] == opt && next <= len) if (smp->data.u.str.area[curr] == opt && next <= len)
@ -605,7 +605,7 @@ static int sample_conv_tcp_options_list(const struct arg *arg_p, struct sample *
/* kind1 = NOP and is a single byte, others have a length field */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
ofs++; ofs++;
else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1] > 1) else if (ofs + 1 < len && smp->data.u.str.area[ofs + 1])
ofs += (uchar)smp->data.u.str.area[ofs + 1]; ofs += (uchar)smp->data.u.str.area[ofs + 1];
else else
break; break;
@ -780,7 +780,7 @@ static int sample_conv_ip_fp(const struct arg *arg_p, struct sample *smp, void *
/* kind1 = NOP and is a single byte, others have a length field */ /* kind1 = NOP and is a single byte, others have a length field */
if (smp->data.u.str.area[ofs] == 1) if (smp->data.u.str.area[ofs] == 1)
next = ofs + 1; next = ofs + 1;
else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1] > 1) else if ((ofs + 1 < tcplen) && smp->data.u.str.area[ofs + 1]) /* optlen 0 will cause an infinite loop */
next = ofs + (uchar)smp->data.u.str.area[ofs + 1]; next = ofs + (uchar)smp->data.u.str.area[ofs + 1];
else else
break; break;

View file

@ -69,7 +69,7 @@
#include <haproxy/uri_auth.h> #include <haproxy/uri_auth.h>
/* Lock to ensure multiple backends deletion concurrently is safe */ /* Lock to ensure multiple backends deletion concurrently is safe */
__decl_spinlock(proxies_del_lock); static __decl_spinlock(proxies_del_lock);
int listeners; /* # of proxy listeners, set by cfgparse */ int listeners; /* # of proxy listeners, set by cfgparse */
struct proxy *proxies_list = NULL; /* list of main proxies */ struct proxy *proxies_list = NULL; /* list of main proxies */

View file

@ -70,10 +70,6 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_reset_stream *rst_frm = &frm.reset_stream; struct qf_reset_stream *rst_frm = &frm.reset_stream;
qcc_recv_reset_stream(qcc, rst_frm->id, rst_frm->app_error_code, rst_frm->final_size); qcc_recv_reset_stream(qcc, rst_frm->id, rst_frm->app_error_code, rst_frm->final_size);
} }
else if (frm.type == QUIC_FT_STOP_SENDING) {
struct qf_stop_sending *ss_frm = &frm.stop_sending;
qcc_recv_stop_sending(qcc, ss_frm->id, ss_frm->app_error_code);
}
else if (frm.type == QUIC_FT_MAX_DATA) { else if (frm.type == QUIC_FT_MAX_DATA) {
struct qf_max_data *md_frm = &frm.max_data; struct qf_max_data *md_frm = &frm.max_data;
qcc_recv_max_data(qcc, md_frm->max_data); qcc_recv_max_data(qcc, md_frm->max_data);
@ -86,26 +82,13 @@ static int qmux_parse_frm(struct qcc *qcc, struct buffer *buf)
struct qf_max_streams *ms_frm = &frm.max_streams_bidi; struct qf_max_streams *ms_frm = &frm.max_streams_bidi;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 1); qcc_recv_max_streams(qcc, ms_frm->max_streams, 1);
} }
else if (frm.type == QUIC_FT_MAX_STREAMS_UNI) {
struct qf_max_streams *ms_frm = &frm.max_streams_uni;
qcc_recv_max_streams(qcc, ms_frm->max_streams, 0);
}
else if (frm.type == QUIC_FT_DATA_BLOCKED || else if (frm.type == QUIC_FT_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAM_DATA_BLOCKED || frm.type == QUIC_FT_STREAM_DATA_BLOCKED ||
frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI || frm.type == QUIC_FT_STREAMS_BLOCKED_BIDI ||
frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) { frm.type == QUIC_FT_STREAMS_BLOCKED_UNI) {
/* TODO */ /* TODO */
CHECK_IF("received flow control blocked frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_PADDING) {
CHECK_IF("received padding frame not yet handled in QMux");
}
else if (frm.type == QUIC_FT_CONNECTION_CLOSE ||
frm.type == QUIC_FT_CONNECTION_CLOSE_APP) {
CHECK_IF("received connection_close frame not yet handled in QMux");
} }
else { else {
/* qmux_is_frm_valid() must prevent this */
ABORT_NOW(); ABORT_NOW();
} }

View file

@ -378,7 +378,7 @@ int quic_get_cid_tid(const unsigned char *cid, size_t cid_len,
tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)]; tree = &quic_fe_cid_trees[quic_cid_tree_idx(&derive_cid)];
HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock); HA_RWLOCK_RDLOCK(QC_CID_LOCK, &tree->lock);
node = ebmb_lookup(&tree->root, derive_cid.data, derive_cid.len); node = ebmb_lookup(&tree->root, cid, cid_len);
if (node) { if (node) {
conn_id = ebmb_entry(node, struct quic_connection_id, node); conn_id = ebmb_entry(node, struct quic_connection_id, node);
cid_tid = HA_ATOMIC_LOAD(&conn_id->tid); cid_tid = HA_ATOMIC_LOAD(&conn_id->tid);

View file

@ -444,7 +444,7 @@ INITCALL0(STG_REGISTER, regex_register_build_options);
#ifdef USE_PCRE2 #ifdef USE_PCRE2
static int init_pcre2_per_thread(void) static int init_pcre2_per_thread(void)
{ {
local_pcre2_match = pcre2_match_data_create(MAX_MATCH, NULL); local_pcre2_match = pcre2_match_data_create(MAX_MATCH - 1, NULL);
if (!local_pcre2_match) { if (!local_pcre2_match) {
ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid); ha_alert("Failed to allocate PCRE2 match data context for thread %u.\n", tid);
return 0; return 0;

View file

@ -226,7 +226,7 @@ struct show_resolvers_ctx {
}; };
/* returns the currently accepted address families as a combination of /* returns the currently accepted address families as a combination of
* RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt * RSLV_ACCEPT_IPV4 and RSLV_ACCEPT_IPV6 only. It will dynamically adapt adapt
* the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set, * the IPv6 status to sock_inet6_seems_reachable if RSLV_AUTO_FAMILY is set,
* otherwise returns the relevant bits of resolv_accept_families. * otherwise returns the relevant bits of resolv_accept_families.
*/ */
@ -509,7 +509,7 @@ resolv_run_resolution(struct resolv_resolution *resolution)
return 0; return 0;
/* Check if a resolution has already been started for this server return /* Check if a resolution has already been started for this server return
* directly to avoid resolution pile up. */ * directly to avoid resolution pill up. */
if (resolution->step != RSLV_STEP_NONE) if (resolution->step != RSLV_STEP_NONE)
return 0; return 0;
@ -1236,7 +1236,8 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = read_n32(reader); answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1497,7 +1498,8 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
if (reader + 4 > bufend) if (reader + 4 > bufend)
goto invalid_resp; goto invalid_resp;
answer_record->ttl = read_n32(reader); answer_record->ttl = reader[0] * 16777216 + reader[1] * 65536
+ reader[2] * 256 + reader[3];
reader += 4; reader += 4;
/* Now reading data len */ /* Now reading data len */
@ -1597,6 +1599,7 @@ static int resolv_validate_dns_response(unsigned char *resp, unsigned char *bufe
tmp_record->ar_item == NULL && tmp_record->ar_item == NULL &&
memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) { memcmp(tmp_record->data.target, answer_record->name, tmp_record->data_len) == 0) {
/* Always use the received additional record to refresh info */ /* Always use the received additional record to refresh info */
pool_free(resolv_answer_item_pool, tmp_record->ar_item);
tmp_record->ar_item = answer_record; tmp_record->ar_item = answer_record;
answer_record = NULL; answer_record = NULL;
break; break;
@ -1852,15 +1855,7 @@ int resolv_dn_label_to_str(const char *dn, int dn_len, char *str, int str_len)
ptr = str; ptr = str;
for (i = 0; i < dn_len; ++i) { for (i = 0; i < dn_len; ++i) {
sz = (unsigned char)dn[i]; sz = dn[i];
if (!sz)
break;
/* Check str_len adding 1 for the dot if (i!=0) and 1 for null terminator */
if (str_len < sz+i+(!!i)+1)
return -1;
if (i) if (i)
*ptr++ = '.'; *ptr++ = '.';
/* copy the string at i+1 to lower case */ /* copy the string at i+1 to lower case */

View file

@ -2150,11 +2150,11 @@ static int sample_conv_be2hex_check(struct arg *args, struct sample_conv *conv,
*/ */
static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private) static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *private)
{ {
struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data * 2); struct buffer *trash = get_trash_chunk_sz(smp->data.u.str.data);
int chunk_size = args[1].data.sint; int chunk_size = args[1].data.sint;
const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data; const int last = args[2].data.sint ? smp->data.u.str.data - chunk_size + 1 : smp->data.u.str.data;
int i; int i;
size_t max_size; int max_size;
int ptr = 0; int ptr = 0;
unsigned char c; unsigned char c;
@ -2163,9 +2163,7 @@ static int sample_conv_be2hex(const struct arg *args, struct sample *smp, void *
trash->data = 0; trash->data = 0;
if (args[0].data.str.data == 0 && args[2].data.sint == 0) if (args[0].data.str.data == 0 && args[2].data.sint == 0)
chunk_size = smp->data.u.str.data; chunk_size = smp->data.u.str.data;
if (2 * (size_t)chunk_size > trash->size) max_size = trash->size - 2 * chunk_size;
return 0;
max_size = trash->size - 2 * (size_t)chunk_size;
while (ptr < last && trash->data <= max_size) { while (ptr < last && trash->data <= max_size) {
if (ptr) { if (ptr) {

View file

@ -241,17 +241,14 @@ int session_accept_fd(struct connection *cli_conn)
if (l->bind_conf->options & BC_O_ACC_CIP) if (l->bind_conf->options & BC_O_ACC_CIP)
cli_conn->flags |= CO_FL_ACCEPT_CIP; cli_conn->flags |= CO_FL_ACCEPT_CIP;
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt == XPRT_QMUX)
cli_conn->flags |= (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
/* Add the handshake pseudo-XPRT */ /* Add the handshake pseudo-XPRT */
if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) { if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) {
if (xprt_add_hs(cli_conn) != 0) if (xprt_add_hs(cli_conn) != 0)
goto out_free_conn; goto out_free_conn;
} }
/* Add handshake layer prior to MUX init if required. Does nothing if SSL layer is active though. */
if (l->bind_conf->mux_proto && l->bind_conf->mux_proto->init_xprt) {
if (xprt_add_l6hs(cli_conn, l->bind_conf->mux_proto->init_xprt))
goto out_free_conn;
}
} }
/* Reversed conns already have an assigned session, do not recreate it. */ /* Reversed conns already have an assigned session, do not recreate it. */
@ -354,7 +351,7 @@ int session_accept_fd(struct connection *cli_conn)
* v | | | * v | | |
* conn -- owner ---> task <-----+ * conn -- owner ---> task <-----+
*/ */
if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS | CO_FL_WAIT_XPRT_L6)) { if (cli_conn->flags & (CO_FL_WAIT_XPRT | CO_FL_EARLY_SSL_HS)) {
int timeout; int timeout;
int clt_tmt = p->timeout.client; int clt_tmt = p->timeout.client;
int hs_tmt = p->timeout.client_hs; int hs_tmt = p->timeout.client_hs;

View file

@ -356,10 +356,8 @@ int ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind
ssl_ctx = (SSL_CTX *)lru->data; ssl_ctx = (SSL_CTX *)lru->data;
if (!ssl_ctx && lru) { if (!ssl_ctx && lru) {
ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl); ssl_ctx = ssl_sock_do_create_cert(servername, bind_conf, ssl);
if (!ssl_ctx) { if (!ssl_ctx)
HA_RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
goto error; goto error;
}
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free); lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
} }
SSL_set_SSL_CTX(ssl, ssl_ctx); SSL_set_SSL_CTX(ssl, ssl_ctx);

View file

@ -290,8 +290,6 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
int ret = 1; int ret = 1;
#ifdef HAVE_ASN1_TIME_TO_TM #ifdef HAVE_ASN1_TIME_TO_TM
struct tm nextupd_tm = {0}; struct tm nextupd_tm = {0};
#else
long expire = 0;
#endif #endif
resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p, resp = d2i_OCSP_RESPONSE(NULL, (const unsigned char **)&p,
@ -393,12 +391,11 @@ int ssl_sock_load_ocsp_response(struct buffer *ocsp_response,
} }
ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW; ocsp->expire = my_timegm(&nextupd_tm) - OCSP_MAX_RESPONSE_TIME_SKEW;
#else #else
expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW; ocsp->expire = asn1_generalizedtime_to_epoch(nextupd) - OCSP_MAX_RESPONSE_TIME_SKEW;
if (expire < 0) { if (ocsp->expire < 0) {
memprintf(err, "OCSP single response: Invalid \"Next Update\" time"); memprintf(err, "OCSP single response: Invalid \"Next Update\" time");
goto out; goto out;
} }
ocsp->expire = expire;
#endif #endif
if (ocsp->expire < date.tv_sec) { if (ocsp->expire < date.tv_sec) {

View file

@ -6973,8 +6973,12 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
mux = !conn_is_back(conn) ? mux = !conn_is_back(conn) ?
conn_select_mux_fe(conn) : conn_select_mux_be(conn); conn_select_mux_fe(conn) : conn_select_mux_be(conn);
if (mux->init_xprt) { if (ctx->conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND) ||
ret = xprt_add_l6hs(conn, mux->init_xprt); mux->init_xprt == XPRT_QMUX) {
const struct xprt_ops *ops = xprt_get(XPRT_QMUX);
void *xprt_ctx_hs = NULL;
ret = ops->init(conn, &xprt_ctx_hs);
/* Frontend conn must be freed in case of XPRT init failure. */ /* Frontend conn must be freed in case of XPRT init failure. */
if (ret) { if (ret) {
if (!conn_is_back(conn)) { if (!conn_is_back(conn)) {
@ -6986,7 +6990,15 @@ struct task *ssl_sock_io_cb(struct task *t, void *context, unsigned int state)
goto leave; goto leave;
} }
ret = conn_xprt_start(conn); ret = ops->add_xprt(conn, xprt_ctx_hs,
conn->xprt_ctx, conn->xprt, NULL, NULL);
BUG_ON(ret); /* xprt_qmux add_xprt always succeeds */
conn->xprt = ops;
conn->xprt_ctx = xprt_ctx_hs;
ret = conn->xprt->start(conn, xprt_ctx_hs);
BUG_ON(ret);
} }
else { else {
/* TODO MUX selection already performs by conn_select_mux_fe/be(). /* TODO MUX selection already performs by conn_select_mux_fe/be().

View file

@ -834,8 +834,6 @@ enum tcpcheck_eval_ret tcpcheck_spop_expect_hello(struct check *check, struct tc
goto invalid_frame; goto invalid_frame;
if (decode_varint(&ptr, end, &sz) == -1) if (decode_varint(&ptr, end, &sz) == -1)
goto invalid_frame; goto invalid_frame;
if (sz >= SPOP_ERR_ENTRIES)
sz = SPOP_ERR_UNKNOWN;
check->code = sz; check->code = sz;
} }
@ -991,7 +989,7 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
const char *sc = NULL; /* maxconn */ const char *sc = NULL; /* maxconn */
const char *err = NULL; /* first error to report */ const char *err = NULL; /* first error to report */
const char *wrn = NULL; /* first warning to report */ const char *wrn = NULL; /* first warning to report */
char *cmd, *p, *end; char *cmd, *p;
TRACE_ENTER(CHK_EV_TCPCHK_EXP, check); TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
@ -1020,11 +1018,10 @@ enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct t
*/ */
p = b_head(&check->bi); p = b_head(&check->bi);
end = b_tail(&check->bi); while (*p && *p != '\n' && *p != '\r')
while (p < end && *p && *p != '\n' && *p != '\r')
p++; p++;
if (!*p || p == end) { if (!*p) {
if (!last_read) if (!last_read)
goto wait_more_data; goto wait_more_data;

View file

@ -12,9 +12,6 @@
#include <haproxy/quic_frame.h> #include <haproxy/quic_frame.h>
#include <haproxy/quic_tp-t.h> #include <haproxy/quic_tp-t.h>
/* Default protocol when not running over SSL layer. */
#define XPRT_QMUX_DEFAULT_ALPN "h3"
struct xprt_qmux_ctx { struct xprt_qmux_ctx {
struct connection *conn; struct connection *conn;
struct wait_event wait_event; struct wait_event wait_event;
@ -210,7 +207,7 @@ struct task *xprt_qmux_io_cb(struct task *t, void *context, unsigned int state)
out: out:
if ((conn->flags & CO_FL_ERROR) || if ((conn->flags & CO_FL_ERROR) ||
!(conn->flags & CO_FL_WAIT_XPRT_L6)) { !(conn->flags & (CO_FL_QMUX_RECV|CO_FL_QMUX_SEND))) {
/* XPRT should be unsubscribed when transfer done or on error. */ /* XPRT should be unsubscribed when transfer done or on error. */
BUG_ON(ctx->wait_event.events); BUG_ON(ctx->wait_event.events);
@ -335,7 +332,7 @@ static void xprt_qmux_close(struct connection *conn, void *xprt_ctx)
if (ctx->ops_lower && ctx->ops_lower->close) if (ctx->ops_lower && ctx->ops_lower->close)
ctx->ops_lower->close(conn, ctx->ctx_lower); ctx->ops_lower->close(conn, ctx->ctx_lower);
conn->flags &= ~CO_FL_WAIT_XPRT_L6; conn->flags &= ~(CO_FL_QMUX_RECV|CO_FL_QMUX_SEND);
BUG_ON(conn->xprt_ctx != ctx); BUG_ON(conn->xprt_ctx != ctx);
conn->xprt_ctx = ctx->ctx_lower; conn->xprt_ctx = ctx->ctx_lower;
@ -349,14 +346,6 @@ static int xprt_qmux_get_alpn(const struct connection *conn, void *xprt_ctx,
const char **str, int *len) const char **str, int *len)
{ {
struct xprt_qmux_ctx *ctx = xprt_ctx; struct xprt_qmux_ctx *ctx = xprt_ctx;
/* Return a the default ALPN if lower layer is not able to negotiate it. */
if (!ctx->ops_lower || !ctx->ops_lower->get_alpn) {
*str = XPRT_QMUX_DEFAULT_ALPN;
*len = strlen(XPRT_QMUX_DEFAULT_ALPN);
return 1;
}
return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len); return ctx->ops_lower->get_alpn(conn, ctx->ctx_lower, str, len);
} }