From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202512 header.b=eFS7gYh3; dkim-atps=neutral Received: from mail.ozlabs.org (gandalf.ozlabs.org [150.107.74.76]) by passt.top (Postfix) with ESMTPS id E426C5A004E for ; Mon, 12 Jan 2026 04:16:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202512; t=1768187796; bh=Z857y20B/Drwz5fbv4GWcKktWG+1TDtaBmonUL6y2cQ=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=eFS7gYh3kr2Q8hxwOmJ8MSPGU+vMBnO00ojW7N8uNRDXliIKNgsu388Zh+zNrQElK 8ia9L5voUwS/VYcrBBAIti3X9F0uvtryzucobk9KYk5iXTb+2yuEWq93WqEVM6sPXn sK3SEF6cLfYF86bhzO+rK+SOi/mjAUj7QmverzAC1htWOX6kQ6oVGij74kZNiP9r2N kHPwdjnSyxqhvWJE7NFA82g4/cNz+UOih3DM4nAxfGyxQ7zFyGEO1RvIMcMOVFPczY hXXNkDZfwhlm+j5kTUqJu8LzKq5ODAxF8QvrMhSgIFxYoe6+CExVcZ1X7ZkvzUVIXu HiA4WVtFVGYZg== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4dqHbS71Vdz4w9k; Mon, 12 Jan 2026 14:16:36 +1100 (AEDT) Date: Mon, 12 Jan 2026 14:16:31 +1100 From: David Gibson To: Jon Maloy Subject: Re: [PATCH v4] conf: Support CIDR notation for -a/--address option Message-ID: References: <20260110225936.3200705-1-jmaloy@redhat.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="EC8lHrd1c7BiVDYY" Content-Disposition: inline In-Reply-To: <20260110225936.3200705-1-jmaloy@redhat.com> Message-ID-Hash: W6WCKXY3C7W2FY5TE2NLSJIJNNKSEUM6 X-Message-ID-Hash: W6WCKXY3C7W2FY5TE2NLSJIJNNKSEUM6 X-MailFrom: dgibson@gandalf.ozlabs.org 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: --EC8lHrd1c7BiVDYY Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Sat, Jan 10, 2026 at 05:59:36PM -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. >=20 > 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 >=20 > 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. >=20 > Also fix a bug in conf_ip4_prefix() that was incorrectly using the > global 'optarg' instead of its 'arg' parameter. >=20 > Signed-off-by: Jon Maloy >=20 > --- > v3: Fixes after feedback from Laurent, David and Stefano > Notably, updated man page for the -a option >=20 > 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 | 91 +++++++++++++++++++++++++++++++++++++++++++++++---------- > inany.c | 29 ++++++++++++++++++ > inany.h | 1 + > passt.1 | 17 +++++++---- > 4 files changed, 118 insertions(+), 20 deletions(-) >=20 > diff --git a/conf.c b/conf.c > index 2942c8c..b36abf3 100644 > --- a/conf.c > +++ b/conf.c > @@ -682,7 +682,7 @@ static int conf_ip4_prefix(const char *arg) > return -1; > } else { > errno =3D 0; > - len =3D strtoul(optarg, NULL, 0); > + len =3D strtoul(arg, NULL, 0); > if (len > 32 || errno) > return -1; > } > @@ -690,6 +690,50 @@ static int conf_ip4_prefix(const char *arg) > return len; > } > =20 > +/** > + * 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 =3D 0; > + > + /* Check for /prefix_len suffix */ > + slash =3D strchr(arg, '/'); > + if (slash) { > + unsigned long len; > + char *end; > + > + *slash =3D '\0'; > + errno =3D 0; > + len =3D strtoul(slash + 1, &end, 10); > + if (errno || *end) > + return -1; > + > + *prefix_len =3D len; > + } > + > + if (!inany_prefix_pton(arg, addr, prefix_len)) > + return -1; The handling of prefix lengths is correct now, but in a really confusing way. This function now returns addresses in a uniform way (essentially as IPv6), but the prefix has a different meaning depending on what type of address it is internally. We parse the prefix length here but the actualy address elsewhere. I think what would make more sense would be to make inany_prefix_pton() parse a full CIDR address itself, with prefix_length becoming a pure output parameter. prefix_length should always be returned in IPv6 form - so add 96 if the input is an IPv4 address. Yes, that means re-correcting it when going from the v4-mapped inany address to the v4-only fields in ctx.ip4, but I think that's preferable to having non-consistent semantics in the interface of a single function. > + > + 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 +940,7 @@ static void usage(const char *name, FILE *f, int stat= us) > " 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 +1543,7 @@ void conf(struct ctx *c, int argc, char **argv) > const char *logname =3D (c->mode =3D=3D MODE_PASTA) ? "pasta" : "passt"; > char userns[PATH_MAX] =3D { 0 }, netns[PATH_MAX] =3D { 0 }; > bool copy_addrs_opt =3D false, copy_routes_opt =3D false; > + bool prefix_from_cidr =3D false, prefix_from_opt =3D false; > enum fwd_ports_mode fwd_default =3D FWD_NONE; > bool v4_only =3D false, v6_only =3D false; > unsigned dns4_idx =3D 0, dns6_idx =3D 0; > @@ -1808,23 +1853,36 @@ void conf(struct ctx *c, int argc, char **argv) > c->mtu =3D 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 =3D 0; > + int af; > + > + af =3D 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 =3D=3D AF_INET6) { > + c->ip6.addr =3D addr.a6; > if (c->mode =3D=3D MODE_PASTA) > c->ip6.no_copy_addrs =3D true; Is there a reason not to do the prefix_from_cidr / prefix_from_out checks in the IPv6 case? > break; > } > =20 > - 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)) { > + a4 =3D inany_v4(&addr); > + if (af =3D=3D AF_INET && a4) { > + c->ip4.addr =3D *a4; > + if (prefix_len) { > + if (prefix_from_opt) > + die("Can't mix CIDR with -n"); > + c->ip4.prefix_len =3D prefix_len; > + prefix_from_cidr =3D true; > + } > if (c->mode =3D=3D MODE_PASTA) > c->ip4.no_copy_addrs =3D true; > break; > @@ -1832,11 +1890,14 @@ void conf(struct ctx *c, int argc, char **argv) > =20 > 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 =3D conf_ip4_prefix(optarg); > if (c->ip4.prefix_len < 0) > die("Invalid netmask: %s", optarg); > - > + prefix_from_opt =3D 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) > =20 > return 0; > } > + > +/** inany_prefix_pton - Parse an IPv[46] address with prefix length adju= stment > + * @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 *prefi= x_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 -=3D 96; > + } > + 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, > =20 > 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 *prefi= x_len); > =20 > #endif /* INANY_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). > =20 > .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 t= ype 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 int= erfaces > @@ -172,10 +176,13 @@ is assigned for IPv4, and no additional address wil= l be assigned for IPv6. > .TP > .BR \-n ", " \-\-netmask " " \fImask > Assign IPv4 netmask \fImask\fR, expressed as dot-decimal or number of bi= ts, via > -DHCP (option 1). > -By default, the netmask associated to the host address matching the assi= gned one > -is used. If there's no matching address on the host, the netmask is dete= rmined > -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.1= 68.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 o= ther > +cases, the netmask is determined by using the indicated prefix length. > =20 > .TP > .BR \-M ", " \-\-mac-addr " " \fIaddr > --=20 > 2.52.0 >=20 --=20 David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson --EC8lHrd1c7BiVDYY Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmlkZ34ACgkQzQJF27ox 2GdWOw//WAL1+4xR1smZwqcrC9QN4c4IvoGb1DpO4q0GY2DTQYSkDmNswXl2v/G3 BA59jz9Ty2AUuF8uyupIlgzsAURN3AHwcxyMPrlkavENLYLrmB44rIpjxtvKAamn 8KdFPNkUCgFKRDScJ+VkQ0uGbdPrno+4l+vPNu2Ym5jp8RBYtxn482p01/YwGsmo pFDv6eSvLwspSuzEMA46zcVY+2G8qs1yRP+/Lnrgt7AL9IZn6dpzbe1tg0kZwggX 4bnmxhe11UFS01dTXbj8hbvssnyLFRHRtQPYQRCOSw8Hq19a9UhE6k73H5b0Mq9u qFbGrcWf8cGTGBHuIjnhZLro0YzSJyT7JG4euJstuvLLDe9mYmLROtSnEi+NSC4M UaMwgLdqaX9sB13ETMzzRTtQ58REgkXZ9tLY0mUs1N1tk+doNlKzcoE3y8SW3fRX n/uk+W1rzeiQjwYxw5lV+kZ7nE9UgvJx48yclHajwks7wOPYWXgvgCmiops4YztO Zq9QyvxkV0hPno0tsow3y1/9PryyVOzijfhsokKyNzvhWADuMDtGRTdDejgCsuDr svAnCe+nVp6fdVx0CWm7/Sd8a3XsYuJebLH+FsH/Xkpd2U6Gt9YAg1WHi8mhjeef zesLJfBEM72UEXDqkJR5bkRDM1CEp1DoKaZ61IvkjBMlHn/FaAY= =pEQ4 -----END PGP SIGNATURE----- --EC8lHrd1c7BiVDYY--