We can't cross-mount ipv4/ipv6.

I thought this would just work between ipv4 and ipv6 based quorum
members, but it turns out it will only work one way by default. While
we could make this work (multiple sockets, special sockopts) it is
highly unlikely and very undesirable.

Much stronger feels to just disallow it explicitly and reject mixed
v4/v6 configurations outright (mkfs/change-quorum, and mount) to avoid
this. I can't imagine this doing any good for users' fencing setups.

The test cases added validate the 2 easy userspace checks. The mount
check isn't easily testable because we disallow userspace from creating
such a failure path.

One additional test section tests the migration path from v4->v6->v4
so there's at least some test that checks that this actually works.

Signed-off-by: Auke Kok <auke.kok@versity.com>
This commit is contained in:
Auke Kok
2026-05-14 14:42:26 -07:00
parent 2c46e37543
commit c137637007
5 changed files with 120 additions and 0 deletions
+17
View File
@@ -1244,6 +1244,7 @@ static int verify_quorum_slots(struct super_block *sb, struct quorum_info *qinf,
struct sockaddr_in *other4;
struct sockaddr_in6 *sin6;
struct sockaddr_in6 *other6;
__le16 family = cpu_to_le16(SCOUTFS_AF_NONE);
int found = 0;
int ret;
int i;
@@ -1255,6 +1256,14 @@ static int verify_quorum_slots(struct super_block *sb, struct quorum_info *qinf,
continue;
if (quorum_slot_ipv4(qconf, i)) {
if (family == cpu_to_le16(SCOUTFS_AF_NONE)) {
family = cpu_to_le16(SCOUTFS_AF_IPV4);
} else if (family != cpu_to_le16(SCOUTFS_AF_IPV4)) {
scoutfs_err(sb, "quorum slot #%d is IPv4 but earlier slots are IPv6; mixed IPv4/IPv6 quorum is not supported",
i);
return -EINVAL;
}
scoutfs_quorum_slot_sin(qconf, i, &sin);
sin4 = (struct sockaddr_in *)&sin;
@@ -1287,6 +1296,14 @@ static int verify_quorum_slots(struct super_block *sb, struct quorum_info *qinf,
found++;
} else if (quorum_slot_ipv6(qconf, i)) {
if (family == cpu_to_le16(SCOUTFS_AF_NONE)) {
family = cpu_to_le16(SCOUTFS_AF_IPV6);
} else if (family != cpu_to_le16(SCOUTFS_AF_IPV6)) {
scoutfs_err(sb, "quorum slot #%d is IPv6 but earlier slots are IPv4; mixed IPv4/IPv6 quorum is not supported",
i);
return -EINVAL;
}
quorum_slot_sin(qconf, i, &sin);
sin6 = (struct sockaddr_in6 *)&sin;
+7
View File
@@ -0,0 +1,7 @@
== mkfs rejects mixed v4/v6 quorum
rc: 64
== mkfs all-v4, mount three members, cross-mount signature visible
== change-quorum-config rejects mixed v4/v6 quorum
rc: 64
== switch v4 -> v6, signature survives, cross-mount write again
== switch v6 -> v4, signatures survive
+1
View File
@@ -1,6 +1,7 @@
export-get-name-parent.sh
basic-block-counts.sh
basic-bad-mounts.sh
basic-inetaddr.sh
basic-posix-acl.sh
basic-acl-consistency.sh
inode-items-updated.sh
+78
View File
@@ -0,0 +1,78 @@
#
# Test that mixed ipv4/6 fails through mkfs/quorum change and that
# users can migrate from ipv4 to v6 and back.
#
t_require_commands dmsetup blockdev cmp
P0=$T_SCRATCH_PORT
P1=$((T_SCRATCH_PORT + 1))
P2=$((T_SCRATCH_PORT + 2))
SIG=$T_TMP.sig
seq 1 4096 > "$SIG"
trap '
umount $T_TMPDIR/m0 $T_TMPDIR/m1 $T_TMPDIR/m2 2>/dev/null
dmsetup remove _bia_m0 _bia_m1 _bia_m2 _bia_d0 _bia_d1 _bia_d2 2>/dev/null
' EXIT
mkdir -p "$T_TMPDIR/m0" "$T_TMPDIR/m1" "$T_TMPDIR/m2"
for nv in "m0 $T_EX_META_DEV" "m1 $T_EX_META_DEV" "m2 $T_EX_META_DEV" \
"d0 $T_EX_DATA_DEV" "d1 $T_EX_DATA_DEV" "d2 $T_EX_DATA_DEV"; do
set -- $nv
t_quiet dmsetup create _bia_$1 --table "0 $(blockdev --getsz $2) linear $2 0"
done
mnt() {
mount -t scoutfs \
-o metadev_path=/dev/mapper/_bia_m$1,quorum_slot_nr=$1 \
/dev/mapper/_bia_d$1 "$T_TMPDIR/m$1"
}
mount_all() {
mnt 0 &
mnt 1 &
mnt 2 &
wait
}
umount_all() {
umount $T_TMPDIR/m0 &
umount $T_TMPDIR/m1 &
umount $T_TMPDIR/m2 &
wait
}
verify() {
cmp -s "$SIG" "$T_TMPDIR/m0/sig" &&
cmp -s "$SIG" "$T_TMPDIR/m1/sig" &&
cmp -s "$SIG" "$T_TMPDIR/m2/sig" || t_fail "$1"
}
echo "== mkfs rejects mixed v4/v6 quorum"
t_rc scoutfs mkfs -f -Q 0,127.0.0.1,$P0 -Q 1,::1,$P1 -Q 2,127.0.0.1,$P2 /dev/mapper/_bia_m0 /dev/mapper/_bia_d0
echo "== mkfs all-v4, mount three members, cross-mount signature visible"
t_quiet scoutfs mkfs -f -Q 0,127.0.0.1,$P0 -Q 1,127.0.0.1,$P1 -Q 2,127.0.0.1,$P2 /dev/mapper/_bia_m0 /dev/mapper/_bia_d0
mount_all
cp "$SIG" "$T_TMPDIR/m0/sig"
verify "v4 initial"
umount_all
echo "== change-quorum-config rejects mixed v4/v6 quorum"
t_rc scoutfs change-quorum-config --offline -Q 0,127.0.0.1,$P0 -Q 1,::1,$P1 -Q 2,127.0.0.1,$P2 /dev/mapper/_bia_m0
echo "== switch v4 -> v6, signature survives, cross-mount write again"
t_quiet scoutfs change-quorum-config --offline -Q 0,::1,$P0 -Q 1,::1,$P1 -Q 2,::1,$P2 /dev/mapper/_bia_m0
mount_all
verify "after v4->v6"
cp "$SIG" "$T_TMPDIR/m1/sig-v6"
cmp -s "$SIG" "$T_TMPDIR/m0/sig-v6" || t_fail "v6 cross-mount write not visible on m0"
cmp -s "$SIG" "$T_TMPDIR/m2/sig-v6" || t_fail "v6 cross-mount write not visible on m2"
umount_all
echo "== switch v6 -> v4, signatures survive"
t_quiet scoutfs change-quorum-config --offline -Q 0,127.0.0.1,$P0 -Q 1,127.0.0.1,$P1 -Q 2,127.0.0.1,$P2 /dev/mapper/_bia_m0
mount_all
verify "after v6->v4"
cmp -s "$SIG" "$T_TMPDIR/m0/sig-v6" || t_fail "after v6->v4 sig-v6 lost"
umount_all
t_pass
+17
View File
@@ -20,11 +20,20 @@ bool valid_quorum_slots(struct scoutfs_quorum_slot *slots)
bool valid = true;
char *addr;
char ip6addr[INET6_ADDRSTRLEN];
__le16 family = cpu_to_le16(SCOUTFS_AF_NONE);
int i;
int j;
for (i = 0; i < SCOUTFS_QUORUM_MAX_SLOTS; i++) {
if (slots[i].addr.v4.family == cpu_to_le16(SCOUTFS_AF_IPV4)) {
if (family == cpu_to_le16(SCOUTFS_AF_NONE)) {
family = cpu_to_le16(SCOUTFS_AF_IPV4);
} else if (family != cpu_to_le16(SCOUTFS_AF_IPV4)) {
fprintf(stderr, "quorum slot nr %u is IPv4 but earlier slots are IPv6; mixed IPv4/IPv6 quorum is not supported\n",
i);
valid = false;
}
for (j = i + 1; j < SCOUTFS_QUORUM_MAX_SLOTS; j++) {
if (slots[i].addr.v4.addr == slots[j].addr.v4.addr &&
slots[i].addr.v4.port == slots[j].addr.v4.port) {
@@ -38,6 +47,14 @@ bool valid_quorum_slots(struct scoutfs_quorum_slot *slots)
}
}
} else if (slots[i].addr.v6.family == cpu_to_le16(SCOUTFS_AF_IPV6)) {
if (family == cpu_to_le16(SCOUTFS_AF_NONE)) {
family = cpu_to_le16(SCOUTFS_AF_IPV6);
} else if (family != cpu_to_le16(SCOUTFS_AF_IPV6)) {
fprintf(stderr, "quorum slot nr %u is IPv6 but earlier slots are IPv4; mixed IPv4/IPv6 quorum is not supported\n",
i);
valid = false;
}
for (j = i + 1; j < SCOUTFS_QUORUM_MAX_SLOTS; j++) {
if ((IN6_ARE_ADDR_EQUAL(slots[i].addr.v6.addr, slots[j].addr.v6.addr)) &&
(slots[i].addr.v6.port == slots[j].addr.v6.port)) {