* [PATCH v3] udp: Split activity timeouts for UDP flows
@ 2026-02-14 7:31 Yumei Huang
0 siblings, 0 replies; only message in thread
From: Yumei Huang @ 2026-02-14 7:31 UTC (permalink / raw)
To: passt-dev, sbrivio; +Cc: david, yuhuang
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 | 34 +++++++++++++++++++++++++++--
udp.h | 4 ++++
udp_flow.c | 30 ++++++++++++++++++++-----
udp_flow.h | 4 ++++
5 files changed, 69 insertions(+), 7 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..19adda0 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 \
@@ -953,7 +963,7 @@ void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
int s = ref.fd;
flow_trace(uflow, "Received data on reply socket");
- uflow->ts = now->tv_sec;
+ udp_flow_activity(uflow, !tosidx.sidei, now);
if (pif_is_socket(topif)) {
udp_sock_to_sock(c, ref.fd, n, tosidx);
@@ -1179,6 +1189,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 +1217,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..0023837 100644
--- a/udp_flow.c
+++ b/udp_flow.c
@@ -17,8 +17,6 @@
#include "udp_internal.h"
#include "epoll_ctl.h"
-#define UDP_CONN_TIMEOUT 180 /* s, timeout for ephemeral or local bind */
-
/**
* udp_at_sidx() - Get UDP specific flow at given sidx
* @sidx: Flow and side to retrieve
@@ -152,6 +150,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]))
@@ -228,7 +228,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, now);
return flow_sidx_opposite(sidx);
}
@@ -285,7 +285,7 @@ flow_sidx_t udp_flow_from_tap(const struct ctx *c,
sidx = flow_lookup_af(c, IPPROTO_UDP, pif, af, saddr, daddr,
srcport, dstport);
if ((uflow = udp_at_sidx(sidx))) {
- uflow->ts = now->tv_sec;
+ udp_flow_activity(uflow, sidx.sidei, now);
return flow_sidx_opposite(sidx);
}
@@ -362,9 +362,29 @@ 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);
return true;
}
+
+/**
+ * udp_flow_activity() - Track activity of a UDP flow
+ * @uflow: UDP flow
+ * @sidei: Side index of the flow (INISIDE or TGTSIDE)
+ * @now: Current timestamp
+ */
+void udp_flow_activity(struct udp_flow *uflow, unsigned int sidei,
+ const struct timespec *now)
+{
+ uflow->ts = now->tv_sec;
+ if (uflow->activity[sidei] < UINT8_MAX)
+ uflow->activity[sidei]++;
+}
diff --git a/udp_flow.h b/udp_flow.h
index 14e0f92..3ca49c5 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,7 @@ 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,
+ const struct timespec *now);
#endif /* UDP_FLOW_H */
--
2.52.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-02-14 7:31 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-14 7:31 [PATCH v3] udp: Split activity timeouts for UDP flows Yumei Huang
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).