[PATCH v7 00/13] Introduce multiple addresses and late binding
This series adds handling of multiple addresses into a unified address array, so that a guest can see the same addresses on his own interface. o All addresses are stored as union inany_addr o User configured addresses are marked with a USER flag. o Host provided addresses are marked with a HOST flag. o Link local addresses are also marked with a LINKLOCAL flag. o Addresses the guest is actually using are marked with an OBSERVED flag. o Addresses eligible for DHCP assignments are marked with an DHCP flag. o Addresses eligible for DHCPv6 advertisement are marked with an DHCPV6 flag. o Addresses eligible for NDP advertisement are marked with an NDP flag. v2: - Added the earlier standalone CIDR commit to the head of the series. - Replaced the guest namespace interface subscriptions with just an address observation feature, so that it works with both PASTA and PASST. - Unified 'no_copy_addrs' and 'copy_addrs' code paths, as suggested by David G. - Multiple other changes, also based on feedback from David. - Removed the host interface subscription patches, -for now. I intend to re-add them once this series is applied. - Outstanding question: When do we add an IPv4 link local address to the guest? Only in local/opaque mode? Only when explicitly requested? Always? v3: - Unified the IPv4 and IPv6 arrays into one array - Changed prefix_len to always be in IPv6/IpV4 mapped format - Updated migration protocol to v3, handling multiple addresses - Many other smaller changes, based on feedback from the PASST team v4: - Numerous changes based on feedback - Added several new commits, mostly broken out of the pre-existing ones. v5: - Re-introduced multiple OBSERVED addresses. This actually turned out to be cleaner and with more predictable behaviour than allowing only one. - Included the DHCP and NDP patches from previous versions, improved and updated according to feedback from the team. - Likewise re-included the host-side netlink commit to support late binding. v6: - Skipped late binding commit for now. - Added commit for using a single print buffer in conf_print - Added commit for reading and adding all addresses from template interface. - Added commit for refactoring pasta_ns_conf(). - Added separate address flags for DHCP, DHCPv6, and NDP, so that those are easy to recognize for their respective functions. - Split DHCP and DHCPv6 address selection into separate commits. - Updated migration protocol to v3 for multi-address support. - Numerous other smaller changes, both after feedback from David G. and issues I have identified myself. v7: - Replaced commit #1 with one that fixes a return address issue with DHCPv6 - Modified for_each_addr() macro to take 4 arguments - Many more fixes and changes based on feedback and own findings. Jon Maloy (13): dhcpv6: Fix reply destination to match client's source address passt, pasta: Introduce unified multi-address data structures fwd: Unify guest accessibility checks with unified address array arp: Check all configured addresses in ARP filtering conf: Allow multiple -a/--address options per address family netlink, conf: Read all addresses from template interface at startup netlink, pasta: refactor function pasta_ns_conf() conf, pasta: Track observed guest IPv4 addresses in unified address array conf, pasta: Track observed guest IPv6 addresses in unified address array migrate: Update protocol to v3 for multi-address support dhcp: Select address for DHCP distribution dhcpv6: Select addresses for DHCPv6 distribution ndp: Support advertising multiple prefixes in Router Advertisements arp.c | 20 +++- conf.c | 200 ++++++++++++++++++++--------------- dhcp.c | 22 ++-- dhcpv6.c | 115 +++++++++++--------- dhcpv6.h | 2 +- fwd.c | 305 ++++++++++++++++++++++++++++++++++++++++-------------- fwd.h | 8 ++ inany.h | 44 ++++++++ ip.h | 2 + migrate.c | 240 ++++++++++++++++++++++++++++++++++++++++-- ndp.c | 131 ++++++++++++++++------- netlink.c | 70 +++++++------ netlink.h | 7 +- passt.1 | 7 +- passt.h | 78 +++++++++++--- pasta.c | 224 ++++++++++++++++++++------------------- tap.c | 37 ++----- tap.h | 2 - 18 files changed, 1054 insertions(+), 460 deletions(-) -- 2.52.0
tap_ip6_daddr() selects the reply destination based on our source
address type (link-local), so it always returns addr_ll_seen. But if
the client sent from a global address, we would reply to an address
different from what the client is expecting. Since RFC 8415 allows
clients to use global addresses for DHCPv6, we now correct this, and
always respond to the address the client was using.
We also remove a redundant addr_ll_seen assignment, since this is
already done by tap.c when processing IPv6 packets.
Signed-off-by: Jon Maloy
We replace the fwd_guest_accessible4() and fwd_guest_accessible6()
functions with a unified fwd_guest_accessible() function that handles
both address families. With the unified address array, we can check
all configured addresses in a single pass using for_each_addr() with
family filter AF_UNSPEC.
Signed-off-by: Jon Maloy
As a preparation for handling multiple addresses, we update ignore_arp()
to check against all addresses in the unified addrs[] array using the
for_each_addr() macro.
Signed-off-by: Jon Maloy
Add nl_addr_get_all() to read all addresses from the template interface
into c->addrs[] array, rather than just selecting the "best" one.
This allows multi-address configurations where the template interface
has multiple IPv4 or IPv6 addresses assigned to it, all of which
will now be copied to the guest namespace when using --config-net.
For IPv6, the function also captures the link-local address into
c->ip6.our_tap_ll as a side effect.
Update conf_ip4() and conf_ip6() to use nl_addr_get_all() when no
user-specified addresses are present.
Signed-off-by: Jon Maloy
Allow specifying multiple addresses per family with -a/--address.
The first address of each family is used for DHCP/DHCPv6 assignment.
Signed-off-by: Jon Maloy
We remove the addr_seen field in struct ip4_ctx and replace it by
setting a new CONF_ADDR_OBSERVED flag in the corresponding entry
in the unified address array.
The observed IPv4 address is always added at or moved to position 0,
increasing chances for a fast lookup.
Signed-off-by: Jon Maloy
After the previous changes in this series it becomes possible
to simplify the pasta_ns_conf() function.
We extract address and route configuration into helper functions
pasta_conf_addrs() and pasta_conf_routes(), reducing nesting
and improving readability.
To allow pasta_conf_addrs() to handle both address families
uniformly, we change nl_addr_set() to take a union inany_addr pointer
instead of void pointer, moving the address family handling into
the function itself.
We also fix a bug where the IPv6 code path incorrectly wrote to
req.set.a4.rta_l.rta_type instead of req.set.a6.rta_l.rta_type.
Signed-off-by: Jon Maloy
We remove the addr_seen and addr_ll_seen fields in struct ip6_ctx
and replace them by setting CONF_ADDR_OBSERVED and CONF_ADDR_LINKLOCAL
flags in the corresponding entry in the unified address array.
The observed IPv6 address is always added/moved to position 0
in the array, improving chances for fast lookup.
The separate check against addr_seen in fwd_guest_accessible() can now
be removed because the observed address is now in the unified array,
and the existing for_each_addr() loop already checks against all
addresses, including this one.
This completes the unification of address storage for both IPv4 and
IPv6, enabling future support for multiple guest addresses per family.
Signed-off-by: Jon Maloy
We introduce a CONF_ADDR_DHCP flag to mark if an added address is
eligible for DHCP advertisement. By doing this once and for all
in the fwd_set_addr() function, the DHCP code only needs to check
for this flag to know that all criteria for advertisement are
fulfilled. Hence, we update the code in dhcp.c correspondingly.
We also let the conf_print() function use this flag to determine
and print the selected address.
Signed-off-by: Jon Maloy
We update the migration protocol to version 3 to support distributing
multiple addresses from the unified address array. The new protocol
migrates all address entries in the array, along with their prefix
lengths and flags, and leaves it to the receiver to filter which
ones he wants to apply.
Signed-off-by: Jon Maloy
We introduce a CONF_ADDR_DHCPV6 flag to mark if an added address is
eligible for DHCP advertisement. By doing this once and for all
in the fwd_set_addr() function, the DHCPv6 code only needs to check
for this flag to know that all criteria for advertisement are fulfilled.
We update the code in dhcpv6.c both to use the new flag and to make
it possible to send multiple addresses in a single reply message,
per RFC 8415.
We also let the conf_print() function use this flag to identify and
print the eligible addresses.
Signed-off-by: Jon Maloy
We extend NDP to advertise all suitable IPv6 prefixes in Router
Advertisements, per RFC 4861. Observed and link-local addresses,
plus addresses with a prefix length != 64, are excluded.
Signed-off-by: Jon Maloy
On Sun, Apr 12, 2026 at 08:53:07PM -0400, Jon Maloy wrote:
tap_ip6_daddr() selects the reply destination based on our source address type (link-local), so it always returns addr_ll_seen.
I think there might have been more callers of tap_ip6_daddr() in the past, which might have made this not true.
But if the client sent from a global address, we would reply to an address different from what the client is expecting. Since RFC 8415 allows clients to use global addresses for DHCPv6, we now correct this, and always respond to the address the client was using.
Responding to the same address the client used is a good idea in general. However, for this specific case, I don't think it will quite do what we want. The problem is that we're still always using our_tap_ll (link local) as the source address. So if the client used a global address we'll send a packet with mismatched address scopes. AFAIU that won't usually work.
We also remove a redundant addr_ll_seen assignment, since this is already done by tap.c when processing IPv6 packets.
Signed-off-by: Jon Maloy
--- dhcpv6.c | 14 ++++++-------- dhcpv6.h | 2 +- tap.c | 15 --------------- tap.h | 2 -- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/dhcpv6.c b/dhcpv6.c index 97c04e2..2db0944 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -370,12 +370,14 @@ notonlink: /** * dhcpv6_send_ia_notonlink() - Send NotOnLink status * @c: Execution context + * @saddr: Source address of client message (reply destination)
@saddr is a bad name in this context, since it's the source address of an earlier packet, not the one this function sends. Maybe @caddr or @client_addr?
* @ia_base: Non-appropriate IA_NA or IA_TA base * @client_id_base: Client ID message option base * @len: Client ID length * @xid: Transaction ID for message exchange */ static void dhcpv6_send_ia_notonlink(struct ctx *c, + const struct in6_addr *saddr, const struct iov_tail *ia_base, const struct iov_tail *client_id_base, int len, uint32_t xid) @@ -405,8 +407,7 @@ static void dhcpv6_send_ia_notonlink(struct ctx *c,
resp_not_on_link.hdr.xid = xid;
- tap_udp6_send(c, src, 547, tap_ip6_daddr(c, src), 546, - xid, &resp_not_on_link, n); + tap_udp6_send(c, src, 547, saddr, 546, xid, &resp_not_on_link, n); }
/** @@ -543,7 +544,7 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data, * dhcpv6() - Check if this is a DHCPv6 message, reply as needed * @c: Execution context * @data: Single packet starting from UDP header - * @saddr: Source IPv6 address of original message + * @saddr: Source IPv6 address of original message (for reply destination)
I don't know that the addition really adds anything useful.
* @daddr: Destination IPv6 address of original message * * Return: 0 if it's not a DHCPv6 message, 1 if handled, -1 on failure @@ -590,8 +591,6 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, if (mlen + sizeof(*uh) != ntohs(uh->len) || mlen < sizeof(*mh)) return -1;
- c->ip6.addr_ll_seen = *saddr; - src = &c->ip6.our_tap_ll;
If the client is using a global address, I think we need to too. Which is a bit of a problem, since we don't really have any way to allocate one. Have you seen a guest using a global address for DHCPv6 in practice, or is it just a theoretical possibility? I am wondering if that's only something that would happen if we advertised a global address for the DHCPv6 server via NDP (which we don't, and can't).
mh = IOV_REMOVE_HEADER(data, mh_storage); @@ -630,7 +629,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
if (dhcpv6_ia_notonlink(data, &c->ip6.addr)) {
- dhcpv6_send_ia_notonlink(c, data, &client_id_base, + dhcpv6_send_ia_notonlink(c, saddr, data, &client_id_base, ntohs(client_id->l), mh->xid);
return 1; @@ -680,8 +679,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data,
resp.hdr.xid = mh->xid;
- tap_udp6_send(c, src, 547, tap_ip6_daddr(c, src), 546, - mh->xid, &resp, n); + tap_udp6_send(c, src, 547, saddr, 546, mh->xid, &resp, n); c->ip6.addr_seen = c->ip6.addr;
return 1; diff --git a/dhcpv6.h b/dhcpv6.h index c706dfd..1015a1a 100644 --- a/dhcpv6.h +++ b/dhcpv6.h @@ -7,7 +7,7 @@ #define DHCPV6_H
int dhcpv6(struct ctx *c, struct iov_tail *data, - struct in6_addr *saddr, struct in6_addr *daddr); + const struct in6_addr *saddr, const struct in6_addr *daddr); void dhcpv6_init(const struct ctx *c);
#endif /* DHCPV6_H */ diff --git a/tap.c b/tap.c index eaa6111..59c45a3 100644 --- a/tap.c +++ b/tap.c @@ -161,21 +161,6 @@ void tap_send_single(const struct ctx *c, const void *data, size_t l2len) } }
-/** - * tap_ip6_daddr() - Normal IPv6 destination address for inbound packets - * @c: Execution context - * @src: Source address - * - * Return: pointer to IPv6 address - */ -const struct in6_addr *tap_ip6_daddr(const struct ctx *c, - const struct in6_addr *src) -{ - if (IN6_IS_ADDR_LINKLOCAL(src)) - return &c->ip6.addr_ll_seen; - return &c->ip6.addr_seen; -} -
Nice to see this ugly thing go away, though.
/** * tap_push_l2h() - Build an L2 header for an inbound packet * @c: Execution context diff --git a/tap.h b/tap.h index 07ca096..b335933 100644 --- a/tap.h +++ b/tap.h @@ -96,8 +96,6 @@ void tap_udp4_send(const struct ctx *c, struct in_addr src, in_port_t sport, const void *in, size_t dlen); void tap_icmp4_send(const struct ctx *c, struct in_addr src, struct in_addr dst, const void *in, const void *src_mac, size_t l4len); -const struct in6_addr *tap_ip6_daddr(const struct ctx *c, - const struct in6_addr *src); void *tap_push_ip6h(struct ipv6hdr *ip6h, const struct in6_addr *src, const struct in6_addr *dst, size_t l4len, uint8_t proto, uint32_t flow); -- 2.52.0
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
participants (2)
-
David Gibson
-
Jon Maloy