From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=IwiAoAlf; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by passt.top (Postfix) with ESMTPS id 88E2F5A0265 for ; Fri, 13 Feb 2026 10:57:02 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1770976621; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=fRxzl2xmSaDML4LX/Ntz+E3VQ8nP3ElFhlcWgXSH2Fo=; b=IwiAoAlfUgZjrKGbzn9nU5I6DrxD+GCv4vfOxFUxfrVpDEpat8ZaOvMovj+PhdZdaxLUsB uk2AHKJNAGCev616ZDXGw+rtyKYmB6a7omOMK4VvPCO00vJfC7A33Ula6BLMQ0Wji7UeyP 2ch+wpkljqh1MgQitvwN1KiKt6aeoP4= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-534-EbrygzpHPIuF0u3yySfZoQ-1; Fri, 13 Feb 2026 04:57:00 -0500 X-MC-Unique: EbrygzpHPIuF0u3yySfZoQ-1 X-Mimecast-MFC-AGG-ID: EbrygzpHPIuF0u3yySfZoQ_1770976619 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 9ECC718003FC; Fri, 13 Feb 2026 09:56:58 +0000 (UTC) Received: from fedora.redhat.com (unknown [10.72.116.69]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id B0C7E30001B9; Fri, 13 Feb 2026 09:56:55 +0000 (UTC) From: Yumei Huang To: passt-dev@passt.top, sbrivio@redhat.com Subject: [PATCH v2] udp: Split activity timeouts for UDP flows Date: Fri, 13 Feb 2026 17:56:51 +0800 Message-ID: <20260213095651.155596-1-yuhuang@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: UahfeYjXe1iWljQs79_y4oOK9J--dKPlZ_dwwHD2OO4_1770976619 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: AR25KDKVTO3UA3DUULAMNWMS2MGBVOW5 X-Message-ID-Hash: AR25KDKVTO3UA3DUULAMNWMS2MGBVOW5 X-MailFrom: yuhuang@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: david@gibson.dropbear.id.au, yuhuang@redhat.com X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: 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 Signed-off-by: Yumei Huang --- 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