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=DV2Ch8xu; 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 172395A0624 for ; Thu, 22 Jan 2026 01:06:16 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1769040374; 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=m0FLZBN+GjpwguwhXiZc8Z0l4aVBDSieuGCC8qgKqgc=; b=DV2Ch8xuius1W9Hq2ylkOeqnpLZPUrLrQ8daO4dyJ/RbDqbGst29TS71dYqy5rSmsFd93B EIc3ZMTlhUsXBul31/61RndDdltFm9tSWNwXRfU5VePH/vR2DVdaOB3igg9m6rF3kMDmlN v7vmMbwn8lF5/mKOyaHhH6kAxLSfFx4= Received: from mail-qv1-f72.google.com (mail-qv1-f72.google.com [209.85.219.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-695-ZxdbgyTrOpa7Ug1GWzAxaA-1; Wed, 21 Jan 2026 19:06:13 -0500 X-MC-Unique: ZxdbgyTrOpa7Ug1GWzAxaA-1 X-Mimecast-MFC-AGG-ID: ZxdbgyTrOpa7Ug1GWzAxaA_1769040373 Received: by mail-qv1-f72.google.com with SMTP id 6a1803df08f44-88a360b8086so13172076d6.3 for ; Wed, 21 Jan 2026 16:06:13 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769040372; x=1769645172; h=content-transfer-encoding:in-reply-to:from:content-language :references:cc:to:subject:user-agent:mime-version:date:message-id :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=m0FLZBN+GjpwguwhXiZc8Z0l4aVBDSieuGCC8qgKqgc=; b=tH89uBGQ3hxK1GZ/txjSN7Dl9EkfdZ8OgrE/H3lUy/LuaGswIlaTMsobBgHM5bWsSb UGi7r6XANBAFZEzY5v1eEk4Bue9p6dKKXtqx6vJ30Kj7FW7RhzUnUW167/1M4SUCA3Y7 p+yBGhWIaKV3/9zb+zs6BYigwJ33VurtankTi1m+CJN7QubR4BdFN8K+omPjif8LHctt vvDQS155Kq7W68jjl3LxZqHm3mGgTnMbfuMUmLf9SVj/hM2O3Oww2GmysLVRNiwNaS1U ReJtezHZ7QVYxvHYK8mSZ2yI9pIQyKjRjy+8Dt85Zt3XYbZ2t84sxEV79Ug9ZAd4tt8r hG+w== X-Forwarded-Encrypted: i=1; AJvYcCWCVFsjpTvO842sRmThKQixSSTek32P32yV4Wa6wETjaiJT5bBzIdkM8PjKXnwWqUVJsXtLQEOCJ6o=@passt.top X-Gm-Message-State: AOJu0YzBrJnB1KFxqHYqiIyFtl0xykb0VAYASAJ55TM3eSmgimf52s9N nx8WHUCtZYcZO9pqHnW1RCh6IcBDeZS1VTrXbbNOSFl2IWTlLuUrj/xf/zwrhPVsPHLJwKKStLb L4Z+tCLwVR3pSY9vKp+n9Ui2Sr4NEe/wGhVMM/WomNuMe5M8WHTO/sA== X-Gm-Gg: AZuq6aKYwGgSr40WSpprFobh47uDcteotrAna57MdHOLO9rJk9fGuLOgDjBxQa6dVFC SY6H8QQzmYiKPHkaOaNdUZv9in0ovX/jP9p7la+vbGuH1I9uXcGekmEgy/kUIWMJkE5xX73w0WD a40s5vamaMan2n1Wt2YsGKAptLtYOjuEWf9ub9oTn7DhFv+H9mnAjSy98liIszkh0WUbMHXTkAV lMxWa4aXAl2X3UuxvdsaGskSlnoAUBdndhVjpFYSn3p9o/o2AUzBYMOX/GpY0eaM2IJGL2FPxbR UwUE7S5D3Fd3VLIrWSHB0Ca3r1/b35zU1o/xamV64k9GW2Rup0/4Q2STIqolgqTdc4rs4/uUBB1 98xd1SDCXGYp/c5ER0OCaLmB5El7xqJreE+vGtiavzvkuNppLlU3wcw+NEfpc5BMu3z2xHp7Z4t WOnFme X-Received: by 2002:a05:6214:1d06:b0:888:4930:4c8b with SMTP id 6a1803df08f44-8942ddf3920mr261993666d6.69.1769040372534; Wed, 21 Jan 2026 16:06:12 -0800 (PST) X-Received: by 2002:a05:6214:1d06:b0:888:4930:4c8b with SMTP id 6a1803df08f44-8942ddf3920mr261993266d6.69.1769040371969; Wed, 21 Jan 2026 16:06:11 -0800 (PST) Received: from [192.168.2.15] (lnsm3-montreal02-142-116-222-198.internet.virginmobile.ca. [142.116.222.198]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8942e6ad8b0sm139177946d6.28.2026.01.21.16.06.11 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Wed, 21 Jan 2026 16:06:11 -0800 (PST) Message-ID: <8ac549ba-c893-4e68-ac1c-72974781fa4e@redhat.com> Date: Wed, 21 Jan 2026 19:06:10 -0500 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH v2 1/9] conf: Support CIDR notation for -a/--address option To: David Gibson References: <20260118221612.2115386-1-jmaloy@redhat.com> <20260118221612.2115386-2-jmaloy@redhat.com> From: Jon Maloy In-Reply-To: X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: P1dasUi6Zbdw8CXqEbQ6dLGxvIJDVP8Z2bN0JjZ4Piw_1769040373 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit Message-ID-Hash: SFOET2TXAEHAF3ZE4AFENA2CJDZA2FYM X-Message-ID-Hash: SFOET2TXAEHAF3ZE4AFENA2CJDZA2FYM 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 CC: sbrivio@redhat.com, dgibson@redhat.com, passt-dev@passt.top 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: On 2026-01-19 00:02, David Gibson wrote: > On Sun, Jan 18, 2026 at 05:16:04PM -0500, Jon Maloy wrote: >> 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. >> >> We add a new conf_addr_prefix_len() helper function that: >> - Parses address strings with optional /prefix_len suffix >> - Validates prefix length based on address family (0-32 for IPv4, >> 0-128 for IPv6), including handling of IPv4-to-IPv6 mapping case. >> - Returns address family via union inany_addr output parameter >> >> For IPv4, the prefix length is stored in ip4.prefix_len when provided. >> 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 >> >> --- >> v3: Fixes after feedback from Laurent, David and Stefano >> Notably, updated man page for the -a option >> >> v4: Fixes based on feedback from David G: >> - Handling prefix length adjustment when IPv4-to-IPv6 mapping >> - Removed redundant !IN6_IS_ADDR_V4MAPPED(&addr.a6) test >> - Simplified tests of acceptable address types >> - Merged documentation and code commits >> - Some documentation text clarifications >> --- >> conf.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++---------- >> inany.c | 29 +++++++++++++++++ >> inany.h | 1 + >> ip.c | 21 +++++++++++++ >> ip.h | 2 ++ >> passt.1 | 17 +++++++--- >> 6 files changed, 145 insertions(+), 22 deletions(-) >> >> diff --git a/conf.c b/conf.c >> index 2942c8c..7178a0e 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,52 @@ static int conf_ip4_prefix(const char *arg) >> return len; >> } >> >> +/** >> + * conf_addr_prefix_len() - Parse address with optional prefix length >> + * @arg: Address string, optionally with /prefix_len suffix (modified) >> + * @addr: Output for parsed address >> + * @prefix_len: 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_len(char *arg, union inany_addr *addr, >> + int *prefix_len) >> +{ >> + char *slash; >> + >> + *prefix_len = 0; >> + >> + /* Check for /prefix_len suffix */ >> + slash = strchr(arg, '/'); >> + if (slash) { >> + unsigned long len; >> + char *end; >> + >> + *slash = '\0'; >> + errno = 0; >> + len = strtoul(slash + 1, &end, 10); >> + if (errno || *end) >> + return -1; >> + >> + *prefix_len = len; >> + } >> + >> + if (!inany_prefix_pton(arg, addr, prefix_len)) >> + return -1; > > Oh, sorry, I wasn't clear. My idea was that inany_prefix_pton() would > handle the parsing (strchr(), strtoul() etc.) of the prefix length > internally, rather than doing that here then adjusting it in there. Ok. So I basically eliminate conf_addr_perfix_len() and replace it with a inany_prefix_pton() which does all the job. Makes sense. Regarding the prefix length adjustment: if I give some address in one of the following two formats: 192.168.1.1/24 or ::ffff:192.168.1.1/120 they will both be stored in the array in exactly the same format, as ::ffff:192.168.1.1. It makes little sense to use two different prefix lengths just because the user happened to use some specific format, and would only create problems for anybody trying to access that entry later. I think this one *should* be adjusted. ///jon >> + >> + if (inany_v4(addr)) { >> + if (*prefix_len > 32) >> + return -1; >> + >> + return AF_INET; >> + } >> + >> + if (*prefix_len > 128) >> + return -1; >> + >> + return AF_INET6; >> +} >> + >> /** >> * conf_ip4() - Verify or detect IPv4 support, get relevant addresses >> * @ifi: Host interface to attempt (0 to determine one) >> @@ -896,7 +942,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[/PREFIXLEN]\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 +1545,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,35 +1855,51 @@ 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': { >> + union inany_addr addr; >> + const struct in_addr *a4; >> + int prefix_len = 0; >> + int af; >> + >> + af = conf_addr_prefix_len(optarg, &addr, &prefix_len); >> + >> + if (inany_is_unspecified(&addr) || >> + inany_is_multicast(&addr) || >> + inany_is_loopback(&addr) || >> + IN6_IS_ADDR_V4COMPAT(&addr.a6)) >> + die("Invalid address: %s", optarg); >> + >> + if (af == AF_INET6) { >> + c->ip6.addr = addr.a6; >> 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 (c->mode == MODE_PASTA) >> - c->ip4.no_copy_addrs = true; >> + a4 = inany_v4(&addr); >> + if (af == AF_INET && a4) { >> + c->ip4.addr = *a4; >> + if (prefix_len) { >> + if (prefix_from_opt) >> + die("Can't mix CIDR with -n"); >> + prefix_from_cidr = true; >> + } else { >> + prefix_len = ip4_default_prefix_len(a4); >> + } >> + c->ip4.prefix_len = prefix_len; >> break; >> } >> >> die("Invalid address: %s", optarg); >> break; >> + } >> case 'n': >> + if (prefix_from_cidr) >> + die("Can't use both -n and CIDR prefix length"); >> 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); >> diff --git a/inany.c b/inany.c >> index 7680439..f142a76 100644 >> --- a/inany.c >> +++ b/inany.c >> @@ -57,3 +57,32 @@ int inany_pton(const char *src, union inany_addr *dst) >> >> return 0; >> } >> + >> +/** inany_prefix_pton - Parse an IPv[46] address with prefix length adjustment >> + * @src: IPv[46] address string >> + * @dst: output buffer, filled with parsed address >> + * @prefix_len: pointer to prefix length >> + * >> + * Return: on success, 1, if no parseable address is found, 0 >> + */ >> +int inany_prefix_pton(const char *src, union inany_addr *dst, int *prefix_len) >> +{ >> + /* First try parsing as plain IPv4 */ >> + if (inet_pton(AF_INET, src, &dst->v4mapped.a4)) { >> + memset(&dst->v4mapped.zero, 0, sizeof(dst->v4mapped.zero)); >> + memset(&dst->v4mapped.one, 0xff, sizeof(dst->v4mapped.one)); >> + return 1; >> + } >> + >> + /* Try parsing as IPv6, adjust prefix length if mapped IPv4 address */ >> + if (inet_pton(AF_INET6, src, &dst->a6)) { >> + if (inany_v4(dst) && prefix_len && *prefix_len > 0) { >> + if (*prefix_len < 96) >> + return 0; >> + *prefix_len -= 96; > > Because inany_addr is essentially an IPv6 representation, I think it > makes more sense to always return the prefix_length as it would be for > IPv6, not dependent on the address type. (So, add 96 in the > inet_pton() case, rather than subtracting 96 in the explicit v4-mapped > case). > >> + } >> + return 1; >> + } >> + >> + return 0; >> +} >> diff --git a/inany.h b/inany.h >> index 61b36fb..36865f9 100644 >> --- a/inany.h >> +++ b/inany.h >> @@ -295,5 +295,6 @@ static inline void inany_siphash_feed(struct siphash_state *state, >> >> const char *inany_ntop(const union inany_addr *src, char *dst, socklen_t size); >> int inany_pton(const char *src, union inany_addr *dst); >> +int inany_prefix_pton(const char *src, union inany_addr *dst, int *prefix_len); >> >> #endif /* INANY_H */ >> diff --git a/ip.c b/ip.c >> index 9a7f4c5..2519c71 100644 >> --- a/ip.c >> +++ b/ip.c >> @@ -13,6 +13,8 @@ >> */ >> >> #include >> +#include >> + >> #include "util.h" >> #include "ip.h" >> >> @@ -67,3 +69,22 @@ found: >> *proto = nh; >> return true; >> } >> + >> +/** >> + * ip4_default_prefix_len() - Get default prefix length for IPv4 address >> + * @addr: IPv4 address >> + * >> + * Return: prefix length based on address class (8/16/24), or 32 for other >> + */ >> +int ip4_default_prefix_len(const struct in_addr *addr) >> +{ >> + in_addr_t a = ntohl(addr->s_addr); >> + >> + if (IN_CLASSA(a)) >> + return 8; >> + if (IN_CLASSB(a)) >> + return 16; >> + if (IN_CLASSC(a)) >> + return 24; >> + return 32; >> +} >> diff --git a/ip.h b/ip.h >> index 5830b92..e5f5198 100644 >> --- a/ip.h >> +++ b/ip.h >> @@ -135,4 +135,6 @@ static const struct in_addr in4addr_broadcast = { 0xffffffff }; >> #define IPV6_MIN_MTU 1280 >> #endif >> >> +int ip4_default_prefix_len(const struct in_addr *addr); >> + >> #endif /* IP_H */ >> diff --git a/passt.1 b/passt.1 >> index db0d662..7ca03be 100644 >> --- a/passt.1 >> +++ b/passt.1 >> @@ -156,10 +156,14 @@ By default, the advertised MTU is 65520 bytes, that is, the maximum 802.3 MTU >> minus the length of a 802.3 header, rounded to 32 bits (IPv4 words). >> >> .TP >> -.BR \-a ", " \-\-address " " \fIaddr >> +.BR \-a ", " \-\-address " " \fIaddr\fR[\fB/\fR\fIprefix_len\fR] >> Assign IPv4 \fIaddr\fR via DHCP (\fByiaddr\fR), or \fIaddr\fR via DHCPv6 (option >> 5) and an \fIaddr\fR-based prefix via NDP Router Advertisement (option type 3) >> for an IPv6 \fIaddr\fR. >> +An optional \fB/\fR\fIprefix_len\fR (0-32 for IPv4, 0-128 for IPv6) can be >> +appended in CIDR notation (e.g., 192.168.1.1/24). This is an alternative to >> +using the \fB-n\fR, \fB--netmask\fR option. Mixing CIDR notation with >> +\fB-n\fR results in an error. >> This option can be specified zero (for defaults) to two times (once for IPv4, >> once for IPv6). >> By default, assigned IPv4 and IPv6 addresses are taken from the host interfaces >> @@ -172,10 +176,13 @@ is assigned for IPv4, and no additional address will be assigned for IPv6. >> .TP >> .BR \-n ", " \-\-netmask " " \fImask >> Assign IPv4 netmask \fImask\fR, expressed as dot-decimal or number of bits, via >> -DHCP (option 1). >> -By default, the netmask associated to the host address matching the assigned one >> -is used. If there's no matching address on the host, the netmask is determined >> -according to the CIDR block of the assigned address (RFC 4632). >> +DHCP (option 1). Alternatively, the prefix length can be specified using CIDR >> +notation with the \fB-a\fR, \fB--address\fR option (e.g., \fB-a\fR 192.168.1.1/24). >> +Mixing \fB-n\fR with CIDR notation results in an error. >> +If no address is indicated, the netmask associated with the adopted host address, >> +if any, is used. If an address is indicated, but without a prefix length, the >> +netmask is determined based on the corresponding network class. In all other >> +cases, the netmask is determined by using the indicated prefix length. >> >> .TP >> .BR \-M ", " \-\-mac-addr " " \fIaddr >> -- >> 2.52.0 >> >