On Sat, 21 Mar 2026 20:43:30 -0400
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
--- v4: - Broke out as separate commit - Made number of transferable addresses variable
v6: - Separated internal and wire transfer format --- migrate.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+)
diff --git a/migrate.c b/migrate.c index a92301b..7b9a2f6 100644 --- a/migrate.c +++ b/migrate.c @@ -44,6 +44,71 @@ struct migrate_seen_addrs_v2 { unsigned char mac[ETH_ALEN]; } __attribute__((packed));
+/** + * Wire format flags for address migration (v3)
"Wire format" is perfectly clear to me but I'm afraid it's not really that much of a universal term. What about "Migration format" or "Stable format"?
+ * These are stable values - do not change existing assignments + */ +#define MIGRATE_ADDR_USER BIT(0) +#define MIGRATE_ADDR_HOST BIT(1) +#define MIGRATE_ADDR_LINKLOCAL BIT(2) +#define MIGRATE_ADDR_OBSERVED BIT(3) + +/** + * struct migrate_addr_v3 - Wire format for a single address entry + * @addr: IPv6 or IPv4-mapped address (16 bytes) + * @prefix_len: Prefix length + * @flags: MIGRATE_ADDR_* flags (wire format) + */ +struct migrate_addr_v3 { + struct in6_addr addr; + uint8_t prefix_len; + uint8_t flags; +} __attribute__((__packed__)); + +/** + * flags_to_wire() - Convert internal flags to stable wire format + * @flags: Internal CONF_ADDR_* flags + * + * Return: Wire format MIGRATE_ADDR_* flags + */ +static uint8_t flags_to_wire(uint8_t flags) +{ + uint8_t wire = 0; + + if (flags & CONF_ADDR_USER) + wire |= MIGRATE_ADDR_USER; + if (flags & CONF_ADDR_HOST) + wire |= MIGRATE_ADDR_HOST; + if (flags & CONF_ADDR_LINKLOCAL) + wire |= MIGRATE_ADDR_LINKLOCAL; + if (flags & CONF_ADDR_OBSERVED) + wire |= MIGRATE_ADDR_OBSERVED; + + return wire; +} + +/** + * flags_from_wire() - Convert wire format flags to internal format + * @wire: Wire format MIGRATE_ADDR_* flags + * + * Return: Internal CONF_ADDR_* flags + */ +static uint8_t flags_from_wire(uint8_t wire) +{ + uint8_t flags = 0; + + if (wire & MIGRATE_ADDR_USER) + flags |= CONF_ADDR_USER; + if (wire & MIGRATE_ADDR_HOST) + flags |= CONF_ADDR_HOST; + if (wire & MIGRATE_ADDR_LINKLOCAL) + flags |= CONF_ADDR_LINKLOCAL; + if (wire & MIGRATE_ADDR_OBSERVED) + flags |= CONF_ADDR_OBSERVED; + + return flags; +} + /** * seen_addrs_source_v2() - Copy and send guest observed addresses from source * @c: Execution context @@ -126,6 +191,98 @@ static int seen_addrs_target_v2(struct ctx *c, return 0; }
+/** + * addrs_source_v3() - Send all addresses with flags from source + * @c: Execution context + * @stage: Migration stage, unused + * @fd: File descriptor for state transfer + * + * Send all address entries using a stable wire format. Each field is + * serialized explicitly to avoid coupling the wire format to internal
Nit, for consistency: "serialised".
+ * structure layout or flag bit assignments. + * + * Return: 0 on success, positive error code on failure + */ +/* cppcheck-suppress [constParameterCallback, unmatchedSuppression] */ +static int addrs_source_v3(struct ctx *c, + const struct migrate_stage *stage, int fd) +{ + uint8_t addr_count = c->addr_count; + const struct guest_addr *a; + + (void)stage; + + /* Send count first */ + if (write_all_buf(fd, &addr_count, sizeof(addr_count))) + return errno; + + /* Send each address in stable wire format */ + for_each_addr(a, c, 0) { + struct migrate_addr_v3 wire = { + .addr = a->addr.a6, + .prefix_len = a->prefix_len, + .flags = flags_to_wire(a->flags), + }; + + if (write_all_buf(fd, &wire, sizeof(wire))) + return errno; + } + + /* Send MAC */
Nit: it's a MAC _address_, not a control... something.
+ if (write_all_buf(fd, c->guest_mac, ETH_ALEN)) + return errno; + + return 0; +} + +/** + * addrs_target_v3() - Receive addresses on target + * @c: Execution context + * @stage: Migration stage, unused + * @fd: File descriptor for state transfer + * + * Receive address entries from the stable wire format and merge only + * observed addresses into local array. Source sends all addresses for + * forward compatibility, but target only applies those marked as observed. + * + * Return: 0 on success, positive error code on failure + */ +static int addrs_target_v3(struct ctx *c, + const struct migrate_stage *stage, int fd) +{ + uint8_t addr_count, i; + + (void)stage; + + if (read_all_buf(fd, &addr_count, sizeof(addr_count))) + return errno; + + if (addr_count > MAX_GUEST_ADDRS) + addr_count = MAX_GUEST_ADDRS; + + /* Read each address from stable wire format */ + for (i = 0; i < addr_count; i++) { + struct migrate_addr_v3 wire; + struct guest_addr addr; + + if (read_all_buf(fd, &wire, sizeof(wire))) + return errno; + + addr.addr.a6 = wire.addr; + addr.prefix_len = wire.prefix_len; + addr.flags = flags_from_wire(wire.flags); + + if (addr.flags & CONF_ADDR_OBSERVED) + fwd_set_addr(c, &addr.addr, addr.flags, + addr.prefix_len);
Nit: curly brackets for coding style.
+ } + + if (read_all_buf(fd, c->guest_mac, ETH_ALEN)) + return errno; + + return 0; +} + /* Stages for version 2 */ static const struct migrate_stage stages_v2[] = { { @@ -146,8 +303,29 @@ static const struct migrate_stage stages_v2[] = { { 0 }, };
+/* Stages for version 3 (multiple observed IPv4 addresses) */
It's not just observed (anymore), and it's not just IPv4 addresses. Maybe this should actually state "(all addresses, with flags)"?
+static const struct migrate_stage stages_v3[] = { + { + .name = "addresses", + .source = addrs_source_v3, + .target = addrs_target_v3, + }, + { + .name = "prepare flows", + .source = flow_migrate_source_pre, + .target = NULL, + }, + { + .name = "transfer flows", + .source = flow_migrate_source, + .target = flow_migrate_target, + }, + { 0 }, +}; + /* Supported encoding versions, from latest (most preferred) to oldest */ static const struct migrate_version versions[] = { + { 3, stages_v3, }, { 2, stages_v2, }, /* v1 was released, but not widely used. It had bad endianness for the * MSS and omitted timestamps, which meant it usually wouldn't work.
-- Stefano