* [PATCH v9] conf: Support CIDR notation for -a/--address option
@ 2026-02-09 0:37 Jon Maloy
2026-02-09 23:19 ` David Gibson
2026-02-10 11:36 ` Stefano Brivio
0 siblings, 2 replies; 5+ messages in thread
From: Jon Maloy @ 2026-02-09 0:37 UTC (permalink / raw)
To: sbrivio, dgibson, david, jmaloy, passt-dev
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 inany_prefix_pton() helper function that:
- Parses address strings with a compulsory /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.
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 <jmaloy@redhat.com>
---
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
v5: - Moved address/prefix parsing into a refactored
inany_prefix_pton() function.
- inany_prefix_pton() now only caluclates IPv6 style
prefix lengths
- Stricter distinction between error causes.
- Some refactoring of the 'case a:' branch in conf()
- Some small fixes in passt.1
v6: - Refactored inany_prefix_pton() and conf()::'case -a'
code after input from David Gibson.
v7: - More refactoring after input from David Gibson.
- I kept the return values 1 and 0. This is consistent
with the return values of inet_pton() and inany_pton().
v8: - Changed condition for updating ipv4 prefix length
v9: - Made char *src and char *pstr in inany_prefix_pton() const
- Updated logics in conf.c:: case 'a' and case 'n' to
be clearer (I think)
---
conf.c | 72 +++++++++++++++++++++++++++++++++++++--------------------
inany.c | 51 ++++++++++++++++++++++++++++++++++++++++
inany.h | 15 ++++++++++++
ip.c | 21 +++++++++++++++++
ip.h | 2 ++
passt.1 | 17 ++++++++++----
6 files changed, 148 insertions(+), 30 deletions(-)
diff --git a/conf.c b/conf.c
index 2942c8c..46cfb6e 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;
}
@@ -896,7 +896,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"
@@ -1505,6 +1505,8 @@ void conf(struct ctx *c, int argc, char **argv)
unsigned long max_mtu = IP_MAX_MTU;
struct fqdn *dnss = c->dns_search;
unsigned int ifi4 = 0, ifi6 = 0;
+ bool prefix_from_cidr = false;
+ uint8_t prefix_from_opt = 0;
const char *logfile = NULL;
size_t logsize = 0;
char *runas = NULL;
@@ -1808,36 +1810,56 @@ 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)) {
- if (c->mode == MODE_PASTA)
- c->ip6.no_copy_addrs = true;
- break;
- }
+ case 'a': {
+ union inany_addr addr;
+ uint8_t prefix_len;
+
+ prefix_from_cidr =
+ inany_prefix_pton(optarg, &addr, &prefix_len);
+
+ if (prefix_from_cidr && prefix_from_opt)
+ die("Can't mix CIDR with -n");
+
+ if (!prefix_from_cidr && !inany_pton(optarg, &addr))
+ die("Invalid address: %s", optarg);
+
+ if (prefix_from_opt && inany_v4(&addr))
+ prefix_len = prefix_from_opt;
+ else if (!prefix_from_cidr)
+ prefix_len = inany_default_prefix_len(&addr);
+
+ 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 (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 (inany_v4(&addr)) {
+ c->ip4.addr = *inany_v4(&addr);
+ c->ip4.prefix_len = prefix_len - 96;
if (c->mode == MODE_PASTA)
c->ip4.no_copy_addrs = true;
- break;
+ } else {
+ c->ip6.addr = addr.a6;
+ if (c->mode == MODE_PASTA)
+ c->ip6.no_copy_addrs = true;
}
-
- die("Invalid address: %s", optarg);
break;
- case 'n':
- c->ip4.prefix_len = conf_ip4_prefix(optarg);
- if (c->ip4.prefix_len < 0)
- die("Invalid netmask: %s", optarg);
+ }
+ case 'n': {
+ int plen;
+
+ if (prefix_from_cidr)
+ die("Can't use both -n and CIDR prefix length");
+ plen = conf_ip4_prefix(optarg);
+ if (plen < 0)
+ die("Invalid prefix length: %s", optarg);
+
+ prefix_from_opt = plen + 96;
+ c->ip4.prefix_len = plen;
break;
+ }
case 'M':
parse_mac(c->our_tap_mac, optarg);
break;
diff --git a/inany.c b/inany.c
index 7680439..df6a126 100644
--- a/inany.c
+++ b/inany.c
@@ -11,6 +11,7 @@
#include <assert.h>
#include <netinet/in.h>
#include <arpa/inet.h>
+#include <errno.h>
#include "util.h"
#include "ip.h"
@@ -57,3 +58,53 @@ int inany_pton(const char *src, union inany_addr *dst)
return 0;
}
+
+/**
+ * inany_prefix_pton() - Parse an IPv[46] address with prefix length
+ * @src: IPv[46] address and prefix length string in CIDR format
+ * @dst: Output buffer, filled with parsed address
+ * @prefix_len: Prefix length, to be filled in IPv6 format
+ *
+ * Return: on success, 1, if no parseable address or prefix is found, 0
+ */
+int inany_prefix_pton(const char *src, union inany_addr *dst,
+ uint8_t *prefix_len)
+{
+ char astr[INANY_ADDRSTRLEN] = {0,};
+ size_t alen = strcspn(src, "/");
+ const char *pstr = &src[alen + 1];
+ unsigned long plen;
+ char *end;
+
+ if (alen >= INANY_ADDRSTRLEN)
+ return 0;
+
+ if (src[alen] != '/')
+ return 0;
+
+ strncpy(astr, src, alen);
+
+ /* Read prefix length */
+ errno = 0;
+ plen = strtoul(pstr, &end, 10);
+ if (errno || *end || plen > 128)
+ return 0;
+
+ *prefix_len = plen;
+
+ /* Read address */
+ if (inet_pton(AF_INET6, astr, dst)) {
+ if (inany_v4(dst) && *prefix_len < 96)
+ return 0;
+ return 1;
+ }
+
+ if (inany_pton(astr, dst)) {
+ if (*prefix_len > 32)
+ return 0;
+ *prefix_len += 96;
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/inany.h b/inany.h
index 61b36fb..c7d6027 100644
--- a/inany.h
+++ b/inany.h
@@ -96,6 +96,19 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr)
return (struct in_addr *)&addr->v4mapped.a4;
}
+/** inany_default_prefix_len() - Get default prefix length for address
+ * @addr: IPv4 or iPv6 address
+ *
+ * Return: Class-based prefix length for IPv4 (in IPv6 format: 104-128),
+ * or 64 for IPv6
+ */
+static inline int inany_default_prefix_len(const union inany_addr *addr)
+{
+ const struct in_addr *v4 = inany_v4(addr);
+
+ return v4 ? ip4_class_prefix_len(v4) + 96 : 64;
+}
+
/** inany_equals - Compare two IPv[46] addresses
* @a, @b: IPv[46] addresses
*
@@ -295,5 +308,7 @@ 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,
+ uint8_t *prefix_len);
#endif /* INANY_H */
diff --git a/ip.c b/ip.c
index 9a7f4c5..40dc24e 100644
--- a/ip.c
+++ b/ip.c
@@ -13,6 +13,8 @@
*/
#include <stddef.h>
+#include <netinet/in.h>
+
#include "util.h"
#include "ip.h"
@@ -67,3 +69,22 @@ found:
*proto = nh;
return true;
}
+
+/**
+ * ip4_class_prefix_len() - Get class based prefix length for IPv4 address
+ * @addr: IPv4 address
+ *
+ * Return: prefix length based on address class, or 32 for other
+ */
+int ip4_class_prefix_len(const struct in_addr *addr)
+{
+ in_addr_t a = ntohl(addr->s_addr);
+
+ if (IN_CLASSA(a))
+ return 32 - IN_CLASSA_NSHIFT;
+ if (IN_CLASSB(a))
+ return 32 - IN_CLASSB_NSHIFT;
+ if (IN_CLASSC(a))
+ return 32 - IN_CLASSC_NSHIFT;
+ return 32;
+}
diff --git a/ip.h b/ip.h
index 5830b92..bd28640 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_class_prefix_len(const struct in_addr *addr);
+
#endif /* IP_H */
diff --git a/passt.1 b/passt.1
index db0d662..2c3eae9 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[/\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 /\fIprefix_len\fR (0-32 for IPv4, 0-128 for IPv6) can be
+appended in CIDR notation (e.g. 192.0.2.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.0.2.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
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v9] conf: Support CIDR notation for -a/--address option
2026-02-09 0:37 [PATCH v9] conf: Support CIDR notation for -a/--address option Jon Maloy
@ 2026-02-09 23:19 ` David Gibson
2026-02-10 11:36 ` Stefano Brivio
1 sibling, 0 replies; 5+ messages in thread
From: David Gibson @ 2026-02-09 23:19 UTC (permalink / raw)
To: Jon Maloy; +Cc: sbrivio, dgibson, passt-dev
[-- Attachment #1: Type: text/plain, Size: 13312 bytes --]
On Sun, Feb 08, 2026 at 07:37:27PM -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 inany_prefix_pton() helper function that:
> - Parses address strings with a compulsory /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.
>
> 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 <jmaloy@redhat.com>
I have some comments below, but none of them are showstoppers, so:
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
>
> ---
> 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
>
> v5: - Moved address/prefix parsing into a refactored
> inany_prefix_pton() function.
> - inany_prefix_pton() now only caluclates IPv6 style
> prefix lengths
> - Stricter distinction between error causes.
> - Some refactoring of the 'case a:' branch in conf()
> - Some small fixes in passt.1
>
> v6: - Refactored inany_prefix_pton() and conf()::'case -a'
> code after input from David Gibson.
>
> v7: - More refactoring after input from David Gibson.
> - I kept the return values 1 and 0. This is consistent
> with the return values of inet_pton() and inany_pton().
Ah, good point, my mistake.
> v8: - Changed condition for updating ipv4 prefix length
>
> v9: - Made char *src and char *pstr in inany_prefix_pton() const
> - Updated logics in conf.c:: case 'a' and case 'n' to
> be clearer (I think)
> ---
> conf.c | 72 +++++++++++++++++++++++++++++++++++++--------------------
> inany.c | 51 ++++++++++++++++++++++++++++++++++++++++
> inany.h | 15 ++++++++++++
> ip.c | 21 +++++++++++++++++
> ip.h | 2 ++
> passt.1 | 17 ++++++++++----
> 6 files changed, 148 insertions(+), 30 deletions(-)
>
> diff --git a/conf.c b/conf.c
> index 2942c8c..46cfb6e 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;
> }
> @@ -896,7 +896,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"
> @@ -1505,6 +1505,8 @@ void conf(struct ctx *c, int argc, char **argv)
> unsigned long max_mtu = IP_MAX_MTU;
> struct fqdn *dnss = c->dns_search;
> unsigned int ifi4 = 0, ifi6 = 0;
> + bool prefix_from_cidr = false;
> + uint8_t prefix_from_opt = 0;
I see why, but having two variables with very similarly formatted
names, but different types and semantics is potentially confusing.
Using '0' as a nil value for the prefix length also makes me nervous,
since 0 length prefixes are in general valid, even if they're not
useful for the -a option.
> const char *logfile = NULL;
> size_t logsize = 0;
> char *runas = NULL;
> @@ -1808,36 +1810,56 @@ 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)) {
> - if (c->mode == MODE_PASTA)
> - c->ip6.no_copy_addrs = true;
> - break;
> - }
> + case 'a': {
> + union inany_addr addr;
> + uint8_t prefix_len;
> +
> + prefix_from_cidr =
> + inany_prefix_pton(optarg, &addr, &prefix_len);
> +
> + if (prefix_from_cidr && prefix_from_opt)
> + die("Can't mix CIDR with -n");
> +
> + if (!prefix_from_cidr && !inany_pton(optarg, &addr))
> + die("Invalid address: %s", optarg);
> +
> + if (prefix_from_opt && inany_v4(&addr))
> + prefix_len = prefix_from_opt;
> + else if (!prefix_from_cidr)
> + prefix_len = inany_default_prefix_len(&addr);
> +
> + 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 (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 (inany_v4(&addr)) {
> + c->ip4.addr = *inany_v4(&addr);
> + c->ip4.prefix_len = prefix_len - 96;
> if (c->mode == MODE_PASTA)
> c->ip4.no_copy_addrs = true;
> - break;
> + } else {
> + c->ip6.addr = addr.a6;
> + if (c->mode == MODE_PASTA)
> + c->ip6.no_copy_addrs = true;
> }
> -
> - die("Invalid address: %s", optarg);
> break;
> - case 'n':
> - c->ip4.prefix_len = conf_ip4_prefix(optarg);
> - if (c->ip4.prefix_len < 0)
> - die("Invalid netmask: %s", optarg);
> + }
> + case 'n': {
> + int plen;
> +
> + if (prefix_from_cidr)
> + die("Can't use both -n and CIDR prefix length");
>
> + plen = conf_ip4_prefix(optarg);
> + if (plen < 0)
> + die("Invalid prefix length: %s", optarg);
> +
> + prefix_from_opt = plen + 96;
Since it's only used for IPv4 adddresses, prefix_from_opt could encode
in IPv4-style (0..32, rather than 0..128). I don't have a strong
preference, though.
> + c->ip4.prefix_len = plen;
> break;
> + }
> case 'M':
> parse_mac(c->our_tap_mac, optarg);
> break;
> diff --git a/inany.c b/inany.c
> index 7680439..df6a126 100644
> --- a/inany.c
> +++ b/inany.c
> @@ -11,6 +11,7 @@
> #include <assert.h>
> #include <netinet/in.h>
> #include <arpa/inet.h>
> +#include <errno.h>
>
> #include "util.h"
> #include "ip.h"
> @@ -57,3 +58,53 @@ int inany_pton(const char *src, union inany_addr *dst)
>
> return 0;
> }
> +
> +/**
> + * inany_prefix_pton() - Parse an IPv[46] address with prefix length
> + * @src: IPv[46] address and prefix length string in CIDR format
> + * @dst: Output buffer, filled with parsed address
> + * @prefix_len: Prefix length, to be filled in IPv6 format
> + *
> + * Return: on success, 1, if no parseable address or prefix is found, 0
> + */
> +int inany_prefix_pton(const char *src, union inany_addr *dst,
> + uint8_t *prefix_len)
> +{
> + char astr[INANY_ADDRSTRLEN] = {0,};
I don't believe this needs an initializer.
> + size_t alen = strcspn(src, "/");
> + const char *pstr = &src[alen + 1];
> + unsigned long plen;
> + char *end;
> +
> + if (alen >= INANY_ADDRSTRLEN)
> + return 0;
> +
> + if (src[alen] != '/')
> + return 0;
> +
> + strncpy(astr, src, alen);
> +
> + /* Read prefix length */
> + errno = 0;
> + plen = strtoul(pstr, &end, 10);
> + if (errno || *end || plen > 128)
> + return 0;
> +
> + *prefix_len = plen;
It would be slightly cleaner not to clobber any of the output
parameters on failure, but it's not a big deal.
> +
> + /* Read address */
> + if (inet_pton(AF_INET6, astr, dst)) {
> + if (inany_v4(dst) && *prefix_len < 96)
> + return 0;
> + return 1;
> + }
> +
> + if (inany_pton(astr, dst)) {
> + if (*prefix_len > 32)
> + return 0;
> + *prefix_len += 96;
> + return 1;
> + }
> +
> + return 0;
> +}
> diff --git a/inany.h b/inany.h
> index 61b36fb..c7d6027 100644
> --- a/inany.h
> +++ b/inany.h
> @@ -96,6 +96,19 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr)
> return (struct in_addr *)&addr->v4mapped.a4;
> }
>
> +/** inany_default_prefix_len() - Get default prefix length for address
> + * @addr: IPv4 or iPv6 address
> + *
> + * Return: Class-based prefix length for IPv4 (in IPv6 format: 104-128),
> + * or 64 for IPv6
> + */
> +static inline int inany_default_prefix_len(const union inany_addr *addr)
> +{
> + const struct in_addr *v4 = inany_v4(addr);
> +
> + return v4 ? ip4_class_prefix_len(v4) + 96 : 64;
> +}
> +
> /** inany_equals - Compare two IPv[46] addresses
> * @a, @b: IPv[46] addresses
> *
> @@ -295,5 +308,7 @@ 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,
> + uint8_t *prefix_len);
>
> #endif /* INANY_H */
> diff --git a/ip.c b/ip.c
> index 9a7f4c5..40dc24e 100644
> --- a/ip.c
> +++ b/ip.c
> @@ -13,6 +13,8 @@
> */
>
> #include <stddef.h>
> +#include <netinet/in.h>
> +
> #include "util.h"
> #include "ip.h"
>
> @@ -67,3 +69,22 @@ found:
> *proto = nh;
> return true;
> }
> +
> +/**
> + * ip4_class_prefix_len() - Get class based prefix length for IPv4 address
> + * @addr: IPv4 address
> + *
> + * Return: prefix length based on address class, or 32 for other
> + */
> +int ip4_class_prefix_len(const struct in_addr *addr)
> +{
> + in_addr_t a = ntohl(addr->s_addr);
> +
> + if (IN_CLASSA(a))
> + return 32 - IN_CLASSA_NSHIFT;
> + if (IN_CLASSB(a))
> + return 32 - IN_CLASSB_NSHIFT;
> + if (IN_CLASSC(a))
> + return 32 - IN_CLASSC_NSHIFT;
> + return 32;
> +}
> diff --git a/ip.h b/ip.h
> index 5830b92..bd28640 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_class_prefix_len(const struct in_addr *addr);
> +
> #endif /* IP_H */
> diff --git a/passt.1 b/passt.1
> index db0d662..2c3eae9 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[/\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 /\fIprefix_len\fR (0-32 for IPv4, 0-128 for IPv6) can be
> +appended in CIDR notation (e.g. 192.0.2.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.0.2.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
>
--
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
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v9] conf: Support CIDR notation for -a/--address option
2026-02-09 0:37 [PATCH v9] conf: Support CIDR notation for -a/--address option Jon Maloy
2026-02-09 23:19 ` David Gibson
@ 2026-02-10 11:36 ` Stefano Brivio
2026-02-10 16:39 ` Jon Maloy
1 sibling, 1 reply; 5+ messages in thread
From: Stefano Brivio @ 2026-02-10 11:36 UTC (permalink / raw)
To: Jon Maloy; +Cc: dgibson, david, passt-dev
Note that this has now a trivial conflict with 0c611bcd3120 ("ip: Add
ipproto_name() function"), you should rebase it. Review below:
On Sun, 8 Feb 2026 19:37:27 -0500
Jon Maloy <jmaloy@redhat.com> 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 inany_prefix_pton() helper function that:
> - Parses address strings with a compulsory /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.
>
> 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 <jmaloy@redhat.com>
>
> ---
> 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
>
> v5: - Moved address/prefix parsing into a refactored
> inany_prefix_pton() function.
> - inany_prefix_pton() now only caluclates IPv6 style
> prefix lengths
> - Stricter distinction between error causes.
> - Some refactoring of the 'case a:' branch in conf()
> - Some small fixes in passt.1
>
> v6: - Refactored inany_prefix_pton() and conf()::'case -a'
> code after input from David Gibson.
>
> v7: - More refactoring after input from David Gibson.
> - I kept the return values 1 and 0. This is consistent
> with the return values of inet_pton() and inany_pton().
>
> v8: - Changed condition for updating ipv4 prefix length
>
> v9: - Made char *src and char *pstr in inany_prefix_pton() const
> - Updated logics in conf.c:: case 'a' and case 'n' to
> be clearer (I think)
> ---
> conf.c | 72 +++++++++++++++++++++++++++++++++++++--------------------
> inany.c | 51 ++++++++++++++++++++++++++++++++++++++++
> inany.h | 15 ++++++++++++
> ip.c | 21 +++++++++++++++++
> ip.h | 2 ++
> passt.1 | 17 ++++++++++----
> 6 files changed, 148 insertions(+), 30 deletions(-)
>
> diff --git a/conf.c b/conf.c
> index 2942c8c..46cfb6e 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;
> }
> @@ -896,7 +896,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"
> @@ -1505,6 +1505,8 @@ void conf(struct ctx *c, int argc, char **argv)
> unsigned long max_mtu = IP_MAX_MTU;
> struct fqdn *dnss = c->dns_search;
> unsigned int ifi4 = 0, ifi6 = 0;
> + bool prefix_from_cidr = false;
> + uint8_t prefix_from_opt = 0;
Neither of these variables contain a prefix. One might be the length, I
think, the other one is a boolean. This is a bit confusing.
> const char *logfile = NULL;
> size_t logsize = 0;
> char *runas = NULL;
> @@ -1808,36 +1810,56 @@ 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)) {
> - if (c->mode == MODE_PASTA)
> - c->ip6.no_copy_addrs = true;
> - break;
> - }
> + case 'a': {
> + union inany_addr addr;
> + uint8_t prefix_len;
> +
> + prefix_from_cidr =
> + inany_prefix_pton(optarg, &addr, &prefix_len);
I guess this variable represents whether the address specification
carries the prefix length as well, so perhaps addr_has_prefix or
addr_has_prefix_len would suit it better.
The current name seems to indicate a variable containing a prefix that
comes from RFC 1519 somehow, but that's clearly not the case.
By the way, the usual indentation would be:
long_variable_name = inany_prefix_pton(optarg, &addr,
&prefix_len);
> +
> + if (prefix_from_cidr && prefix_from_opt)
> + die("Can't mix CIDR with -n");
Well, sure, CIDR means "Classless Inter-Domain Routing", so that would
be like mixing bridges with concrete. What about:
die("Redundant prefix length specification");
?
Sorry for not mentioning it earlier, but only now I realised that the
current implementation is not really trivial: with commit 65923ba79877
("conf: Accept duplicate and conflicting options, the last one wins"),
we finally embraced reality over correctness and gave up with all the
careful "conflict" checking.
Couldn't we just let the last option win? "-n" would override
everything, while per-address specifiers would override the prefix
length only for that specific address / IP version. The code would also
be simpler, I think.
> +
> + if (!prefix_from_cidr && !inany_pton(optarg, &addr))
> + die("Invalid address: %s", optarg);
> +
> + if (prefix_from_opt && inany_v4(&addr))
> + prefix_len = prefix_from_opt;
Now I see why prefix_from_opt is a number! If that variable name had
'len' in it, it would have been clearer.
I haven't checked how this looks like elsewhere, but what about
'prefix_len_from_opt'?
> + else if (!prefix_from_cidr)
> + prefix_len = inany_default_prefix_len(&addr);
> +
> + 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 (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 (inany_v4(&addr)) {
> + c->ip4.addr = *inany_v4(&addr);
> + c->ip4.prefix_len = prefix_len - 96;
> if (c->mode == MODE_PASTA)
> c->ip4.no_copy_addrs = true;
> - break;
> + } else {
> + c->ip6.addr = addr.a6;
> + if (c->mode == MODE_PASTA)
> + c->ip6.no_copy_addrs = true;
> }
> -
> - die("Invalid address: %s", optarg);
> break;
> - case 'n':
> - c->ip4.prefix_len = conf_ip4_prefix(optarg);
> - if (c->ip4.prefix_len < 0)
> - die("Invalid netmask: %s", optarg);
> + }
> + case 'n': {
> + int plen;
> +
> + if (prefix_from_cidr)
> + die("Can't use both -n and CIDR prefix length");
>
> + plen = conf_ip4_prefix(optarg);
> + if (plen < 0)
> + die("Invalid prefix length: %s", optarg);
> +
> + prefix_from_opt = plen + 96;
> + c->ip4.prefix_len = plen;
> break;
> + }
> case 'M':
> parse_mac(c->our_tap_mac, optarg);
> break;
> diff --git a/inany.c b/inany.c
> index 7680439..df6a126 100644
> --- a/inany.c
> +++ b/inany.c
> @@ -11,6 +11,7 @@
> #include <assert.h>
> #include <netinet/in.h>
> #include <arpa/inet.h>
> +#include <errno.h>
>
> #include "util.h"
> #include "ip.h"
> @@ -57,3 +58,53 @@ int inany_pton(const char *src, union inany_addr *dst)
>
> return 0;
> }
> +
> +/**
> + * inany_prefix_pton() - Parse an IPv[46] address with prefix length
> + * @src: IPv[46] address and prefix length string in CIDR format
> + * @dst: Output buffer, filled with parsed address
> + * @prefix_len: Prefix length, to be filled in IPv6 format
> + *
> + * Return: on success, 1, if no parseable address or prefix is found, 0
I already commented on this. I think it's not clear. See
https://archives.passt.top/passt-dev/20260121091517.626962f1@elisabeth/.
> + */
> +int inany_prefix_pton(const char *src, union inany_addr *dst,
> + uint8_t *prefix_len)
> +{
> + char astr[INANY_ADDRSTRLEN] = {0,};
> + size_t alen = strcspn(src, "/");
> + const char *pstr = &src[alen + 1];
> + unsigned long plen;
> + char *end;
> +
> + if (alen >= INANY_ADDRSTRLEN)
> + return 0;
> +
> + if (src[alen] != '/')
> + return 0;
> +
> + strncpy(astr, src, alen);
> +
> + /* Read prefix length */
> + errno = 0;
> + plen = strtoul(pstr, &end, 10);
> + if (errno || *end || plen > 128)
> + return 0;
> +
> + *prefix_len = plen;
> +
> + /* Read address */
> + if (inet_pton(AF_INET6, astr, dst)) {
> + if (inany_v4(dst) && *prefix_len < 96)
> + return 0;
> + return 1;
> + }
> +
> + if (inany_pton(astr, dst)) {
> + if (*prefix_len > 32)
> + return 0;
> + *prefix_len += 96;
> + return 1;
> + }
> +
> + return 0;
> +}
> diff --git a/inany.h b/inany.h
> index 61b36fb..c7d6027 100644
> --- a/inany.h
> +++ b/inany.h
> @@ -96,6 +96,19 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr)
> return (struct in_addr *)&addr->v4mapped.a4;
> }
>
> +/** inany_default_prefix_len() - Get default prefix length for address
> + * @addr: IPv4 or iPv6 address
> + *
> + * Return: Class-based prefix length for IPv4 (in IPv6 format: 104-128),
> + * or 64 for IPv6
> + */
> +static inline int inany_default_prefix_len(const union inany_addr *addr)
> +{
> + const struct in_addr *v4 = inany_v4(addr);
> +
> + return v4 ? ip4_class_prefix_len(v4) + 96 : 64;
> +}
> +
> /** inany_equals - Compare two IPv[46] addresses
> * @a, @b: IPv[46] addresses
> *
> @@ -295,5 +308,7 @@ 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,
> + uint8_t *prefix_len);
>
> #endif /* INANY_H */
> diff --git a/ip.c b/ip.c
> index 9a7f4c5..40dc24e 100644
> --- a/ip.c
> +++ b/ip.c
> @@ -13,6 +13,8 @@
> */
>
> #include <stddef.h>
> +#include <netinet/in.h>
> +
> #include "util.h"
> #include "ip.h"
>
> @@ -67,3 +69,22 @@ found:
> *proto = nh;
> return true;
> }
> +
> +/**
> + * ip4_class_prefix_len() - Get class based prefix length for IPv4 address
> + * @addr: IPv4 address
> + *
> + * Return: prefix length based on address class, or 32 for other
> + */
> +int ip4_class_prefix_len(const struct in_addr *addr)
> +{
> + in_addr_t a = ntohl(addr->s_addr);
> +
> + if (IN_CLASSA(a))
> + return 32 - IN_CLASSA_NSHIFT;
> + if (IN_CLASSB(a))
> + return 32 - IN_CLASSB_NSHIFT;
> + if (IN_CLASSC(a))
> + return 32 - IN_CLASSC_NSHIFT;
> + return 32;
> +}
> diff --git a/ip.h b/ip.h
> index 5830b92..bd28640 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_class_prefix_len(const struct in_addr *addr);
> +
> #endif /* IP_H */
> diff --git a/passt.1 b/passt.1
> index db0d662..2c3eae9 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[/\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 /\fIprefix_len\fR (0-32 for IPv4, 0-128 for IPv6) can be
> +appended in CIDR notation (e.g. 192.0.2.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.0.2.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
I also would have raised most of the comments David already raised,
other than those, the rest looks good to me.
--
Stefano
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v9] conf: Support CIDR notation for -a/--address option
2026-02-10 11:36 ` Stefano Brivio
@ 2026-02-10 16:39 ` Jon Maloy
2026-02-10 17:40 ` Stefano Brivio
0 siblings, 1 reply; 5+ messages in thread
From: Jon Maloy @ 2026-02-10 16:39 UTC (permalink / raw)
To: Stefano Brivio; +Cc: dgibson, david, passt-dev
On 2026-02-10 06:36, Stefano Brivio wrote:
> Note that this has now a trivial conflict with 0c611bcd3120 ("ip: Add
> ipproto_name() function"), you should rebase it. Review below:
>
> On Sun, 8 Feb 2026 19:37:27 -0500
> Jon Maloy <jmaloy@redhat.com> 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 inany_prefix_pton() helper function that:
>> - Parses address strings with a compulsory /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.
>>
>> 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 <jmaloy@redhat.com>
>>
>> ---
>> 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
>>
>> v5: - Moved address/prefix parsing into a refactored
>> inany_prefix_pton() function.
>> - inany_prefix_pton() now only caluclates IPv6 style
>> prefix lengths
>> - Stricter distinction between error causes.
>> - Some refactoring of the 'case a:' branch in conf()
>> - Some small fixes in passt.1
>>
>> v6: - Refactored inany_prefix_pton() and conf()::'case -a'
>> code after input from David Gibson.
>>
>> v7: - More refactoring after input from David Gibson.
>> - I kept the return values 1 and 0. This is consistent
>> with the return values of inet_pton() and inany_pton().
>>
>> v8: - Changed condition for updating ipv4 prefix length
>>
>> v9: - Made char *src and char *pstr in inany_prefix_pton() const
>> - Updated logics in conf.c:: case 'a' and case 'n' to
>> be clearer (I think)
>> ---
>> conf.c | 72 +++++++++++++++++++++++++++++++++++++--------------------
>> inany.c | 51 ++++++++++++++++++++++++++++++++++++++++
>> inany.h | 15 ++++++++++++
>> ip.c | 21 +++++++++++++++++
>> ip.h | 2 ++
>> passt.1 | 17 ++++++++++----
>> 6 files changed, 148 insertions(+), 30 deletions(-)
>>
>> diff --git a/conf.c b/conf.c
>> index 2942c8c..46cfb6e 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;
>> }
>> @@ -896,7 +896,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"
>> @@ -1505,6 +1505,8 @@ void conf(struct ctx *c, int argc, char **argv)
>> unsigned long max_mtu = IP_MAX_MTU;
>> struct fqdn *dnss = c->dns_search;
>> unsigned int ifi4 = 0, ifi6 = 0;
>> + bool prefix_from_cidr = false;
>> + uint8_t prefix_from_opt = 0;
>
> Neither of these variables contain a prefix. One might be the length, I
> think, the other one is a boolean. This is a bit confusing.
ok
>
>> const char *logfile = NULL;
>> size_t logsize = 0;
>> char *runas = NULL;
>> @@ -1808,36 +1810,56 @@ 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)) {
>> - if (c->mode == MODE_PASTA)
>> - c->ip6.no_copy_addrs = true;
>> - break;
>> - }
>> + case 'a': {
>> + union inany_addr addr;
>> + uint8_t prefix_len;
>> +
>> + prefix_from_cidr =
>> + inany_prefix_pton(optarg, &addr, &prefix_len);
>
> I guess this variable represents whether the address specification
> carries the prefix length as well, so perhaps addr_has_prefix or
> addr_has_prefix_len would suit it better.
>
> The current name seems to indicate a variable containing a prefix that
> comes from RFC 1519 somehow, but that's clearly not the case.
>
> By the way, the usual indentation would be:
>
> long_variable_name = inany_prefix_pton(optarg, &addr,
> &prefix_len);
>
>> +
>> + if (prefix_from_cidr && prefix_from_opt)
>> + die("Can't mix CIDR with -n");
>
> Well, sure, CIDR means "Classless Inter-Domain Routing", so that would
> be like mixing bridges with concrete. What about:
>
> die("Redundant prefix length specification");
> ?
ok.
>
> Sorry for not mentioning it earlier, but only now I realised that the
> current implementation is not really trivial: with commit 65923ba79877
> ("conf: Accept duplicate and conflicting options, the last one wins"),
> we finally embraced reality over correctness and gave up with all the
> careful "conflict" checking.
Since we are introducing something new here wa have the freedom to deny
mixing of the options, which I think is what we should do here.
>
> Couldn't we just let the last option win? "-n" would override
> everything, while per-address specifiers would override the prefix
> length only for that specific address / IP version. The code would also
> be simpler, I think.
-n cannot be automatically correlated to any particular address, so the
logical
thing to do would be to overrwite the prefix length in all ipv4 addresses.
Imagine the surprise of the user when he configures
pasta -a <addr1> -n prefix_len1 -a <addr2>/prefix_len2 and finds that
addr2 now is associated with prefix_1.
I simply think this is a bad idea.
>
>> +
>> + if (!prefix_from_cidr && !inany_pton(optarg, &addr))
>> + die("Invalid address: %s", optarg);
>> +
>> + if (prefix_from_opt && inany_v4(&addr))
>> + prefix_len = prefix_from_opt;
>
> Now I see why prefix_from_opt is a number! If that variable name had
> 'len' in it, it would have been clearer.
>
> I haven't checked how this looks like elsewhere, but what about
> 'prefix_len_from_opt'?
ok. I'll find a new name.
>
>> + else if (!prefix_from_cidr)
>> + prefix_len = inany_default_prefix_len(&addr);
>> +
>> + 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 (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 (inany_v4(&addr)) {
>> + c->ip4.addr = *inany_v4(&addr);
>> + c->ip4.prefix_len = prefix_len - 96;
>> if (c->mode == MODE_PASTA)
>> c->ip4.no_copy_addrs = true;
>> - break;
>> + } else {
>> + c->ip6.addr = addr.a6;
>> + if (c->mode == MODE_PASTA)
>> + c->ip6.no_copy_addrs = true;
>> }
>> -
>> - die("Invalid address: %s", optarg);
>> break;
>> - case 'n':
>> - c->ip4.prefix_len = conf_ip4_prefix(optarg);
>> - if (c->ip4.prefix_len < 0)
>> - die("Invalid netmask: %s", optarg);
>> + }
>> + case 'n': {
>> + int plen;
>> +
>> + if (prefix_from_cidr)
>> + die("Can't use both -n and CIDR prefix length");
>>
>> + plen = conf_ip4_prefix(optarg);
>> + if (plen < 0)
>> + die("Invalid prefix length: %s", optarg);
>> +
>> + prefix_from_opt = plen + 96;
>> + c->ip4.prefix_len = plen;
>> break;
>> + }
>> case 'M':
>> parse_mac(c->our_tap_mac, optarg);
>> break;
>> diff --git a/inany.c b/inany.c
>> index 7680439..df6a126 100644
>> --- a/inany.c
>> +++ b/inany.c
>> @@ -11,6 +11,7 @@
>> #include <assert.h>
>> #include <netinet/in.h>
>> #include <arpa/inet.h>
>> +#include <errno.h>
>>
>> #include "util.h"
>> #include "ip.h"
>> @@ -57,3 +58,53 @@ int inany_pton(const char *src, union inany_addr *dst)
>>
>> return 0;
>> }
>> +
>> +/**
>> + * inany_prefix_pton() - Parse an IPv[46] address with prefix length
>> + * @src: IPv[46] address and prefix length string in CIDR format
>> + * @dst: Output buffer, filled with parsed address
>> + * @prefix_len: Prefix length, to be filled in IPv6 format
>> + *
>> + * Return: on success, 1, if no parseable address or prefix is found, 0
>
> I already commented on this. I think it's not clear. See
> https://archives.passt.top/passt-dev/20260121091517.626962f1@elisabeth/.
Following the link, I see nothing commenting on the return value. What
do you mean?
/jon
>
>> + */
>> +int inany_prefix_pton(const char *src, union inany_addr *dst,
>> + uint8_t *prefix_len)
>> +{
>> + char astr[INANY_ADDRSTRLEN] = {0,};
>> + size_t alen = strcspn(src, "/");
>> + const char *pstr = &src[alen + 1];
>> + unsigned long plen;
>> + char *end;
>> +
>> + if (alen >= INANY_ADDRSTRLEN)
>> + return 0;
>> +
>> + if (src[alen] != '/')
>> + return 0;
>> +
>> + strncpy(astr, src, alen);
>> +
>> + /* Read prefix length */
>> + errno = 0;
>> + plen = strtoul(pstr, &end, 10);
>> + if (errno || *end || plen > 128)
>> + return 0;
>> +
>> + *prefix_len = plen;
>> +
>> + /* Read address */
>> + if (inet_pton(AF_INET6, astr, dst)) {
>> + if (inany_v4(dst) && *prefix_len < 96)
>> + return 0;
>> + return 1;
>> + }
>> +
>> + if (inany_pton(astr, dst)) {
>> + if (*prefix_len > 32)
>> + return 0;
>> + *prefix_len += 96;
>> + return 1;
>> + }
>> +
>> + return 0;
>> +}
>> diff --git a/inany.h b/inany.h
>> index 61b36fb..c7d6027 100644
>> --- a/inany.h
>> +++ b/inany.h
>> @@ -96,6 +96,19 @@ static inline struct in_addr *inany_v4(const union inany_addr *addr)
>> return (struct in_addr *)&addr->v4mapped.a4;
>> }
>>
>> +/** inany_default_prefix_len() - Get default prefix length for address
>> + * @addr: IPv4 or iPv6 address
>> + *
>> + * Return: Class-based prefix length for IPv4 (in IPv6 format: 104-128),
>> + * or 64 for IPv6
>> + */
>> +static inline int inany_default_prefix_len(const union inany_addr *addr)
>> +{
>> + const struct in_addr *v4 = inany_v4(addr);
>> +
>> + return v4 ? ip4_class_prefix_len(v4) + 96 : 64;
>> +}
>> +
>> /** inany_equals - Compare two IPv[46] addresses
>> * @a, @b: IPv[46] addresses
>> *
>> @@ -295,5 +308,7 @@ 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,
>> + uint8_t *prefix_len);
>>
>> #endif /* INANY_H */
>> diff --git a/ip.c b/ip.c
>> index 9a7f4c5..40dc24e 100644
>> --- a/ip.c
>> +++ b/ip.c
>> @@ -13,6 +13,8 @@
>> */
>>
>> #include <stddef.h>
>> +#include <netinet/in.h>
>> +
>> #include "util.h"
>> #include "ip.h"
>>
>> @@ -67,3 +69,22 @@ found:
>> *proto = nh;
>> return true;
>> }
>> +
>> +/**
>> + * ip4_class_prefix_len() - Get class based prefix length for IPv4 address
>> + * @addr: IPv4 address
>> + *
>> + * Return: prefix length based on address class, or 32 for other
>> + */
>> +int ip4_class_prefix_len(const struct in_addr *addr)
>> +{
>> + in_addr_t a = ntohl(addr->s_addr);
>> +
>> + if (IN_CLASSA(a))
>> + return 32 - IN_CLASSA_NSHIFT;
>> + if (IN_CLASSB(a))
>> + return 32 - IN_CLASSB_NSHIFT;
>> + if (IN_CLASSC(a))
>> + return 32 - IN_CLASSC_NSHIFT;
>> + return 32;
>> +}
>> diff --git a/ip.h b/ip.h
>> index 5830b92..bd28640 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_class_prefix_len(const struct in_addr *addr);
>> +
>> #endif /* IP_H */
>> diff --git a/passt.1 b/passt.1
>> index db0d662..2c3eae9 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[/\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 /\fIprefix_len\fR (0-32 for IPv4, 0-128 for IPv6) can be
>> +appended in CIDR notation (e.g. 192.0.2.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.0.2.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
>
> I also would have raised most of the comments David already raised,
> other than those, the rest looks good to me.
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v9] conf: Support CIDR notation for -a/--address option
2026-02-10 16:39 ` Jon Maloy
@ 2026-02-10 17:40 ` Stefano Brivio
0 siblings, 0 replies; 5+ messages in thread
From: Stefano Brivio @ 2026-02-10 17:40 UTC (permalink / raw)
To: Jon Maloy; +Cc: dgibson, david, passt-dev
On Tue, 10 Feb 2026 11:39:39 -0500
Jon Maloy <jmaloy@redhat.com> wrote:
> On 2026-02-10 06:36, Stefano Brivio wrote:
> > Note that this has now a trivial conflict with 0c611bcd3120 ("ip: Add
> > ipproto_name() function"), you should rebase it. Review below:
> >
> > On Sun, 8 Feb 2026 19:37:27 -0500
> > Jon Maloy <jmaloy@redhat.com> 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 inany_prefix_pton() helper function that:
> >> - Parses address strings with a compulsory /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.
> >>
> >> 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 <jmaloy@redhat.com>
> >>
> >> ---
> >> 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
> >>
> >> v5: - Moved address/prefix parsing into a refactored
> >> inany_prefix_pton() function.
> >> - inany_prefix_pton() now only caluclates IPv6 style
> >> prefix lengths
> >> - Stricter distinction between error causes.
> >> - Some refactoring of the 'case a:' branch in conf()
> >> - Some small fixes in passt.1
> >>
> >> v6: - Refactored inany_prefix_pton() and conf()::'case -a'
> >> code after input from David Gibson.
> >>
> >> v7: - More refactoring after input from David Gibson.
> >> - I kept the return values 1 and 0. This is consistent
> >> with the return values of inet_pton() and inany_pton().
> >>
> >> v8: - Changed condition for updating ipv4 prefix length
> >>
> >> v9: - Made char *src and char *pstr in inany_prefix_pton() const
> >> - Updated logics in conf.c:: case 'a' and case 'n' to
> >> be clearer (I think)
> >> ---
> >> conf.c | 72 +++++++++++++++++++++++++++++++++++++--------------------
> >> inany.c | 51 ++++++++++++++++++++++++++++++++++++++++
> >> inany.h | 15 ++++++++++++
> >> ip.c | 21 +++++++++++++++++
> >> ip.h | 2 ++
> >> passt.1 | 17 ++++++++++----
> >> 6 files changed, 148 insertions(+), 30 deletions(-)
> >>
> >> diff --git a/conf.c b/conf.c
> >> index 2942c8c..46cfb6e 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;
> >> }
> >> @@ -896,7 +896,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"
> >> @@ -1505,6 +1505,8 @@ void conf(struct ctx *c, int argc, char **argv)
> >> unsigned long max_mtu = IP_MAX_MTU;
> >> struct fqdn *dnss = c->dns_search;
> >> unsigned int ifi4 = 0, ifi6 = 0;
> >> + bool prefix_from_cidr = false;
> >> + uint8_t prefix_from_opt = 0;
> >
> > Neither of these variables contain a prefix. One might be the length, I
> > think, the other one is a boolean. This is a bit confusing.
> ok
>
> >
> >> const char *logfile = NULL;
> >> size_t logsize = 0;
> >> char *runas = NULL;
> >> @@ -1808,36 +1810,56 @@ 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)) {
> >> - if (c->mode == MODE_PASTA)
> >> - c->ip6.no_copy_addrs = true;
> >> - break;
> >> - }
> >> + case 'a': {
> >> + union inany_addr addr;
> >> + uint8_t prefix_len;
> >> +
> >> + prefix_from_cidr =
> >> + inany_prefix_pton(optarg, &addr, &prefix_len);
> >
> > I guess this variable represents whether the address specification
> > carries the prefix length as well, so perhaps addr_has_prefix or
> > addr_has_prefix_len would suit it better.
> >
> > The current name seems to indicate a variable containing a prefix that
> > comes from RFC 1519 somehow, but that's clearly not the case.
> >
> > By the way, the usual indentation would be:
> >
> > long_variable_name = inany_prefix_pton(optarg, &addr,
> > &prefix_len);
> >
> >> +
> >> + if (prefix_from_cidr && prefix_from_opt)
> >> + die("Can't mix CIDR with -n");
> >
> > Well, sure, CIDR means "Classless Inter-Domain Routing", so that would
> > be like mixing bridges with concrete. What about:
> >
> > die("Redundant prefix length specification");
> > ?
>
> ok.
>
> >
> > Sorry for not mentioning it earlier, but only now I realised that the
> > current implementation is not really trivial: with commit 65923ba79877
> > ("conf: Accept duplicate and conflicting options, the last one wins"),
> > we finally embraced reality over correctness and gave up with all the
> > careful "conflict" checking.
>
> Since we are introducing something new here wa have the freedom to deny
> mixing of the options, which I think is what we should do here.
Sure, we can, maybe even should, just see one additional remark below.
> > Couldn't we just let the last option win? "-n" would override
> > everything, while per-address specifiers would override the prefix
> > length only for that specific address / IP version. The code would also
> > be simpler, I think.
>
> -n cannot be automatically correlated to any particular address, so the
> logical
> thing to do would be to overrwite the prefix length in all ipv4 addresses.
My suggestion, based on the approach we eventually adopted for other
options (not my initial choice either, see that commit I mentioned)
would have been that -n overwrites the prefix length of all the
*preceding* addresses, so that especially Podman users (and libvirt and
rootlesskit users, hopefully, one day) will be able to
deterministically override whatever was implicitly configured (by e.g.
Podman). That is:
> Imagine the surprise of the user when he configures
> pasta -a <addr1> -n prefix_len1 -a <addr2>/prefix_len2 and finds that
> addr2 now is associated with prefix_1.
this would be equivalent to -a addr1/prefix_len1 -a addr2/prefix_len2,
instead, and:
- -a a1/p1 -n p2 -a a3/p3 => -a a1/p2 -a a3/p3
- -a a1/p1 -n p2 -a a3/p3 -n p4 => -a a1/p4 -a a3/p4
and so on. That is, the last -n option wins, everything else remains
the same.
> I simply think this is a bad idea.
Maybe. I'm not insisting, of course, but still I think that, with the
interpretation I gave above, it's not *that* surprising and it saves
many lines of code (not adding any to the man page, at the same time).
> >> +
> >> + if (!prefix_from_cidr && !inany_pton(optarg, &addr))
> >> + die("Invalid address: %s", optarg);
> >> +
> >> + if (prefix_from_opt && inany_v4(&addr))
> >> + prefix_len = prefix_from_opt;
> >
> > Now I see why prefix_from_opt is a number! If that variable name had
> > 'len' in it, it would have been clearer.
> >
> > I haven't checked how this looks like elsewhere, but what about
> > 'prefix_len_from_opt'?
>
> ok. I'll find a new name.
>
> >
> >> + else if (!prefix_from_cidr)
> >> + prefix_len = inany_default_prefix_len(&addr);
> >> +
> >> + 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 (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 (inany_v4(&addr)) {
> >> + c->ip4.addr = *inany_v4(&addr);
> >> + c->ip4.prefix_len = prefix_len - 96;
> >> if (c->mode == MODE_PASTA)
> >> c->ip4.no_copy_addrs = true;
> >> - break;
> >> + } else {
> >> + c->ip6.addr = addr.a6;
> >> + if (c->mode == MODE_PASTA)
> >> + c->ip6.no_copy_addrs = true;
> >> }
> >> -
> >> - die("Invalid address: %s", optarg);
> >> break;
> >> - case 'n':
> >> - c->ip4.prefix_len = conf_ip4_prefix(optarg);
> >> - if (c->ip4.prefix_len < 0)
> >> - die("Invalid netmask: %s", optarg);
> >> + }
> >> + case 'n': {
> >> + int plen;
> >> +
> >> + if (prefix_from_cidr)
> >> + die("Can't use both -n and CIDR prefix length");
> >>
> >> + plen = conf_ip4_prefix(optarg);
> >> + if (plen < 0)
> >> + die("Invalid prefix length: %s", optarg);
> >> +
> >> + prefix_from_opt = plen + 96;
> >> + c->ip4.prefix_len = plen;
> >> break;
> >> + }
> >> case 'M':
> >> parse_mac(c->our_tap_mac, optarg);
> >> break;
> >> diff --git a/inany.c b/inany.c
> >> index 7680439..df6a126 100644
> >> --- a/inany.c
> >> +++ b/inany.c
> >> @@ -11,6 +11,7 @@
> >> #include <assert.h>
> >> #include <netinet/in.h>
> >> #include <arpa/inet.h>
> >> +#include <errno.h>
> >>
> >> #include "util.h"
> >> #include "ip.h"
> >> @@ -57,3 +58,53 @@ int inany_pton(const char *src, union inany_addr *dst)
> >>
> >> return 0;
> >> }
> >> +
> >> +/**
> >> + * inany_prefix_pton() - Parse an IPv[46] address with prefix length
> >> + * @src: IPv[46] address and prefix length string in CIDR format
> >> + * @dst: Output buffer, filled with parsed address
> >> + * @prefix_len: Prefix length, to be filled in IPv6 format
> >> + *
> >> + * Return: on success, 1, if no parseable address or prefix is found, 0
> >
> > I already commented on this. I think it's not clear. See
> > https://archives.passt.top/passt-dev/20260121091517.626962f1@elisabeth/.
>
> Following the link, I see nothing commenting on the return value. What
> do you mean?
Quoting from that message (the function was already called
inany_prefix_pton(), and git helpfully includes enough code context):
---
> + * Return: on success, 1, if no parseable address is found, 0
I think it's a bit difficult to read like this, what about "1 on
success, 0 if no parsable address is found"?
---
--
Stefano
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-02-10 17:40 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-09 0:37 [PATCH v9] conf: Support CIDR notation for -a/--address option Jon Maloy
2026-02-09 23:19 ` David Gibson
2026-02-10 11:36 ` Stefano Brivio
2026-02-10 16:39 ` Jon Maloy
2026-02-10 17:40 ` Stefano Brivio
Code repositories for project(s) associated with this public inbox
https://passt.top/passt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).