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