From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=J16/AqoB; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 70EDB5A0269 for ; Mon, 30 Mar 2026 23:57:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1774907836; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1Zx7ItYt0SAAbdmrnqfB4nkPx3U46Je6dwk2gX0yGkI=; b=J16/AqoBH7DNZT47V5r5ZXJ6P3on6es4ItbXevM7xF+0UpdWCZUXxZDvNt42ZA/dBOzFVV nLxC5gpwqnQuP9/05wDaugQZXv9rrYRPAmOD9m1so3wkLww5rlMJqFEcp1jSoh9DCG/qBN ohKyNzQvpcJIPuWTeJeyh0odlksj+ck= Received: from mail-wm1-f72.google.com (mail-wm1-f72.google.com [209.85.128.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-207-XIK1YBgYNIGHqO281JGCxQ-1; Mon, 30 Mar 2026 17:57:14 -0400 X-MC-Unique: XIK1YBgYNIGHqO281JGCxQ-1 X-Mimecast-MFC-AGG-ID: XIK1YBgYNIGHqO281JGCxQ_1774907833 Received: by mail-wm1-f72.google.com with SMTP id 5b1f17b1804b1-483786a09b1so60290555e9.3 for ; Mon, 30 Mar 2026 14:57:14 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774907833; x=1775512633; h=date:content-transfer-encoding:mime-version:organization:references :in-reply-to:message-id:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=1Zx7ItYt0SAAbdmrnqfB4nkPx3U46Je6dwk2gX0yGkI=; b=Kpeld24Dg0wIjHnkh4eAFOBCFx0uSKS9kR20aADXHCSqO9XqYLAZFgFYv14KDTJ3W7 Ae9k5JHVOeuUMqw1BCzoGt3N91kBrEY+4lR3E1Zfb+f+2hwEAk3St0xNYQxPGvLE0EmZ 6QL1z49FVxc6dt1defdkBHpOPiprQ7X2aezRrdlJLu9yZHLy2VjIvKjTqxr9s0hVN9G5 arn2kWGZGJJnmKpKarWriRlbO02VBfw1hPi3n9KarPIz2FMGIEGzQStC0uX3h2HcBXEy IeKcFv+6eNbpFQgVroxl0JjKhIUd7WmR2+H/+44lbYTY+zHQfCpQ4T18S3jxAMD/Lg/E nLYw== X-Forwarded-Encrypted: i=1; AJvYcCXEeyYfrkfJLAigzCDgf6n9tUBTOzrT6hLkxHfyIvhctDclyTZskJYAQhlte5/tyYqxyIXao9JbS/0=@passt.top X-Gm-Message-State: AOJu0YwcK4iqpgdPta1fAClq4sdRSruarR/5ShpSmw3czwl2DW4xAOwT NPNUwDle7gjuUXW91rUFcjOdYMmhSkfSLh7hbGTBNlRjOQA1KS6a2ZW3tdc/gV9MnjFy10Vz6d6 LDLhy3erlEAaYx2NcVLpvvHrfnNYCG8LyFxyW33MZpOlqdldZIn+dNw== X-Gm-Gg: ATEYQzyzWKnr01Mr1zaP2UiUNGRTunOfwVhazdb0w1vMJaIK/dRt/hYkG0x/1xAAz7i b2+qQb1pTwUO+1qjNRjZca5dvWSq0zJqwUoDQ4Weo3dNYRffBS/SNhcwI+T6ghktcennmePkT7G o/Ntc/sjPez0oKtXJ3ywUbYs3/9npD9z0plCvAvNHOPF6M213yyFAZof5E7P5M+brPIcsmtV21F iFMwjcDzfMl8US2grhsdTdLIzY+YpyT5UDsr4P1xqnJ7f4/BuX8JIXcvbNg3oIiN7UuXrtydx2I 9J0GCXvLH64MHzHrc/Jvjaj/gwUNIo+7iuzkgeANJk8BFAF01BT0UFDyYfpdZNN/z7p5hAdodTM VBcicRv2PLZbVn8KSIuhQLcRwW9xB1UUc X-Received: by 2002:a05:600c:a4f:b0:485:3ee1:eba5 with SMTP id 5b1f17b1804b1-48727f32177mr229221905e9.27.1774907832708; Mon, 30 Mar 2026 14:57:12 -0700 (PDT) X-Received: by 2002:a05:600c:a4f:b0:485:3ee1:eba5 with SMTP id 5b1f17b1804b1-48727f32177mr229221555e9.27.1774907831877; Mon, 30 Mar 2026 14:57:11 -0700 (PDT) Received: from maya.myfinge.rs (ifcgrfdd.trafficplex.cloud. [2a10:fc81:a806:d6a9::1]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-48722d236a9sm550286905e9.11.2026.03.30.14.57.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 30 Mar 2026 14:57:11 -0700 (PDT) From: Stefano Brivio To: Jon Maloy Subject: Re: [PATCH v6 02/13] ip: Introduce unified multi-address data structures Message-ID: <20260330235710.3b0570fe@elisabeth> In-Reply-To: <20260322004333.365713-3-jmaloy@redhat.com> References: <20260322004333.365713-1-jmaloy@redhat.com> <20260322004333.365713-3-jmaloy@redhat.com> Organization: Red Hat X-Mailer: Claws Mail 4.2.0 (GTK 3.24.49; x86_64-pc-linux-gnu) MIME-Version: 1.0 Date: Mon, 30 Mar 2026 23:57:10 +0200 (CEST) X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: VhPEtWMu5hFcwUAGTIzuieTywfWAz-lWetEdMf2ZtXA_1774907833 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Message-ID-Hash: GDCOM4PQVPR7ZWCVHRLRIC5E4X3HBXQL X-Message-ID-Hash: GDCOM4PQVPR7ZWCVHRLRIC5E4X3HBXQL X-MailFrom: sbrivio@redhat.com 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: david@gibson.dropbear.id.au, 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: On Sat, 21 Mar 2026 20:43:22 -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. > > Despite some 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 > 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 > > 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. > --- > arp.c | 10 +++- > conf.c | 142 +++++++++++++++++++++++++++++-------------------------- > dhcp.c | 13 +++-- > dhcpv6.c | 15 ++++-- > fwd.c | 104 +++++++++++++++++++++++++++++++++------- > fwd.h | 4 ++ > igmp.c | 1 + > ip.h | 2 + > ndp.c | 16 +++++-- > passt.h | 82 ++++++++++++++++++++++++++++---- > pasta.c | 21 ++++---- > tap.c | 7 ++- > 12 files changed, 302 insertions(+), 115 deletions(-) > > diff --git a/arp.c b/arp.c > index bb042e9..18ff5de 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 = fwd_get_addr(c, AF_INET, 0, 0); It's not _that_ expensive, but still it looks a bit silly to call this for packets we ignore. Moving the assignment before the memcmp() below is one additional line, true, but it avoids that and I think it's easier to follow. > + > 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 (a && !memcmp(am->tip, inany_v4(&a->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) > { > + const struct guest_addr *a = > struct { > struct ethhdr eh; > struct arphdr ah; > struct arpmsg am; > } __attribute__((__packed__)) req; > > + if (!a) Similar to the case above (but a much weaker argument): if (!(a = fwd_get_addr(c, AF_INET, 0, 0))) > + return; ...would be easier to follow, I think (and no additional lines). > + > /* 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(&a->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 9bcd9de..8f02494 100644 > --- a/conf.c > +++ b/conf.c > @@ -728,13 +728,15 @@ 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 ip4_ctx *ip4 = &c->ip4; > + > if (!ifi) > ifi = nl_get_ext_if(nl_sock, AF_INET); > > @@ -753,60 +755,57 @@ static unsigned int conf_ip4(unsigned int ifi, struct ip4_ctx *ip4) > } > } > > - 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 = 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; > + fwd_set_addr(c, &inany_from_v4(addr), CONF_ADDR_HOST, > + prefix_len + 96); Somewhat symmetric to David's comment to inany_prefix_len(): fwd_set_addr() is already checking if the address is IPv4 or IPv6, so you could pass the actual prefix length. Having to add 96 for IPv4 looks rather bug-prone for the future. > + 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 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; > + fwd_set_addr(c, &inany_from_v4(IP4_LL_GUEST_ADDR), > + CONF_ADDR_HOST | CONF_ADDR_LINKLOCAL, > + IP4_LL_PREFIX_LEN + 96); Same as above for the + 96. > } > > /** > * 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) > { > + const struct guest_addr *a = fwd_get_addr(c, AF_INET6, 0, 0); > + struct ip6_ctx *ip6 = &c->ip6; > + union inany_addr addr; > int prefix_len = 0; > int rc; > > @@ -827,21 +826,24 @@ 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 (!a) { Same as above about the place where a is initialised. > + fwd_set_addr(c, &addr, CONF_ADDR_HOST, prefix_len); > + ip6->addr_seen = addr.a6; > + } else { > + ip6->addr_seen = a->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; > @@ -849,13 +851,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; > } > > /** > @@ -1137,6 +1139,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) > static void conf_print(const struct ctx *c) > { > char buf[INET6_ADDRSTRLEN]; > + const struct guest_addr *a; > int i; > > if (c->ifi4 > 0 || c->ifi6 > 0) { > @@ -1182,19 +1185,19 @@ static void conf_print(const struct ctx *c) > inet_ntop(AF_INET, &c->ip4.map_host_loopback, > buf, sizeof(buf))); > > - if (!c->no_dhcp) { > + a = fwd_get_addr(c, AF_INET, 0, 0); > + if (a && !c->no_dhcp) { > uint32_t mask; > > - mask = htonl(0xffffffff << (32 - c->ip4.prefix_len)); > + mask = IN4_MASK(inany_prefix_len(a)); > > info("DHCP:"); > - info(" assign: %s", > - inet_ntop(AF_INET, &c->ip4.addr, buf, sizeof(buf))); > - info(" mask: %s", > - inet_ntop(AF_INET, &mask, buf, sizeof(buf))); > - info(" router: %s", > - inet_ntop(AF_INET, &c->ip4.guest_gw, > - buf, sizeof(buf))); > + inany_ntop(&a->addr, buf, sizeof(buf)); > + info(" assign: %s", buf); > + inet_ntop(AF_INET, &mask, buf, sizeof(buf)); > + info(" mask: %s", buf); > + inet_ntop(AF_INET, &c->ip4.guest_gw, buf, sizeof(buf)); > + info(" router: %s", buf); With the current version, if inet_ntop() encounters an error, we'll print "(null)" (or similar). Now the outcome is undefined. It should never happen but it looked more robust before. > } > > for (i = 0; i < ARRAY_SIZE(c->ip4.dns); i++) { > @@ -1228,13 +1231,14 @@ static void conf_print(const struct ctx *c) > else > goto dns6; > > - info(" assign: %s", > - inet_ntop(AF_INET6, &c->ip6.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))); > + a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + if (a) > + inany_ntop(&a->addr, buf, sizeof(buf)); > + info(" assign: %s", !a ? "" : buf); Why the double negation here? It looks less readable than the alternative. > + inet_ntop(AF_INET6, &c->ip6.guest_gw, buf, sizeof(buf)); > + info(" router: %s", buf); > + inet_ntop(AF_INET6, &c->ip6.our_tap_ll, buf, sizeof(buf)); > + info(" our link-local: %s", buf); Same here about inet_ntop() and errors. > > dns6: > for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { > @@ -1887,16 +1891,12 @@ void conf(struct ctx *c, int argc, char **argv) > IN6_IS_ADDR_V4COMPAT(&addr.a6)) > die("Invalid address: %s", optarg); > > - if (inany_v4(&addr)) { > - c->ip4.addr = *inany_v4(&addr); > - c->ip4.prefix_len = prefix_len - 96; > - if (c->mode == MODE_PASTA) > - c->ip4.no_copy_addrs = true; > - } else { > - c->ip6.addr = addr.a6; > - if (c->mode == MODE_PASTA) > - c->ip6.no_copy_addrs = 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 = true; > + else > + c->ip6.no_copy_addrs = true; > break; > } > case 'n': { > @@ -1910,7 +1910,13 @@ 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; > + > + for (int i = 0; i < c->addr_count; i++) { > + if (!inany_v4(&c->addrs[i].addr)) > + continue; > + c->addrs[i].prefix_len = prefix_len_from_opt; > + break; > + } > break; > } > case 'M': > @@ -2104,9 +2110,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)", > @@ -2129,7 +2135,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; > } > @@ -2137,7 +2143,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; > } > @@ -2202,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 (!fwd_get_addr(c, AF_INET6, 0, 0)) { > c->no_dhcpv6 = 1; > } > > diff --git a/dhcp.c b/dhcp.c > index 1ff8cba..6d9def5 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) > { > + const struct guest_addr *a = fwd_get_addr(c, AF_INET, 0, 0); > 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(a); Same as above about the place where you assign 'a'. > + addr = *inany_v4(&a->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_prefix_len(a)); > 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..3a007bf 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] = { OPT_IA_NA, OPT_IA_TA }; > struct opt_ia_addr opt_addr_storage; > @@ -566,6 +566,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; > @@ -573,6 +574,8 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > const struct udphdr *uh; > size_t mlen, n; > > + a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + > uh = IOV_REMOVE_HEADER(data, uh_storage); > if (!uh) > return -1; > @@ -628,7 +631,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 (a && dhcpv6_ia_notonlink(data, &a->addr.a6)) { > > dhcpv6_send_ia_notonlink(c, data, &client_id_base, > ntohs(client_id->l), mh->xid); > @@ -682,7 +685,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 (a) > + c->ip6.addr_seen = a->addr.a6; > > return 1; > } > @@ -693,6 +697,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 = 946684800; /* Epoch to 2000-01-01T00:00:00Z, no mktime() */ > uint32_t duid_time; > > @@ -706,5 +711,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)); > > - resp.ia_addr.addr = c->ip6.addr; > + a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); > + if (a) > + resp.ia_addr.addr = a->addr.a6; > } > diff --git a/fwd.c b/fwd.c > index bedbf98..9101d6d 100644 > --- a/fwd.c > +++ b/fwd.c > @@ -249,6 +249,64 @@ void fwd_neigh_table_init(const struct ctx *c) > fwd_neigh_table_update(c, &mga, c->our_tap_mac, true); > } > > +/** > + * 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/mapped format, 0-128 > + * > + * 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 (int i = 0; i < c->addr_count; i++) { > + a = &c->addrs[i]; > + if ((inany_v4(addr) && inany_v4(&a->addr)) || > + (!inany_v4(addr) && !inany_v4(&a->addr))) > + goto found; > + } > + > + if (c->addr_count >= MAX_GUEST_ADDRS) > + return; > + > + a = &c->addrs[c->addr_count++]; > + > +found: > + a->addr = *addr; > + a->prefix_len = prefix_len; > + a->flags = 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 af, > + uint8_t incl, uint8_t excl) > +{ > + const struct guest_addr *a; > + > + for_each_addr(a, c, af) { > + if (incl && !(a->flags & incl)) > + continue; > + if (a->flags & excl) > + continue; Slightly less generic, but maybe good enough for this purpose: you could admit a set of flags, or a negation of a flag (for example ~CONF_ADDR_USER), in a single argument. See how conn_flag_do() does that. Here it would be something like: if (flags & (flags - 1)) { if (a->flags & ~flag) continue; } else { if (!(a->flags & flag)) continue; } ...it makes callers more readable in my opinion, for example: a = fwd_get_addr(c, AF_INET6, ~CONF_ADDR_LINKLOCAL); which makes it entirely clear you're selecting all addresses that are link-local, compared to: a = fwd_get_addr(c, AF_INET6, 0, CONF_ADDR_LINKLOCAL); which forces the reader to look up the prototype. But again it's less generic, you can't exclude multiple flags like that. Unless... you define a reserved bit which is always 0 in that uint8_t, so that if you pass it as 1, you can conclude a negated set of flags was passed. > + return a; > + } > + > + return NULL; > +} > + > /** fwd_probe_ephemeral() - Determine what ports this host considers ephemeral > * > * Work out what ports the host thinks are emphemeral and record it for later > @@ -941,8 +999,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 = fwd_get_addr(c, AF_INET, 0, 0); > + > if (IN4_IS_ADDR_LOOPBACK(addr)) > return false; > > @@ -957,7 +1017,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 ((a && IN4_ARE_ADDR_EQUAL(addr, inany_v4(&a->addr))) || Same as above about the initialisation of 'a'. > IN4_ARE_ADDR_EQUAL(addr, &c->ip4.addr_seen)) > return false; > > @@ -975,10 +1035,12 @@ 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 = fwd_get_addr(c, AF_INET6, 0, 0); > + > if (IN6_IS_ADDR_LOOPBACK(addr)) > return false; > > - if (IN6_ARE_ADDR_EQUAL(addr, &c->ip6.addr)) > + if (a && IN6_ARE_ADDR_EQUAL(addr, &a->addr.a6)) Same here. > return false; > > /* For IPv6, addr_seen starts unspecified, because we don't know what LL > @@ -1023,14 +1085,19 @@ static bool fwd_guest_accessible(const struct ctx *c, > static void nat_outbound(const struct ctx *c, const union inany_addr *addr, > union inany_addr *translated) > { > + const struct guest_addr *ga; > + > if (inany_equals4(addr, &c->ip4.map_host_loopback)) > *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 (inany_equals4(addr, &c->ip4.map_guest_addr)) { > + ga = fwd_get_addr(c, AF_INET, 0, 0); > + *translated = ga ? ga->addr : inany_any4; > + } else if (inany_equals6(addr, &c->ip6.map_guest_addr)) { > + ga = fwd_get_addr(c, AF_INET6, 0, 0); > + translated->a6 = ga ? ga->addr.a6 : in6addr_any; > + } > else This should be moved to the line above, and you should add curly brackets to all the clauses for consistency. I wonder whether fwd_get_addr() returning inany_any4 on failure to match would be more convenient. > *translated = *addr; > } > @@ -1137,16 +1204,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 = c->ip6.map_host_loopback; > - } else if (!IN4_IS_ADDR_UNSPECIFIED(&c->ip4.map_guest_addr) && > - inany_equals4(addr, &c->ip4.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)) { > - translated->a6 = c->ip6.map_guest_addr; > - } else if (fwd_guest_accessible(c, addr)) { > - *translated = *addr; > } else { > - return false; > + const struct guest_addr *ga4 = fwd_get_addr(c, AF_INET, 0, 0); > + const struct guest_addr *ga6 = 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 = 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 = c->ip6.map_guest_addr; > + } else if (fwd_guest_accessible(c, addr)) { > + *translated = *addr; > + } else { > + return false; > + } > } > > 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; > > 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 af, > + uint8_t incl, uint8_t excl); > > /** > * 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); > > #endif /* FWD_H */ > diff --git a/igmp.c b/igmp.c > index a3971fc..0e77584 100644 > --- a/igmp.c > +++ b/igmp.c > @@ -13,4 +13,5 @@ > */ > > /* TO BE IMPLEMENTED */ > +/* cppcheck-suppress unusedFunction */ > __attribute__((__unused__)) static void unused(void) { } > 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 == 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 \ > 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 = ~0U, > .pref_lifetime = ~0U, > }, > - .prefix = c->ip6.addr, > .source_ll = { > .header = { > .type = 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 = fwd_get_addr(c, AF_INET6, 0, 0); > unsigned char *ptr = NULL; > > + ASSERT(a); > + > + ra.prefix = a->addr.a6; > + > ptr = &ra.var[0]; > > if (c->mtu) { > @@ -460,6 +464,7 @@ first: > */ > void ndp_send_init_req(const struct ctx *c) > { > + const struct guest_addr *a = fwd_get_addr(c, AF_INET6, 0, 0); > struct ndp_ns ns = { > .ih = { > .icmp6_type = NS, > @@ -468,8 +473,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 (!a) > + return; > + > + ns.target_addr = 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..6bca778 100644 > --- a/passt.h > +++ b/passt.h > @@ -64,11 +64,31 @@ enum passt_modes { > MODE_VU, > }; > > +/* Maximum number of addresses in context address array */ > +#define MAX_GUEST_ADDRS 32 > + > +/* Flags indicating origin and role of a guest address We might want to split right away the origin and the role, because it might make it easier, for example, to represent the fact that we want to offer IPv4 link-local addresses via DHCP (say, ADDR_SOURCE_LINKLOCAL but not ADDR_USE_DHCP), but not IPv6 ones. > + * To be used in struct guest_addr > + */ > +#define CONF_ADDR_USER BIT(0) /* User set via -a */ > +#define CONF_ADDR_HOST BIT(1) /* From host interface */ > +#define CONF_ADDR_LINKLOCAL BIT(2) /* Link-local address */ If those defines only make sense for a specific field of a struct, we typically define those near the field itself, as they describe the field. See 'events' or 'flags' in struct tcp_tap_conn or tcp_splice_conn (tcp_conn.h), or 'flags' in struct vhost_user_header (vhost_user.h). > + > +/** > + * 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 ...do we want to introduce a separate type for inany_addr plus prefix length? I'm not sure. > + * @flags: CONF_ADDR_* flags > + */ > +struct guest_addr { > + 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 +104,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 +124,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 +141,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 +195,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 +276,9 @@ struct ctx { > int ifi6; > struct ip6_ctx ip6; > > + 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 +320,53 @@ struct ctx { > bool migrate_exit; > }; > > +/** > + * inany_prefix_len() - Get address-family-appropriate prefix length > + * @a: Address entry > + * > + * Return: prefix length in native format (0-32 for IPv4, 0-128 for IPv6) > + */ > +static inline int inany_prefix_len(const struct guest_addr *a) > +{ > + if (inany_v4(&a->addr)) > + return a->prefix_len - 96; > + return a->prefix_len; > +} > + > +/** > + * _next_addr_idx() - Find next address index matching family filter > + * @c: Pointer to struct ctx > + * @i: Starting index > + * @af: Address family filter: AF_INET, AF_INET6, or 0 for all > + * > + * Return: next matching index, or addr_count if none found > + */ > +static inline int _next_addr_idx(const struct ctx *c, int i, sa_family_t af) > +{ > + for (; i < c->addr_count; i++) { > + sa_family_t entry_af; > + > + entry_af = inany_v4(&c->addrs[i].addr) ? AF_INET : AF_INET6; > + > + if (af == AF_UNSPEC || af == entry_af) > + return i; > + } > + return i; > +} > + > +/** > + * for_each_addr() - Iterate over addresses in unified array > + * @a: Pointer variable for current entry (struct guest_addr *) > + * @c: Pointer to struct ctx > + * @af: Address family filter: AF_INET, AF_INET6, or 0 for all > + * > + * Note: @_i is the internal loop counter, uses _next_addr_idx() helper > + */ > +#define for_each_addr(a, c, af) \ > + for (int _i = _next_addr_idx((c), 0, (af)); \ > + _i < (c)->addr_count && ((a) = &(c)->addrs[_i], true); \ > + _i = _next_addr_idx((c), _i + 1, (af))) > + > void proto_update_l2_buf(const unsigned char *eth_d); > > #endif /* PASST_H */ > diff --git a/pasta.c b/pasta.c > index bab945f..e88a893 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; > + const struct guest_addr *a; > > 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); > + a = fwd_get_addr(c, AF_INET, 0, 0); > + if (a) > + rc = nl_addr_set(nl_sock_ns, > + c->pasta_ifi, AF_INET, > + inany_v4(&a->addr), > + a->prefix_len - 96); This, and the IPv6 equivalent below, starts being uncomfortably nested. I wouldn't know how to even call a separate function doing this bit, but if you swap the logic (if (!c->ip4.no_copy_addrs) { ... }) you can assign 'a' unconditionally, and then use a else if (a) ... later. > } 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)) { > + a = fwd_get_addr(c, AF_INET6, 0, 0); > + if (a) > rc = 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); Same as above. > } 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..78a494a 100644 > --- a/tap.c > +++ b/tap.c > @@ -951,8 +951,11 @@ resume: > c->ip6.addr_seen = *saddr; > } > > - if (IN6_IS_ADDR_UNSPECIFIED(&c->ip6.addr)) > - c->ip6.addr = *saddr; > + if (!fwd_get_addr(c, AF_INET6, 0, 0)) { > + union inany_addr addr = { .a6 = *saddr }; > + > + fwd_set_addr(c, &addr, CONF_ADDR_LINKLOCAL, 64); > + } > } else if (!IN6_IS_ADDR_UNSPECIFIED(saddr)){ > c->ip6.addr_seen = *saddr; > } -- Stefano