public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Yumei Huang <yuhuang@redhat.com>
To: passt-dev@passt.top, sbrivio@redhat.com
Cc: david@gibson.dropbear.id.au, yuhuang@redhat.com
Subject: [PATCH v2] udp: Split activity timeouts for UDP flows
Date: Fri, 13 Feb 2026 17:56:51 +0800	[thread overview]
Message-ID: <20260213095651.155596-1-yuhuang@redhat.com> (raw)

Frequent DNS queries over UDP from a container or guest can result
in many sockets shown in ss(8), typically one per flow. This is
expected and harmless, but it can make the output of ss(8) look
noisy and potentially concern users.

This patch splits UDP flow timeouts into two, mirroring the Linux
kernel, and sources the values from kernel parameters. The shorter
timeout is applied to unidirectional flows and minimal bidirectional
exchanges (single datagram and reply), while the longer timeout is
used for bidirectional flows with multiple datagrams on either side.

Link: https://bugs.passt.top/show_bug.cgi?id=197
Suggested-by: Stefano Brivio <sbrivio@redhat.com>
Signed-off-by: Yumei Huang <yuhuang@redhat.com>
---
 contrib/apparmor/abstractions/passt |  4 ++++
 udp.c                               | 33 ++++++++++++++++++++++++++++-
 udp.h                               |  4 ++++
 udp_flow.c                          | 23 ++++++++++++++++++--
 udp_flow.h                          |  3 +++
 5 files changed, 64 insertions(+), 3 deletions(-)

diff --git a/contrib/apparmor/abstractions/passt b/contrib/apparmor/abstractions/passt
index 0ffadaf..85bd1ee 100644
--- a/contrib/apparmor/abstractions/passt
+++ b/contrib/apparmor/abstractions/passt
@@ -41,6 +41,10 @@
   @{PROC}/sys/net/ipv4/tcp_syn_linear_timeouts r,
   @{PROC}/sys/net/ipv4/tcp_rto_max_ms r,
 
+  # udp_get_timeout_params(), udp.c
+  @{PROC}/sys/net/netfilter/nf_conntrack_udp_timeout r,
+  @{PROC}/sys/net/netfilter/nf_conntrack_udp_timeout_stream r,
+
   network netlink raw,				# nl_sock_init_do(), netlink.c
 
   network inet stream,				# tcp.c
diff --git a/udp.c b/udp.c
index b2383e2..3afec35 100644
--- a/udp.c
+++ b/udp.c
@@ -26,7 +26,10 @@
  *
  * We track pseudo-connections of this type as flow table entries of type
  * FLOW_UDP.  We store the time of the last traffic on the flow in uflow->ts,
- * and let the flow expire if there is no traffic for UDP_CONN_TIMEOUT seconds.
+ * and let the flow expire if there is no traffic for UDP_TIMEOUT seconds for
+ * unidirectional flows and flows with only one datagram and one reply, or
+ * UDP_TIMEOUT_STREAM seconds for bidirectional flows with more than one
+ * datagram on either side.
  *
  * NOTE: This won't handle multicast protocols, or some protocols with different
  * port usage.  We'll need specific logic if we want to handle those.
@@ -118,6 +121,13 @@
 
 #define UDP_MAX_FRAMES		32  /* max # of frames to receive at once */
 
+#define UDP_TIMEOUT	"/proc/sys/net/netfilter/nf_conntrack_udp_timeout"
+#define UDP_TIMEOUT_STREAM	\
+	"/proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream"
+
+#define UDP_TIMEOUT_DEFAULT		30	/* s */
+#define UDP_TIMEOUT_STREAM_DEFAULT	120	/* s */
+
 /* Maximum UDP data to be returned in ICMP messages */
 #define ICMP4_MAX_DLEN 8
 #define ICMP6_MAX_DLEN (IPV6_MIN_MTU			\
@@ -954,6 +964,7 @@ void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
 
 		flow_trace(uflow, "Received data on reply socket");
 		uflow->ts = now->tv_sec;
+		udp_flow_activity(uflow, !tosidx.sidei);
 
 		if (pif_is_socket(topif)) {
 			udp_sock_to_sock(c, ref.fd, n, tosidx);
@@ -1179,6 +1190,24 @@ static void udp_splice_iov_init(void)
 	}
 }
 
