From nobody Sat Nov 15 20:48:57 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=quarantine dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1747909881; cv=none; d=zohomail.com; s=zohoarc; b=kpifsBvLrGMIG3+SR2WhW8Y+nYUj3tfJyHPq3VevJcnxputpsoNzAiCswqa2fYW/qSoiq95GvuTqsQBpHyPj9jiJowF6WVdyLFbs2QYiLOEUKbrnhgRxnohiX4brK4A5QIdBk5IHit9S+Qh9srakGuYrt8My+Fn4ABlxErQR1zg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1747909881; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=qFH+p8HR+JF9ppvRioJ+Oz56ZSrJiqELILA5yNuqU+Q=; b=M+AGhObrq/Lpxszq4Oo7m9hAMw2jhKIokou5FCkJk/732X3yXHBOLxDjeYByUdxv6ikVIG2qOslA2DZuVfq9GBQV7HB8eVzWQxUgeozW15mslt6zi2i0ODHTUn1Hs8+D1ac/UvZAGH4/+b8S0DXSrtdn3yRDRQjOiFAYOC27TZU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=quarantine dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1747909881445548.944306861466; Thu, 22 May 2025 03:31:21 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1uI3CV-0003vm-4a; Thu, 22 May 2025 06:30:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uI3CR-0003lk-VX for qemu-devel@nongnu.org; Thu, 22 May 2025 06:30:56 -0400 Received: from us-smtp-delivery-124.mimecast.com ([170.10.133.124]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1uI3CN-0006hG-WB for qemu-devel@nongnu.org; Thu, 22 May 2025 06:30:54 -0400 Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-298-NVXMuodON2qlNsTgNU2Yww-1; Thu, 22 May 2025 06:30:49 -0400 Received: from mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.40]) (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-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F2066195609E; Thu, 22 May 2025 10:30:48 +0000 (UTC) Received: from toolbx.redhat.com (unknown [10.42.28.179]) by mx-prod-int-04.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id F39851958014; Thu, 22 May 2025 10:30:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747909851; 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: in-reply-to:in-reply-to:references:references; bh=qFH+p8HR+JF9ppvRioJ+Oz56ZSrJiqELILA5yNuqU+Q=; b=XRYbElV+Uw2jhEuvV90Z6lTNIbefEsqCn2UKFPRAHJOWJX4rZ8EGomUFBJiEeHaG30oW6c Yy1lLMnKkrhe/w0AfzxmtKBTEUygGpNyicqjinCdCpIXn06Uc68EDKz15CcDDXzaCtbEWi sZ62CqXoNGIxY4gASN+FvLjYrW7j9t8= X-MC-Unique: NVXMuodON2qlNsTgNU2Yww-1 X-Mimecast-MFC-AGG-ID: NVXMuodON2qlNsTgNU2Yww_1747909849 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: qemu-devel@nongnu.org Cc: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Markus Armbruster , Paolo Bonzini , Eric Blake , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , Juraj Marcin Subject: [PULL 22/23] util/qemu-sockets: Introduce inet socket options controlling TCP keep-alive Date: Thu, 22 May 2025 11:29:22 +0100 Message-ID: <20250522102923.309452-23-berrange@redhat.com> In-Reply-To: <20250522102923.309452-1-berrange@redhat.com> References: <20250522102923.309452-1-berrange@redhat.com> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.40 Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=170.10.133.124; envelope-from=berrange@redhat.com; helo=us-smtp-delivery-124.mimecast.com X-Spam_score_int: -22 X-Spam_score: -2.3 X-Spam_bar: -- X-Spam_report: (-2.3 / 5.0 requ) BAYES_00=-1.9, DKIMWL_WL_HIGH=-0.184, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H5=0.001, RCVD_IN_MSPIKE_WL=0.001, RCVD_IN_VALIDITY_CERTIFIED_BLOCKED=0.001, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1747909882546116600 From: Juraj Marcin With the default TCP stack configuration, it could be even 2 hours before the connection times out due to the other side not being reachable. However, in some cases, the application needs to be aware of a connection issue much sooner. This is the case, for example, for postcopy live migration. If there is no traffic from the migration destination guest (server-side) to the migration source guest (client-side), the destination keeps waiting for pages indefinitely and does not switch to the postcopy-paused state. This can happen, for example, if the destination QEMU instance is started with the '-S' command line option and the machine is not started yet, or if the machine is idle and produces no new page faults for not-yet-migrated pages. This patch introduces new inet socket parameters that control count, idle period, and interval of TCP keep-alive packets before the connection is considered broken. These parameters are available on systems where the respective TCP socket options are defined, that includes Linux, Windows, macOS, but not OpenBSD. Additionally, macOS defines TCP_KEEPIDLE as TCP_KEEPALIVE instead, so the patch supplies its own definition. The default value for all is 0, which means the system configuration is used. Signed-off-by: Juraj Marcin Reviewed-by: Daniel P. Berrang=C3=A9 Signed-off-by: Daniel P. Berrang=C3=A9 --- meson.build | 30 +++++++++++++ qapi/sockets.json | 19 ++++++++ tests/unit/test-util-sockets.c | 39 +++++++++++++++++ util/qemu-sockets.c | 80 ++++++++++++++++++++++++++++++++++ 4 files changed, 168 insertions(+) diff --git a/meson.build b/meson.build index ad2053f968..fdad3fb528 100644 --- a/meson.build +++ b/meson.build @@ -2760,6 +2760,36 @@ if linux_io_uring.found() config_host_data.set('HAVE_IO_URING_PREP_WRITEV2', cc.has_header_symbol('liburing.h', 'io_uring_prep_w= ritev2')) endif +config_host_data.set('HAVE_TCP_KEEPCNT', + cc.has_header_symbol('netinet/tcp.h', 'TCP_KEEPCNT') = or + cc.compiles(''' + #include + #ifndef TCP_KEEPCNT + #error + #endif + int main(void) { return 0; }''', + name: 'Win32 TCP_KEEPCNT')) +# On Darwin TCP_KEEPIDLE is available under different name, TCP_KEEPALIVE. +# https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/bsd/man/man4/tcp.= 4#L172 +config_host_data.set('HAVE_TCP_KEEPIDLE', + cc.has_header_symbol('netinet/tcp.h', 'TCP_KEEPIDLE')= or + cc.has_header_symbol('netinet/tcp.h', 'TCP_KEEPALIVE'= ) or + cc.compiles(''' + #include + #ifndef TCP_KEEPIDLE + #error + #endif + int main(void) { return 0; }''', + name: 'Win32 TCP_KEEPIDLE')) +config_host_data.set('HAVE_TCP_KEEPINTVL', + cc.has_header_symbol('netinet/tcp.h', 'TCP_KEEPINTVL'= ) or + cc.compiles(''' + #include + #ifndef TCP_KEEPINTVL + #error + #endif + int main(void) { return 0; }''', + name: 'Win32 TCP_KEEPINTVL')) =20 # has_member config_host_data.set('HAVE_SIGEV_NOTIFY_THREAD_ID', diff --git a/qapi/sockets.json b/qapi/sockets.json index 62797cd027..f9f559daba 100644 --- a/qapi/sockets.json +++ b/qapi/sockets.json @@ -59,6 +59,22 @@ # @keep-alive: enable keep-alive when connecting to/listening on this sock= et. # (Since 4.2, not supported for listening sockets until 10.1) # +# @keep-alive-count: number of keep-alive packets sent before the connecti= on is +# closed. Only supported for TCP sockets on systems where TCP_KEEPCNT +# socket option is defined (this includes Linux, Windows, macOS, FreeB= SD, +# but not OpenBSD). When set to 0, system setting is used. (Since 10= .1) +# +# @keep-alive-idle: time in seconds the connection needs to be idle before +# sending a keepalive packet. Only supported for TCP sockets on syste= ms +# where TCP_KEEPIDLE socket option is defined (this includes Linux, +# Windows, macOS, FreeBSD, but not OpenBSD). When set to 0, system se= tting +# is used. (Since 10.1) +# +# @keep-alive-interval: time in seconds between keep-alive packets. Only +# supported for TCP sockets on systems where TCP_KEEPINTVL is defined = (this +# includes Linux, Windows, macOS, FreeBSD, but not OpenBSD). When set= to +# 0, system setting is used. (Since 10.1) +# # @mptcp: enable multi-path TCP. (Since 6.1) # # Since: 1.3 @@ -71,6 +87,9 @@ '*ipv4': 'bool', '*ipv6': 'bool', '*keep-alive': 'bool', + '*keep-alive-count': { 'type': 'uint32', 'if': 'HAVE_TCP_KEEPCNT' }, + '*keep-alive-idle': { 'type': 'uint32', 'if': 'HAVE_TCP_KEEPIDLE' }, + '*keep-alive-interval': { 'type': 'uint32', 'if': 'HAVE_TCP_KEEPINTVL'= }, '*mptcp': { 'type': 'bool', 'if': 'HAVE_IPPROTO_MPTCP' } } } =20 ## diff --git a/tests/unit/test-util-sockets.c b/tests/unit/test-util-sockets.c index 9e39b92e7c..8492f4d68f 100644 --- a/tests/unit/test-util-sockets.c +++ b/tests/unit/test-util-sockets.c @@ -359,6 +359,24 @@ static void inet_parse_test_helper(const char *str, g_assert_cmpint(addr.ipv6, =3D=3D, exp_addr->ipv6); g_assert_cmpint(addr.has_keep_alive, =3D=3D, exp_addr->has_keep_al= ive); g_assert_cmpint(addr.keep_alive, =3D=3D, exp_addr->keep_alive); +#ifdef HAVE_TCP_KEEPCNT + g_assert_cmpint(addr.has_keep_alive_count, =3D=3D, + exp_addr->has_keep_alive_count); + g_assert_cmpint(addr.keep_alive_count, =3D=3D, + exp_addr->keep_alive_count); +#endif +#ifdef HAVE_TCP_KEEPIDLE + g_assert_cmpint(addr.has_keep_alive_idle, =3D=3D, + exp_addr->has_keep_alive_idle); + g_assert_cmpint(addr.keep_alive_idle, =3D=3D, + exp_addr->keep_alive_idle); +#endif +#ifdef HAVE_TCP_KEEPINTVL + g_assert_cmpint(addr.has_keep_alive_interval, =3D=3D, + exp_addr->has_keep_alive_interval); + g_assert_cmpint(addr.keep_alive_interval, =3D=3D, + exp_addr->keep_alive_interval); +#endif #ifdef HAVE_IPPROTO_MPTCP g_assert_cmpint(addr.has_mptcp, =3D=3D, exp_addr->has_mptcp); g_assert_cmpint(addr.mptcp, =3D=3D, exp_addr->mptcp); @@ -460,6 +478,18 @@ static void test_inet_parse_all_options_good(void) .ipv6 =3D true, .has_keep_alive =3D true, .keep_alive =3D true, +#ifdef HAVE_TCP_KEEPCNT + .has_keep_alive_count =3D true, + .keep_alive_count =3D 10, +#endif +#ifdef HAVE_TCP_KEEPIDLE + .has_keep_alive_idle =3D true, + .keep_alive_idle =3D 60, +#endif +#ifdef HAVE_TCP_KEEPINTVL + .has_keep_alive_interval =3D true, + .keep_alive_interval =3D 30, +#endif #ifdef HAVE_IPPROTO_MPTCP .has_mptcp =3D true, .mptcp =3D false, @@ -467,6 +497,15 @@ static void test_inet_parse_all_options_good(void) }; inet_parse_test_helper( "[::1]:5000,numeric=3Don,to=3D5006,ipv4=3Doff,ipv6=3Don,keep-alive= =3Don" +#ifdef HAVE_TCP_KEEPCNT + ",keep-alive-count=3D10" +#endif +#ifdef HAVE_TCP_KEEPIDLE + ",keep-alive-idle=3D60" +#endif +#ifdef HAVE_TCP_KEEPINTVL + ",keep-alive-interval=3D30" +#endif #ifdef HAVE_IPPROTO_MPTCP ",mptcp=3Doff" #endif diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c index 403dc26b36..4773755fd5 100644 --- a/util/qemu-sockets.c +++ b/util/qemu-sockets.c @@ -45,6 +45,14 @@ # define AI_NUMERICSERV 0 #endif =20 +/* + * On macOS TCP_KEEPIDLE is available under a different name, TCP_KEEPALIV= E. + * https://github.com/apple/darwin-xnu/blob/xnu-4570.1.46/bsd/man/man4/tcp= .4#L172 + */ +#if defined(TCP_KEEPALIVE) && !defined(TCP_KEEPIDLE) +# define TCP_KEEPIDLE TCP_KEEPALIVE +#endif + =20 static int inet_getport(struct addrinfo *e) { @@ -218,6 +226,42 @@ static int inet_set_sockopts(int sock, InetSocketAddre= ss *saddr, Error **errp) "Unable to set keep-alive option on socket"); return -1; } +#ifdef HAVE_TCP_KEEPCNT + if (saddr->has_keep_alive_count && saddr->keep_alive_count) { + int keep_count =3D saddr->keep_alive_count; + ret =3D setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keep_count, + sizeof(keep_count)); + if (ret < 0) { + error_setg_errno(errp, errno, + "Unable to set TCP keep-alive count optio= n on socket"); + return -1; + } + } +#endif +#ifdef HAVE_TCP_KEEPIDLE + if (saddr->has_keep_alive_idle && saddr->keep_alive_idle) { + int keep_idle =3D saddr->keep_alive_idle; + ret =3D setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, + sizeof(keep_idle)); + if (ret < 0) { + error_setg_errno(errp, errno, + "Unable to set TCP keep-alive idle option= on socket"); + return -1; + } + } +#endif +#ifdef HAVE_TCP_KEEPINTVL + if (saddr->has_keep_alive_interval && saddr->keep_alive_interval) { + int keep_interval =3D saddr->keep_alive_interval; + ret =3D setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keep_int= erval, + sizeof(keep_interval)); + if (ret < 0) { + error_setg_errno(errp, errno, + "Unable to set TCP keep-alive interval op= tion on socket"); + return -1; + } + } +#endif } return 0; } @@ -630,6 +674,24 @@ static QemuOptsList inet_opts =3D { .name =3D "keep-alive", .type =3D QEMU_OPT_BOOL, }, +#ifdef HAVE_TCP_KEEPCNT + { + .name =3D "keep-alive-count", + .type =3D QEMU_OPT_NUMBER, + }, +#endif +#ifdef HAVE_TCP_KEEPIDLE + { + .name =3D "keep-alive-idle", + .type =3D QEMU_OPT_NUMBER, + }, +#endif +#ifdef HAVE_TCP_KEEPINTVL + { + .name =3D "keep-alive-interval", + .type =3D QEMU_OPT_NUMBER, + }, +#endif #ifdef HAVE_IPPROTO_MPTCP { .name =3D "mptcp", @@ -695,6 +757,24 @@ int inet_parse(InetSocketAddress *addr, const char *st= r, Error **errp) addr->has_keep_alive =3D true; addr->keep_alive =3D qemu_opt_get_bool(opts, "keep-alive", false); } +#ifdef HAVE_TCP_KEEPCNT + if (qemu_opt_find(opts, "keep-alive-count")) { + addr->has_keep_alive_count =3D true; + addr->keep_alive_count =3D qemu_opt_get_number(opts, "keep-alive-c= ount", 0); + } +#endif +#ifdef HAVE_TCP_KEEPIDLE + if (qemu_opt_find(opts, "keep-alive-idle")) { + addr->has_keep_alive_idle =3D true; + addr->keep_alive_idle =3D qemu_opt_get_number(opts, "keep-alive-id= le", 0); + } +#endif +#ifdef HAVE_TCP_KEEPINTVL + if (qemu_opt_find(opts, "keep-alive-interval")) { + addr->has_keep_alive_interval =3D true; + addr->keep_alive_interval =3D qemu_opt_get_number(opts, "keep-aliv= e-interval", 0); + } +#endif #ifdef HAVE_IPPROTO_MPTCP if (qemu_opt_find(opts, "mptcp")) { addr->has_mptcp =3D true; --=20 2.49.0