From nobody Sat Nov 15 20:48:59 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=1747909941; cv=none; d=zohomail.com; s=zohoarc; b=E1iom9Mx75HB9wVP7UQSKGTJW9Frnt2h6JiE9U6CE27p+ViC7JiAbJ8Nr1Qg7mz3s0f784tuqZM9u36hoIa1UIrxGuYXcVwOGAMoXllWT0o3dgXRXXlRI7ojaxxAsMG/KECM6k/gPL+57xaODaBP4IfvBAPn1Rv1ccDLGuM4fnU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1747909941; 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=zyDczoF8Ea/zibDFBxcUI7jCKMlSBi/g/H/4wnvbA90=; b=gFJSfSH1ztqubm1GOlaxtsPjkWBGk44UzsRRdv8xZ1EdZWpz+eZx193pX9lH1TChW9l9Wb9BJfHxZ/pEY/7eVJfKINjkVlyEyKQ1G5viyzfXhZ5FdaMOYVLf5180VpmPoYwE728O3GaATqswLrSsIL4gEJjuW4onWPBkcx7MQNs= 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 1747909941123692.354486020462; Thu, 22 May 2025 03:32:21 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1uI3Ci-00058D-Mx; Thu, 22 May 2025 06:31:12 -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 1uI3Cg-0004wn-45 for qemu-devel@nongnu.org; Thu, 22 May 2025 06:31:10 -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 1uI3CV-0006hz-IM for qemu-devel@nongnu.org; Thu, 22 May 2025 06:31:09 -0400 Received: from mx-prod-mc-06.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-324-OaSZizkaP765Lb2o6h8yDw-1; Thu, 22 May 2025 06:30:46 -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-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6682718003FC; Thu, 22 May 2025 10:30:45 +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 B24A71958019; Thu, 22 May 2025 10:30:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1747909857; 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=zyDczoF8Ea/zibDFBxcUI7jCKMlSBi/g/H/4wnvbA90=; b=W0zGlYX2U6z9smP12jHN2QwmKCS6tSb/neCpE+/TN2aD/nG4uPUUhLtkINi9oQz8MotmLZ zrQVFO+64UBoUivK0dI5PwbJM9zPRKjnouYYNj1KbgblQWOCqFhHtaBUYMiEnc6mzqTiBR tlmW2dU2+oVmQBnNbWwqk63XO8bX09g= X-MC-Unique: OaSZizkaP765Lb2o6h8yDw-1 X-Mimecast-MFC-AGG-ID: OaSZizkaP765Lb2o6h8yDw_1747909845 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 21/23] util/qemu-sockets: Refactor inet_parse() to use QemuOpts Date: Thu, 22 May 2025 11:29:21 +0100 Message-ID: <20250522102923.309452-22-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: -16 X-Spam_score: -1.7 X-Spam_bar: - X-Spam_report: (-1.7 / 5.0 requ) BAYES_00=-1.9, DKIM_INVALID=0.1, DKIM_SIGNED=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=no 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: 1747909943174116600 From: Juraj Marcin Currently, the inet address parser cannot handle multiple options where one is prefixed with the name of the other. For example, with the 'keep-alive-idle' option added, the current parser cannot parse '127.0.0.1:5000,keep-alive-idle=3D60,keep-alive' correctly. Instead, it fails with "error parsing 'keep-alive' flag '-idle=3D60,keep-alive'". To resolve these issues, this patch rewrites the inet address parsing using the QemuOpts parser, which the inet_parse_flag() function tries to mimic. This new parser supports all previously supported options and on top of that the 'numeric' flag is now also supported. The only difference is, the new parser produces an error if an unknown option is passed, instead of silently ignoring it. Signed-off-by: Juraj Marcin Reviewed-by: Daniel P. Berrang=C3=A9 Signed-off-by: Daniel P. Berrang=C3=A9 --- tests/unit/test-util-sockets.c | 196 +++++++++++++++++++++++++++++++++ util/qemu-sockets.c | 158 +++++++++++++------------- 2 files changed, 270 insertions(+), 84 deletions(-) diff --git a/tests/unit/test-util-sockets.c b/tests/unit/test-util-sockets.c index 4c9dd0b271..9e39b92e7c 100644 --- a/tests/unit/test-util-sockets.c +++ b/tests/unit/test-util-sockets.c @@ -332,6 +332,177 @@ static void test_socket_unix_abstract(void) =20 #endif /* CONFIG_LINUX */ =20 +static void inet_parse_test_helper(const char *str, + InetSocketAddress *exp_addr, bool succe= ss) +{ + InetSocketAddress addr; + Error *error =3D NULL; + + int rc =3D inet_parse(&addr, str, &error); + + if (success) { + g_assert_cmpint(rc, =3D=3D, 0); + } else { + g_assert_cmpint(rc, <, 0); + } + if (exp_addr !=3D NULL) { + g_assert_cmpstr(addr.host, =3D=3D, exp_addr->host); + g_assert_cmpstr(addr.port, =3D=3D, exp_addr->port); + /* Own members: */ + g_assert_cmpint(addr.has_numeric, =3D=3D, exp_addr->has_numeric); + g_assert_cmpint(addr.numeric, =3D=3D, exp_addr->numeric); + g_assert_cmpint(addr.has_to, =3D=3D, exp_addr->has_to); + g_assert_cmpint(addr.to, =3D=3D, exp_addr->to); + g_assert_cmpint(addr.has_ipv4, =3D=3D, exp_addr->has_ipv4); + g_assert_cmpint(addr.ipv4, =3D=3D, exp_addr->ipv4); + g_assert_cmpint(addr.has_ipv6, =3D=3D, exp_addr->has_ipv6); + 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_IPPROTO_MPTCP + g_assert_cmpint(addr.has_mptcp, =3D=3D, exp_addr->has_mptcp); + g_assert_cmpint(addr.mptcp, =3D=3D, exp_addr->mptcp); +#endif + } + + g_free(addr.host); + g_free(addr.port); +} + +static void test_inet_parse_nohost_good(void) +{ + char host[] =3D ""; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + }; + inet_parse_test_helper(":5000", &exp_addr, true); +} + +static void test_inet_parse_empty_bad(void) +{ + inet_parse_test_helper("", NULL, false); +} + +static void test_inet_parse_only_colon_bad(void) +{ + inet_parse_test_helper(":", NULL, false); +} + +static void test_inet_parse_ipv4_good(void) +{ + char host[] =3D "127.0.0.1"; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + }; + inet_parse_test_helper("127.0.0.1:5000", &exp_addr, true); +} + +static void test_inet_parse_ipv4_noport_bad(void) +{ + inet_parse_test_helper("127.0.0.1", NULL, false); +} + +static void test_inet_parse_ipv6_good(void) +{ + char host[] =3D "::1"; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + }; + inet_parse_test_helper("[::1]:5000", &exp_addr, true); +} + +static void test_inet_parse_ipv6_noend_bad(void) +{ + inet_parse_test_helper("[::1", NULL, false); +} + +static void test_inet_parse_ipv6_noport_bad(void) +{ + inet_parse_test_helper("[::1]:", NULL, false); +} + +static void test_inet_parse_ipv6_empty_bad(void) +{ + inet_parse_test_helper("[]:5000", NULL, false); +} + +static void test_inet_parse_hostname_good(void) +{ + char host[] =3D "localhost"; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + }; + inet_parse_test_helper("localhost:5000", &exp_addr, true); +} + +static void test_inet_parse_all_options_good(void) +{ + char host[] =3D "::1"; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + .has_numeric =3D true, + .numeric =3D true, + .has_to =3D true, + .to =3D 5006, + .has_ipv4 =3D true, + .ipv4 =3D false, + .has_ipv6 =3D true, + .ipv6 =3D true, + .has_keep_alive =3D true, + .keep_alive =3D true, +#ifdef HAVE_IPPROTO_MPTCP + .has_mptcp =3D true, + .mptcp =3D false, +#endif + }; + inet_parse_test_helper( + "[::1]:5000,numeric=3Don,to=3D5006,ipv4=3Doff,ipv6=3Don,keep-alive= =3Don" +#ifdef HAVE_IPPROTO_MPTCP + ",mptcp=3Doff" +#endif + , &exp_addr, true); +} + +static void test_inet_parse_all_implicit_bool_good(void) +{ + char host[] =3D "::1"; + char port[] =3D "5000"; + InetSocketAddress exp_addr =3D { + .host =3D host, + .port =3D port, + .has_numeric =3D true, + .numeric =3D true, + .has_to =3D true, + .to =3D 5006, + .has_ipv4 =3D true, + .ipv4 =3D true, + .has_ipv6 =3D true, + .ipv6 =3D true, + .has_keep_alive =3D true, + .keep_alive =3D true, +#ifdef HAVE_IPPROTO_MPTCP + .has_mptcp =3D true, + .mptcp =3D true, +#endif + }; + inet_parse_test_helper( + "[::1]:5000,numeric,to=3D5006,ipv4,ipv6,keep-alive" +#ifdef HAVE_IPPROTO_MPTCP + ",mptcp" +#endif + , &exp_addr, true); +} + int main(int argc, char **argv) { bool has_ipv4, has_ipv6; @@ -377,6 +548,31 @@ int main(int argc, char **argv) test_socket_unix_abstract); #endif =20 + g_test_add_func("/util/socket/inet-parse/nohost-good", + test_inet_parse_nohost_good); + g_test_add_func("/util/socket/inet-parse/empty-bad", + test_inet_parse_empty_bad); + g_test_add_func("/util/socket/inet-parse/only-colon-bad", + test_inet_parse_only_colon_bad); + g_test_add_func("/util/socket/inet-parse/ipv4-good", + test_inet_parse_ipv4_good); + g_test_add_func("/util/socket/inet-parse/ipv4-noport-bad", + test_inet_parse_ipv4_noport_bad); + g_test_add_func("/util/socket/inet-parse/ipv6-good", + test_inet_parse_ipv6_good); + g_test_add_func("/util/socket/inet-parse/ipv6-noend-bad", + test_inet_parse_ipv6_noend_bad); + g_test_add_func("/util/socket/inet-parse/ipv6-noport-bad", + test_inet_parse_ipv6_noport_bad); + g_test_add_func("/util/socket/inet-parse/ipv6-empty-bad", + test_inet_parse_ipv6_empty_bad); + g_test_add_func("/util/socket/inet-parse/hostname-good", + test_inet_parse_hostname_good); + g_test_add_func("/util/socket/inet-parse/all-options-good", + test_inet_parse_all_options_good); + g_test_add_func("/util/socket/inet-parse/all-bare-bool-good", + test_inet_parse_all_implicit_bool_good); + end: return g_test_run(); } diff --git a/util/qemu-sockets.c b/util/qemu-sockets.c index 4fbf1ed5bf..403dc26b36 100644 --- a/util/qemu-sockets.c +++ b/util/qemu-sockets.c @@ -30,6 +30,7 @@ #include "qapi/qobject-input-visitor.h" #include "qapi/qobject-output-visitor.h" #include "qemu/cutils.h" +#include "qemu/option.h" #include "trace.h" =20 #ifndef AI_ADDRCONFIG @@ -600,115 +601,104 @@ err: return -1; } =20 -/* compatibility wrapper */ -static int inet_parse_flag(const char *flagname, const char *optstr, bool = *val, - Error **errp) -{ - char *end; - size_t len; - - end =3D strstr(optstr, ","); - if (end) { - if (end[1] =3D=3D ',') { /* Reject 'ipv6=3Don,,foo' */ - error_setg(errp, "error parsing '%s' flag '%s'", flagname, opt= str); - return -1; - } - len =3D end - optstr; - } else { - len =3D strlen(optstr); - } - if (len =3D=3D 0 || (len =3D=3D 3 && strncmp(optstr, "=3Don", len) =3D= =3D 0)) { - *val =3D true; - } else if (len =3D=3D 4 && strncmp(optstr, "=3Doff", len) =3D=3D 0) { - *val =3D false; - } else { - error_setg(errp, "error parsing '%s' flag '%s'", flagname, optstr); - return -1; - } - return 0; -} +static QemuOptsList inet_opts =3D { + .name =3D "InetSocketAddress", + .head =3D QTAILQ_HEAD_INITIALIZER(inet_opts.head), + .implied_opt_name =3D "addr", + .desc =3D { + { + .name =3D "addr", + .type =3D QEMU_OPT_STRING, + }, + { + .name =3D "numeric", + .type =3D QEMU_OPT_BOOL, + }, + { + .name =3D "to", + .type =3D QEMU_OPT_NUMBER, + }, + { + .name =3D "ipv4", + .type =3D QEMU_OPT_BOOL, + }, + { + .name =3D "ipv6", + .type =3D QEMU_OPT_BOOL, + }, + { + .name =3D "keep-alive", + .type =3D QEMU_OPT_BOOL, + }, +#ifdef HAVE_IPPROTO_MPTCP + { + .name =3D "mptcp", + .type =3D QEMU_OPT_BOOL, + }, +#endif + { /* end of list */ } + }, +}; =20 int inet_parse(InetSocketAddress *addr, const char *str, Error **errp) { - const char *optstr, *h; - char host[65]; - char port[33]; - int to; - int pos; - char *begin; - + QemuOpts *opts =3D qemu_opts_parse(&inet_opts, str, true, errp); + if (!opts) { + return -1; + } memset(addr, 0, sizeof(*addr)); =20 /* parse address */ - if (str[0] =3D=3D ':') { - /* no host given */ - host[0] =3D '\0'; - if (sscanf(str, ":%32[^,]%n", port, &pos) !=3D 1) { - error_setg(errp, "error parsing port in address '%s'", str); - return -1; - } - } else if (str[0] =3D=3D '[') { + const char *addr_str =3D qemu_opt_get(opts, "addr"); + if (!addr_str) { + error_setg(errp, "error parsing address ''"); + return -1; + } + if (str[0] =3D=3D '[') { /* IPv6 addr */ - if (sscanf(str, "[%64[^]]]:%32[^,]%n", host, port, &pos) !=3D 2) { - error_setg(errp, "error parsing IPv6 address '%s'", str); + const char *ip_end =3D strstr(addr_str, "]:"); + if (!ip_end || ip_end - addr_str < 2 || strlen(ip_end) < 3) { + error_setg(errp, "error parsing IPv6 address '%s'", addr_str); return -1; } + addr->host =3D g_strndup(addr_str + 1, ip_end - addr_str - 1); + addr->port =3D g_strdup(ip_end + 2); } else { - /* hostname or IPv4 addr */ - if (sscanf(str, "%64[^:]:%32[^,]%n", host, port, &pos) !=3D 2) { - error_setg(errp, "error parsing address '%s'", str); + /* no host, hostname or IPv4 addr */ + const char *port =3D strchr(addr_str, ':'); + if (!port || strlen(port) < 2) { + error_setg(errp, "error parsing address '%s'", addr_str); return -1; } + addr->host =3D g_strndup(addr_str, port - addr_str); + addr->port =3D g_strdup(port + 1); } =20 - addr->host =3D g_strdup(host); - addr->port =3D g_strdup(port); - /* parse options */ - optstr =3D str + pos; - h =3D strstr(optstr, ",to=3D"); - if (h) { - h +=3D 4; - if (sscanf(h, "%d%n", &to, &pos) !=3D 1 || - (h[pos] !=3D '\0' && h[pos] !=3D ',')) { - error_setg(errp, "error parsing to=3D argument"); - return -1; - } + if (qemu_opt_find(opts, "numeric")) { + addr->has_numeric =3D true, + addr->numeric =3D qemu_opt_get_bool(opts, "numeric", false); + } + if (qemu_opt_find(opts, "to")) { addr->has_to =3D true; - addr->to =3D to; + addr->to =3D qemu_opt_get_number(opts, "to", 0); } - begin =3D strstr(optstr, ",ipv4"); - if (begin) { - if (inet_parse_flag("ipv4", begin + 5, &addr->ipv4, errp) < 0) { - return -1; - } + if (qemu_opt_find(opts, "ipv4")) { addr->has_ipv4 =3D true; + addr->ipv4 =3D qemu_opt_get_bool(opts, "ipv4", false); } - begin =3D strstr(optstr, ",ipv6"); - if (begin) { - if (inet_parse_flag("ipv6", begin + 5, &addr->ipv6, errp) < 0) { - return -1; - } + if (qemu_opt_find(opts, "ipv6")) { addr->has_ipv6 =3D true; + addr->ipv6 =3D qemu_opt_get_bool(opts, "ipv6", false); } - begin =3D strstr(optstr, ",keep-alive"); - if (begin) { - if (inet_parse_flag("keep-alive", begin + strlen(",keep-alive"), - &addr->keep_alive, errp) < 0) - { - return -1; - } + if (qemu_opt_find(opts, "keep-alive")) { addr->has_keep_alive =3D true; + addr->keep_alive =3D qemu_opt_get_bool(opts, "keep-alive", false); } #ifdef HAVE_IPPROTO_MPTCP - begin =3D strstr(optstr, ",mptcp"); - if (begin) { - if (inet_parse_flag("mptcp", begin + strlen(",mptcp"), - &addr->mptcp, errp) < 0) - { - return -1; - } + if (qemu_opt_find(opts, "mptcp")) { addr->has_mptcp =3D true; + addr->mptcp =3D qemu_opt_get_bool(opts, "mptcp", 0); } #endif return 0; --=20 2.49.0