From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=none (p=none dis=none) header.from=gibson.dropbear.id.au Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=gibson.dropbear.id.au header.i=@gibson.dropbear.id.au header.a=rsa-sha256 header.s=202602 header.b=UDJA5UhJ; dkim-atps=neutral Received: from mail.ozlabs.org (mail.ozlabs.org [IPv6:2404:9400:2221:ea00::3]) by passt.top (Postfix) with ESMTPS id 0CD3E5A0269 for ; Thu, 14 May 2026 08:30:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gibson.dropbear.id.au; s=202602; t=1778740221; bh=zVJEN4KGNb6kx6k5OPdtRormG9VrYOK2OzOvwSi7oqM=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=UDJA5UhJf6XX0r0pdRO+jz2JantzUcPM7sLpi7MpdNASvNu1OFVBr+RWkDP5fWJ1d JC8ZndiC9+yAqHLP9SqZxPqGEfeFne8zNm5tetyR2xSLuac7kdlhxlkfSsx15P2Zfy s0czfYNQDdHrRj6dFIpfdJaSbDdmRAqgniPOlSMeLKlvual7eERRkRt1zy4SRKb+pZ n8iZWBMUp7VHa+Lq20lOseEp2TeYDV6HxL2kfkkFvp2MogTLq7v6P6zD+YfX5PF7U9 Suc1aDVE5CbGpCFtgJTuPg/Q6DAuGH4ep88vFeGaF+huJsM46ISqYb69hRxQYn2ewX RpJT5cESJ6+cA== Received: by gandalf.ozlabs.org (Postfix, from userid 1007) id 4gGL6j3ccGz4wCW; Thu, 14 May 2026 16:30:21 +1000 (AEST) Date: Thu, 14 May 2026 16:30:16 +1000 From: David Gibson To: Jon Maloy Subject: Re: [PATCH v7 02/13] passt, pasta: Introduce unified multi-address data structures Message-ID: References: <20260413005319.3295910-1-jmaloy@redhat.com> <20260413005319.3295910-3-jmaloy@redhat.com> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="V4uPjQ3tldNbsEzo" Content-Disposition: inline In-Reply-To: <20260413005319.3295910-3-jmaloy@redhat.com> Message-ID-Hash: 3QUWOD3IWM4OTHGZYLXKVNX4OWDD3IDU X-Message-ID-Hash: 3QUWOD3IWM4OTHGZYLXKVNX4OWDD3IDU X-MailFrom: dgibson@gandalf.ozlabs.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: sbrivio@redhat.com, passt-dev@passt.top X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: --V4uPjQ3tldNbsEzo Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Content-Transfer-Encoding: quoted-printable On Sun, Apr 12, 2026 at 08:53:08PM -0400, 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. >=20 > Despite a lot of code refactoring, there are only two real functional > changes: > - The indicated IPv6 prefix length is now properly stored, instead > of being ignored and overridden with the hardcoded value 64, 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]. >=20 > Signed-off-by: Jon Maloy >=20 > --- > v2: -Using inany_addr instead of protocol specific addresses as > entry address field. >=20 > 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 >=20 > v4: -Updated according to changes in previous commits > -Updated according to feedback from David G. > -Squashed IP4_MASK macro commit into this one >=20 > v6: -Renamed and moved some definitions > -Introduced fwd_set_addr() and fwd_get_addr() already in this commit > -Eliminated first_v4/v6() functions, replaced with fwd_get_addr() > -Some other changes as suggested by David G. > -I kept the flag CONF_ADDR_LINKLOCAL, since it will be > needed later in an address selection function. >=20 > v7: -Introduced CONF_ADDR_GENERATED flag > -Other fixes based on feedback from David and Stefano. > -I changed signature of inany_prefix_len(), but I did not change > its semantics, since the premise of David's comment is wrong: the > caller does *not* explicitly know he is dealing with an IPv4 address. > In fact, there are examples later in this series where it may be an > IPv6 address, and the caller just trusts he gets the return value in > the appropriate format. Can you elaborate on how that arises? I can't really see how you'd need the IPv4 format prefix length, except in a context where you're also dealing with an explicitly IPv4 address. > -Introduced the inverse of inany_prefix_len(), called inany_prefix_le= n6() > which always returns the prefix in IPv6 or mapped IPv4 format. > The name of the function isn't great, but any alternative I came up > with became too long to be practical. > --- > arp.c | 12 ++++- > conf.c | 143 ++++++++++++++++++++++++++++++------------------------- > dhcp.c | 14 ++++-- > dhcpv6.c | 15 ++++-- > fwd.c | 109 ++++++++++++++++++++++++++++++++++-------- > fwd.h | 4 ++ > inany.h | 41 ++++++++++++++++ > ip.h | 2 + > ndp.c | 16 +++++-- > passt.h | 67 ++++++++++++++++++++++---- > pasta.c | 25 ++++++---- > tap.c | 7 ++- > 12 files changed, 340 insertions(+), 115 deletions(-) >=20 > diff --git a/arp.c b/arp.c > index bb042e9..a7fd82f 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) > { > + const struct guest_addr *a; > + > if (ah->ar_hrd !=3D htons(ARPHRD_ETHER) || > ah->ar_pro !=3D htons(ETH_P_IP) || > ah->ar_hln !=3D ETH_ALEN || > @@ -54,7 +56,8 @@ static bool ignore_arp(const struct ctx *c, > return true; > =20 > /* Don't resolve the guest's assigned address, either. */ > - if (!memcmp(am->tip, &c->ip4.addr, sizeof(am->tip))) > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + if (a && !memcmp(am->tip, inany_v4(&a->addr), sizeof(am->tip))) > return true; > =20 > return false; > @@ -123,12 +126,17 @@ int arp(const struct ctx *c, struct iov_tail *data) > */ > void arp_send_init_req(const struct ctx *c) > { > + const struct guest_addr *a; > struct { > struct ethhdr eh; > struct arphdr ah; > struct arpmsg am; > } __attribute__((__packed__)) req; > =20 > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + if (!a) > + return; > + > /* Ethernet header */ > req.eh.h_proto =3D htons(ETH_P_ARP); > memcpy(req.eh.h_dest, MAC_BROADCAST, sizeof(req.eh.h_dest)); > @@ -145,7 +153,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(&a->addr), sizeof(req.am.tip)); > =20 > 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 f13fef6..591f561 100644 > --- a/conf.c > +++ b/conf.c > @@ -728,13 +728,15 @@ static int conf_ip4_prefix(const char *arg) > =20 > /** > * 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 ip4_ctx *ip4 =3D &c->ip4; > + > if (!ifi) > ifi =3D nl_get_ext_if(nl_sock, AF_INET); > =20 > @@ -753,60 +755,57 @@ static unsigned int conf_ip4(unsigned int ifi, stru= ct ip4_ctx *ip4) > } > } > =20 > - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) { > + if (!fwd_get_addr(c, AF_INET, 0, 0)) { > + struct in_addr addr; > + int prefix_len; > int rc =3D 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; > =20 > - if (!ip4->prefix_len) { > - in_addr_t addr =3D ntohl(ip4->addr.s_addr); > - if (IN_CLASSA(addr)) > - ip4->prefix_len =3D (32 - IN_CLASSA_NSHIFT); > - else if (IN_CLASSB(addr)) > - ip4->prefix_len =3D (32 - IN_CLASSB_NSHIFT); > - else if (IN_CLASSC(addr)) > - ip4->prefix_len =3D (32 - IN_CLASSC_NSHIFT); > - else > - ip4->prefix_len =3D 32; > + fwd_set_addr(c, &inany_from_v4(addr), CONF_ADDR_HOST, > + prefix_len); I think fwd_set_addr too should always take prefix_len in IPv6 format, since it takes an inany. > + ip4->addr_seen =3D addr; If I'm following correctly, moving this into the if statement will mean that if an address is explicitly configured with -a, then addr_seen will no longer be initialised to it. It all goes away later in the series, but that seems incorrect at this point. > } > =20 > - ip4->addr_seen =3D ip4->addr; > - > ip4->our_tap_addr =3D ip4->guest_gw; > =20 > - if (IN4_IS_ADDR_UNSPECIFIED(&ip4->addr)) > - return 0; > - > return ifi; > } > =20 > /** > * 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 =3D ip4->addr =3D IP4_LL_GUEST_ADDR; > - ip4->our_tap_addr =3D ip4->guest_gw =3D IP4_LL_GUEST_GW; > - ip4->prefix_len =3D IP4_LL_PREFIX_LEN; > + struct ip4_ctx *ip4 =3D &c->ip4; > =20 > + ip4->addr_seen =3D IP4_LL_GUEST_ADDR; > + ip4->our_tap_addr =3D ip4->guest_gw =3D IP4_LL_GUEST_GW; > ip4->no_copy_addrs =3D ip4->no_copy_routes =3D true; > + fwd_set_addr(c, &inany_from_v4(IP4_LL_GUEST_ADDR), > + CONF_ADDR_GENERATED | CONF_ADDR_LINKLOCAL, > + IP4_LL_PREFIX_LEN); Aside: CONF_ADDR_LINKLOCAL is conveying a pretty different classification from most of the other flag bits. I also if at any point we might need to consider address scopes further than "link local" or "not link local". It might be worth considering an explicit scope field, using the RT_SCOPE_* constants, rather than using the flag bits for this > } > =20 > /** > * 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 ip6_ctx *ip6 =3D &c->ip6; > + const struct guest_addr *a; > + union inany_addr addr; > int prefix_len =3D 0; > int rc; > =20 > @@ -827,21 +826,28 @@ static unsigned int conf_ip6(unsigned int ifi, stru= ct ip6_ctx *ip6) > } > } > =20 > - rc =3D nl_addr_get(nl_sock, ifi, AF_INET6, > - IN6_IS_ADDR_UNSPECIFIED(&ip6->addr) ? &ip6->addr : NULL, > + rc =3D 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; > } > =20 > - ip6->addr_seen =3D ip6->addr; > + a =3D fwd_get_addr(c, AF_INET6, 0, 0); > + if (!a) { > + if (IN6_IS_ADDR_UNSPECIFIED(&addr)) > + return 0; > + > + fwd_set_addr(c, &addr, CONF_ADDR_HOST, prefix_len); > + ip6->addr_seen =3D addr.a6; > + } else { > + ip6->addr_seen =3D a->addr.a6; > + } > =20 > if (IN6_IS_ADDR_LINKLOCAL(&ip6->guest_gw)) > ip6->our_tap_ll =3D ip6->guest_gw; > =20 > - 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; > =20 > return ifi; > @@ -849,13 +855,13 @@ static unsigned int conf_ip6(unsigned int ifi, stru= ct ip6_ctx *ip6) > =20 > /** > * 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 =3D ip6->guest_gw =3D IP6_LL_GUEST_GW; > + c->ip6.our_tap_ll =3D c->ip6.guest_gw =3D IP6_LL_GUEST_GW; > =20 > - ip6->no_copy_addrs =3D ip6->no_copy_routes =3D true; > + c->ip6.no_copy_addrs =3D c->ip6.no_copy_routes =3D true; > } > =20 > /** > @@ -1137,6 +1143,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) > static void conf_print(const struct ctx *c) > { > char buf[INANY_ADDRSTRLEN]; > + const struct guest_addr *a; > int i; > =20 > if (c->ifi4 > 0 || c->ifi6 > 0) { > @@ -1181,16 +1188,18 @@ static void conf_print(const struct ctx *c) > inet_ntop(AF_INET, &c->ip4.map_host_loopback, > buf, sizeof(buf))); > =20 > - if (!c->no_dhcp) { > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + if (a && !c->no_dhcp) { > uint32_t mask; > =20 > - mask =3D htonl(0xffffffff << (32 - c->ip4.prefix_len)); > + mask =3D IN4_MASK(inany_prefix_len(&a->addr, > + a->prefix_len)); > =20 > info("DHCP:"); > info(" assign: %s", > - inet_ntop(AF_INET, &c->ip4.addr, buf, sizeof(buf))); > + inany_ntop(&a->addr, buf, sizeof(buf))); > info(" mask: %s", > - inet_ntop(AF_INET, &mask, buf, sizeof(buf))); > + inet_ntop(AF_INET, &mask, buf, sizeof(buf))); > info(" router: %s", > inet_ntop(AF_INET, &c->ip4.guest_gw, > buf, sizeof(buf))); > @@ -1201,8 +1210,8 @@ static void conf_print(const struct ctx *c) > break; > if (!i) > info("DNS:"); > - inet_ntop(AF_INET, &c->ip4.dns[i], buf, sizeof(buf)); > - info(" %s", buf); > + info(" %s", inet_ntop(AF_INET, &c->ip4.dns[i], > + buf, sizeof(buf))); > } > =20 > for (i =3D 0; *c->dns_search[i].n; i++) { > @@ -1227,13 +1236,14 @@ static void conf_print(const struct ctx *c) > else > goto dns6; > =20 > - info(" assign: %s", > - inet_ntop(AF_INET6, &c->ip6.addr, buf, sizeof(buf))); > + a =3D fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + if (a) > + info(" assign: %s", > + inany_ntop(&a->addr, buf, sizeof(buf))); > info(" router: %s", > inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf))); > info(" our link-local: %s", > - inet_ntop(AF_INET6, &c->ip6.our_tap_ll, > - buf, sizeof(buf))); > + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, buf, sizeof(buf))); > =20 > dns6: > for (i =3D 0; i < ARRAY_SIZE(c->ip6.dns); i++) { > @@ -1241,8 +1251,10 @@ dns6: > break; > if (!i) > info("DNS:"); > - inet_ntop(AF_INET6, &c->ip6.dns[i], buf, sizeof(buf)); > - info(" %s", buf); > + info(" %s", > + inet_ntop(AF_INET6, &c->ip6.dns[i], > + buf, sizeof(buf))); > + > } > =20 > for (i =3D 0; *c->dns_search[i].n; i++) { > @@ -1886,19 +1898,16 @@ void conf(struct ctx *c, int argc, char **argv) > IN6_IS_ADDR_V4COMPAT(&addr.a6)) > die("Invalid address: %s", optarg); > =20 > - if (inany_v4(&addr)) { > - c->ip4.addr =3D *inany_v4(&addr); > - c->ip4.prefix_len =3D prefix_len - 96; > - if (c->mode =3D=3D MODE_PASTA) > - c->ip4.no_copy_addrs =3D true; > - } else { > - c->ip6.addr =3D addr.a6; > - if (c->mode =3D=3D MODE_PASTA) > - c->ip6.no_copy_addrs =3D true; > - } > + /* Legacy behaviour: replace existing address if any */ > + fwd_set_addr(c, &addr, CONF_ADDR_USER, prefix_len); > + if (inany_v4(&addr)) > + c->ip4.no_copy_addrs =3D true; > + else > + c->ip6.no_copy_addrs =3D true; > break; > } > case 'n': { > + struct guest_addr *a; > int plen; > =20 > if (addr_has_prefix_len) > @@ -1908,8 +1917,12 @@ void conf(struct ctx *c, int argc, char **argv) > if (plen < 0) > die("Invalid prefix length: %s", optarg); > =20 > - prefix_len_from_opt =3D plen + 96; > - c->ip4.prefix_len =3D plen; > + prefix_len_from_opt =3D plen; The change in encoding of prefix_len_from_opt only works / is necessary because fwd_set_addr() takes the prefix length in different formats depending on the address type, which I think is a bad idea. > + > + for_each_addr(a, c->addrs, c->addr_count, AF_INET) { > + a->prefix_len =3D inany_prefix_len(&a->addr, plen); I still think a should always have the prefix length in IPv6 format, which is not what inany_prefix_len() returns. Essentially, when the prefix length is attached to an inany, it should always bein IPv6 format. Only when it is attached to a struct in_addr specifically should it be in IPv4 format. > + break; > + } > break; > } > case 'M': > @@ -2103,9 +2116,9 @@ void conf(struct ctx *c, int argc, char **argv) > =20 > nl_sock_init(c, false); > if (!v6_only && !c->splice_only) > - c->ifi4 =3D conf_ip4(ifi4, &c->ip4); > + c->ifi4 =3D conf_ip4(c, ifi4); > if (!v4_only && !c->splice_only) > - c->ifi6 =3D conf_ip6(ifi6, &c->ip6); > + c->ifi6 =3D conf_ip6(c, ifi6); > =20 > if (c->ifi4 && c->mtu < IPV4_MIN_MTU) { > warn("MTU %"PRIu16" is too small for IPv4 (minimum %u)", > @@ -2128,7 +2141,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 =3D -1; > } > @@ -2136,7 +2149,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 =3D -1; > } > @@ -2201,7 +2214,7 @@ void conf(struct ctx *c, int argc, char **argv) > if (!c->ifi6) { > c->no_ndp =3D 1; > c->no_dhcpv6 =3D 1; > - } else if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { > + } else if (!fwd_get_addr(c, AF_INET6, 0, 0)) { > c->no_dhcpv6 =3D 1; > } > =20 > diff --git a/dhcp.c b/dhcp.c > index 1ff8cba..f0fa212 100644 > --- a/dhcp.c > +++ b/dhcp.c > @@ -303,6 +303,7 @@ static void opt_set_dns_search(const struct ctx *c, s= ize_t max_len) > int dhcp(const struct ctx *c, struct iov_tail *data) > { > char macstr[ETH_ADDRSTRLEN]; > + const struct guest_addr *a; > size_t mlen, dlen, opt_len; > struct in_addr mask, dst; > struct ethhdr eh_storage; > @@ -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; > =20 > @@ -344,6 +346,10 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > m->op !=3D BOOTREQUEST) > return -1; > =20 > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + assert(a); This assert() is only safe, AFAICT, because if there's no IPv4 address we enable c->no_dhcp. I think it would be more robust to have dhcp() itself harmlessly no-op if !a. > + addr =3D *inany_v4(&a->addr); > + > reply.op =3D BOOTREPLY; > reply.htype =3D m->htype; > reply.hlen =3D m->hlen; > @@ -352,7 +358,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > reply.secs =3D 0; > reply.flags =3D m->flags; > reply.ciaddr =3D m->ciaddr; > - reply.yiaddr =3D c->ip4.addr; > + reply.yiaddr =3D addr; > reply.siaddr =3D 0; > reply.giaddr =3D m->giaddr; > memcpy(&reply.chaddr, m->chaddr, sizeof(reply.chaddr)); > @@ -404,7 +410,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > =20 > info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr))); > =20 > - mask.s_addr =3D htonl(0xffffffff << (32 - c->ip4.prefix_len)); > + mask.s_addr =3D IN4_MASK(inany_prefix_len(&a->addr, a->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 +418,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) > !=3D (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 =3D 14; > @@ -471,7 +477,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data) > if (m->flags & FLAG_BROADCAST) > dst =3D in4addr_broadcast; > else > - dst =3D c->ip4.addr; > + dst =3D addr; > =20 > tap_udp4_send(c, c->ip4.our_tap_addr, 67, dst, 68, &reply, dlen); > =20 > diff --git a/dhcpv6.c b/dhcpv6.c > index 2db0944..0a064a9 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -318,7 +318,7 @@ static bool dhcpv6_opt(struct iov_tail *data, uint16_= t type) > * false otherwise and @data is unmodified > */ > static bool dhcpv6_ia_notonlink(struct iov_tail *data, > - struct in6_addr *la) > + const struct in6_addr *la) > { > int ia_types[2] =3D { OPT_IA_NA, OPT_IA_TA }; > struct opt_ia_addr opt_addr_storage; > @@ -567,6 +567,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > struct opt_hdr client_id_storage; > /* cppcheck-suppress [variableScope,unmatchedSuppression] */ > struct opt_ia_na ia_storage; > + const struct guest_addr *a; > const struct in6_addr *src; > struct msg_hdr mh_storage; > const struct msg_hdr *mh; > @@ -574,6 +575,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > const struct udphdr *uh; > size_t mlen, n; > =20 > + a =3D fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + > uh =3D IOV_REMOVE_HEADER(data, uh_storage); > if (!uh) > return -1; > @@ -627,7 +630,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > if (mh->type =3D=3D TYPE_CONFIRM && server_id) > return -1; > =20 > - if (dhcpv6_ia_notonlink(data, &c->ip6.addr)) { > + if (a && dhcpv6_ia_notonlink(data, &a->addr.a6)) { > =20 > dhcpv6_send_ia_notonlink(c, saddr, data, &client_id_base, > ntohs(client_id->l), mh->xid); > @@ -680,7 +683,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > resp.hdr.xid =3D mh->xid; > =20 > tap_udp6_send(c, src, 547, saddr, 546, mh->xid, &resp, n); > - c->ip6.addr_seen =3D c->ip6.addr; > + if (a) > + c->ip6.addr_seen =3D a->addr.a6; Not really in scope for this patch, I guess, but this seems like a bit of an inconsistency. We initialise addr_seen at conf_ip*() time, but here we initialise it again when we actually send the configured IP to the guest with DHCPv6. Arguably the latter approach makes more sense - it's not reasonable to assume the guest will use its assigned address before we've told it the assigned address. But if we use that approach we should remove it from conf_ip*() and instead initialise addr_seen in the DHCP, DHCPv6 and --config-net paths. > return 1; > } > @@ -691,6 +695,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > */ > void dhcpv6_init(const struct ctx *c) > { > + const struct guest_addr *a; > time_t y2k =3D 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime()= */ > uint32_t duid_time; > =20 > @@ -704,5 +709,7 @@ 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)); > =20 > - resp.ia_addr.addr =3D c->ip6.addr; > + a =3D fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + if (a) > + resp.ia_addr.addr =3D a->addr.a6; > } > diff --git a/fwd.c b/fwd.c > index bedbf98..14ce0a7 100644 > --- a/fwd.c > +++ b/fwd.c > @@ -249,6 +249,61 @@ void fwd_neigh_table_init(const struct ctx *c) > fwd_neigh_table_update(c, &mga, c->our_tap_mac, true); > } > =20 > +/** > + * fwd_set_addr() - Add or update an address in the unified address array > + * @c: Execution context > + * @addr: Address to add (IPv4-mapped or IPv6) > + * @flags: CONF_ADDR_* flags for this address > + * @prefix_len: Prefix length in IPv6 or IPv4 format As noted above, I think this should always be IPv6 format. > + * > + * Find the first existing entry of the same address family and > + * overwrite it, or create a new one if none exists > + */ > +void fwd_set_addr(struct ctx *c, const union inany_addr *addr, > + uint8_t flags, int prefix_len) > +{ > + struct guest_addr *a; > + > + for_each_addr(a, c->addrs, c->addr_count, inany_af(addr)) { > + goto found; > + } > + > + if (c->addr_count >=3D MAX_GUEST_ADDRS) > + return; > + > + a =3D &c->addrs[c->addr_count++]; > + > +found: > + a->addr =3D *addr; > + a->prefix_len =3D inany_prefix_len6(addr, prefix_len); Then you don't need the inany_prefix_len6() call here. More importantly... this doesn't match the -n option handling, which stored a->prefix_len in IPv4 format. > + a->flags =3D flags; > +} > + > +/** > + * fwd_get_addr() - Get guest address entry matching criteria > + * @c: Execution context > + * @af: Address family (AF_INET, AF_INET6, or 0 for any) > + * @incl: Flags that must be present (any-match) > + * @excl: Flags that must not be present > + * > + * Return: first address entry matching criteria, or NULL > + */ > +const struct guest_addr *fwd_get_addr(const struct ctx *c, sa_family_t a= f, > + uint8_t incl, uint8_t excl) > +{ > + const struct guest_addr *a; > + > + for_each_addr(a, c->addrs, c->addr_count, af) { > + if (incl && !(a->flags & incl)) > + continue; > + if (a->flags & excl) > + continue; > + return a; > + } > + > + return NULL; > +} > + > /** fwd_probe_ephemeral() - Determine what ports this host considers eph= emeral > * > * Work out what ports the host thinks are emphemeral and record it for = later > @@ -941,8 +996,10 @@ static bool is_dns_flow(uint8_t proto, const struct = flowside *ini) > * translation, false otherwise > */ > static bool fwd_guest_accessible4(const struct ctx *c, > - const struct in_addr *addr) > + const struct in_addr *addr) > { > + const struct guest_addr *a; > + > if (IN4_IS_ADDR_LOOPBACK(addr)) > return false; > =20 > @@ -957,7 +1014,8 @@ 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) || > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + if ((a && IN4_ARE_ADDR_EQUAL(addr, inany_v4(&a->addr))) || > IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) > return false; > =20 > @@ -975,10 +1033,13 @@ static bool fwd_guest_accessible4(const struct ctx= *c, > static bool fwd_guest_accessible6(const struct ctx *c, > const struct in6_addr *addr) > { > + const struct guest_addr *a; > + > if (IN6_IS_ADDR_LOOPBACK(addr)) > return false; > =20 > - if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr)) > + a =3D fwd_get_addr(c, AF_INET6, 0, 0); > + if (a && IN6_ARE_ADDR_EQUAL(addr, &a->addr.a6)) > return false; > =20 > /* For IPv6, addr_seen starts unspecified, because we don't know what LL > @@ -1023,16 +1084,21 @@ static bool fwd_guest_accessible(const struct ctx= *c, > static void nat_outbound(const struct ctx *c, const union inany_addr *ad= dr, > union inany_addr *translated) > { > - if (inany_equals4(addr, &c->ip4.map_host_loopback)) > + const struct guest_addr *ga; > + > + if (inany_equals4(addr, &c->ip4.map_host_loopback)) { > *translated =3D inany_loopback4; > - else if (inany_equals6(addr, &c->ip6.map_host_loopback)) > + } else if (inany_equals6(addr, &c->ip6.map_host_loopback)) { > *translated =3D inany_loopback6; > - else if (inany_equals4(addr, &c->ip4.map_guest_addr)) > - *translated =3D inany_from_v4(c->ip4.addr); > - else if (inany_equals6(addr, &c->ip6.map_guest_addr)) > - translated->a6 =3D c->ip6.addr; > - else > + } else if (inany_equals4(addr, &c->ip4.map_guest_addr)) { > + ga =3D fwd_get_addr(c, AF_INET, 0, 0); > + *translated =3D ga ? ga->addr : inany_any4; > + } else if (inany_equals6(addr, &c->ip6.map_guest_addr)) { > + ga =3D fwd_get_addr(c, AF_INET6, 0, 0); > + translated->a6 =3D ga ? ga->addr.a6 : in6addr_any; > + } else { > *translated =3D *addr; > + } > } > =20 > /** > @@ -1137,16 +1203,21 @@ bool nat_inbound(const struct ctx *c, const union= inany_addr *addr, > } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_host_loopback) && > inany_equals6(addr, &in6addr_loopback)) { > translated->a6 =3D c->ip6.map_host_loopback; > - } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && > - inany_equals4(addr, &c->ip4.addr)) { > - *translated =3D 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)) { > - translated->a6 =3D c->ip6.map_guest_addr; > - } else if (fwd_guest_accessible(c, addr)) { > - *translated =3D *addr; > } else { > - return false; > + const struct guest_addr *ga4 =3D fwd_get_addr(c, AF_INET, 0, 0); > + const struct guest_addr *ga6 =3D fwd_get_addr(c, AF_INET6, 0, 0); > + > + if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && > + ga4 && inany_equals(addr, &ga4->addr)) { > + *translated =3D inany_from_v4(c->ip4.map_guest_addr); > + } else if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.map_guest_addr) && > + ga6 && inany_equals(addr, &ga6->addr)) { > + translated->a6 =3D c->ip6.map_guest_addr; > + } else if (fwd_guest_accessible(c, addr)) { > + *translated =3D *addr; > + } else { > + return false; > + } > } > =20 > return true; > diff --git a/fwd.h b/fwd.h > index 958eee2..c5a1068 100644 > --- a/fwd.h > +++ b/fwd.h > @@ -23,6 +23,8 @@ struct flowside; > =20 > void fwd_probe_ephemeral(void); > bool fwd_port_is_ephemeral(in_port_t port); > +const struct guest_addr *fwd_get_addr(const struct ctx *c, sa_family_t a= f, > + uint8_t incl, uint8_t excl); > =20 > /** > * struct fwd_rule - Forwarding rule governing a range of ports > @@ -141,5 +143,7 @@ void fwd_neigh_table_free(const struct ctx *c, > void fwd_neigh_mac_get(const struct ctx *c, const union inany_addr *addr, > uint8_t *mac); > void fwd_neigh_table_init(const struct ctx *c); > +void fwd_set_addr(struct ctx *c, const union inany_addr *addr, > + uint8_t flags, int prefix_len); > =20 > #endif /* FWD_H */ > diff --git a/inany.h b/inany.h > index 9891ed6..0450c45 100644 > --- a/inany.h > +++ b/inany.h > @@ -102,6 +102,16 @@ static inline struct in_addr *inany_v4(const union i= nany_addr *addr) > return (struct in_addr *)&addr->v4mapped.a4; > } > =20 > +/** inany_af - Get address family of IPv[46] address > + * @addr: IPv4 or IPv6 address > + * > + * Return: AF_INET for IPv4, AF_INET6 for IPv6 > + */ > +static inline sa_family_t inany_af(const union inany_addr *addr) > +{ > + return inany_v4(addr) ? AF_INET : AF_INET6; > +} > + > /** inany_default_prefix_len() - Get default prefix length for address > * @addr: IPv4 or iPv6 address > * > @@ -115,6 +125,37 @@ static inline int inany_default_prefix_len(const uni= on inany_addr *addr) > return v4 ? ip4_class_prefix_len(v4) + 96 : 64; > } > =20 > + > +/** inany_prefix_len() - Convert prefix length to native format > + * @addr: IPv4 or IPv6 address > + * @prefix_len: prefix length (any format, auto-detected) If it's pertaining to an inany, it should always be IPv6 format. Auto-detecting just masks bugs elsewhere. > + * > + * Return: prefix length in native format (0-32 for IPv4, 0-128 for IPv6) > + */ > +static inline int inany_prefix_len(const union inany_addr *addr, > + int prefix_len) > +{ > + if (inany_v4(addr) && prefix_len >=3D 96) Yikes. Even ignoring the fact I don't like this function at all, this isn't good. If we somehow get a IPv4-mapped inany with a prefix_len that's < 96, something has already gone wrong. This, however, will just fall back to the IPv6 case. So if prefix_len is 33..96 it will return something entirely nonsensical, and if it's 0..32 it will return something that looks ok, but ignores the fact that something is already wrong. > + return prefix_len - 96; > + > + return prefix_len; > +} > + > +/** inany_prefix_len6() - Convert prefix length to generic format > + * @addr: IPv4 or IPv6 address > + * @prefix_len: prefix length (any format, auto-detected) > + * > + * Return: prefix length in generic format (96-128 for IPv4, 0-128 for I= Pv6) > + */ > +static inline int inany_prefix_len6(const union inany_addr *addr, > + int prefix_len) This function should not be necessary. If the prefix_len is attached to an inany, it should already be in IPv6 format. > +{ > + if (inany_v4(addr) && prefix_len && prefix_len <=3D 32) > + return prefix_len + 96; Similar comments here. Also a 0.0.0.0/0 is a valid prefix which should be translated to ::ffff:0:0/96, not to ::ffff:0:0/0 as this function will. > + > + return prefix_len; > +} > + > /** inany_equals - Compare two IPv[46] addresses > * @a, @b: IPv[46] addresses > * > diff --git a/ip.h b/ip.h > index d0de6c8..933d98c 100644 > --- a/ip.h > +++ b/ip.h > @@ -19,6 +19,8 @@ > (ntohl(((struct in_addr *)(a))->s_addr) >> IN_CLASSA_NSHIFT =3D=3D IN_L= OOPBACKNET) > #define IN4_IS_ADDR_MULTICAST(a) \ > (IN_MULTICAST(ntohl(((struct in_addr *)(a))->s_addr))) > +#define IN4_MASK(prefix) \ > + ((prefix) <=3D 0 ? 0 : htonl(0xffffffff << (32 - (prefix)))) > #define IN4_ARE_ADDR_EQUAL(a, b) \ > (((struct in_addr *)(a))->s_addr =3D=3D ((struct in_addr *)b)->s_addr) > #define IN4ADDR_LOOPBACK_INIT \ > diff --git a/ndp.c b/ndp.c > index 1f2bcb0..3750fc5 100644 > --- a/ndp.c > +++ b/ndp.c > @@ -257,7 +257,6 @@ static void ndp_ra(const struct ctx *c, const struct = in6_addr *dst) > .valid_lifetime =3D ~0U, > .pref_lifetime =3D ~0U, > }, > - .prefix =3D c->ip6.addr, > .source_ll =3D { > .header =3D { > .type =3D OPT_SRC_L2_ADDR, > @@ -265,8 +264,13 @@ static void ndp_ra(const struct ctx *c, const struct= in6_addr *dst) > }, > }, > }; > + const struct guest_addr *a =3D fwd_get_addr(c, AF_INET6, 0, 0); > unsigned char *ptr =3D NULL; > =20 > + ASSERT(a); We use lowercase assert() these days. Plus as for DHCP, I think it would be more robust to have this be a safe no-op, rather than an assert(). > + > + ra.prefix =3D a->addr.a6; > + > ptr =3D &ra.var[0]; > =20 > if (c->mtu) { > @@ -460,6 +464,7 @@ first: > */ > void ndp_send_init_req(const struct ctx *c) > { > + const struct guest_addr *a =3D fwd_get_addr(c, AF_INET6, 0, 0); > struct ndp_ns ns =3D { > .ih =3D { > .icmp6_type =3D NS, > @@ -468,8 +473,13 @@ void ndp_send_init_req(const struct ctx *c) > .icmp6_solicited =3D 0, /* Reserved */ > .icmp6_override =3D 0, /* Reserved */ > }, > - .target_addr =3D c->ip6.addr > + .target_addr =3D IN6ADDR_ANY_INIT > }; > + > + if (!a) > + return; > + > + ns.target_addr =3D a->addr.a6; > debug("Sending initial NDP NS request for guest MAC address"); > - ndp_send(c, &c->ip6.addr, &ns, sizeof(ns)); > + ndp_send(c, &a->addr.a6, &ns, sizeof(ns)); > } > diff --git a/passt.h b/passt.h > index b614bdf..f75656d 100644 > --- a/passt.h > +++ b/passt.h > @@ -64,11 +64,28 @@ enum passt_modes { > MODE_VU, > }; > =20 > +/* Maximum number of addresses in context address array */ > +#define MAX_GUEST_ADDRS 32 > + > +/** > + * struct guest_addr - Unified IPv4/IPv6 address entry > + * @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 guest_addr { > + union inany_addr addr; > + uint8_t prefix_len; > + uint8_t flags; > +#define CONF_ADDR_USER BIT(0) /* User set via -a */ > +#define CONF_ADDR_HOST BIT(1) /* From host interface */ > +#define CONF_ADDR_GENERATED BIT(2) /* Generated by PASST/PASTA */ > +#define CONF_ADDR_LINKLOCAL BIT(3) /* Link-local address */ > +}; > + > /** > * 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 t= o the > * host's 127.0.0.1 > @@ -84,10 +101,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 +121,6 @@ struct ip4_ctx { > =20 > /** > * 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 +138,6 @@ struct ip4_ctx { > * @no_copy_addrs: Don't copy all addresses when configuring namespace > */ > struct ip6_ctx { > - /* PIF_TAP addresses */ The comment is still relevant for the addresses which remain (for now). > - struct in6_addr addr; > struct in6_addr addr_seen; > struct in6_addr addr_ll_seen; > struct in6_addr guest_gw; > @@ -181,6 +192,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 > @@ -260,6 +273,9 @@ struct ctx { > int ifi6; > struct ip6_ctx ip6; > =20 > + struct guest_addr addrs[MAX_GUEST_ADDRS]; > + int addr_count; > + > char pasta_ifn[IF_NAMESIZE]; > unsigned int pasta_ifi; > int pasta_conf_ns; > @@ -301,6 +317,41 @@ struct ctx { > bool migrate_exit; > }; > =20 > +/** > + * next_addr_idx_() - Find next address index matching family filter > + * @addrs: Array of guest addresses > + * @count: Number of addresses in array > + * @i: Starting index > + * @af: Address family filter: AF_INET, AF_INET6, or 0 for all > + * > + * Return: next matching index, or count if none found > + */ > +static inline int next_addr_idx_(const struct guest_addr *addrs, int cou= nt, > + int i, sa_family_t af) > +{ > + for (; i < count; i++) { > + sa_family_t entry_af; > + > + entry_af =3D inany_v4(&addrs[i].addr) ? AF_INET : AF_INET6; > + > + if (af =3D=3D AF_UNSPEC || af =3D=3D entry_af) > + return i; > + } > + return i; > +} > + > +/** > + * for_each_addr() - Iterate over addresses in array > + * @a: Pointer variable for current entry (struct guest_addr *) > + * @addrs: Array of guest addresses (e.g., c->addrs) > + * @count: Number of addresses (e.g., c->addr_count) > + * @af: Address family filter: AF_INET, AF_INET6, or 0 for all > + */ > +#define for_each_addr(a, addrs, count, af) \ > + for (int i_ =3D next_addr_idx_((addrs), (count), 0, (af)); \ > + i_ < (count) && ((a) =3D &(addrs)[i_], true); \ > + i_ =3D next_addr_idx_((addrs), (count), i_ + 1, (af))) > + > void proto_update_l2_buf(const unsigned char *eth_d); > =20 > #endif /* PASST_H */ > diff --git a/pasta.c b/pasta.c > index bab945f..c51e4cd 100644 > --- a/pasta.c > +++ b/pasta.c > @@ -330,6 +330,8 @@ void pasta_ns_conf(struct ctx *c) > =20 > if (c->pasta_conf_ns) { > unsigned int flags =3D IFF_UP; > + const struct guest_addr *a; > + int plen; > =20 > if (c->mtu) > nl_link_set_mtu(nl_sock_ns, c->pasta_ifi, c->mtu); > @@ -341,10 +343,15 @@ void pasta_ns_conf(struct ctx *c) > =20 > if (c->ifi4) { > if (c->ip4.no_copy_addrs) { > - rc =3D nl_addr_set(nl_sock_ns, c->pasta_ifi, > - AF_INET, > - &c->ip4.addr, > - c->ip4.prefix_len); > + a =3D fwd_get_addr(c, AF_INET, 0, 0); > + if (a) { > + plen =3D inany_prefix_len(&a->addr, > + a->prefix_len); > + rc =3D nl_addr_set(nl_sock_ns, > + c->pasta_ifi, AF_INET, > + inany_v4(&a->addr), > + plen); > + } > } else { > rc =3D nl_addr_dup(nl_sock, c->ifi4, > nl_sock_ns, c->pasta_ifi, > @@ -397,11 +404,13 @@ ipv4_done: > 0, IFF_NOARP); > =20 > if (c->ip6.no_copy_addrs) { > - if (!IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) { > + a =3D fwd_get_addr(c, AF_INET6, 0, 0); > + if (a) > rc =3D nl_addr_set(nl_sock_ns, > - c->pasta_ifi, AF_INET6, > - &c->ip6.addr, 64); > - } > + c->pasta_ifi, > + AF_INET6, > + &a->addr.a6, > + a->prefix_len); > } else { > rc =3D nl_addr_dup(nl_sock, c->ifi6, > nl_sock_ns, c->pasta_ifi, > diff --git a/tap.c b/tap.c > index 59c45a3..eb93f74 100644 > --- a/tap.c > +++ b/tap.c > @@ -936,8 +936,11 @@ resume: > c->ip6.addr_seen =3D *saddr; > } > =20 > - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) > - c->ip6.addr =3D *saddr; > + if (!fwd_get_addr(c, AF_INET6, 0, 0)) { > + union inany_addr addr =3D { .a6 =3D *saddr }; > + > + fwd_set_addr(c, &addr, CONF_ADDR_LINKLOCAL, 64); > + } > } else if (!IN6_IS_ADDR_UNSPECIFIED(saddr)){ > c->ip6.addr_seen =3D *saddr; > } > --=20 > 2.52.0 >=20 --=20 David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson --V4uPjQ3tldNbsEzo Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEO+dNsU4E3yXUXRK2zQJF27ox2GcFAmoFa/cACgkQzQJF27ox 2GcsihAAj/uoLm30RkE/b6rd/C/mXDa0YC6GKUwFKMCaRhzabTk6LNh6UMX/Wi63 nmVo+U0wzHny+Njoi0bCHByD8H1B588oFJGD3VtJUgls7R3EIACt3f4yvDWNJLYN HX7K1QwjuNaYx38P4gyy1CCo1Dk96Zxer6Kw3TJMtLwh6xSJcnFQtVn3/PH+2eeh rAjfQ2J9tROScS8Ju7v9bBXEHLG6HvP4946PPQTPi2ju9CskO7y2Xh4gv0FvJOE8 KcMqmHPFON2kRPmUTCE7RkJpzUJPZJdrCuqkpkz0qybjFr3+9jH374gqW6xeKLIh KhH5ovKDVJrUSdrjperIDhi8VFfjDmL62cjkEUfA1bdVRk/WSWpfo3dxlhwAfPsG kR4qc1hWXPmmmDsAyyo2R3mUM1mEym1tJvtn7gwD3A5I9/NlPrFBo6swgXjmZzFE bVbIhxHP4bvVIm0kc2rFS3q54K492Jhz0Q2agfU915Zc/smIRLROW8P3Ek3TiwvX l6tUycDKg/d5oNaBukK+IiCp1DwcqIE8KXb3/7vI4RX6tCRu3ghCi7NVEKfKi23W P9i+XavJOJB23lpjj2Ri5mSD/bzAwBeFDK+6ytqdaofMbnqPb5E5Zjr2/ugrGkZ5 XuIkQl0l2FaRce+AH60JhmrSwvXEPAKiFGLUwcd4s3z2CqEiFgQ= =Thqt -----END PGP SIGNATURE----- --V4uPjQ3tldNbsEzo--