On Sun, Feb 22, 2026 at 12:44:33PM -0500, Jon Maloy wrote: > As preparation for supporting multiple addresses per interface, we > replace the single addr/prefix_len fields with an array. The > array consists of a new struct inany_addr_entry containing an > address and prefix length, both in inany_addr format. > > Despite some necessary code refactoring, there are only two > functional changes: > - The indicated IPv6 prefix length is now properly stored, instead > of being ignored and overridden with the hardcoded value 64, as > as has been the case until now. > - Since even IPv4 addresses now are stored in IPv6 format, we > also store the corresponding prefix length in that format, > i.e. using the range [96,128] instead of [0,32]. > > Signed-off-by: Jon Maloy > > --- > v2: -Using inany_addr instead of protocol specific addresses as > entry address field. > > v3: -Merging into one array, directly in struct ctx > -Changed prefix_len and flags fields in struct inany_addr_entry > to uint8_t, since that makes the struct directly migratable > > v4: -Updated according to changes in previous commits > -Updated according to feedback from David G. > -Squashed IP4_MASK macro commit into this one > --- > arp.c | 10 ++++- > conf.c | 124 +++++++++++++++++++++++++++++++++---------------------- > conf.h | 7 ++++ > dhcp.c | 13 ++++-- > dhcpv6.c | 9 ++-- > fwd.c | 18 ++++---- > ip.h | 5 +++ > ndp.c | 17 ++++++-- > passt.h | 64 ++++++++++++++++++++++++---- > pasta.c | 21 ++++++---- > tap.c | 11 ++++- > 11 files changed, 211 insertions(+), 88 deletions(-) > > diff --git a/arp.c b/arp.c > index bb042e9..99a6a67 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) > { > + struct inany_addr_entry *e = first_v4(c); > + > if (ah->ar_hrd != htons(ARPHRD_ETHER) || > ah->ar_pro != htons(ETH_P_IP) || > ah->ar_hln != ETH_ALEN || > @@ -54,7 +56,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 (e && !memcmp(am->tip, inany_v4(&e->addr), sizeof(am->tip))) > return true; > > return false; > @@ -123,12 +125,16 @@ int arp(const struct ctx *c, struct iov_tail *data) > */ > void arp_send_init_req(const struct ctx *c) > { > + struct inany_addr_entry *e = first_v4(c); > struct { > struct ethhdr eh; > struct arphdr ah; > struct arpmsg am; > } __attribute__((__packed__)) req; > > + if (!e) > + return; > + > /* Ethernet header */ > req.eh.h_proto = htons(ETH_P_ARP); > memcpy(req.eh.h_dest, MAC_BROADCAST, sizeof(req.eh.h_dest)); > @@ -145,7 +151,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(&e->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 11d8453..f2ff8af 100644 > --- a/conf.c > +++ b/conf.c > @@ -42,6 +42,7 @@ > #include "tap.h" > #include "udp.h" > #include "tcp.h" > +#include "conf.h" > #include "pasta.h" > #include "lineread.h" > #include "isolation.h" > @@ -701,13 +702,16 @@ static int conf_ip4_prefix(const char *arg) > > /** > * conf_ip4() - Verify or detect IPv4 support, get relevant addresses > + * @c: Execution context > * @ifi: Host interface to attempt (0 to determine one) > - * @ip4: IPv4 context (will be written) > * > * Return: interface index for IPv4, or 0 on failure. > */ > -static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) > +static unsigned int conf_ip4(struct ctx *c, unsigned int ifi) > { > + struct inany_addr_entry *e = first_v4(c); > + struct ip4_ctx *ip4 = &c->ip4; > + > if (!ifi) > ifi = nl_get_ext_if(nl_sock, AF_INET); > > @@ -726,60 +730,60 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) > } > } > > - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) { > + if (!e) { > + struct in_addr addr; > + int prefix_len = 0; If nl_addr_get() fails, you exit early, so I don't think you need this initialization. > 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; > } > - } > + if (IN4_IS_ADDR_UNSPECIFIED(&addr)) > + return 0; > > - 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; I don't see where this prefix length from class code has moved to. I'm not necessarily against removing it entirely - network classes are seriously anachronistic at this point - but if so that should be an explicit change in its own patch. > + e = &c->addrs[c->addr_count++]; I'm pretty sure at this point addr_count must be small. But it really makes me nervous to see this direct array assignment without bounds checking addr_count (here and in several other cases below). I really think you could do with an add_address() helper function which could do that bounds checking amongst other things. > + e->addr = inany_from_v4(addr); > + e->prefix_len = prefix_len + 96; > + e->flags = CONF_ADDR_HOST; > + ip4->addr_seen = addr; > } > > - ip4->addr_seen = ip4->addr; > - > ip4->our_tap_addr = ip4->guest_gw; > > - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) > - return 0; > - > return ifi; > } > > /** > * conf_ip4_local() - Configure IPv4 addresses and attributes for local mode > - * @ip4: IPv4 context (will be written) > + * @c: Execution context (will be written) > */ > -static void conf_ip4_local(struct ip4_ctx *ip4) > +static void conf_ip4_local(struct ctx *c) > { > - ip4->addr_seen = ip4->addr = IP4_LL_GUEST_ADDR; > - ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW; > - ip4->prefix_len = IP4_LL_PREFIX_LEN; > + struct inany_addr_entry *e = &c->addrs[c->addr_count++]; > + struct ip4_ctx *ip4 = &c->ip4; > > + ip4->addr_seen = IP4_LL_GUEST_ADDR; > + ip4->our_tap_addr = ip4->guest_gw = IP4_LL_GUEST_GW; > ip4->no_copy_addrs = ip4->no_copy_routes = true; > + e->addr = inany_from_v4(IP4_LL_GUEST_ADDR); > + e->prefix_len = IP4_LL_PREFIX_LEN + 96; > + e->flags = CONF_ADDR_HOST | CONF_ADDR_LINKLOCAL; > } > > /** > * conf_ip6() - Verify or detect IPv6 support, get relevant addresses > + * @c: Execution context > * @ifi: Host interface to attempt (0 to determine one) > - * @ip6: IPv6 context (will be written) > * > * Return: interface index for IPv6, or 0 on failure. > */ > -static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) > +static unsigned int conf_ip6(struct ctx *c, unsigned int ifi) > { > + struct inany_addr_entry *e = first_v6(c); > + struct ip6_ctx *ip6 = &c->ip6; > + union inany_addr addr; > int prefix_len = 0; > int rc; > > @@ -800,21 +804,26 @@ 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, > + rc = nl_addr_get(nl_sock, ifi, AF_INET6, &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 (!e) { > + e = &c->addrs[c->addr_count++]; > + e->addr = addr; > + e->prefix_len = prefix_len ? prefix_len : 64; So.. it looks like nl_addr_get() never fills in *prefix_len for IPv6 addresses, which seems like a bug in that function. So this test here is never exercised. Maybe just hardcode the prefix length to 64 here, and correct it both here and in nl_addr_get() in a different patch? > + e->flags = CONF_ADDR_HOST; > + } > + > + ip6->addr_seen = e->addr.a6; > > if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) > ip6->our_tap_ll = ip6->guest_gw; > > - if (IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) || > - IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) > + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->our_tap_ll)) > return 0; > > return ifi; > @@ -822,13 +831,13 @@ static unsigned int conf_ip6(unsigned int ifi, struct ip6_ctx *ip6) > > /** > * conf_ip6_local() - Configure IPv6 addresses and attributes for local mode > - * @ip6: IPv6 context (will be written) > + * @c: Execution context (will be written) > */ > -static void conf_ip6_local(struct ip6_ctx *ip6) > +static void conf_ip6_local(struct ctx *c) > { > - ip6->our_tap_ll = ip6->guest_gw = IP6_LL_GUEST_GW; > + c->ip6.our_tap_ll = c->ip6.guest_gw = IP6_LL_GUEST_GW; > > - ip6->no_copy_addrs = ip6->no_copy_routes = true; > + c->ip6.no_copy_addrs = c->ip6.no_copy_routes = true; > } > > /** > @@ -1111,6 +1120,7 @@ static void conf_print(const struct ctx *c) > { > char buf4[INET_ADDRSTRLEN], buf6[INET6_ADDRSTRLEN]; > char bufmac[ETH_ADDRSTRLEN], ifn[IFNAMSIZ]; > + struct inany_addr_entry *e; > int i; > > if (c->ifi4 > 0 || c->ifi6 > 0) { > @@ -1154,14 +1164,16 @@ static void conf_print(const struct ctx *c) > inet_ntop(AF_INET, &c->ip4.map_host_loopback, > buf4, sizeof(buf4))); > > - if (!c->no_dhcp) { > + e = first_v4(c); > + if (e && !c->no_dhcp) { > uint32_t mask; > > - mask = htonl(0xffffffff << (32 - c->ip4.prefix_len)); > + mask = IN4_MASK(inany_prefix4(e)); > > info("DHCP:"); > info(" assign: %s", > - inet_ntop(AF_INET, &c->ip4.addr, buf4, sizeof(buf4))); > + 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", > @@ -1200,8 +1212,9 @@ static void conf_print(const struct ctx *c) > else > goto dns6; > > - info(" assign: %s", > - inet_ntop(AF_INET6, &c->ip6.addr, buf6, sizeof(buf6))); > + e = first_v6(c); > + info(" assign: %s", !e ? "" : > + inet_ntop(AF_INET6, &e->addr.a6, buf6, sizeof(buf6))); > info(" router: %s", > inet_ntop(AF_INET6, &c->ip6.guest_gw, buf6, sizeof(buf6))); > info(" our link-local: %s", > @@ -1837,6 +1850,7 @@ void conf(struct ctx *c, int argc, char **argv) > break; > } > case 'a': { > + struct inany_addr_entry *e; > union inany_addr addr; > uint8_t prefix_len; > > @@ -1860,19 +1874,27 @@ void conf(struct ctx *c, int argc, char **argv) > IN6_IS_ADDR_V4COMPAT(&addr.a6)) > die("Invalid address: %s", optarg); > > + /* Legacy behaviour: overwrite existing address if any */ > if (inany_v4(&addr)) { > - c->ip4.addr = *inany_v4(&addr); > - c->ip4.prefix_len = prefix_len - 96; > + e = first_v4(c); > + if (!e) > + e = &c->addrs[c->addr_count++]; > if (c->mode == MODE_PASTA) > c->ip4.no_copy_addrs = true; > } else { > - c->ip6.addr = addr.a6; > + e = first_v6(c); > + if (!e) > + e = &c->addrs[c->addr_count++]; > if (c->mode == MODE_PASTA) > c->ip6.no_copy_addrs = true; > } > + e->prefix_len = prefix_len; > + e->addr = addr; > + e->flags = CONF_ADDR_USER; > break; > } > case 'n': { > + struct inany_addr_entry *e; > int plen; > > if (addr_has_prefix_len) > @@ -1883,7 +1905,9 @@ void conf(struct ctx *c, int argc, char **argv) > die("Invalid prefix length: %s", optarg); > > prefix_len_from_opt = plen + 96; > - c->ip4.prefix_len = plen; > + e = first_v4(c); > + if (e) > + e->prefix_len = prefix_len_from_opt; > break; > } > case 'M': > @@ -2042,9 +2066,9 @@ void conf(struct ctx *c, int argc, char **argv) > > nl_sock_init(c, false); > if (!v6_only && !c->splice_only) > - c->ifi4 = conf_ip4(ifi4, &c->ip4); > + c->ifi4 = conf_ip4(c, ifi4); > if (!v4_only && !c->splice_only) > - c->ifi6 = conf_ip6(ifi6, &c->ip6); > + c->ifi6 = conf_ip6(c, ifi6); > > if (c->ifi4 && c->mtu < IPV4_MIN_MTU) { > warn("MTU %"PRIu16" is too small for IPv4 (minimum %u)", > @@ -2067,7 +2091,7 @@ void conf(struct ctx *c, int argc, char **argv) > if (!c->ifi4 && !v6_only) { > if (!c->splice_only) { > info("IPv4: no external interface as template, use local mode"); > - conf_ip4_local(&c->ip4); > + conf_ip4_local(c); > } > c->ifi4 = -1; > } > @@ -2075,7 +2099,7 @@ void conf(struct ctx *c, int argc, char **argv) > if (!c->ifi6 && !v4_only) { > if (!c->splice_only) { > info("IPv6: no external interface as template, use local mode"); > - conf_ip6_local(&c->ip6); > + conf_ip6_local(c); > } > c->ifi6 = -1; > } > @@ -2184,7 +2208,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 (!first_v6(c)) { > c->no_dhcpv6 = 1; > } > > diff --git a/conf.h b/conf.h > index b45ad74..bfad36f 100644 > --- a/conf.h > +++ b/conf.h > @@ -6,6 +6,13 @@ > #ifndef CONF_H > #define CONF_H > > +/* Flags indicating origin and role of an address > + * To be used in struct inany_addr_entry > + */ > +#define CONF_ADDR_USER BIT(0) /* User set via -a */ > +#define CONF_ADDR_HOST BIT(1) /* From host interface */ Shouldn't these definitions go next to the definition of struct inany_addr_entry, where they're used? > +#define CONF_ADDR_LINKLOCAL BIT(2) /* Link-local address */ This bit is redundant with the value of the address itself, and AFAICT, you don't actually test it at any point. Do you need it? > + > enum passt_modes conf_mode(int argc, char *argv[]); > void conf(struct ctx *c, int argc, char **argv); > > diff --git a/dhcp.c b/dhcp.c > index 1ff8cba..af473ee 100644 > --- a/dhcp.c > +++ b/dhcp.c > @@ -302,6 +302,7 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len) > */ > int dhcp(const struct ctx *c, struct iov_tail *data) > { > + struct inany_addr_entry *e = first_v4(c); > char macstr[ETH_ADDRSTRLEN]; > size_t mlen, dlen, opt_len; > struct in_addr mask, dst; > @@ -313,6 +314,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > const struct udphdr *uh; > struct msg m_storage; > struct msg const *m; > + struct in_addr addr; > struct msg reply; > unsigned int i; > > @@ -344,6 +346,9 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > m->op != BOOTREQUEST) > return -1; > > + ASSERT(e); > + addr = *inany_v4(&e->addr); > + > reply.op = BOOTREPLY; > reply.htype = m->htype; > reply.hlen = m->hlen; > @@ -352,7 +357,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 = addr; > reply.siaddr = 0; > reply.giaddr = m->giaddr; > memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); > @@ -404,7 +409,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 = IN4_MASK(inany_prefix4(e)); > 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 +417,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 ((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; > @@ -471,7 +476,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 = addr; > > tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen); > > diff --git a/dhcpv6.c b/dhcpv6.c > index 97c04e2..55c17cf 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -552,6 +552,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > const struct in6_addr *saddr, const struct in6_addr *daddr) > { > const struct opt_server_id *server_id = NULL; > + struct inany_addr_entry *e = first_v6(c); > const struct opt_hdr *client_id = NULL; > /* The _storage variables can't be local to the blocks they're used in, > * because IOV_*_HEADER() may return pointers to them which are > @@ -628,7 +629,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 (e && dhcpv6_ia_notonlink(data, &e->addr.a6)) { > > dhcpv6_send_ia_notonlink(c, data, &client_id_base, > ntohs(client_id->l), mh->xid); > @@ -682,7 +683,8 @@ 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; > + if (e) > + c->ip6.addr_seen = e->addr.a6; Kind of pre-existing, but this seems an odd place to initialise addr_seen to addr. Shouldn't that be done somewhere during conf()? > > return 1; > } > @@ -706,5 +708,6 @@ 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; > + if (first_v6(c)) > + resp.ia_addr.addr = first_v6(c)->addr.a6; > } > diff --git a/fwd.c b/fwd.c > index 4052b79..cf13ddd 100644 > --- a/fwd.c > +++ b/fwd.c > @@ -890,6 +890,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) > { > + struct inany_addr_entry *e = first_v4(c); > + > if (IN4_IS_ADDR_LOOPBACK(addr)) > return false; > > @@ -904,7 +906,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 ((e && IN4_ARE_ADDR_EQUAL(addr, inany_v4(&e->addr))) || > IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) > return false; > > @@ -925,7 +927,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 (first_v6(c) && IN6_ARE_ADDR_EQUAL(addr, &first_v6(c)->addr.a6)) > return false; > > /* For IPv6, addr_seen starts unspecified, because we don't know what LL > @@ -974,10 +976,10 @@ static void nat_outbound(const struct ctx *c, const union inany_addr *addr, > *translated = inany_loopback4; > 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); > - else if (inany_equals6(addr, &c->ip6.map_guest_addr)) > - translated->a6 = c->ip6.addr; > + else if (first_v4(c) && inany_equals4(addr, &c->ip4.map_guest_addr)) > + *translated = first_v4(c)->addr; > + else if (first_v6(c) && inany_equals6(addr, &c->ip6.map_guest_addr)) > + translated->a6 = first_v6(c)->addr.a6; This introduces a subtle behavioural change. Before, the if condition depended only on map_guest_addr - if there was no address to translate to (ip4.addr == 0.0.0.0), we'd attempt to translate to 0.0.0.0. Which... would be weird, and we probably should catch and simply drop the forward in the callers for that case. The new code puts the condition in the test, which means if there is no address, we proceed to the next case and will eventually leave the address untranslated. That potentially leaks a guest side address (map_guest_addr) untranslated onto the host side, which we shouldn't do. (I'd argue that it's worse than using 0.0.0.0 on the host side). > else > *translated = *addr; > } > @@ -1085,10 +1087,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)) { > + first_v4(c) && inany_equals(addr, &first_v4(c)->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)) { > + first_v6(c) && inany_equals(addr, &first_v6(c)->addr)) { > translated->a6 = c->ip6.map_guest_addr; > } else if (fwd_guest_accessible(c, addr)) { > *translated = *addr; > diff --git a/ip.h b/ip.h > index 57d8661..c68c366 100644 > --- a/ip.h > +++ b/ip.h > @@ -17,6 +17,8 @@ > (ntohl(((struct in_addr *)(a))->s_addr) >> IN_CLASSA_NSHIFT == IN_LOOPBACKNET) > #define IN4_IS_ADDR_MULTICAST(a) \ > (IN_MULTICAST(ntohl(((struct in_addr *)(a))->s_addr))) > +#define IN4_MASK(prefix) \ > + ((prefix) <= 0 ? 0 : htonl(0xffffffff << (32 - (prefix)))) > #define IN4_ARE_ADDR_EQUAL(a, b) \ > (((struct in_addr *)(a))->s_addr == ((struct in_addr *)b)->s_addr) > #define IN4ADDR_LOOPBACK_INIT \ > @@ -136,6 +138,9 @@ static const struct in_addr in4addr_broadcast = { 0xffffffff }; > #define IPV6_MIN_MTU 1280 > #endif > > +/* Maximum number of addresses in context address array */ > +#define INANY_MAX_ADDRS 32 This seems like it belongs on passt.h with the array whose size it controls, rather than in ip.h - this is a parameter about our internal data structures, not something inherent to IP. > + > int ip4_class_prefix_len(const struct in_addr *addr); > > #endif /* IP_H */ > diff --git a/ndp.c b/ndp.c > index 1f2bcb0..ed8c6ae 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 = IN6ADDR_ANY_INIT, You unconditionally write this below, so you don't need an explicit initializer. > .source_ll = { > .header = { > .type = OPT_SRC_L2_ADDR, > @@ -265,8 +265,13 @@ static void ndp_ra(const struct ctx *c, const struct in6_addr *dst) > }, > }, > }; > + struct inany_addr_entry *e = first_v6(c); > unsigned char *ptr = NULL; > > + ASSERT(e); > + > + ra.prefix = e->addr.a6; > + > ptr = &ra.var[0]; > > if (c->mtu) { > @@ -460,6 +465,7 @@ first: > */ > void ndp_send_init_req(const struct ctx *c) > { > + struct inany_addr_entry *e = first_v6(c); > struct ndp_ns ns = { > .ih = { > .icmp6_type = NS, > @@ -468,8 +474,13 @@ void ndp_send_init_req(const struct ctx *c) > .icmp6_solicited = 0, /* Reserved */ > .icmp6_override = 0, /* Reserved */ > }, > - .target_addr = c->ip6.addr > + .target_addr = IN6ADDR_ANY_INIT > }; > + > + if (!e) > + return; > + > + ns.target_addr = e->addr.a6; > debug("Sending initial NDP NS request for guest MAC address"); > - ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); > + ndp_send(c, &e->addr.a6, &ns, sizeof(ns)); > } > diff --git a/passt.h b/passt.h > index 299185b..1aa71f0 100644 > --- a/passt.h > +++ b/passt.h > @@ -64,11 +64,21 @@ enum passt_modes { > MODE_VU, > }; > > +/** > + * struct inany_addr_entry - Unified IPv4/IPv6 address entry "inany" isn't really relevant to the structure name, and is misleading (it suggests this is defined in inany.h). Maybe struct guest_addr? > + * @addr: IPv4 (as mapped) or IPv6 address > + * @prefix_len: Prefix length in IPv6/IPv4-mapped [0,128]/[96,128] format > + * @flags: CONF_ADDR_* flags > + */ > +struct inany_addr_entry { > + union inany_addr addr; > + uint8_t prefix_len; > + uint8_t flags; > +}; > + > /** > * struct ip4_ctx - IPv4 execution context > - * @addr: IPv4 address assigned to guest > * @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 > @@ -84,10 +94,7 @@ enum passt_modes { > * @no_copy_addrs: Don't copy all addresses when configuring namespace > */ > struct ip4_ctx { > - /* PIF_TAP addresses */ > - struct in_addr addr; > 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 +114,6 @@ struct ip4_ctx { > > /** > * struct ip6_ctx - IPv6 execution context > - * @addr: IPv6 address assigned to guest > * @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 > @@ -125,8 +131,6 @@ struct ip4_ctx { > * @no_copy_addrs: Don't copy all addresses when configuring namespace > */ > struct ip6_ctx { > - /* PIF_TAP addresses */ > - struct in6_addr addr; > struct in6_addr addr_seen; > struct in6_addr addr_ll_seen; > struct in6_addr guest_gw; > @@ -181,6 +185,8 @@ struct ip6_ctx { > * @fqdn: Guest FQDN > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled > * @ip6: IPv6 configuration > + * @addrs: Unified address array for both IPv4 (mapped) and IPv6 > + * @addr_count: Number of active entries in @addrs array > * @pasta_ifn: Name of namespace interface for pasta > * @pasta_ifi: Index of namespace interface for pasta > * @pasta_conf_ns: Configure namespace after creating it > @@ -258,6 +264,9 @@ struct ctx { > int ifi6; > struct ip6_ctx ip6; > > + struct inany_addr_entry addrs[INANY_MAX_ADDRS]; > + int addr_count; > + > char pasta_ifn[IF_NAMESIZE]; > unsigned int pasta_ifi; > int pasta_conf_ns; > @@ -296,6 +305,45 @@ struct ctx { > bool migrate_exit; > }; > > +/** > + * first_v4() - Get first IPv4 address entry > + * @c: Pointer to struct ctx > + * > + * Return: pointer to first IPv4 entry, or NULL if none > + */ > +static inline struct inany_addr_entry *first_v4(const struct ctx *c) > +{ > + for (int i = 0; i < c->addr_count; i++) > + if (inany_v4(&c->addrs[i].addr)) > + return (struct inany_addr_entry *)&c->addrs[i]; AFAICT this, by design, returns an editable (non const) address entry. In which case it shouldn't take a const ctx pointer, which would avoid this const discarding cast. > + return NULL; > +} > + > +/** > + * first_v6() - Get first IPv6 address entry > + * @c: Pointer to struct ctx > + * > + * Return: pointer to first IPv6 entry, or NULL if none > + */ > +static inline struct inany_addr_entry *first_v6(const struct ctx *c) > +{ > + for (int i = 0; i < c->addr_count; i++) > + if (!inany_v4(&c->addrs[i].addr)) > + return (struct inany_addr_entry *)&c->addrs[i]; > + return NULL; > +} > + > +/** > + * inany_prefix4() - Get IPv4 prefix length from address entry > + * @e: Address entry (must be IPv4) > + * > + * Return: prefix length in IPv4 format (0-32) > + */ > +static inline int inany_prefix4(const struct inany_addr_entry *e) > +{ > + return e->prefix_len - 96; > +} > + > void proto_update_l2_buf(const unsigned char *eth_d); > > #endif /* PASST_H */ > diff --git a/pasta.c b/pasta.c > index bab945f..54010de 100644 > --- a/pasta.c > +++ b/pasta.c > @@ -330,6 +330,7 @@ void pasta_ns_conf(struct ctx *c) > > if (c->pasta_conf_ns) { > unsigned int flags = IFF_UP; > + struct inany_addr_entry *e; > > if (c->mtu) > nl_link_set_mtu(nl_sock_ns, c->pasta_ifi, c->mtu); > @@ -341,10 +342,12 @@ 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, > - &c->ip4.addr, > - c->ip4.prefix_len); > + e = first_v4(c); > + if (e) > + rc = nl_addr_set(nl_sock_ns, > + c->pasta_ifi, AF_INET, > + inany_v4(&e->addr), > + e->prefix_len - 96); > } else { > rc = nl_addr_dup(nl_sock, c->ifi4, > nl_sock_ns, c->pasta_ifi, > @@ -397,11 +400,13 @@ ipv4_done: > 0, IFF_NOARP); > > if (c->ip6.no_copy_addrs) { > - if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { > + e = first_v6(c); > + if (e) > rc = nl_addr_set(nl_sock_ns, > - c->pasta_ifi, AF_INET6, > - &c->ip6.addr, 64); > - } > + c->pasta_ifi, > + AF_INET6, > + &e->addr.a6, > + e->prefix_len); > } else { > rc = nl_addr_dup(nl_sock, c->ifi6, > nl_sock_ns, c->pasta_ifi, > diff --git a/tap.c b/tap.c > index eaa6111..7f79add 100644 > --- a/tap.c > +++ b/tap.c > @@ -47,6 +47,7 @@ > #include "ip.h" > #include "iov.h" > #include "passt.h" > +#include "conf.h" > #include "arp.h" > #include "dhcp.h" > #include "ndp.h" > @@ -951,8 +952,14 @@ resume: > c->ip6.addr_seen = *saddr; > } > > - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) > - c->ip6.addr = *saddr; > + if (!first_v6(c) && c->addr_count < INANY_MAX_ADDRS) { > + struct inany_addr_entry *e; > + > + e = &c->addrs[c->addr_count++]; > + e->addr.a6 = *saddr; > + e->prefix_len = 64; > + e->flags = CONF_ADDR_LINKLOCAL; > + } > } 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