[PATCH v2 0/9] Introduce multiple addresses
This version contains what I perceive as the least controversial parts of my previous RFC series. It basically makes address handling behave like before, but now allowing multiple addresses both at the host side and the guest side. v2: - Added the earlier standalone CIDR commit to the head of the series. - Replaced the guest namespace interface subscriptions with just an address observation feature, so that it works with both PASTA and PASST. - Unified 'no_copy_addrs' and 'copy_addrs' code paths, as suggested by David G. - Multiple other changes, also based on feedback from David. - Removed the host interface subscription patches, -for now. I intend to re-add them once this series is applied. - Outstanding question: When do we add an IPv4 link local address to the guest? Only in local/opaque mode? Only when explicitly requested? Always? Jon Maloy (9): conf: Support CIDR notation for -a/--address option ip: Introduce unified multi-address data structures conf: Refactor conf_print() for multi-address support fwd: Check all configured addresses in guest accessibility functions arp: Check all configured addresses in ARP filtering conf: Allow multiple -a/--address options per address family pasta: Unify address configuration paths using address array ip: Track observed guest IPv4 addresses in unified address array ip: Track observed guest IPv6 addresses in unified address array arp.c | 12 ++- conf.c | 290 +++++++++++++++++++++++++++++++++++++----------------- dhcp.c | 8 +- dhcpv6.c | 10 +- dhcpv6.h | 2 +- fwd.c | 137 ++++++++++++++++++++------ inany.c | 29 ++++++ inany.h | 19 ++++ ip.c | 21 ++++ ip.h | 6 ++ migrate.c | 105 ++++++++++++++++++-- ndp.c | 6 +- netlink.c | 84 ++++++++++++++++ netlink.h | 5 + passt.1 | 17 +++- passt.h | 26 ++--- pasta.c | 54 ++++++---- tap.c | 116 ++++++++++++++++++---- 18 files changed, 744 insertions(+), 203 deletions(-) -- 2.52.0
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
As preparation for supporting multiple addresses per interface, we
replace the single addr/prefix_len fields with arrays.
- We add an ip4_addr_entry and an ip6_addr_entry struct containing
address and prefix length.
- We set the array sizes to IP4_MAX_ADDRS=8 and IP6_MAX_ADDRS=16,
respectively.
The only functional change is that the IPv6 prefix length now is
properly stored instead of being hardcoded to 64 even when set
via the -a option.
Signed-off-by: Jon Maloy
As a preparation for multiple address support, we refactor the
conf_print() function to handle this properly.
Signed-off-by: Jon Maloy
As a preparation for handling multiple addresses, we update
fwd_guest_accessible4() and fwd_guest_accessible6() to check
against all addresses in the addrs[] array.
This ensures that when multiple addresses are configured via -a options,
inbound traffic for any of them is correctly detected as having no valid
forwarding path, and subsequently dropped. This occurs when a peer
address collides with an address the guest is using, and we have no
translation for it.
Signed-off-by: Jon Maloy
As a preparation for handling multiple addresses, we update ignore_arp()
to check against all addresses in the addrs[] array.
Signed-off-by: Jon Maloy
Until now, pasta has maintained two separate code paths for
setting up addresses in the guest namespace:
- "copy addrs" path: When --config-net is used without -a, pasta
would call nl_addr_dup() to copy addresses directly from the host
template interface to the guest.
- "no_copy_addrs" path: When -a is specified, pasta would iterate
through the addrs[] array and call nl_addr_set() for each entry.
This commit unifies these paths by adding a new nl_addr_get_all()
function that retrieves all addresses from an interface into the
addrs[] array, marking them with INANY_ADDR_HOST flag.
We modify conf_ip4() and conf_ip6() to always populate the address
array, either from command-line -a options or from host interface
discovery.
This gives us a single code path for all address configuration scenarios
and a more consistent address handling regardless of source.
Suggested-by: David Gibson
We enable configuration of multiple IPv4 and IPv6 addresses by allowing
repeated use of the -a/--address option.
- We update option parsing to append addresses to the addrs[] array.
- Each address specified via -a does initially get a class-based default
prefix.
- If no -a option is given, address and prefix are inherited from
the template interface.
- If a prefix length is to be added, it has to be done in CIDR format,
except for the very first address.
- We configure all indicated addresses in the namespace interface.
Signed-off-by: Jon Maloy
We remove the addr_seen field in struct ip4_ctx and replace it by
setting a new INANY_ADDR_OBSERVED flag in the corresponding entry in
the new address array. If the seen address is not present in the
array we add it first.
Signed-off-by: Jon Maloy
We remove the addr_seen and addr_ll_seen fields in struct ip6_ctx
and replace them by setting INANY_ADDR_OBSERVED and a new
INANY_ADDR_LINKLOCAL flag in the corresponding entry in the
address array. If the seen address is not present in the array
we add it first.
This completes the unification of address storage for both IPv4 and
IPv6, enabling future support for multiple guest addresses per family.
Signed-off-by: Jon Maloy
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.
+ + 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
-- 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
On Sun, Jan 18, 2026 at 05:16:05PM -0500, Jon Maloy wrote:
As preparation for supporting multiple addresses per interface, we replace the single addr/prefix_len fields with arrays.
- We add an ip4_addr_entry and an ip6_addr_entry struct containing address and prefix length.
- We set the array sizes to IP4_MAX_ADDRS=8 and IP6_MAX_ADDRS=16, respectively.
The only functional change is that the IPv6 prefix length now is properly stored instead of being hardcoded to 64 even when set via the -a option.
Signed-off-by: Jon Maloy
--- v2: Using inany_addr instead of protocol specific addresses as entry address field. --- arp.c | 4 +-- conf.c | 78 +++++++++++++++++++++++++++++++++++--------------------- dhcp.c | 8 +++--- dhcpv6.c | 6 ++--- fwd.c | 12 ++++----- inany.h | 16 ++++++++++++ ip.h | 4 +++ ndp.c | 6 ++--- passt.h | 16 +++++++----- pasta.c | 12 +++++---- tap.c | 4 +-- 11 files changed, 106 insertions(+), 60 deletions(-)
diff --git a/arp.c b/arp.c index bb042e9..bc77a9f 100644 --- a/arp.c +++ b/arp.c @@ -54,7 +54,7 @@ static bool ignore_arp(const struct ctx *c, return true;
/* Don't resolve the guest's assigned address, either. */ - if (!memcmp(am->tip, &c->ip4.addr, sizeof(am->tip))) + if (!memcmp(am->tip, inany_v4(&c->ip4.addrs[0].addr), sizeof(am->tip))) return true;
return false; @@ -145,7 +145,7 @@ void arp_send_init_req(const struct ctx *c) memcpy(req.am.sha, c->our_tap_mac, sizeof(req.am.sha)); memcpy(req.am.sip, &c->ip4.our_tap_addr, sizeof(req.am.sip)); memcpy(req.am.tha, MAC_BROADCAST, sizeof(req.am.tha)); - memcpy(req.am.tip, &c->ip4.addr, sizeof(req.am.tip)); + memcpy(req.am.tip, inany_v4(&c->ip4.addrs[0].addr), sizeof(req.am.tip));
debug("Sending initial ARP request for guest MAC address"); tap_send_single(c, &req, sizeof(req)); diff --git a/conf.c b/conf.c index 7178a0e..9fc5dca 100644 --- a/conf.c +++ b/conf.c @@ -763,33 +763,33 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) } }
- if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) { + if (!ip4->addr_count) { + struct in_addr addr; + int prefix_len = 0; int rc = nl_addr_get(nl_sock, ifi, AF_INET, - &ip4->addr, &ip4->prefix_len, NULL); + &addr, &prefix_len, NULL); if (rc < 0) { debug("Couldn't discover IPv4 address: %s", strerror_(-rc)); return 0; } + ip4->addrs[0].addr = inany_from_v4(addr);
Using an inany_addr type for an array that's strictly for IPv4 addresses seems weird to me. And kind of likely to provoke static checker warnings for the many, many unchecked inany_from_v4() calls that are introduced. Using inany_addr doesn't seem worth it if we're not also unifying at least sone v4/v6 paths and that doesn't seem the case.
+ ip4->addrs[0].prefix_len = prefix_len; + ip4->addrs[0].flags = INANY_ADDR_HOST; + ip4->addr_count = 1; }
- if (!ip4->prefix_len) { - in_addr_t addr = ntohl(ip4->addr.s_addr); - if (IN_CLASSA(addr)) - ip4->prefix_len = (32 - IN_CLASSA_NSHIFT); - else if (IN_CLASSB(addr)) - ip4->prefix_len = (32 - IN_CLASSB_NSHIFT); - else if (IN_CLASSC(addr)) - ip4->prefix_len = (32 - IN_CLASSC_NSHIFT); - else - ip4->prefix_len = 32; + if (!ip4->addrs[0].prefix_len) { + const struct in_addr *a4 = inany_v4(&ip4->addrs[0].addr); + + ip4->addrs[0].prefix_len = ip4_default_prefix_len(a4); }
- ip4->addr_seen = ip4->addr; + ip4->addr_seen = *inany_v4(&ip4->addrs[0].addr);
ip4->our_tap_addr = ip4->guest_gw;
- if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) + if (inany_is_unspecified(&ip4->addrs[0].addr)) return 0;
If this is the case, we must set addr_count back to 0. Or perhaps safer, don't advance it in the first place until we're sure we have a valid address.
return ifi; @@ -801,9 +801,11 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) */ static void conf_ip4_local(struct ip4_ctx *ip4) { - ip4->addr_seen = ip4->addr = IP4_LL_GUEST_ADDR; + ip4->addrs[0].addr = inany_from_v4(IP4_LL_GUEST_ADDR); + ip4->addr_seen = *inany_v4(&ip4->addrs[0].addr); ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW; - ip4->prefix_len = IP4_LL_PREFIX_LEN; + ip4->addrs[0].prefix_len = IP4_LL_PREFIX_LEN; + ip4->addr_count = 1;
ip4->no_copy_addrs = ip4->no_copy_routes = true; } @@ -838,19 +840,25 @@ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) }
rc = nl_addr_get(nl_sock, ifi, AF_INET6, - IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL, + ip6->addr_count ? NULL : &ip6->addrs[0].addr.a6, &prefix_len, &ip6->our_tap_ll); if (rc < 0) { debug("Couldn't discover IPv6 address: %s", strerror_(-rc)); return 0; }
- ip6->addr_seen = ip6->addr; + if (!ip6->addr_count) { + ip6->addrs[0].prefix_len = prefix_len ? prefix_len : 64; + ip6->addrs[0].flags = INANY_ADDR_HOST; + ip6->addr_count = 1; + } + + ip6->addr_seen = ip6->addrs[0].addr.a6;
if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) ip6->our_tap_ll = ip6->guest_gw;
- if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) || + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addrs[0].addr.a6) || IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0;
@@ -1193,11 +1201,13 @@ static void conf_print(const struct ctx *c) if (!c->no_dhcp) { uint32_t mask;
- mask = htonl(0xffffffff << (32 - c->ip4.prefix_len)); + mask = htonl(0xffffffff << + (32 - c->ip4.addrs[0].prefix_len));
info("DHCP:"); info(" assign: %s", - inet_ntop(AF_INET, &c->ip4.addr, buf4, sizeof(buf4))); + inet_ntop(AF_INET, inany_v4(&c->ip4.addrs[0].addr), + buf4, sizeof(buf4))); info(" mask: %s", inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); info(" router: %s", @@ -1235,7 +1245,8 @@ static void conf_print(const struct ctx *c) goto dns6;
info(" assign: %s", - inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6))); + inet_ntop(AF_INET6, &c->ip6.addrs[0].addr.a6, + buf6, sizeof(buf6))); info(" router: %s", inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); info(" our link-local: %s", @@ -1870,15 +1881,20 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid address: %s", optarg);
if (af == AF_INET6) { - c->ip6.addr = addr.a6; + c->ip6.addrs[0].addr.a6 = addr.a6; + c->ip6.addrs[0].flags |= INANY_ADDR_CONFIGURED; + c->ip6.addr_count = 1; if (c->mode == MODE_PASTA) c->ip6.no_copy_addrs = true; break; }
a4 = inany_v4(&addr); + if (af == AF_INET && a4) { - c->ip4.addr = *a4; + c->ip4.addrs[0].addr = inany_from_v4(*a4); + c->ip4.addrs[0].flags |= INANY_ADDR_CONFIGURED; + c->ip4.addr_count = 1; if (prefix_len) { if (prefix_from_opt) die("Can't mix CIDR with -n"); @@ -1886,21 +1902,25 @@ void conf(struct ctx *c, int argc, char **argv) } else { prefix_len = ip4_default_prefix_len(a4); } - c->ip4.prefix_len = prefix_len; + c->ip4.addrs[0].prefix_len = prefix_len; break; }
die("Invalid address: %s", optarg); break; } - case 'n': + case 'n': { + int plen; + 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) + plen = conf_ip4_prefix(optarg); + if (plen < 0) die("Invalid netmask: %s", optarg); + c->ip4.addrs[0].prefix_len = plen;
How will this behave if addr_count > 1?
prefix_from_opt = true; break; + } case 'M': parse_mac(c->our_tap_mac, optarg); break; @@ -2185,7 +2205,7 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->ifi6) { c->no_ndp = 1; c->no_dhcpv6 = 1; - } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { + } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addrs[0].addr.a6)) { c->no_dhcpv6 = 1; }
diff --git a/dhcp.c b/dhcp.c index 6b9c2e3..d2afc3b 100644 --- a/dhcp.c +++ b/dhcp.c @@ -352,7 +352,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) reply.secs = 0; reply.flags = m->flags; reply.ciaddr = m->ciaddr; - reply.yiaddr = c->ip4.addr; + reply.yiaddr = *inany_v4(&c->ip4.addrs[0].addr); reply.siaddr = 0; reply.giaddr = m->giaddr; memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); @@ -404,7 +404,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr)));
- mask.s_addr = htonl(0xffffffff << (32 - c->ip4.prefix_len)); + mask.s_addr = htonl(0xffffffff << (32 - c->ip4.addrs[0].prefix_len)); memcpy(opts[1].s, &mask, sizeof(mask)); memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); memcpy(opts[54].s, &c->ip4.our_tap_addr, sizeof(c->ip4.our_tap_addr)); @@ -412,7 +412,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) /* If the gateway is not on the assigned subnet, send an option 121 * (Classless Static Routing) adding a dummy route to it. */ - if ((c->ip4.addr.s_addr & mask.s_addr) + if ((inany_v4(&c->ip4.addrs[0].addr)->s_addr & mask.s_addr) != (c->ip4.guest_gw.s_addr & mask.s_addr)) { /* a.b.c.d/32:0.0.0.0, 0:a.b.c.d */ opts[121].slen = 14; @@ -469,7 +469,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) if (m->flags & FLAG_BROADCAST) dst = in4addr_broadcast; else - dst = c->ip4.addr; + dst = *inany_v4(&c->ip4.addrs[0].addr);
tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen);
diff --git a/dhcpv6.c b/dhcpv6.c index e4df0db..f45dece 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -625,7 +625,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, if (mh->type == TYPE_CONFIRM && server_id) return -1;
- if (dhcpv6_ia_notonlink(data, &c->ip6.addr)) { + if (dhcpv6_ia_notonlink(data, &c->ip6.addrs[0].addr.a6)) {
dhcpv6_send_ia_notonlink(c, data, &client_id_base, ntohs(client_id->l), mh->xid); @@ -679,7 +679,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
tap_udp6_send(c, src, 547, tap_ip6_daddr(c, src), 546, mh->xid, &resp, n); - c->ip6.addr_seen = c->ip6.addr; + c->ip6.addr_seen = c->ip6.addrs[0].addr.a6;
return 1; } @@ -703,5 +703,5 @@ void dhcpv6_init(const struct ctx *c) memcpy(resp_not_on_link.server_id.duid_lladdr, c->our_tap_mac, sizeof(c->our_tap_mac));
- resp.ia_addr.addr = c->ip6.addr; + resp.ia_addr.addr = c->ip6.addrs[0].addr.a6; } diff --git a/fwd.c b/fwd.c index 44a0e10..8d8151b 100644 --- a/fwd.c +++ b/fwd.c @@ -516,7 +516,7 @@ static bool fwd_guest_accessible4(const struct ctx *c, /* For IPv4, addr_seen is initialised to addr, so is always a valid * address */ - if (IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr) || + if (IN4_ARE_ADDR_EQUAL(addr, inany_v4(&c->ip4.addrs[0].addr)) || IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) return false;
@@ -537,7 +537,7 @@ static bool fwd_guest_accessible6(const struct ctx *c, if (IN6_IS_ADDR_LOOPBACK(addr)) return false;
- if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr)) + if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addrs[0].addr.a6)) return false;
/* For IPv6, addr_seen starts unspecified, because we don't know what LL @@ -587,9 +587,9 @@ static void nat_outbound(const struct ctx *c, const union inany_addr *addr, else if (inany_equals6(addr, &c->ip6.map_host_loopback)) *translated = inany_loopback6; else if (inany_equals4(addr, &c->ip4.map_guest_addr)) - *translated = inany_from_v4(c->ip4.addr); + *translated = c->ip4.addrs[0].addr; else if (inany_equals6(addr, &c->ip6.map_guest_addr)) - translated->a6 = c->ip6.addr; + translated->a6 = c->ip6.addrs[0].addr.a6; else *translated = *addr; } @@ -710,10 +710,10 @@ bool nat_inbound(const struct ctx *c, const union inany_addr *addr, inany_equals6(addr, &in6addr_loopback)) { translated->a6 = c->ip6.map_host_loopback; } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && - inany_equals4(addr, &c->ip4.addr)) { + inany_equals(addr, &c->ip4.addrs[0].addr)) { *translated = inany_from_v4(c->ip4.map_guest_addr); } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) && - inany_equals6(addr, &c->ip6.addr)) { + inany_equals6(addr, &c->ip6.addrs[0].addr.a6)) { translated->a6 = c->ip6.map_guest_addr; } else if (fwd_guest_accessible(c, addr)) { *translated = *addr; diff --git a/inany.h b/inany.h index 36865f9..07bfc3d 100644 --- a/inany.h +++ b/inany.h @@ -297,4 +297,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 *prefix_len);
+/* Flags for struct inany_addr_entry */ +#define INANY_ADDR_CONFIGURED (1 << 0) /* User set via -a */ +#define INANY_ADDR_HOST (1 << 1) /* From host interface */
These flags aren't really anything to do with the addresses themselves, but with how we're configuring them. They belong somewhere else (conf.h or passt.h, I think).
+/** + * struct inany_addr_entry - Unified IPv4/IPv6 address entry + * @addr: IPv4 (as mapped) or IPv6 address + * @prefix_len: Prefix length (0-32 for IPv4, 0-128 for IPv6) + * @flags: INANY_ADDR_* flags + */ +struct inany_addr_entry { + union inany_addr addr; + uint16_t prefix_len; + uint16_t flags; +};
Same with this structure.
+ #endif /* INANY_H */ diff --git a/ip.h b/ip.h index e5f5198..78bd43a 100644 --- a/ip.h +++ b/ip.h @@ -135,6 +135,10 @@ static const struct in_addr in4addr_broadcast = { 0xffffffff }; #define IPV6_MIN_MTU 1280 #endif
+/* Maximum number of addresses per address family */ +#define IP4_MAX_ADDRS 8 +#define IP6_MAX_ADDRS 16 + int ip4_default_prefix_len(const struct in_addr *addr);
#endif /* IP_H */ diff --git a/ndp.c b/ndp.c index eb9e313..5248fd6 100644 --- a/ndp.c +++ b/ndp.c @@ -257,7 +257,7 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) .valid_lifetime = ~0U, .pref_lifetime = ~0U, }, - .prefix = c->ip6.addr, + .prefix = c->ip6.addrs[0].addr.a6, .source_ll = { .header = { .type = OPT_SRC_L2_ADDR, @@ -466,8 +466,8 @@ void ndp_send_init_req(const struct ctx *c) .icmp6_solicited = 0, /* Reserved */ .icmp6_override = 0, /* Reserved */ }, - .target_addr = c->ip6.addr + .target_addr = c->ip6.addrs[0].addr.a6 }; debug("Sending initial NDP NS request for guest MAC address"); - ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); + ndp_send(c, &c->ip6.addrs[0].addr.a6, &ns, sizeof(ns)); } diff --git a/passt.h b/passt.h index 79d01dd..9c0c3fe 100644 --- a/passt.h +++ b/passt.h @@ -66,9 +66,9 @@ enum passt_modes {
/** * struct ip4_ctx - IPv4 execution context - * @addr: IPv4 address assigned to guest + * @addrs: IPv4 addresses assigned to guest + * @addr_count: Number of addresses in addrs[] array * @addr_seen: Latest IPv4 address seen as source from tap - * @prefixlen: IPv4 prefix length (netmask) * @guest_gw: IPv4 gateway as seen by the guest * @map_host_loopback: Outbound connections to this address are NATted to the * host's 127.0.0.1 @@ -85,9 +85,10 @@ enum passt_modes { */ struct ip4_ctx { /* PIF_TAP addresses */ - struct in_addr addr; + struct inany_addr_entry addrs[IP4_MAX_ADDRS]; + int addr_count; + struct in_addr addr_seen; - int prefix_len; struct in_addr guest_gw; struct in_addr map_host_loopback; struct in_addr map_guest_addr; @@ -107,7 +108,8 @@ struct ip4_ctx {
/** * struct ip6_ctx - IPv6 execution context - * @addr: IPv6 address assigned to guest + * @addrs: IPv6 addresses assigned to guest + * @addr_count: Number of addresses in addrs[] array * @addr_seen: Latest IPv6 global/site address seen as source from tap * @addr_ll_seen: Latest IPv6 link-local address seen as source from tap * @guest_gw: IPv6 gateway as seen by the guest @@ -126,7 +128,9 @@ struct ip4_ctx { */ struct ip6_ctx { /* PIF_TAP addresses */ - struct in6_addr addr; + struct inany_addr_entry addrs[IP6_MAX_ADDRS]; + int addr_count; + struct in6_addr addr_seen; struct in6_addr addr_ll_seen; struct in6_addr guest_gw; diff --git a/pasta.c b/pasta.c index c307b8a..1bb3dd0 100644 --- a/pasta.c +++ b/pasta.c @@ -340,8 +340,8 @@ void pasta_ns_conf(struct ctx *c) if (c->ip4.no_copy_addrs) { rc = nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET, - &c->ip4.addr, - c->ip4.prefix_len); + inany_v4(&c->ip4.addrs[0].addr), + c->ip4.addrs[0].prefix_len); } else { rc = nl_addr_dup(nl_sock, c->ifi4, nl_sock_ns, c->pasta_ifi, @@ -387,10 +387,12 @@ void pasta_ns_conf(struct ctx *c) 0, IFF_NOARP);
if (c->ip6.no_copy_addrs) { - if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { + struct in6_addr *a = &c->ip6.addrs[0].addr.a6; + + if (!IN6_IS_ADDR_UNSPECIFIED(a)) { rc = nl_addr_set(nl_sock_ns, - c->pasta_ifi, AF_INET6, - &c->ip6.addr, 64); + c->pasta_ifi, + AF_INET6, a, 64); } } else { rc = nl_addr_dup(nl_sock, c->ifi6, diff --git a/tap.c b/tap.c index 9d1344b..7c50013 100644 --- a/tap.c +++ b/tap.c @@ -951,8 +951,8 @@ resume: c->ip6.addr_seen = *saddr; }
- if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) - c->ip6.addr = *saddr; + if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addrs[0].addr.a6)) + c->ip6.addrs[0].addr.a6 = *saddr; } else if (!IN6_IS_ADDR_UNSPECIFIED(saddr)){ c->ip6.addr_seen = *saddr; } -- 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
On Sun, Jan 18, 2026 at 05:16:07PM -0500, Jon Maloy wrote:
As a preparation for handling multiple addresses, we update fwd_guest_accessible4() and fwd_guest_accessible6() to check against all addresses in the addrs[] array.
This ensures that when multiple addresses are configured via -a options, inbound traffic for any of them is correctly detected as having no valid forwarding path, and subsequently dropped. This occurs when a peer address collides with an address the guest is using, and we have no translation for it.
Signed-off-by: Jon Maloy
--- v2: Updated commit log to make it clearer --- fwd.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/fwd.c b/fwd.c index 8d8151b..f1db34c 100644 --- a/fwd.c +++ b/fwd.c @@ -502,6 +502,8 @@ static bool is_dns_flow(uint8_t proto, const struct flowside *ini) static bool fwd_guest_accessible4(const struct ctx *c, const struct in_addr *addr)
fwd_guest_accesible[46]() are only ever called via fwd_guest_accessible() which takes an inny_addr. Again, you can simplify this a bunch by actually exploiting the fact that the arrays are now also inany_addr, rather than having an inany_addr but still having separate v4 and v6 paths everywhere.
{ + int i; + if (IN4_IS_ADDR_LOOPBACK(addr)) return false;
@@ -513,11 +515,15 @@ static bool fwd_guest_accessible4(const struct ctx *c, if (IN4_IS_ADDR_UNSPECIFIED(addr)) return false;
- /* For IPv4, addr_seen is initialised to addr, so is always a valid - * address + /* Check against all configured guest addresses */ + for (i = 0; i < c->ip4.addr_count; i++) + if (IN4_ARE_ADDR_EQUAL(addr, inany_v4(&c->ip4.addrs[i].addr))) + return false; + + /* Also check addr_seen: it tracks the address the guest is actually + * using, which may differ from configured addresses. */ - if (IN4_ARE_ADDR_EQUAL(addr, inany_v4(&c->ip4.addrs[0].addr)) || - IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) + if (IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) return false;
return true; @@ -534,11 +540,15 @@ static bool fwd_guest_accessible4(const struct ctx *c, static bool fwd_guest_accessible6(const struct ctx *c, const struct in6_addr *addr) { + int i; + if (IN6_IS_ADDR_LOOPBACK(addr)) return false;
- if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addrs[0].addr.a6)) - return false; + /* Check against all configured guest addresses */ + for (i = 0; i < c->ip6.addr_count; i++) + if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addrs[i].addr.a6)) + return false;
/* For IPv6, addr_seen starts unspecified, because we don't know what LL * address the guest will take until we see it. Only check against it -- 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
On Sun, Jan 18, 2026 at 05:16:06PM -0500, Jon Maloy wrote:
As a preparation for multiple address support, we refactor the conf_print() function to handle this properly.
Signed-off-by: Jon Maloy
--- conf.c | 78 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/conf.c b/conf.c index 9fc5dca..3ecd1a0 100644 --- a/conf.c +++ b/conf.c @@ -1199,20 +1199,28 @@ static void conf_print(const struct ctx *c) buf4, sizeof(buf4)));
if (!c->no_dhcp) { - uint32_t mask; - - mask = htonl(0xffffffff << - (32 - c->ip4.addrs[0].prefix_len)); - - info("DHCP:"); - info(" assign: %s", - inet_ntop(AF_INET, inany_v4(&c->ip4.addrs[0].addr), - buf4, sizeof(buf4))); - info(" mask: %s", - inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); - info(" router: %s", - inet_ntop(AF_INET, &c->ip4.guest_gw, - buf4, sizeof(buf4))); + for (i = 0; i < c->ip4.addr_count; i++) { + const struct inany_addr_entry *e; + uint32_t mask; + + e = &c->ip4.addrs[i]; + if (!(e->flags & INANY_ADDR_CONFIGURED) && + c->ip4.addr_count > 1) + continue;
This doesn't seem right - addresses taken from the host will also be used for DHCP.
+ + mask = htonl(0xffffffff << (32 - e->prefix_len)); + + info("DHCP:"); + info(" assign: %s", + inet_ntop(AF_INET, inany_v4(&e->addr), + buf4, sizeof(buf4))); + info(" mask: %s", + inet_ntop(AF_INET, &mask, buf4, sizeof(buf4))); + info(" router: %s", + inet_ntop(AF_INET, &c->ip4.guest_gw, + buf4, sizeof(buf4))); + break; + } }
for (i = 0; !IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i]); i++) { @@ -1230,30 +1238,34 @@ static void conf_print(const struct ctx *c) }
if (c->ifi6) { + bool do_slaac = !c->no_ndp || !c->no_dhcpv6; + if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback)) info(" NAT to host ::1: %s", inet_ntop(AF_INET6, &c->ip6.map_host_loopback, buf6, sizeof(buf6)));
- if (!c->no_ndp && !c->no_dhcpv6) - info("NDP/DHCPv6:"); - else if (!c->no_dhcpv6) - info("DHCPv6:"); - else if (!c->no_ndp) - info("NDP:"); - else - goto dns6; - - info(" assign: %s", - inet_ntop(AF_INET6, &c->ip6.addrs[0].addr.a6, - buf6, sizeof(buf6))); - info(" router: %s", - inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); - info(" our link-local: %s", - inet_ntop(AF_INET6, &c->ip6.our_tap_ll, - buf6, sizeof(buf6))); - -dns6: + if (do_slaac) { + if (!c->no_ndp && !c->no_dhcpv6) + info("NDP/DHCPv6:"); + else if (!c->no_dhcpv6) + info("DHCPv6:"); + else + info("NDP:"); + + for (i = 0; i < c->ip6.addr_count; i++) { + info(" assign: %s", + inet_ntop(AF_INET6, &c->ip6.addrs[i].addr.a6, + buf6, sizeof(buf6)));
There's an inany_ntop()... if you're going to use inany_ntop() for IPv6 only addresses you might at least use the helper functions already implemented for it.
+ } + info(" router: %s", + inet_ntop(AF_INET6, &c->ip6.guest_gw, + buf6, sizeof(buf6))); + info(" our link-local: %s", + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, + buf6, sizeof(buf6))); + } + for (i = 0; !IN6_IS_ADDR_UNSPECIFIED(&c->ip6.dns[i]); i++) { if (!i) info("DNS:"); -- 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
On Sun, Jan 18, 2026 at 05:16:08PM -0500, Jon Maloy wrote:
As a preparation for handling multiple addresses, we update ignore_arp() to check against all addresses in the addrs[] array.
Signed-off-by: Jon Maloy
LGTM, except insofar as I'm still not convinced the v4 only array of inany_addrs makes sense.
--- arp.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/arp.c b/arp.c index bc77a9f..d8063e2 100644 --- a/arp.c +++ b/arp.c @@ -41,6 +41,8 @@ static bool ignore_arp(const struct ctx *c, const struct arphdr *ah, const struct arpmsg *am) { + int i; + if (ah->ar_hrd != htons(ARPHRD_ETHER) || ah->ar_pro != htons(ETH_P_IP) || ah->ar_hln != ETH_ALEN || @@ -53,9 +55,11 @@ static bool ignore_arp(const struct ctx *c, !memcmp(am->sip, am->tip, sizeof(am->sip))) return true;
- /* Don't resolve the guest's assigned address, either. */ - if (!memcmp(am->tip, inany_v4(&c->ip4.addrs[0].addr), sizeof(am->tip))) - return true; + /* Don't resolve any of the guest's addresses */ + for (i = 0; i < c->ip4.addr_count; i++) + if (!memcmp(am->tip, inany_v4(&c->ip4.addrs[i].addr), + sizeof(am->tip))) + return true;
return false; } -- 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
On Sun, Jan 18, 2026 at 05:16:09PM -0500, Jon Maloy wrote:
We enable configuration of multiple IPv4 and IPv6 addresses by allowing repeated use of the -a/--address option.
- We update option parsing to append addresses to the addrs[] array. - Each address specified via -a does initially get a class-based default prefix. - If no -a option is given, address and prefix are inherited from the template interface. - If a prefix length is to be added, it has to be done in CIDR format, except for the very first address. - We configure all indicated addresses in the namespace interface.
Signed-off-by: Jon Maloy
--- v2: Adapted to previous code changes --- conf.c | 42 +++++++++++++++++++++++++++++------------- pasta.c | 24 ++++++++++++++++++------ 2 files changed, 47 insertions(+), 19 deletions(-)
diff --git a/conf.c b/conf.c index 3ecd1a0..32a754d 100644 --- a/conf.c +++ b/conf.c @@ -789,7 +789,7 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4)
ip4->our_tap_addr = ip4->guest_gw;
- if (inany_is_unspecified(&ip4->addrs[0].addr)) + if (!ip4->addr_count) return 0;
return ifi; @@ -858,8 +858,7 @@ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) ip6->our_tap_ll = ip6->guest_gw;
- if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addrs[0].addr.a6) || - IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) + if (!ip6->addr_count || IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) return 0;
return ifi; @@ -951,9 +950,11 @@ static void usage(const char *name, FILE *f, int status) " 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[/PREFIXLEN]\n" - " can be specified zero to two times (for IPv4 and IPv6)\n" + " can be specified multiple times (limit: %d IPv4, %d IPv6)\n" " default: use addresses from interface with default route\n" - " -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits\n" + " -n, --netmask MASK Assign IPv4 MASK, dot-decimal or bits\n", + IP4_MAX_ADDRS, IP6_MAX_ADDRS); + FPRINTF(f, " default: netmask from matching address on the host\n" " -M, --mac-addr ADDR Use source MAC address ADDR\n" " default: 9a:55:9a:55:9a:55 (locally administered)\n" @@ -1882,6 +1883,7 @@ void conf(struct ctx *c, int argc, char **argv) union inany_addr addr; const struct in_addr *a4; int prefix_len = 0; + unsigned int i; int af;
af = conf_addr_prefix_len(optarg, &addr, &prefix_len); @@ -1893,9 +1895,15 @@ void conf(struct ctx *c, int argc, char **argv) die("Invalid address: %s", optarg);
if (af == AF_INET6) { - c->ip6.addrs[0].addr.a6 = addr.a6; - c->ip6.addrs[0].flags |= INANY_ADDR_CONFIGURED; - c->ip6.addr_count = 1; + i = c->ip6.addr_count; + + if (i >= IP6_MAX_ADDRS) + die("Too many IPv6 addresses"); + + c->ip6.addrs[i].addr.a6 = addr.a6; + c->ip6.addrs[i].prefix_len = prefix_len; + c->ip6.addrs[i].flags = INANY_ADDR_CONFIGURED; + c->ip6.addr_count++;
This is getting moderately deeply nested. Maybe worth creating an "add_address" helper? That could also be helpful if we later integrate the addr_seen stuff into the same table.
if (c->mode == MODE_PASTA) c->ip6.no_copy_addrs = true; break; @@ -1904,10 +1912,15 @@ void conf(struct ctx *c, int argc, char **argv) a4 = inany_v4(&addr);
if (af == AF_INET && a4) { - c->ip4.addrs[0].addr = inany_from_v4(*a4); - c->ip4.addrs[0].flags |= INANY_ADDR_CONFIGURED; - c->ip4.addr_count = 1; - if (prefix_len) { + i = c->ip4.addr_count; + + if (i >= IP4_MAX_ADDRS) + die("Too many IPv4 addresses"); + + c->ip4.addrs[i].addr = inany_from_v4(*a4); + c->ip4.addrs[i].prefix_len = prefix_len; + c->ip4.addrs[i].flags = INANY_ADDR_CONFIGURED; + if (i == 0 && prefix_len) { if (prefix_from_opt) die("Can't mix CIDR with -n"); prefix_from_cidr = true; @@ -1915,6 +1928,9 @@ void conf(struct ctx *c, int argc, char **argv) prefix_len = ip4_default_prefix_len(a4); } c->ip4.addrs[0].prefix_len = prefix_len; + c->ip4.addr_count++; + if (c->mode == MODE_PASTA) + c->ip4.no_copy_addrs = true; break; }
@@ -2217,7 +2233,7 @@ void conf(struct ctx *c, int argc, char **argv) if (!c->ifi6) { c->no_ndp = 1; c->no_dhcpv6 = 1; - } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addrs[0].addr.a6)) { + } else if (!c->ip6.addr_count) { c->no_dhcpv6 = 1;
Kind of pre-existing, but AFAICT we entirely disable IPv6 (ifi6 == 0) if we have no addresses - that would seem to superseded disabling just DHCPv6.
}
diff --git a/pasta.c b/pasta.c index 1bb3dd0..27ce6a7 100644 --- a/pasta.c +++ b/pasta.c @@ -338,10 +338,16 @@ void pasta_ns_conf(struct ctx *c)
if (c->ifi4) { if (c->ip4.no_copy_addrs) { - rc = nl_addr_set(nl_sock_ns, c->pasta_ifi, - AF_INET, - inany_v4(&c->ip4.addrs[0].addr), - c->ip4.addrs[0].prefix_len); + int i; + + for (i = 0; i < c->ip4.addr_count; i++) { + rc = nl_addr_set(nl_sock_ns, + c->pasta_ifi, AF_INET, + inany_v4(&c->ip4.addrs[i].addr), + c->ip4.addrs[i].prefix_len); + if (rc < 0) + break; + } } else { rc = nl_addr_dup(nl_sock, c->ifi4, nl_sock_ns, c->pasta_ifi, @@ -387,12 +393,18 @@ void pasta_ns_conf(struct ctx *c) 0, IFF_NOARP);
if (c->ip6.no_copy_addrs) { - struct in6_addr *a = &c->ip6.addrs[0].addr.a6; + struct in6_addr *a; + int i;
- if (!IN6_IS_ADDR_UNSPECIFIED(a)) { + for (i = 0; i < c->ip6.addr_count; i++) { + a = &c->ip6.addrs[i].addr.a6; + if (IN6_IS_ADDR_UNSPECIFIED(a)) + continue; rc = nl_addr_set(nl_sock_ns, c->pasta_ifi, AF_INET6, a, 64); + if (rc < 0) + break; } } else { rc = nl_addr_dup(nl_sock, c->ifi6, -- 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
participants (2)
-
David Gibson
-
Jon Maloy