diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index a8ffd9e7a..6720f2f9a 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -319,7 +319,7 @@ struct qcc_app_ops; * with a connection \ */ \ struct eb_root *cids; \ - struct listener *li; /* only valid for frontend connections */ \ + enum obj_type *target; \ /* Idle timer task */ \ struct task *idle_timer_task; \ unsigned int idle_expire; \ diff --git a/include/haproxy/quic_conn.h b/include/haproxy/quic_conn.h index 409ebf2c3..9bc073df3 100644 --- a/include/haproxy/quic_conn.h +++ b/include/haproxy/quic_conn.h @@ -69,7 +69,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, struct quic_connection_id *conn_id, struct sockaddr_storage *local_addr, struct sockaddr_storage *peer_addr, - int server, int token, void *owner, + int token, void *owner, struct connection *conn); int quic_build_post_handshake_frames(struct quic_conn *qc); const struct quic_version *qc_supported_version(uint32_t version); @@ -164,13 +164,6 @@ static inline void quic_free_ncbuf(struct ncbuf *ncbuf) *ncbuf = NCBUF_NULL; } -/* Return the address of the connection owner object type. */ -static inline enum obj_type *qc_owner_obj_type(struct quic_conn *qc) -{ - return qc_is_listener(qc) ? &qc->li->obj_type : - &objt_server(qc->conn->target)->obj_type; -} - /* Return the address of the QUIC counters attached to the proxy of * the owner of the connection whose object type address is for * listener and servers, or NULL for others object type. diff --git a/include/haproxy/quic_sock.h b/include/haproxy/quic_sock.h index 66f24c58e..d8b45ec32 100644 --- a/include/haproxy/quic_sock.h +++ b/include/haproxy/quic_sock.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -78,7 +79,8 @@ static inline char qc_test_fd(struct quic_conn *qc) */ static inline int qc_fd(struct quic_conn *qc) { - return qc_test_fd(qc) ? qc->fd : qc->li->rx.fd; + /* TODO: check this: For backends, qc->fd is always initialized */ + return qc_test_fd(qc) ? qc->fd : __objt_listener(qc->target)->rx.fd; } /* Try to increment handshake current counter. If listener limit is diff --git a/src/cli.c b/src/cli.c index 5ad88d48d..49e8e3502 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1423,7 +1423,8 @@ static int cli_io_handler_show_fd(struct appctx *appctx) #if defined(USE_QUIC) else if (fdt.iocb == quic_conn_sock_fd_iocb) { qc = fdtab[fd].owner; - li = qc ? qc->li : NULL; + li = qc ? objt_listener(qc->target) : NULL; + sv = qc ? objt_server(qc->target) : NULL; xprt_ctx = qc ? qc->xprt_ctx : NULL; conn = qc ? qc->conn : NULL; xprt = conn ? conn->xprt : NULL; // in fact it's &ssl_quic diff --git a/src/quic_cli.c b/src/quic_cli.c index e3a41e61e..d0300f847 100644 --- a/src/quic_cli.c +++ b/src/quic_cli.c @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -181,9 +181,11 @@ static void dump_quic_oneline(struct show_quic_ctx *ctx, struct quic_conn *qc) char bufaddr[INET6_ADDRSTRLEN], bufport[6]; int ret; unsigned char cid_len; + struct listener *l = objt_listener(qc->target); ret = chunk_appendf(&trash, "%p[%02u]/%-.12s ", qc, ctx->thr, - qc->li->bind_conf->frontend->id); + l ? l->bind_conf->frontend->id : __objt_server(qc->target)->id); + chunk_appendf(&trash, "%*s", 36 - ret, " "); /* align output */ /* State */ diff --git a/src/quic_conn.c b/src/quic_conn.c index 81ae7ce54..2cb9fde9f 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -749,7 +749,7 @@ static struct quic_conn_closed *qc_new_cc_conn(struct quic_conn *qc) cc_qc->dcid = qc->dcid; cc_qc->scid = qc->scid; - cc_qc->li = qc->li; + cc_qc->target = qc->target; cc_qc->cids = qc->cids; cc_qc->idle_timer_task = qc->idle_timer_task; @@ -1067,13 +1067,13 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, struct quic_connection_id *conn_id, struct sockaddr_storage *local_addr, struct sockaddr_storage *peer_addr, - int server, int token, void *owner, + int token, void *target, struct connection *conn) { struct quic_conn *qc = NULL; - struct listener *l = server ? owner : NULL; - struct server *srv = server ? NULL : owner; - struct proxy *prx = l ? l->bind_conf->frontend : srv->proxy; + struct listener *l = objt_listener(target); + struct server *srv = objt_server(target); + struct proxy *prx = l ? l->bind_conf->frontend : __objt_server(target)->proxy; struct quic_cc_algo *cc_algo = NULL; unsigned int next_actconn = 0, next_sslconn = 0, next_handshake = 0; @@ -1092,7 +1092,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, goto err; } - if (server) { + if (l) { next_handshake = quic_increment_curr_handshake(l); if (!next_handshake) { TRACE_STATE("max handshake reached", QUIC_EV_CONN_INIT); @@ -1112,6 +1112,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, goto err; } + qc->target = target; *qc->cids = EB_ROOT; /* Now that quic_conn instance is allocated, quic_conn_release() will * ensure global accounting is decremented. @@ -1167,7 +1168,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module); /* QUIC Server (or listener). */ - if (server) { + if (l) { cc_algo = l->bind_conf->quic_cc_algo; qc->flags = QUIC_FL_CONN_LISTENER; @@ -1180,7 +1181,6 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, /* Copy the packet SCID to reuse it as DCID for sending */ qc->dcid = *scid; qc->tx.buf = BUF_NULL; - qc->li = l; conn_id->qc = qc; } /* QUIC Client (outgoing connection to servers) */ @@ -1209,7 +1209,6 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, conn_id = conn_cid; qc->tx.buf = BUF_NULL; - qc->li = NULL; qc->next_cid_seq_num = 1; conn->handle.qc = qc; } @@ -1219,7 +1218,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, /* Listener only: if connection is instantiated due to an INITIAL packet with an * already checked token, consider the peer address as validated. */ - if (server) { + if (l) { if (token_odcid->len) { TRACE_STATE("validate peer address due to initial token", QUIC_EV_CONN_INIT, qc); @@ -1292,7 +1291,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->max_ack_delay = 0; /* Only one path at this time (multipath not supported) */ qc->path = &qc->paths[0]; - quic_cc_path_init(qc->path, ipv4, server ? l->bind_conf->max_cwnd : 0, + quic_cc_path_init(qc->path, ipv4, l ? l->bind_conf->max_cwnd : 0, cc_algo ? cc_algo : default_quic_cc_algo, qc); if (local_addr) @@ -1301,7 +1300,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, memset(&qc->local_addr, 0, sizeof(qc->local_addr)); memcpy(&qc->peer_addr, peer_addr, sizeof qc->peer_addr); - if (server) { + if (l) { qc_lstnr_params_init(qc, &l->bind_conf->quic_params, conn_id->stateless_reset_token, qc->dcid.data, qc->dcid.len, @@ -1335,7 +1334,7 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, !quic_conn_init_idle_timer_task(qc, prx)) goto err; - if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, dcid->data, dcid->len, server)) + if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, dcid->data, dcid->len, !!l)) goto err; /* Counters initialization */ @@ -1386,7 +1385,7 @@ int qc_handle_conn_migration(struct quic_conn *qc, * used during the handshake, unless the endpoint has acted on a * preferred_address transport parameter from the peer. */ - if (qc->li->bind_conf->quic_params.disable_active_migration) { + if (__objt_listener(qc->target)->bind_conf->quic_params.disable_active_migration) { TRACE_ERROR("Active migration was disabled, datagram dropped", QUIC_EV_CONN_LPKT, qc); goto err; } @@ -1516,8 +1515,8 @@ int quic_conn_release(struct quic_conn *qc) */ if (MT_LIST_INLIST(&qc->accept_list)) { MT_LIST_DELETE(&qc->accept_list); - BUG_ON(qc->li->rx.quic_curr_accept == 0); - HA_ATOMIC_DEC(&qc->li->rx.quic_curr_accept); + BUG_ON(__objt_listener(qc->target)->rx.quic_curr_accept == 0); + HA_ATOMIC_DEC(&__objt_listener(qc->target)->rx.quic_curr_accept); } /* Substract last congestion window from global memory counter. */ @@ -1587,8 +1586,8 @@ int quic_conn_release(struct quic_conn *qc) /* Connection released before handshake completion. */ if (unlikely(qc->state < QUIC_HS_ST_COMPLETE)) { if (qc_is_listener(qc)) { - BUG_ON(qc->li->rx.quic_curr_handshake == 0); - HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake); + BUG_ON(__objt_listener(qc->target)->rx.quic_curr_handshake == 0); + HA_ATOMIC_DEC(&__objt_listener(qc->target)->rx.quic_curr_handshake); } } @@ -1996,8 +1995,8 @@ void qc_bind_tid_commit(struct quic_conn *qc, struct listener *new_li) /* At this point no connection was accounted for yet on this * listener so it's OK to just swap the pointer. */ - if (new_li && new_li != qc->li) - qc->li = new_li; + if (new_li && new_li != __objt_listener(qc->target)) + qc->target = &new_li->obj_type; /* Rebind the connection FD. */ if (qc_test_fd(qc)) { diff --git a/src/quic_rx.c b/src/quic_rx.c index 3b6a678d6..b705d414d 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -1819,7 +1819,7 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt, goto err; qc = qc_new_conn(pkt->version, ipv4, &pkt->dcid, &pkt->scid, &token_odcid, - conn_id, &dgram->daddr, &pkt->saddr, 1, + conn_id, &dgram->daddr, &pkt->saddr, !!pkt->token_len, l, NULL); if (qc == NULL) { pool_free(pool_head_quic_connection_id, conn_id); diff --git a/src/quic_sock.c b/src/quic_sock.c index f3c6341ce..490e5bfb0 100644 --- a/src/quic_sock.c +++ b/src/quic_sock.c @@ -87,13 +87,13 @@ int quic_sock_get_dst(struct connection *conn, struct sockaddr *addr, socklen_t memcpy(addr, &qc->peer_addr, len); } else { struct sockaddr_storage *from; + struct listener *l = objt_listener(qc->target); /* Return listener address if IP_PKTINFO or friends are not * supported by the socket. */ - BUG_ON(!qc->li); - from = is_addr(&qc->local_addr) ? &qc->local_addr : - &qc->li->rx.addr; + BUG_ON(!l); + from = is_addr(&qc->local_addr) ? &qc->local_addr : &l->rx.addr; if (len > sizeof(*from)) len = sizeof(*from); memcpy(addr, from, len); @@ -819,6 +819,7 @@ int qc_rcv_buf(struct quic_conn *qc) struct buffer buf = BUF_NULL; unsigned char *dgram_buf; ssize_t ret = 0; + struct listener *l = objt_listener(qc->target); /* Do not call this if quic-conn FD is uninitialized. */ BUG_ON(qc->fd < 0); @@ -869,7 +870,7 @@ int qc_rcv_buf(struct quic_conn *qc) continue; } - if (qc_is_listener(qc) && !qc_check_dcid(qc, new_dgram->dcid, new_dgram->dcid_len)) { + if (l && !qc_check_dcid(qc, new_dgram->dcid, new_dgram->dcid_len)) { /* Datagram received by error on the connection FD, dispatch it * to its associated quic-conn. * @@ -879,7 +880,6 @@ int qc_rcv_buf(struct quic_conn *qc) struct quic_dgram *tmp_dgram; unsigned char *rxbuf_tail; size_t cspace; - struct listener *l = qc->li; TRACE_STATE("datagram for other connection on quic-conn socket, requeue it", QUIC_EV_CONN_RCV, qc); @@ -928,7 +928,7 @@ int qc_rcv_buf(struct quic_conn *qc) continue; } - quic_dgram_parse(new_dgram, qc, qc_owner_obj_type(qc)); + quic_dgram_parse(new_dgram, qc, qc->target); /* A datagram must always be consumed after quic_parse_dgram(). */ BUG_ON(new_dgram->buf); } while (ret > 0); @@ -950,11 +950,13 @@ int qc_rcv_buf(struct quic_conn *qc) * * Return the socket FD or a negative error code. On error, socket is marked as * uninitialized. + * Note: This function must not be run for backends connection. */ void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src, const struct sockaddr_storage *dst) { - struct bind_conf *bc = qc->li->bind_conf; + struct listener *l = __objt_listener(qc->target); + struct bind_conf *bc = l->bind_conf; struct proxy *p = bc->frontend; int fd = -1; int ret; @@ -1007,7 +1009,7 @@ void qc_alloc_fd(struct quic_conn *qc, const struct sockaddr_storage *src, } /* Fallback to listener socket for this receiver instance. */ - HA_ATOMIC_STORE(&qc->li->rx.quic_mode, QUIC_SOCK_MODE_LSTNR); + HA_ATOMIC_STORE(&l->rx.quic_mode, QUIC_SOCK_MODE_LSTNR); } goto err; } @@ -1061,13 +1063,14 @@ struct quic_accept_queue *quic_accept_queues; void quic_accept_push_qc(struct quic_conn *qc) { struct quic_accept_queue *queue = &quic_accept_queues[tid]; - struct li_per_thread *lthr = &qc->li->per_thr[ti->ltid]; + struct listener *l = __objt_listener(qc->target); + struct li_per_thread *lthr = &l->per_thr[ti->ltid]; /* A connection must only be accepted once per instance. */ BUG_ON(qc->flags & QUIC_FL_CONN_ACCEPT_REGISTERED); BUG_ON(MT_LIST_INLIST(&qc->accept_list)); - HA_ATOMIC_INC(&qc->li->rx.quic_curr_accept); + HA_ATOMIC_INC(&l->rx.quic_curr_accept); qc->flags |= QUIC_FL_CONN_ACCEPT_REGISTERED; /* 1. insert the listener in the accept queue diff --git a/src/quic_ssl.c b/src/quic_ssl.c index a455e6118..b2b05de50 100644 --- a/src/quic_ssl.c +++ b/src/quic_ssl.c @@ -937,14 +937,16 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf, * provided by the stack. This happens after having received the peer * handshake level CRYPTO data which are validated by the TLS stack. */ - if (qc->li->bind_conf->ssl_conf.early_data && - (!qc->ael || !qc->ael->tls_ctx.rx.secret)) { - TRACE_PROTO("SSL handshake in progress", - QUIC_EV_CONN_IO_CB, qc, &state, &ssl_err); - goto out; - } - else { - TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state); + if (qc_is_listener(qc)) { + if (__objt_listener(qc->target)->bind_conf->ssl_conf.early_data && + (!qc->ael || !qc->ael->tls_ctx.rx.secret)) { + TRACE_PROTO("SSL handshake in progress", + QUIC_EV_CONN_IO_CB, qc, &state, &ssl_err); + goto out; + } + else { + TRACE_PROTO("SSL handshake OK", QUIC_EV_CONN_IO_CB, qc, &state); + } } #endif @@ -980,6 +982,7 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf, qc->flags |= QUIC_FL_CONN_NEED_POST_HANDSHAKE_FRMS; if (qc_is_listener(ctx->qc)) { + struct listener *l = __objt_listener(qc->target); /* I/O callback switch */ qc->wait_event.tasklet->process = quic_conn_app_io_cb; qc->state = QUIC_HS_ST_CONFIRMED; @@ -995,8 +998,8 @@ static int qc_ssl_provide_quic_data(struct ncbuf *ncbuf, tasklet_wakeup(qc->wait_event.tasklet); } - BUG_ON(qc->li->rx.quic_curr_handshake == 0); - HA_ATOMIC_DEC(&qc->li->rx.quic_curr_handshake); + BUG_ON(l->rx.quic_curr_handshake == 0); + HA_ATOMIC_DEC(&l->rx.quic_curr_handshake); } else { qc->state = QUIC_HS_ST_COMPLETE; @@ -1224,7 +1227,7 @@ int qc_alloc_ssl_sock_ctx(struct quic_conn *qc, struct connection *conn) ctx->qc = qc; if (qc_is_listener(qc)) { - struct bind_conf *bc = qc->li->bind_conf; + struct bind_conf *bc = __objt_listener(qc->target)->bind_conf; if (qc_ssl_sess_init(qc, bc->initial_ctx, &ctx->ssl, NULL, 1) == -1) goto err; diff --git a/src/quic_tx.c b/src/quic_tx.c index 919fb1282..e36bb2fdf 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -288,8 +288,10 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx) int ret = 0; struct quic_conn *qc; char skip_sendto = 0; + struct listener *l; qc = ctx->qc; + l = objt_listener(qc->target); TRACE_ENTER(QUIC_EV_CONN_SPPKTS, qc); while (b_contig_data(buf, 0)) { unsigned char *pos; @@ -305,7 +307,11 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx) /* If datagram bigger than MTU, several ones were encoded for GSO usage. */ if (dglen > qc->path->mtu) { - if (likely(!(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP))) { + /* TODO: note that at this time for connection to backends this + * part is not run because no more than an MTU has been prepared for + * such connections (dglen <= qc->path->mtu). So, here l is not NULL. + */ + if (likely(!(HA_ATOMIC_LOAD(&l->flags) & LI_F_UDP_GSO_NOTSUPP))) { TRACE_PROTO("send multiple datagrams with GSO", QUIC_EV_CONN_SPPKTS, qc); gso = qc->path->mtu; } @@ -327,11 +333,15 @@ static int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx) int ret = qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0, gso); if (ret < 0) { if (gso && ret == -EIO) { + /* TODO: note that at this time for connection to backends this + * part is not run because no more than an MTU has been + * prepared for such connections (l is not NULL). + */ /* Disable permanently UDP GSO for this listener. * Retry standard emission. */ TRACE_ERROR("mark listener UDP GSO as unsupported", QUIC_EV_CONN_SPPKTS, qc, first_pkt); - HA_ATOMIC_OR(&qc->li->flags, LI_F_UDP_GSO_NOTSUPP); + HA_ATOMIC_OR(&l->flags, LI_F_UDP_GSO_NOTSUPP); continue; } @@ -576,6 +586,7 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf, int dgram_cnt = 0; /* Restrict GSO emission to comply with sendmsg limitation. See QUIC_MAX_GSO_DGRAMS for more details. */ uchar gso_dgram_cnt = 0; + struct listener *l = objt_listener(qc->target); TRACE_ENTER(QUIC_EV_CONN_IO_CB, qc); /* Currently qc_prep_pkts() does not handle buffer wrapping so the @@ -765,11 +776,13 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf, prv_pkt = cur_pkt; } else if (!(quic_tune.options & QUIC_TUNE_NO_UDP_GSO) && - !(HA_ATOMIC_LOAD(&qc->li->flags) & LI_F_UDP_GSO_NOTSUPP) && dglen == qc->path->mtu && (char *)end < b_wrap(buf) && - ++gso_dgram_cnt < QUIC_MAX_GSO_DGRAMS) { - + ++gso_dgram_cnt < QUIC_MAX_GSO_DGRAMS && + l && !(HA_ATOMIC_LOAD(&l->flags) & LI_F_UDP_GSO_NOTSUPP)) { + /* TODO: note that for backends GSO is not used. No more than + * an MTU is prepared. + */ /* A datagram covering the full MTU has been * built, use GSO to built next entry. Do not * reserve extra space for datagram header. diff --git a/src/ssl_clienthello.c b/src/ssl_clienthello.c index 19f141154..ca0d6fb27 100644 --- a/src/ssl_clienthello.c +++ b/src/ssl_clienthello.c @@ -177,7 +177,7 @@ int ssl_sock_switchctx_cbk(SSL *ssl, int *al, void *arg) s = __objt_listener(conn->target)->bind_conf; #ifdef USE_QUIC else if (qc) - s = qc->li->bind_conf; + s = __objt_listener(qc->target)->bind_conf; #endif /* USE_QUIC */ if (!s) { diff --git a/src/ssl_ocsp.c b/src/ssl_ocsp.c index a6355e6f0..d8d85a76c 100644 --- a/src/ssl_ocsp.c +++ b/src/ssl_ocsp.c @@ -127,7 +127,7 @@ int ssl_sock_ocsp_stapling_cbk(SSL *ssl, void *arg) struct quic_conn *qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); /* null if not a listener */ - li = qc->li; + li = objt_listener(qc->target); } #endif diff --git a/src/ssl_sock.c b/src/ssl_sock.c index 663a14da4..6c787d7b7 100644 --- a/src/ssl_sock.c +++ b/src/ssl_sock.c @@ -926,7 +926,7 @@ static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned ref = __objt_listener(conn->target)->bind_conf->keys_ref; #ifdef USE_QUIC else if (qc) - ref = qc->li->bind_conf->keys_ref; + ref = __objt_listener(qc->target)->bind_conf->keys_ref; #endif if (!ref) { @@ -1482,7 +1482,7 @@ int ssl_sock_bind_verifycbk(int ok, X509_STORE_CTX *x_store) else { qc = SSL_get_ex_data(ssl, ssl_qc_app_data_index); BUG_ON(!qc); /* Must never happen */ - bind_conf = qc->li->bind_conf; + bind_conf = __objt_listener(qc->target)->bind_conf; ctx = qc->xprt_ctx; } #endif diff --git a/src/xprt_quic.c b/src/xprt_quic.c index 8244bf02c..47a8843a3 100644 --- a/src/xprt_quic.c +++ b/src/xprt_quic.c @@ -123,7 +123,7 @@ static int qc_conn_init(struct connection *conn, void **xprt_ctx) int ipv4 = conn->dst->ss_family == AF_INET; struct server *srv = objt_server(conn->target); qc = qc_new_conn(quic_version_1, ipv4, NULL, NULL, NULL, - NULL, NULL, &srv->addr, 0, 0, srv, conn); + NULL, NULL, &srv->addr, 0, srv, conn); } if (!qc)