On Fri, Jul 31, 2026 at 06:16:16PM +0200, Laurent Vivier wrote:
Add a qpair field to struct flow_common so each flow knows which queue pair (and thus which worker thread) owns it.
flow_alloc() takes a qpair parameter, set at creation time. All protocol handlers pass their qpair when allocating flows from tap, and use QPAIR_DEFAULT for flows initiated from the host side.
tcp_keepalive() and tcp_inactivity() filter by qpair so each worker only processes its own flows.
Signed-off-by: Laurent Vivier
--- flow.c | 34 +++++++++++++++++++++++++++++++++- flow.h | 13 ++++++++++++- flow_table.h | 2 +- icmp.c | 3 +-- tcp.c | 20 +++++++++++++------- udp_flow.c | 9 ++++----- 6 files changed, 64 insertions(+), 17 deletions(-) diff --git a/flow.c b/flow.c index c63491804709..40b2e68be0ab 100644 --- a/flow.c +++ b/flow.c @@ -417,6 +417,37 @@ void flow_epollid_register(int epollid, int epollfd) epoll_id_to_fd[epollid] = epollfd; }
+/** + * flow_qp() - Get the queue pair for a flow + * @f: Flow to query (may be NULL) + * + * Return: queue pair number for the flow, or 0 if flow is NULL or has no
This implies it's safe to call on NULL, which doesn't appear to be the case. 0 also seems a dubious value for "no valid queue pair assignment", since it is a valid qpair number.
+ * valid queue pair assignment + */ +/* cppcheck-suppress unusedFunction */ +unsigned int flow_qp(const struct flow_common *f) +{ + return f->qpair; +} + +/** + * flow_setqp() - Set queue pair assignment for a flow + * @f: Flow to update + * @qpair: Queue pair number to assign + */ +static void flow_setqp(struct flow_common *f, unsigned int qpair) +{ + assert(qpair < FLOW_QPAIR_MAX); + + if (f->qpair == qpair) + return; + + flow_trace((union flow *)f, "updating queue pair from %d to %d", + f->qpair, qpair); + + f->qpair = qpair; +} + /** * flow_initiate_() - Move flow to INI, setting pif[INISIDE] * @flow: Flow to change state @@ -603,7 +634,7 @@ void flow_activate(struct flow_common *f) * * Return: pointer to an unused flow entry, or NULL if the table is full */ -union flow *flow_alloc(void) +union flow *flow_alloc(unsigned int qpair)
Comment for new parameter?
{ union flow *flow = &flowtab[flow_first_free];
@@ -637,6 +668,7 @@ union flow *flow_alloc(void)
flow_new_entry = flow; memset(flow, 0, sizeof(*flow)); + flow_setqp(&flow->f, qpair); flow_set_state(&flow->f, FLOW_STATE_NEW);
return flow; diff --git a/flow.h b/flow.h index 8d85e27427db..b6f980b4f826 100644 --- a/flow.h +++ b/flow.h @@ -184,7 +184,7 @@ int flowside_connect(const struct ctx *c, int s, * @pif[]: Interface for each side of the flow * @side[]: Information for each side of the flow * @tap_omac: MAC address of remote endpoint as seen from the guest - * @epollid: epollfd identifier + * @qpair: Queue pair number assigned to this flow
In the comments, @epollfd is replaced with @qpair, but in the structure, @qpair is added as well as @epollfd. Did you mean to leave the comment, or remove the field?
*/ struct flow_common { #ifdef __GNUC__ @@ -205,11 +205,18 @@ struct flow_common {
#define EPOLLFD_ID_BITS 8 unsigned int epollid:EPOLLFD_ID_BITS; +#define FLOW_QPAIR_BITS 5 + unsigned int qpair:FLOW_QPAIR_BITS; };
#define EPOLLFD_ID_DEFAULT 0 #define EPOLLFD_ID_SIZE (1 << EPOLLFD_ID_BITS)
+#define FLOW_QPAIR_NUM (1 << FLOW_QPAIR_BITS) +#define FLOW_QPAIR_MAX (FLOW_QPAIR_NUM - 1) + +static_assert(VHOST_USER_MAX_VQS <= FLOW_QPAIR_MAX * 2); + #define FLOW_INDEX_BITS 17 /* 128k - 1 */ #define FLOW_MAX MAX_FROM_BITS(FLOW_INDEX_BITS)
@@ -270,6 +277,10 @@ void flow_epollid_set(struct flow_common *f, int epollid); int flow_epoll_set(const struct flow_common *f, int command, uint32_t events, int fd, unsigned int sidei); void flow_epollid_register(int epollid, int epollfd); +unsigned int flow_qp(const struct flow_common *f); +#define FLOW_QP(flow_) \ + (flow_qp(&(flow_)->f)) + void flow_defer_handler(const struct ctx *c, const struct timespec *now, unsigned int qpair); int flow_migrate_source_early(struct ctx *c, const struct migrate_stage *stage, diff --git a/flow_table.h b/flow_table.h index e4ff6f73c35c..3a33eef15f1e 100644 --- a/flow_table.h +++ b/flow_table.h @@ -196,7 +196,7 @@ static inline flow_sidx_t flow_sidx(const struct flow_common *f, */ #define FLOW_SIDX(f_, sidei) (flow_sidx(&(f_)->f, (sidei)))
-union flow *flow_alloc(void); +union flow *flow_alloc(unsigned int qpair); void flow_alloc_cancel(union flow *flow);
const struct flowside *flow_initiate_af(union flow *flow, uint8_t pif, diff --git a/icmp.c b/icmp.c index 92cb48dfaafe..e75d66d168be 100644 --- a/icmp.c +++ b/icmp.c @@ -183,11 +183,10 @@ static struct icmp_ping_flow *icmp_ping_new(const struct ctx *c, { uint8_t proto = af == AF_INET ? IPPROTO_ICMP : IPPROTO_ICMPV6; uint8_t flowtype = af == AF_INET ? FLOW_PING4 : FLOW_PING6; - union flow *flow = flow_alloc(); + union flow *flow = flow_alloc(qpair); struct icmp_ping_flow *pingf; const struct flowside *tgt;
- (void)qpair; if (!flow) return NULL;
diff --git a/tcp.c b/tcp.c index 982f8a4a845a..39f732f8bea3 100644 --- a/tcp.c +++ b/tcp.c @@ -1707,7 +1707,7 @@ static void tcp_conn_from_tap(const struct ctx *c, unsigned int qpair, int s = -1, mss; uint64_t hash;
- if (!(flow = flow_alloc())) + if (!(flow = flow_alloc(qpair))) return;
ini = flow_initiate_af(flow, PIF_TAP, @@ -2304,7 +2304,7 @@ static void tcp_rst_no_conn(const struct ctx *c, unsigned int qpair, int af, /** * tcp_tap_handler() - Handle packets from tap and state transitions * @c: Execution context - * @qpair: Queue pair on which to send packets + * @qpair: Queue pair to process
This looks like a correction to an earlier patch in the series, which ideally would be folded in there. Plus, "Queue pair to process" isn't terribly informative as noted elsewhere.
* @pif: pif on which the packet is arriving * @af: Address family, AF_INET or AF_INET6 * @saddr: Source address @@ -2625,7 +2625,7 @@ void tcp_listen_handler(const struct ctx *c, union epoll_ref ref,
assert(!c->no_tcp);
- if (!(flow = flow_alloc())) + if (!(flow = flow_alloc(QPAIR_DEFAULT))) return;
s = accept4(ref.fd, &sa.sa, &sl, SOCK_NONBLOCK); @@ -3007,6 +3007,9 @@ static void tcp_keepalive(struct ctx *c, const struct timespec *now, flow_foreach_of_type(flow, FLOW_TCP) { struct tcp_tap_conn *conn = &flow->tcp;
+ if (conn->f.qpair != qpair) + continue; + if (conn->tap_inactive) { flow_dbg(conn, "No tap activity for least %us, send keepalive", KEEPALIVE_INTERVAL); @@ -3038,6 +3041,9 @@ static void tcp_inactivity(struct ctx *c, const struct timespec *now, flow_foreach_of_type(flow, FLOW_TCP) { struct tcp_tap_conn *conn = &flow->tcp;
+ if (conn->f.qpair != qpair) + continue; + if (conn->inactive) { /* No activity in this interval, reset */ flow_dbg(conn, "Inactive for at least %us, resetting", @@ -3828,7 +3834,7 @@ int tcp_flow_migrate_target(struct ctx *c, int fd) union flow *flow; int rc;
- if (!(flow = flow_alloc())) { + if (!(flow = flow_alloc(QPAIR_DEFAULT))) { err("Flow table full on migration target"); return 0; } @@ -4050,10 +4056,10 @@ int tcp_flow_migrate_target_ext(struct ctx *c, struct tcp_tap_conn *conn, if (tcp_set_peek_offset(conn, peek_offset, now)) goto fail;
- if (tcp_send_flag(c, conn, ACK, now, QPAIR_DEFAULT)) + if (tcp_send_flag(c, conn, ACK, now, conn->f.qpair)) goto fail;
- tcp_data_from_sock(c, conn, now, QPAIR_DEFAULT); + tcp_data_from_sock(c, conn, now, conn->f.qpair);
if ((rc = tcp_epoll_ctl(conn))) { flow_dbg(conn, @@ -4071,7 +4077,7 @@ fail: }
conn->flags = 0; /* Not waiting for ACK, don't schedule timer */ - tcp_rst(c, conn, now, QPAIR_DEFAULT); + tcp_rst(c, conn, now, conn->f.qpair);
return 0; } diff --git a/udp_flow.c b/udp_flow.c index ff6abcb8eba0..2578d640a3da 100644 --- a/udp_flow.c +++ b/udp_flow.c @@ -84,7 +84,6 @@ static int udp_flow_sock(const struct ctx *c, return s; }
- flow_epollid_set(&uflow->f, EPOLLFD_ID_DEFAULT); if (flow_epoll_set(&uflow->f, EPOLL_CTL_ADD, EPOLLIN, s, sidei) < 0) { rc = -errno; close(s); @@ -155,6 +154,7 @@ static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow, uflow->ttl[INISIDE] = uflow->ttl[TGTSIDE] = 0; uflow->activity[INISIDE] = 1; uflow->activity[TGTSIDE] = 0; + flow_epollid_set(&uflow->f, EPOLLFD_ID_DEFAULT);
Moving flow_epollid_set() looks correct (it was in a pretty odd place before). Arguably, not logically related to this patch, though.
flow_foreach_sidei(sidei) { if (pif_is_socket(uflow->f.pif[sidei])) @@ -236,8 +236,7 @@ flow_sidx_t udp_flow_from_sock(const struct ctx *c, unsigned int qpair, return flow_sidx_opposite(sidx); }
- (void)qpair; - if (!(flow = flow_alloc())) { + if (!(flow = flow_alloc(qpair))) { char sastr[SOCKADDR_STRLEN];
err_ratelimit(now, "Couldn't allocate flow for UDP datagram from %s %s", @@ -273,6 +272,7 @@ flow_sidx_t udp_flow_from_sock(const struct ctx *c, unsigned int qpair, * @daddr: Destination address guest side * @srcport: Source port on guest side * @dstport: Destination port on guest side + * @now: Current timestamp
This also looks like a (correct) fix that's more or less unrelated to the rest of this patch.
* * Return: sidx for the destination side of the flow for this packet, or * FLOW_SIDX_NONE if we couldn't find or create a flow. @@ -297,8 +297,7 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c, unsigned int qpair, return flow_sidx_opposite(sidx); }
- (void)qpair; - if (!(flow = flow_alloc())) { + if (!(flow = flow_alloc(qpair))) { char sstr[INET6_ADDRSTRLEN], dstr[INET6_ADDRSTRLEN];
err_ratelimit(now, "Couldn't allocate flow for UDP datagram from %s %s:%hu -> %s:%hu", -- 2.54.0
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson