public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: Stefano Brivio <sbrivio@redhat.com>, passt-dev@passt.top
Cc: jmaloy@redhat.com, David Gibson <david@gibson.dropbear.id.au>
Subject: [PATCH v6 19/26] udp: Create flow table entries for UDP
Date: Fri, 14 Jun 2024 16:13:41 +1000	[thread overview]
Message-ID: <20240614061348.3814736-20-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20240614061348.3814736-1-david@gibson.dropbear.id.au>

Currently UDP only has a very rudimentary (and buggy) form of connection
tracking implemented with per-port flags.  Make a start on converting this
to more robust tracking via the flow table.

Start matching UDP packets to flow table entries, creating them when
necessary.  We also add a timer so that the flows will expire.  For now
don't actually use the information in the flow table, that will come later.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 Makefile     |   2 +-
 flow.c       |  31 +++++++
 flow.h       |   4 +
 flow_table.h |   2 +
 udp.c        | 228 ++++++++++++++++++++++++++++++++++++++++++++++++---
 udp_flow.h   |  25 ++++++
 6 files changed, 278 insertions(+), 14 deletions(-)
 create mode 100644 udp_flow.h

diff --git a/Makefile b/Makefile
index 09fc461d..92cbd5a6 100644
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ PASST_HEADERS = arch.h arp.h checksum.h conf.h dhcp.h dhcpv6.h flow.h fwd.h \
 	flow_table.h icmp.h icmp_flow.h inany.h iov.h ip.h isolation.h \
 	lineread.h log.h ndp.h netlink.h packet.h passt.h pasta.h pcap.h pif.h \
 	siphash.h tap.h tcp.h tcp_buf.h tcp_conn.h tcp_internal.h tcp_splice.h \
-	udp.h util.h
+	udp.h udp_flow.h util.h
 HEADERS = $(PASST_HEADERS) seccomp.h
 
 C := \#include <linux/tcp.h>\nstruct tcp_info x = { .tcpi_snd_wnd = 0 };
diff --git a/flow.c b/flow.c
index cf799082..c3d54f6e 100644
--- a/flow.c
+++ b/flow.c
@@ -35,6 +35,7 @@ const char *flow_type_str[] = {
 	[FLOW_TCP_SPLICE]	= "TCP connection (spliced)",
 	[FLOW_PING4]		= "ICMP ping sequence",
 	[FLOW_PING6]		= "ICMPv6 ping sequence",
+	[FLOW_UDP]		= "UDP flow",
 };
 static_assert(ARRAY_SIZE(flow_type_str) == FLOW_NUM_TYPES,
 	      "flow_type_str[] doesn't match enum flow_type");
@@ -44,6 +45,7 @@ const uint8_t flow_proto[] = {
 	[FLOW_TCP_SPLICE]	= IPPROTO_TCP,
 	[FLOW_PING4]		= IPPROTO_ICMP,
 	[FLOW_PING6]		= IPPROTO_ICMPV6,
+	[FLOW_UDP]		= IPPROTO_UDP,
 };
 static_assert(ARRAY_SIZE(flow_proto) == FLOW_NUM_TYPES,
 	      "flow_proto[] doesn't match enum flow_type");
@@ -641,6 +643,31 @@ flow_sidx_t flow_lookup_af(const struct ctx *c,
 	return flowside_lookup(c, proto, pif, &fside);
 }
 
+/**
+ * flow_lookup_sa() - Look up a flow given and endpoint socket address
+ * @c:		Execution context
+ * @proto:	Protocol of the flow (IP L4 protocol number)
+ * @pif:	Interface of the flow
+ * @esa:	Socket address of the endpoint
+ * @fport:	Forwarding port number
+ *
+ * Return: sidx of the matching flow & side, FLOW_SIDX_NONE if not found
+ */
+flow_sidx_t flow_lookup_sa(const struct ctx *c, uint8_t proto, uint8_t pif,
+			   const void *esa, in_port_t fport)
+{
+	struct flowside fside = {
+		.fport = fport,
+	};
+
+	inany_from_sockaddr(&fside.eaddr, &fside.eport, esa);
+	if (inany_v4(&fside.eaddr))
+		fside.faddr = inany_any4;
+	else
+		fside.faddr = inany_any6;
+	return flowside_lookup(c, proto, pif, &fside);
+}
+
 /**
  * flow_defer_handler() - Handler for per-flow deferred and timed tasks
  * @c:		Execution context
@@ -720,6 +747,10 @@ void flow_defer_handler(const struct ctx *c, const struct timespec *now)
 			if (timer)
 				closed = icmp_ping_timer(c, &flow->ping, now);
 			break;
+		case FLOW_UDP:
+			if (timer)
+				closed = udp_flow_timer(c, &flow->udp, now);
+			break;
 		default:
 			/* Assume other flow types don't need any handling */
 			;
diff --git a/flow.h b/flow.h
index 948f2ea9..b5ca2792 100644
--- a/flow.h
+++ b/flow.h
@@ -115,6 +115,8 @@ enum flow_type {
 	FLOW_PING4,
 	/* ICMPv6 echo requests from guest to host and matching replies back */
 	FLOW_PING6,
+	/* UDP packets with the matching unicast endpoints */
+	FLOW_UDP,
 
 	FLOW_NUM_TYPES,
 };
@@ -227,6 +229,8 @@ flow_sidx_t flow_lookup_af(const struct ctx *c,
 			   uint8_t proto, uint8_t pif, sa_family_t af,
 			   const void *eaddr, const void *faddr,
 			   in_port_t eport, in_port_t fport);
+flow_sidx_t flow_lookup_sa(const struct ctx *c, uint8_t proto, uint8_t pif,
+			   const void *esa, in_port_t fport);
 
 union flow;
 
diff --git a/flow_table.h b/flow_table.h
index 07c59041..6cf4f2b7 100644
--- a/flow_table.h
+++ b/flow_table.h
@@ -9,6 +9,7 @@
 
 #include "tcp_conn.h"
 #include "icmp_flow.h"
+#include "udp_flow.h"
 
 /**
  * struct flow_free_cluster - Information about a cluster of free entries
@@ -35,6 +36,7 @@ union flow {
 	struct tcp_tap_conn tcp;
 	struct tcp_splice_conn tcp_splice;
 	struct icmp_ping_flow ping;
+	struct udp_flow udp;
 };
 
 /* Global Flow Table */
diff --git a/udp.c b/udp.c
index e79ca938..cb6db5c5 100644
--- a/udp.c
+++ b/udp.c
@@ -15,10 +15,49 @@
 /**
  * DOC: Theory of Operation
  *
+ * Flow Table
+ * ==========
  *
- * For UDP, a reduced version of port-based connection tracking is implemented
- * with two purposes:
- * - binding ephemeral ports when they're used as source port by the guest, so
+ * UDP does not have connections, but to reliably forward reply packets back to
+ * the original requested, we must keep track of pseudo-connections.  We do this
+ * via the generic flow table.
+ *
+ * - Finding an existing flow
+ *
+ *   When we receive a datagram we attempt to match it to an existing flow: one
+ *   with matching interface, addresses and ports (both forwarding and
+ *   endpoint).  For socket interfaces, we treat the forwarding address as the
+ *   bound address of the receiving socket, which may be unspecified, rather
+ *   than the datagram's actual destination address (which is awkward to
+ *   determine for unbound sockets).
+ *
+ * - Creating a new flow
+ *
+ *   If no matching flow exists, and the datagram comes either from the tap
+ *   interface, or from a socket with the 'orig' flag set we create a new one.
+ *   The initiating side records the interface, endpoint and forwarding
+ *   addresses and ports of this first datagram.  Again, we treat the forwarding
+ *   address for sockets as the socket's bound address, regardless of the
+ *   datagram's actual destination.
+ *
+ *   The target side interface and addresses are assigned by the general code in
+ *   fwd.c.  When the target is a socket interface, the target forwarding
+ *   address may be left unspecified - in this case, the kernel will determine
+ *   the source address when we send the datagram.
+ *
+ * - Flow expiry
+ *
+ *   Every time a datagram is received that matches a flow (or creates a new
+ *   one), we update the flow's timestamp to the current time.  Periodically we
+ *   scan flows and those which are older than UDP_CONN_TIMEOUT (180s) are
+ *   removed.
+ *
+ * Port Tracking
+ * =============
+ *
+ * For datagrams not handled by the flow table, a reduced version of port-based
+ * connection tracking is implemented with two purposes:
+  * - binding ephemeral ports when they're used as source port by the guest, so
  *   that replies on those ports can be forwarded back to the guest, with a
  *   fixed timeout for this binding
  * - packets received from the local host get their source changed to a local
@@ -121,6 +160,7 @@
 #include "tap.h"
 #include "pcap.h"
 #include "log.h"
+#include "flow_table.h"
 
 #define UDP_CONN_TIMEOUT	180 /* s, timeout for ephemeral or local bind */
 #define UDP_MAX_FRAMES		32  /* max # of frames to receive at once */
@@ -199,6 +239,7 @@ static struct ethhdr udp6_eth_hdr;
  * @taph:	Tap backend specific header
  * @s_in:	Source socket address, filled in by recvmmsg()
  * @splicesrc:	Source port for splicing, or -1 if not spliceable
+ * @tosidx:	sidx for the destination side of this datagram's flow
  */
 static struct udp_meta_t {
 	struct ipv6hdr ip6h;
@@ -207,6 +248,7 @@ static struct udp_meta_t {
 
 	union sockaddr_inany s_in;
 	int splicesrc;
+	flow_sidx_t tosidx;
 }
 #ifdef __AVX2__
 __attribute__ ((aligned(32)))
@@ -253,6 +295,17 @@ static struct sockaddr_in6 udp6_localname = {
 static struct mmsghdr	udp4_mh_splice		[UDP_MAX_FRAMES];
 static struct mmsghdr	udp6_mh_splice		[UDP_MAX_FRAMES];
 
+struct udp_flow *udp_at_sidx(flow_sidx_t sidx)
+{
+	union flow *flow = flow_at_sidx(sidx);
+
+	if (!flow)
+		return NULL;
+
+	ASSERT(flow->f.type == FLOW_UDP);
+	return &flow->udp;
+}
+
 /**
  * udp_portmap_clear() - Clear UDP port map before configuration
  */
@@ -492,6 +545,67 @@ static int udp_mmh_splice_port(union udp_epoll_ref uref,
 	return -1;
 }
 
+/**
+ * udp_flow_from_sock() - Find or create UDP flow for datagrams from socket
+ * @c:		Execution context
+ * @uref:	UDP epoll reference of the originating socket
+ * @meta:	Metadata buffer for the datagram
+ *
+ * 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.
+ */
+flow_sidx_t udp_flow_from_sock(const struct ctx *c, union udp_epoll_ref uref,
+			       struct udp_meta_t *meta)
+{
+	char sstr[INANY_ADDRSTRLEN];
+	const struct flowside *ini;
+	struct udp_flow *uflow;
+	union flow *flow;
+	flow_sidx_t sidx;
+
+	sidx = flow_lookup_sa(c, IPPROTO_UDP, uref.pif, &meta->s_in, uref.port);
+	if ((flow = flow_at_sidx(sidx)))
+		return FLOW_SIDX(flow, !sidx.side);
+
+	if (!uref.orig)
+		return FLOW_SIDX_NONE;
+
+	if (!(flow = flow_alloc())) {
+		char sastr[SOCKADDR_STRLEN];
+
+		debug("Couldn't allocate flow for UDP datagram from %s %s",
+		      pif_name(uref.pif),
+		      sockaddr_ntop(&meta->s_in, sastr, sizeof(sastr)));
+		return FLOW_SIDX_NONE;
+	}
+
+	ini = flow_initiate_sa(flow, uref.pif, &meta->s_in, uref.port);
+
+	if (!inany_is_unicast(&ini->eaddr) || ini->eport == 0) {
+		flow_dbg(flow, "Invalid endpoint on UDP recv()");
+		/* Invalid endpoint */
+		goto cancel;
+	}
+
+	if (!flow_target(c, flow, IPPROTO_UDP))
+		goto cancel;
+
+	uflow = FLOW_SET_TYPE(flow, FLOW_UDP, udp);
+	flow_hash_insert(c, FLOW_SIDX(uflow, INISIDE));
+	flow_hash_insert(c, FLOW_SIDX(uflow, TGTSIDE));
+	FLOW_ACTIVATE(uflow);
+
+	return FLOW_SIDX(uflow, TGTSIDE);
+
+cancel:
+	flow_dbg(flow, "Couldn't create UDP flow for %s [%s]:%hu -> ?:%hu",
+		 pif_name(uref.pif),
+		 inany_ntop(&ini->eaddr, sstr, sizeof(sstr)),
+		 ini->eport, ini->fport);
+	flow_alloc_cancel(flow);
+	return FLOW_SIDX_NONE;
+}
+
 /**
  * udp_splice_send() - Send datagrams from socket to socket
  * @c:		Execution context
@@ -536,6 +650,7 @@ static unsigned udp_splice_send(const struct ctx *c, size_t start, size_t n,
 			break;
 
 		udp_meta[i].splicesrc = udp_mmh_splice_port(uref, &mmh_recv[i]);
+		udp_meta[i].tosidx = udp_flow_from_sock(c, uref, &udp_meta[i]);
 	} while (udp_meta[i].splicesrc == src);
 
 	if (uref.pif == PIF_SPLICE) {
@@ -758,6 +873,7 @@ static unsigned udp_tap_send(const struct ctx *c, size_t start, size_t n,
 			break;
 
 		udp_meta[i].splicesrc = udp_mmh_splice_port(uref, &mmh_recv[i]);
+		udp_meta[i].tosidx = udp_flow_from_sock(c, uref, &udp_meta[i]);
 	} while (udp_meta[i].splicesrc == -1);
 
 	tap_send_frames(c, &tap_iov[start][0], UDP_NUM_IOVS, i - start);
@@ -786,8 +902,8 @@ void udp_buf_sock_handler(const struct ctx *c, union epoll_ref ref, uint32_t eve
 	 */
 	ssize_t n = (c->mode == MODE_PASTA ? 1 : UDP_MAX_FRAMES);
 	in_port_t dstport = ref.udp.port;
-	bool v6 = ref.udp.v6;
 	struct mmsghdr *mmh_recv;
+	bool v6 = ref.udp.v6;
 	int i, m;
 
 	if (c->no_udp || !(events & EPOLLIN))
@@ -797,6 +913,8 @@ void udp_buf_sock_handler(const struct ctx *c, union epoll_ref ref, uint32_t eve
 		dstport += c->udp.fwd_out.f.delta[dstport];
 	else if (ref.udp.pif == PIF_HOST)
 		dstport += c->udp.fwd_in.f.delta[dstport];
+	else
+		ASSERT(0);
 
 	if (v6)
 		mmh_recv = udp6_l2_mh_sock;
@@ -809,12 +927,13 @@ void udp_buf_sock_handler(const struct ctx *c, union epoll_ref ref, uint32_t eve
 
 	/* We divide things into batches based on how we need to send them,
 	 * determined by udp_meta[i].splicesrc.  To avoid either two passes
-	 * through the array, or recalculating splicesrc for a single entry, we
-	 * have to populate it one entry *ahead* of the loop counter (if
-	 * present).  So we fill in entry 0 before the loop, then udp_*_send()
-	 * populate one entry past where they consume.
+	 * through the array, or recalculating splicesrc and tosidx for a single
+	 * entry, we have to populate them one entry *ahead* of the loop counter
+	 * (if present).  So we fill in entry 0 before the loop, then
+	 * udp_*_send() populate one entry past where they consume.
 	 */
 	udp_meta[0].splicesrc = udp_mmh_splice_port(ref.udp, mmh_recv);
+	udp_meta[0].tosidx = udp_flow_from_sock(c, ref.udp, &udp_meta[0]);
 	for (i = 0; i < n; i += m) {
 		if (udp_meta[i].splicesrc >= 0)
 			m = udp_splice_send(c, i, n, dstport, ref.udp, now);
@@ -823,6 +942,74 @@ void udp_buf_sock_handler(const struct ctx *c, union epoll_ref ref, uint32_t eve
 	}
 }
 
+/**
+ * udp_flow_from_tap() - Find or create UDP flow for tap packets
+ * @c:		Execution context
+ * @pif:	pif on which the packet is arriving
+ * @af:		Address family, AF_INET or AF_INET6
+ * @saddr:	Source address on guest side
+ * @daddr:	Destination address guest side
+ * @srcport:	Source port on guest side
+ * @dstport:	Destination port on guest side
+ *
+ * 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.
+ */
+flow_sidx_t udp_flow_from_tap(const struct ctx *c,
+			      uint8_t pif, sa_family_t af,
+			      const void *saddr, const void *daddr,
+			      in_port_t srcport, in_port_t dstport)
+{
+	const struct flowside *ini;
+	struct udp_flow *uflow;
+	union flow *flow;
+	flow_sidx_t sidx;
+
+	ASSERT(pif == PIF_TAP);
+
+	sidx = flow_lookup_af(c, IPPROTO_UDP, pif, af, saddr, daddr,
+			      srcport, dstport);
+	if ((flow = flow_at_sidx(sidx)))
+		return FLOW_SIDX(flow, !sidx.side);
+
+	if (!(flow = flow_alloc()))
+		return FLOW_SIDX_NONE;
+
+	ini = flow_initiate_af(flow, PIF_TAP, af,
+			       saddr, srcport, daddr, dstport);
+
+	if (!inany_is_unicast(&ini->eaddr) || ini->eport == 0 ||
+	    !inany_is_unicast(&ini->faddr) || ini->fport == 0) {
+		char sstr[INANY_ADDRSTRLEN], dstr[INANY_ADDRSTRLEN];
+
+		debug("Invalid UDP endpoint from %s: %s:%hu -> %s:%hu",
+		      pif_name(pif),
+		      inany_ntop(&ini->eaddr, sstr, sizeof(sstr)), ini->eport,
+		      inany_ntop(&ini->faddr, dstr, sizeof(dstr)), ini->fport);
+		goto cancel;
+	}
+
+	if (!flow_target(c, flow, IPPROTO_UDP))
+		goto cancel;
+
+	if (flow->f.pif[TGTSIDE] != PIF_HOST) {
+		flow_err(flow, "No support for forwarding UDP from %s to %s",
+			 pif_name(flow->f.pif[INISIDE]),
+			 pif_name(flow->f.pif[TGTSIDE]));
+		goto cancel;
+	}
+
+	uflow = FLOW_SET_TYPE(flow, FLOW_UDP, udp);
+	flow_hash_insert(c, FLOW_SIDX(uflow, INISIDE));
+	flow_hash_insert(c, FLOW_SIDX(uflow, TGTSIDE));
+	FLOW_ACTIVATE(uflow);
+	return FLOW_SIDX(uflow, TGTSIDE);
+
+cancel:
+	flow_alloc_cancel(flow);
+	return FLOW_SIDX_NONE;
+}
+
 /**
  * udp_tap_handler() - Handle packets from tap
  * @c:		Execution context
@@ -847,15 +1034,13 @@ int udp_tap_handler(struct ctx *c, uint8_t pif,
 	struct sockaddr_in6 s_in6;
 	struct sockaddr_in s_in;
 	const struct udphdr *uh;
+	struct udp_flow *uflow;
 	struct sockaddr *sa;
 	int i, s, count = 0;
 	in_port_t src, dst;
+	flow_sidx_t sidx;
 	socklen_t sl;
 
-	(void)c;
-	(void)saddr;
-	(void)pif;
-
 	uh = packet_get(p, idx, 0, sizeof(*uh), NULL);
 	if (!uh)
 		return 1;
@@ -864,8 +1049,14 @@ int udp_tap_handler(struct ctx *c, uint8_t pif,
 	 * and destination, so we can just take those from the first message.
 	 */
 	src = ntohs(uh->source);
-	src += c->udp.fwd_in.rdelta[src];
 	dst = ntohs(uh->dest);
+	sidx = udp_flow_from_tap(c, pif, af, saddr, daddr, src, dst);
+	if ((uflow = udp_at_sidx(sidx)))
+		uflow->ts = now->tv_sec;
+	else
+		debug("UDP from tap without flow");
+
+	src += c->udp.fwd_in.rdelta[src];
 
 	if (af == AF_INET) {
 		s_in = (struct sockaddr_in) {
@@ -1211,6 +1402,17 @@ static int udp_port_rebind_outbound(void *arg)
 	return 0;
 }
 
+bool udp_flow_timer(const struct ctx *c, const struct udp_flow *uflow,
+		    const struct timespec *now)
+{
+	if (now->tv_sec - uflow->ts <= UDP_CONN_TIMEOUT)
+		return false;
+
+	flow_hash_remove(c, FLOW_SIDX(uflow, INISIDE));
+	flow_hash_remove(c, FLOW_SIDX(uflow, TGTSIDE));
+	return true;
+}
+
 /**
  * udp_timer() - Scan activity bitmaps for ports with associated timed events
  * @c:		Execution context
diff --git a/udp_flow.h b/udp_flow.h
new file mode 100644
index 00000000..18af9ac4
--- /dev/null
+++ b/udp_flow.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ *
+ * UDP flow tracking data structures
+ */
+#ifndef UDP_FLOW_H
+#define UDP_FLOW_H
+
+/**
+ * struct udp - Descriptor for a flow of UDP packets
+ * @f:		Generic flow information
+ * @ts:		Activity timestamp
+ */
+struct udp_flow {
+	/* Must be first element */
+	struct flow_common f;
+
+	time_t ts;
+};
+
+bool udp_flow_timer(const struct ctx *c, const struct udp_flow *uflow,
+		    const struct timespec *now);
+
+#endif /* UDP_FLOW_H */
-- 
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ *
+ * UDP flow tracking data structures
+ */
+#ifndef UDP_FLOW_H
+#define UDP_FLOW_H
+
+/**
+ * struct udp - Descriptor for a flow of UDP packets
+ * @f:		Generic flow information
+ * @ts:		Activity timestamp
+ */
+struct udp_flow {
+	/* Must be first element */
+	struct flow_common f;
+
+	time_t ts;
+};
+
+bool udp_flow_timer(const struct ctx *c, const struct udp_flow *uflow,
+		    const struct timespec *now);
+
+#endif /* UDP_FLOW_H */
-- 
2.45.2


  parent reply	other threads:[~2024-06-14  6:14 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-06-14  6:13 [PATCH v6 00/26] RFC: Unified flow table David Gibson
2024-06-14  6:13 ` [PATCH v6 01/26] flow: Common address information for initiating side David Gibson
2024-06-25 22:23   ` Stefano Brivio
2024-06-26  0:19     ` David Gibson
2024-06-14  6:13 ` [PATCH v6 02/26] flow: Common address information for target side David Gibson
2024-06-25 22:23   ` Stefano Brivio
2024-06-26  0:25     ` David Gibson
2024-06-14  6:13 ` [PATCH v6 03/26] tcp, flow: Remove redundant information, repack connection structures David Gibson
2024-06-25 22:25   ` Stefano Brivio
2024-06-26  0:23     ` David Gibson
2024-06-14  6:13 ` [PATCH v6 04/26] tcp: Obtain guest address from flowside David Gibson
2024-06-14  6:13 ` [PATCH v6 05/26] tcp: Manage outbound address via flow table David Gibson
2024-06-14  6:13 ` [PATCH v6 06/26] tcp: Simplify endpoint validation using flowside information David Gibson
2024-06-14  6:13 ` [PATCH v6 07/26] tcp_splice: Eliminate SPLICE_V6 flag David Gibson
2024-06-14  6:13 ` [PATCH v6 08/26] tcp, flow: Replace TCP specific hash function with general flow hash David Gibson
2024-06-14  6:13 ` [PATCH v6 09/26] flow, tcp: Generalise TCP hash table to general flow hash table David Gibson
2024-06-14  6:13 ` [PATCH v6 10/26] tcp: Re-use flow hash for initial sequence number generation David Gibson
2024-06-14  6:13 ` [PATCH v6 11/26] icmp: Remove redundant id field from flow table entry David Gibson
2024-06-14  6:13 ` [PATCH v6 12/26] icmp: Obtain destination addresses from the flowsides David Gibson
2024-06-14  6:13 ` [PATCH v6 13/26] icmp: Look up ping flows using flow hash David Gibson
2024-06-14  6:13 ` [PATCH v6 14/26] icmp: Eliminate icmp_id_map David Gibson
2024-06-14  6:13 ` [PATCH v6 15/26] icmp: Manage outbound socket address via flow table David Gibson
2024-06-14  6:13 ` [PATCH v6 16/26] flow, tcp: Flow based NAT and port forwarding for TCP David Gibson
2024-06-26 22:49   ` Stefano Brivio
2024-06-27  5:55     ` David Gibson
2024-06-14  6:13 ` [PATCH v6 17/26] flow, icmp: Use general flow forwarding rules for ICMP David Gibson
2024-06-14  6:13 ` [PATCH v6 18/26] fwd: Update flow forwarding logic for UDP David Gibson
2024-06-14  6:13 ` David Gibson [this message]
2024-06-14  6:13 ` [PATCH v6 20/26] udp: Direct traffic from tap according to flow table David Gibson
2024-06-14  6:13 ` [PATCH v6 21/26] udp: Direct traffic from host to guest " David Gibson
2024-06-14  6:13 ` [PATCH v6 22/26] udp: Direct spliced traffic " David Gibson
2024-06-14  6:13 ` [PATCH v6 23/26] udp: Remove 'splicesrc' tracking David Gibson
2024-06-14  6:13 ` [PATCH v6 24/26] udp: Remove tap port flags field David Gibson
2024-06-14  6:13 ` [PATCH v6 25/26] udp: Remove rdelta port forwarding maps David Gibson
2024-06-14  6:13 ` [PATCH v6 26/26] udp: Eliminate 'splice' flag from epoll reference David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240614061348.3814736-20-david@gibson.dropbear.id.au \
    --to=david@gibson.dropbear.id.au \
    --cc=jmaloy@redhat.com \
    --cc=passt-dev@passt.top \
    --cc=sbrivio@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).