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=ecIeJSQj; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 95FF25A0625 for ; Wed, 17 Dec 2025 22:31:51 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1766007110; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=KY/smdmzIT3Uy9qXFAYSVEu6FMWVvi2uj3eAGx5Ir0o=; b=ecIeJSQjKCFYKb6WogxOg7WpPLM8AFfwL5Oi0wEaX5kjy5GF+sSjM163HIFY8bIZnIl610 ruglENegC7jkz4Hsq5DTjL4rEx9zvcU2R/oGoGewFmB2SpX/FEPBAdqs8ADGgQKIKxesM/ pgnbNE2GPUV20ISisVjQ4RwWoO41ql4= 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-353-kXA5iLJjMOuf5LBRd51FlQ-1; Wed, 17 Dec 2025 16:31:47 -0500 X-MC-Unique: kXA5iLJjMOuf5LBRd51FlQ-1 X-Mimecast-MFC-AGG-ID: kXA5iLJjMOuf5LBRd51FlQ_1766007106 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-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id AB05F18001D1; Wed, 17 Dec 2025 21:31:45 +0000 (UTC) Received: from jmaloy-thinkpadp16vgen1.rmtcaqc.csb (unknown [10.22.88.106]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 0662D30001A2; Wed, 17 Dec 2025 21:31:43 +0000 (UTC) From: Jon Maloy To: sbrivio@redhat.com, dgibson@redhat.com, david@gibson.dropbear.id.au, jmaloy@redhat.com, passt-dev@passt.top Subject: [PATCH v2] conf: Support CIDR notation for -a/--address option Date: Wed, 17 Dec 2025 16:31:42 -0500 Message-ID: <20251217213142.358407-1-jmaloy@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: haOF0brZbPa4ymBm9lRJAque8ABAynQEYXOUsb-AoME_1766007106 X-Mimecast-Originator: redhat.com Content-Transfer-Encoding: 8bit content-type: text/plain; charset="US-ASCII"; x-default=true Message-ID-Hash: 7CEZKO256AU67GAMVTFO74YAE77EF44B X-Message-ID-Hash: 7CEZKO256AU67GAMVTFO74YAE77EF44B X-MailFrom: jmaloy@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 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: Extend the -a/--address option to accept addresses in CIDR notation (e.g., 192.168.1.1/24 or 2001:db8::1/64) as an alternative to using separate -a and -n options. Add conf_addr_prefix() helper function that: - Parses address strings with optional /prefix suffix - Validates prefix length based on address family (0-32 for IPv4, 0-128 for IPv6) - Returns address family and fills address/prefix output parameters For IPv4, the prefix is stored in ip4.prefix_len when provided. Multiple CIDR addresses use last-wins semantics for the prefix, consistent with how addresses are handled currently. However, mixing -n and CIDR notation results in an error to catch likely user mistakes. Also fix a bug in conf_ip4_prefix() that was incorrectly using the global 'optarg' instead of its 'arg' parameter. Signed-off-by: Jon Maloy --- v2: Fixed incorrect error printout, as noted by Laurent Vivier. We now keep the old semantics, i.e., allowing multiple -a options for each protocol. This semantics looks wrong, but will anyway be fixed in my upcoming series. Signed-off-by: Jon Maloy --- conf.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/conf.c b/conf.c index 2942c8c..764b910 100644 --- a/conf.c +++ b/conf.c @@ -682,7 +682,7 @@ static int conf_ip4_prefix(const char *arg) return -1; } else { errno = 0; - len = strtoul(optarg, NULL, 0); + len = strtoul(arg, NULL, 0); if (len > 32 || errno) return -1; } @@ -690,6 +690,56 @@ static int conf_ip4_prefix(const char *arg) return len; } +/** + * conf_addr_prefix() - Parse address with optional /prefix notation + * @arg: Address string, optionally with /prefix + * @addr4: Output for IPv4 address + * @addr6: Output for IPv6 address + * @prefix: Output for prefix length (0 if not specified) + * + * Return: AF_INET for IPv4, AF_INET6 for IPv6, -1 on error + */ +static int conf_addr_prefix(const char *arg, struct in_addr *addr4, + struct in6_addr *addr6, int *prefix) +{ + char buf[INET6_ADDRSTRLEN + sizeof("/128")]; + char *slash; + + *prefix = 0; + + if (snprintf(buf, sizeof(buf), "%s", arg) >= (int)sizeof(buf)) + return -1; + + /* Check for /prefix suffix */ + slash = strchr(buf, '/'); + if (slash) { + unsigned long len; + char *end; + + *slash = '\0'; + errno = 0; + len = strtoul(slash + 1, &end, 10); + if (errno || *end) + return -1; + + *prefix = len; + } + + if (inet_pton(AF_INET6, buf, addr6) == 1) { + if (*prefix > 128) + return -1; + return AF_INET6; + } + + if (inet_pton(AF_INET, buf, addr4) == 1) { + if (*prefix > 32) + return -1; + return AF_INET; + } + + return -1; +} + /** * conf_ip4() - Verify or detect IPv4 support, get relevant addresses * @ifi: Host interface to attempt (0 to determine one) @@ -896,7 +946,7 @@ static void usage(const char *name, FILE *f, int status) " a zero value disables assignment\n" " default: 65520: maximum 802.3 MTU minus 802.3 header\n" " length, rounded to 32 bits (IPv4 words)\n" - " -a, --address ADDR Assign IPv4 or IPv6 address ADDR\n" + " -a, --address ADDR Assign IPv4 or IPv6 address ADDR[/PREFIX]\n" " can be specified zero to two times (for IPv4 and IPv6)\n" " default: use addresses from interface with default route\n" " -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits\n" @@ -1499,6 +1549,7 @@ void conf(struct ctx *c, int argc, char **argv) const char *logname = (c->mode == MODE_PASTA) ? "pasta" : "passt"; char userns[PATH_MAX] = { 0 }, netns[PATH_MAX] = { 0 }; bool copy_addrs_opt = false, copy_routes_opt = false; + bool prefix_from_cidr = false, prefix_from_opt = false; enum fwd_ports_mode fwd_default = FWD_NONE; bool v4_only = false, v6_only = false; unsigned dns4_idx = 0, dns6_idx = 0; @@ -1808,23 +1859,38 @@ void conf(struct ctx *c, int argc, char **argv) c->mtu = mtu; break; } - case 'a': - if (inet_pton(AF_INET6, optarg, &c->ip6.addr) && - !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr) && - !IN6_IS_ADDR_LOOPBACK(&c->ip6.addr) && - !IN6_IS_ADDR_V4MAPPED(&c->ip6.addr) && - !IN6_IS_ADDR_V4COMPAT(&c->ip6.addr) && - !IN6_IS_ADDR_MULTICAST(&c->ip6.addr)) { + case 'a': { + struct in6_addr addr6; + struct in_addr addr4; + int prefix = 0; + int af; + + af = conf_addr_prefix(optarg, &addr4, &addr6, &prefix); + + if (af == AF_INET6 && + !IN6_IS_ADDR_UNSPECIFIED(&addr6) && + !IN6_IS_ADDR_LOOPBACK(&addr6) && + !IN6_IS_ADDR_V4MAPPED(&addr6) && + !IN6_IS_ADDR_V4COMPAT(&addr6) && + !IN6_IS_ADDR_MULTICAST(&addr6)) { + c->ip6.addr = addr6; if (c->mode == MODE_PASTA) c->ip6.no_copy_addrs = true; break; } - if (inet_pton(AF_INET, optarg, &c->ip4.addr) && - !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.addr) && - !IN4_IS_ADDR_BROADCAST(&c->ip4.addr) && - !IN4_IS_ADDR_LOOPBACK(&c->ip4.addr) && - !IN4_IS_ADDR_MULTICAST(&c->ip4.addr)) { + if (af == AF_INET && + !IN4_IS_ADDR_UNSPECIFIED(&addr4) && + !IN4_IS_ADDR_BROADCAST(&addr4) && + !IN4_IS_ADDR_LOOPBACK(&addr4) && + !IN4_IS_ADDR_MULTICAST(&addr4)) { + c->ip4.addr = addr4; + if (prefix) { + if (prefix_from_opt) + die("Can't use both -n and CIDR prefix"); + c->ip4.prefix_len = prefix; + prefix_from_cidr = true; + } if (c->mode == MODE_PASTA) c->ip4.no_copy_addrs = true; break; @@ -1832,11 +1898,14 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid address: %s", optarg); break; + } case 'n': + if (prefix_from_cidr) + die("Can't use both -n and CIDR prefix"); c->ip4.prefix_len = conf_ip4_prefix(optarg); if (c->ip4.prefix_len < 0) die("Invalid netmask: %s", optarg); - + prefix_from_opt = true; break; case 'M': parse_mac(c->our_tap_mac, optarg); -- 2.52.0