+/**
+ * udp_get_timeout_params() - Get host kernel UDP timeout parameters
+ * @c:		Execution context
+ */
+static void udp_get_timeout_params(struct ctx *c)
+{
+	intmax_t v;
+
+	v = read_file_integer(UDP_TIMEOUT, UDP_TIMEOUT_DEFAULT);
+	c->udp.timeout = v;
+
+	v = read_file_integer(UDP_TIMEOUT_STREAM, UDP_TIMEOUT_STREAM_DEFAULT);
+	c->udp.stream_timeout = v;
+
+	debug("Using UDP timeout parameters, timeout: %d, stream_timeout: %d",
+	      c->udp.timeout, c->udp.stream_timeout);
+}
+
 /**
  * udp_init() - Initialise per-socket data, and sockets in namespace
  * @c:		Execution context
@@ -1189,6 +1218,8 @@ int udp_init(struct ctx *c)
 {
 	ASSERT(!c->no_udp);
 
+	udp_get_timeout_params(c);
+
 	udp_iov_init(c);
 
 	if (fwd_listen_sync(c, &c->udp.fwd_in, PIF_HOST, IPPROTO_UDP) < 0)
diff --git a/udp.h b/udp.h
index 2b91d72..da9c2df 100644
--- a/udp.h
+++ b/udp.h
@@ -24,11 +24,15 @@ void udp_update_l2_buf(const unsigned char *eth_d);
  * @fwd_in:		Port forwarding configuration for inbound packets
  * @fwd_out:		Port forwarding configuration for outbound packets
  * @timer_run:		Timestamp of most recent timer run
+ * @timeout:		Timeout for unidirectional flows (in s)
+ * @stream_timeout:	Timeout for stream-like flows (in s)
  */
 struct udp_ctx {
 	struct fwd_ports fwd_in;
 	struct fwd_ports fwd_out;
 	struct timespec timer_run;
+	int timeout;
+	int stream_timeout;
 };
 
 #endif /* UDP_H */
diff --git a/udp_flow.c b/udp_flow.c
index 1f5e84e..1eaad1d 100644
--- a/udp_flow.c
+++ b/udp_flow.c
@@ -17,7 +17,16 @@
 #include "udp_internal.h"
 #include "epoll_ctl.h"
 
-#define UDP_CONN_TIMEOUT	180 /* s, timeout for ephemeral or local bind */
+/**
+ * udp_flow_activity() - Track activity of a UDP flow
+ * @uflow:	UDP flow
+ * @sidei:	Side index of the flow (INISIDE or TGTSIDE)
+ */
+void udp_flow_activity(struct udp_flow *uflow, unsigned int sidei)
+{
+	if (uflow->activity[sidei] < UINT8_MAX)
+		uflow->activity[sidei]++;
+}
 
 /**
  * udp_at_sidx() - Get UDP specific flow at given sidx
@@ -152,6 +161,8 @@ static flow_sidx_t udp_flow_new(const struct ctx *c, union flow *flow,
 	uflow->ts = now->tv_sec;
 	uflow->s[INISIDE] = uflow->s[TGTSIDE] = -1;
 	uflow->ttl[INISIDE] = uflow->ttl[TGTSIDE] = 0;
+	uflow->activity[INISIDE] = 1;
+	uflow->activity[TGTSIDE] = 0;
 
 	flow_foreach_sidei(sidei) {
 		if (pif_is_socket(uflow->f.pif[sidei]))
@@ -229,6 +240,7 @@ flow_sidx_t udp_flow_from_sock(const struct ctx *c, uint8_t pif,
 	sidx = flow_lookup_sa(c, IPPROTO_UDP, pif, s_in, dst, port);
 	if ((uflow = udp_at_sidx(sidx))) {
 		uflow->ts = now->tv_sec;
+		udp_flow_activity(uflow, sidx.sidei);
 		return flow_sidx_opposite(sidx);
 	}
 
@@ -286,6 +298,7 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
 			      srcport, dstport);
 	if ((uflow = udp_at_sidx(sidx))) {
 		uflow->ts = now->tv_sec;
+		udp_flow_activity(uflow, sidx.sidei);
 		return flow_sidx_opposite(sidx);
 	}
 
@@ -362,7 +375,13 @@ bool udp_flow_defer(const struct ctx *c, struct udp_flow *uflow,
 bool udp_flow_timer(const struct ctx *c, struct udp_flow *uflow,
 		    const struct timespec *now)
 {
-	if (now->tv_sec - uflow->ts <= UDP_CONN_TIMEOUT)
+	int timeout = c->udp.timeout;
+
+	if (uflow->activity[TGTSIDE] &&
+	    (uflow->activity[INISIDE] > 1 || uflow->activity[TGTSIDE] > 1))
+		timeout = c->udp.stream_timeout;
+
+	if (now->tv_sec - uflow->ts <= timeout)
 		return false;
 
 	udp_flow_close(c, uflow);
diff --git a/udp_flow.h b/udp_flow.h
index 14e0f92..0403260 100644
--- a/udp_flow.h
+++ b/udp_flow.h
@@ -16,6 +16,7 @@
  * @flush1:	@s[1] may have datagrams queued for other flows
  * @ts:		Activity timestamp
  * @s:		Socket fd (or -1) for each side of the flow
+ * @activity:	Packets seen from each side of the flow, up to UINT8_MAX
  */
 struct udp_flow {
 	/* Must be first element */
@@ -29,6 +30,7 @@ struct udp_flow {
 
 	time_t ts;
 	int s[SIDES];
+	uint8_t activity[SIDES];
 };
 
 struct udp_flow *udp_at_sidx(flow_sidx_t sidx);
@@ -46,5 +48,6 @@ bool udp_flow_defer(const struct ctx *c, struct udp_flow *uflow,
 		    const struct timespec *now);
 bool udp_flow_timer(const struct ctx *c, struct udp_flow *uflow,
 		    const struct timespec *now);
+void udp_flow_activity(struct udp_flow *uflow, unsigned int sidei);
 
 #endif /* UDP_FLOW_H */
-- 
2.52.0


                 reply	other threads:[~2026-02-13  9:57 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20260213095651.155596-1-yuhuang@redhat.com \
    --to=yuhuang@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --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).