diff --git a/kmod/src/quorum.c b/kmod/src/quorum.c index 9ef19276..08d5b9d3 100644 --- a/kmod/src/quorum.c +++ b/kmod/src/quorum.c @@ -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; diff --git a/tests/golden/basic-inetaddr b/tests/golden/basic-inetaddr new file mode 100644 index 00000000..02d4d06b --- /dev/null +++ b/tests/golden/basic-inetaddr @@ -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 diff --git a/tests/sequence b/tests/sequence index 909e665a..671bcb80 100644 --- a/tests/sequence +++ b/tests/sequence @@ -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 diff --git a/tests/tests/basic-inetaddr.sh b/tests/tests/basic-inetaddr.sh new file mode 100644 index 00000000..67131896 --- /dev/null +++ b/tests/tests/basic-inetaddr.sh @@ -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 diff --git a/utils/src/quorum.c b/utils/src/quorum.c index b2b13565..3748b120 100644 --- a/utils/src/quorum.c +++ b/utils/src/quorum.c @@ -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)) {