Compare commits

..

1 Commits

Author SHA1 Message Date
Auke Kok
e011c80452 Do not fence connections without valid greeting
There is no reason to fence any connection that hasn't sent a valid
greeting, since they haven't progressed far enough for it to make
sense. Skip the fence call for these connections and let the existing
destroy path tear them down. Any real client will reconnect.

server_notify_down() previously treated any zero rid teardown as
the listening socket going down and called stop_server(). That's
correct for the listener legitimately, but not for any conn that
never completed a greeting.

Adds a test that sends a short garbage payload to each quorum port and
verifies that no greeting-less connection remains past the reconnect
timeout (20s) and that the filesystem still works afterwards.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-05-06 16:44:37 -07:00
9 changed files with 78 additions and 105 deletions

View File

@@ -980,7 +980,7 @@ static bool lock_flags_invalid(int flags)
*/
static int lock_key_range(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
struct scoutfs_key *start, struct scoutfs_key *end,
u64 ino, struct scoutfs_lock **ret_lock)
struct scoutfs_lock **ret_lock)
{
DECLARE_LOCK_INFO(sb, linfo);
struct scoutfs_lock *lock;
@@ -1028,8 +1028,6 @@ static int lock_key_range(struct super_block *sb, enum scoutfs_lock_mode mode, i
/* the fast path where we can use the granted mode */
if (lock_modes_match(lock->mode, mode)) {
lock_inc_count(lock->users, mode);
lock->last_user_pid[mode] = task_pid_nr(current);
lock->last_user_ino[mode] = ino;
*ret_lock = lock;
ret = 0;
break;
@@ -1110,7 +1108,7 @@ int scoutfs_lock_ino(struct super_block *sb, enum scoutfs_lock_mode mode, int fl
end.sk_zone = SCOUTFS_FS_ZONE;
end.ski_ino = cpu_to_le64(ino | SCOUTFS_LOCK_INODE_GROUP_MASK);
return lock_key_range(sb, mode, flags, &start, &end, ino, ret_lock);
return lock_key_range(sb, mode, flags, &start, &end, ret_lock);
}
/*
@@ -1240,7 +1238,7 @@ int scoutfs_lock_rename(struct super_block *sb, enum scoutfs_lock_mode mode, int
.sk_type = SCOUTFS_RENAME_TYPE,
};
return lock_key_range(sb, mode, flags, &key, &key, 0, lock);
return lock_key_range(sb, mode, flags, &key, &key, lock);
}
/*
@@ -1288,7 +1286,7 @@ int scoutfs_lock_inode_index(struct super_block *sb, enum scoutfs_lock_mode mode
scoutfs_lock_get_index_item_range(type, major, ino, &start, &end);
return lock_key_range(sb, mode, 0, &start, &end, ino, ret_lock);
return lock_key_range(sb, mode, 0, &start, &end, ret_lock);
}
/*
@@ -1315,7 +1313,7 @@ int scoutfs_lock_orphan(struct super_block *sb, enum scoutfs_lock_mode mode, int
end.sko_ino = cpu_to_le64(U64_MAX);
end.sk_type = SCOUTFS_ORPHAN_TYPE;
return lock_key_range(sb, mode, flags, &start, &end, ino, lock);
return lock_key_range(sb, mode, flags, &start, &end, lock);
}
int scoutfs_lock_xattr_totl(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
@@ -1326,7 +1324,7 @@ int scoutfs_lock_xattr_totl(struct super_block *sb, enum scoutfs_lock_mode mode,
scoutfs_totl_set_range(&start, &end);
return lock_key_range(sb, mode, flags, &start, &end, 0, lock);
return lock_key_range(sb, mode, flags, &start, &end, lock);
}
int scoutfs_lock_xattr_indx(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
@@ -1337,7 +1335,7 @@ int scoutfs_lock_xattr_indx(struct super_block *sb, enum scoutfs_lock_mode mode,
scoutfs_xattr_indx_get_range(&start, &end);
return lock_key_range(sb, mode, flags, &start, &end, 0, lock);
return lock_key_range(sb, mode, flags, &start, &end, lock);
}
int scoutfs_lock_quota(struct super_block *sb, enum scoutfs_lock_mode mode, int flags,
@@ -1348,7 +1346,7 @@ int scoutfs_lock_quota(struct super_block *sb, enum scoutfs_lock_mode mode, int
scoutfs_quota_get_lock_range(&start, &end);
return lock_key_range(sb, mode, flags, &start, &end, 0, lock);
return lock_key_range(sb, mode, flags, &start, &end, lock);
}
void scoutfs_unlock(struct super_block *sb, struct scoutfs_lock *lock, enum scoutfs_lock_mode mode)
@@ -1465,7 +1463,7 @@ static void lock_tseq_show(struct seq_file *m, struct scoutfs_tseq_entry *ent)
struct scoutfs_lock *lock =
container_of(ent, struct scoutfs_lock, tseq_entry);
seq_printf(m, "start "SK_FMT" end "SK_FMT" refresh_gen %llu mode %d waiters: rd %u wr %u wo %u users: rd %u wr %u wo %u ino: rd %llu wr %llu wo %llu pid: rd %d wr %d wo %d\n",
seq_printf(m, "start "SK_FMT" end "SK_FMT" refresh_gen %llu mode %d waiters: rd %u wr %u wo %u users: rd %u wr %u wo %u\n",
SK_ARG(&lock->start), SK_ARG(&lock->end),
lock->refresh_gen, lock->mode,
lock->waiters[SCOUTFS_LOCK_READ],
@@ -1473,13 +1471,7 @@ static void lock_tseq_show(struct seq_file *m, struct scoutfs_tseq_entry *ent)
lock->waiters[SCOUTFS_LOCK_WRITE_ONLY],
lock->users[SCOUTFS_LOCK_READ],
lock->users[SCOUTFS_LOCK_WRITE],
lock->users[SCOUTFS_LOCK_WRITE_ONLY],
lock->last_user_ino[SCOUTFS_LOCK_READ],
lock->last_user_ino[SCOUTFS_LOCK_WRITE],
lock->last_user_ino[SCOUTFS_LOCK_WRITE_ONLY],
lock->last_user_pid[SCOUTFS_LOCK_READ],
lock->last_user_pid[SCOUTFS_LOCK_WRITE],
lock->last_user_pid[SCOUTFS_LOCK_WRITE_ONLY]);
lock->users[SCOUTFS_LOCK_WRITE_ONLY]);
}
/*

View File

@@ -42,8 +42,6 @@ struct scoutfs_lock {
enum scoutfs_lock_mode invalidating_mode;
unsigned int waiters[SCOUTFS_LOCK_NR_MODES];
unsigned int users[SCOUTFS_LOCK_NR_MODES];
pid_t last_user_pid[SCOUTFS_LOCK_NR_MODES];
u64 last_user_ino[SCOUTFS_LOCK_NR_MODES];
struct scoutfs_tseq_entry tseq_entry;

View File

@@ -1452,15 +1452,23 @@ restart:
set_conn_fl(acc, reconn_freeing);
spin_unlock(&conn->lock);
if (!test_conn_fl(conn, shutting_down)) {
scoutfs_info(sb, "client "SIN_FMT" reconnect timed out, fencing",
SIN_ARG(&acc->last_peername));
ret = scoutfs_fence_start(sb, acc->rid,
acc->last_peername.sin_addr.s_addr,
SCOUTFS_FENCE_CLIENT_RECONNECT);
if (ret) {
scoutfs_err(sb, "client fence returned err %d, shutting down server",
ret);
scoutfs_server_stop(sb);
/*
* Connections that never completed a valid greeting
* (port scans, malformed traffic, half-open peers)
* haven't progressed far enough to warrant fencing.
* Drop them. Any real client will reconnect.
*/
if (test_conn_fl(acc, valid_greeting)) {
scoutfs_info(sb, "client "SIN_FMT" reconnect timed out, fencing",
SIN_ARG(&acc->last_peername));
ret = scoutfs_fence_start(sb, acc->rid,
acc->last_peername.sin_addr.s_addr,
SCOUTFS_FENCE_CLIENT_RECONNECT);
if (ret) {
scoutfs_err(sb, "client fence returned err %d, shutting down server",
ret);
scoutfs_server_stop(sb);
}
}
}
destroy_conn(acc);

View File

@@ -4315,7 +4315,8 @@ static void server_notify_down(struct super_block *sb,
spin_unlock(&server->lock);
free_farewell_requests(sb, rid);
} else {
} else if (!conn->listening_conn) {
/* only the listener going down should stop the server */
stop_server(server);
}
}

View File

@@ -1,6 +0,0 @@
== set up file
== exercise read, write, and write-only modes
== verify FS-zone lock recorded read and write ino+pid
== verify orphan-zone lock recorded write-only ino+pid
== contend on a single inode with concurrent read and write loops
== verify both rd and wr slots populated by concurrent contention

2
tests/golden/portscan Normal file
View File

@@ -0,0 +1,2 @@
== send empty payload to a quorum port
== greeting-less connections still in reconn_wait

View File

@@ -32,7 +32,6 @@ totl-merge-read.sh
quota-invalidate-race.sh
totl-delta-inject.sh
lock-refleak.sh
lock-pid-ino.sh
lock-shrink-consistency.sh
lock-shrink-read-race.sh
lock-pr-cw-conflict.sh
@@ -65,4 +64,5 @@ archive-light-cycle.sh
block-stale-reads.sh
inode-deletion.sh
renameat2-noreplace.sh
portscan.sh
xfstests.sh

View File

@@ -1,68 +0,0 @@
#
# verify debugfs client_locks reports per-mode last-user PID and inode.
#
t_require_commands stat touch awk rm
FILE="$T_D0/file"
echo "== set up file"
touch "$FILE"
INO=$(stat -c %i "$FILE")
GROUP_START=$(( INO & ~1023 ))
echo "== exercise read, write, and write-only modes"
t_quiet stat "$FILE"
echo data > "$FILE"
rm -f "$FILE"
echo "== verify FS-zone lock recorded read and write ino+pid"
ERR=$(awk -v group="$GROUP_START" -v ino="$INO" '
$2 == "16." group ".0.0.0.0" {
if ($25 != ino || $32 <= 0)
print "read mode: ino=" $25 " pid=" $32 " want ino=" ino " pid>0"
if ($27 != ino || $34 <= 0)
print "write mode: ino=" $27 " pid=" $34 " want ino=" ino " pid>0"
found = 1
}
END { if (!found) print "no FS-zone client_locks line for group " group }
' < "$(t_debugfs_path)/client_locks")
[ -n "$ERR" ] && t_fail "$ERR"
echo "== verify orphan-zone lock recorded write-only ino+pid"
ERR=$(awk -v ino="$INO" '
$2 == "8.0.4.0.0.0" {
if ($29 != ino || $36 <= 0)
print "write-only mode: ino=" $29 " pid=" $36 " want ino=" ino " pid>0"
found = 1
}
END { if (!found) print "no orphan-zone client_locks line" }
' < "$(t_debugfs_path)/client_locks")
[ -n "$ERR" ] && t_fail "$ERR"
echo "== contend on a single inode with concurrent read and write loops"
FILE2="$T_D0/file2"
touch "$FILE2"
INO2=$(stat -c %i "$FILE2")
GROUP2=$(( INO2 & ~1023 ))
for i in $(seq 1 5); do t_quiet stat "$FILE2"; done &
RPID=$!
for i in $(seq 1 5); do echo $i > "$FILE2"; done &
WPID=$!
wait $RPID $WPID
echo "== verify both rd and wr slots populated by concurrent contention"
ERR=$(awk -v group="$GROUP2" -v ino="$INO2" '
$2 == "16." group ".0.0.0.0" {
if ($25 != ino || $32 <= 0)
print "concurrent read: ino=" $25 " pid=" $32 " want ino=" ino " pid>0"
if ($27 != ino || $34 <= 0)
print "concurrent write: ino=" $27 " pid=" $34 " want ino=" ino " pid>0"
found = 1
}
END { if (!found) print "no FS-zone client_locks line for group " group }
' < "$(t_debugfs_path)/client_locks")
[ -n "$ERR" ] && t_fail "$ERR"
t_pass

46
tests/tests/portscan.sh Normal file
View File

@@ -0,0 +1,46 @@
#
# portscan tests - assure malformed packets do not cause issues
#
# Send a short garbage payload to a scoutfs server quorum port. The
# accepted connection never completes a valid greeting, so after the
# reconnect timeout the kernel must drop it silently rather than
# fence it (which would restart the server).
#
t_require_commands scoutfs grep wc seq
send_garbage()
{
local port="$1"
(
exec 3<>"/dev/tcp/127.0.0.1/$port" || exit 1
printf ' ' >&3
exec 3>&-
) 2>/dev/null
}
echo "== send empty payload to a quorum port"
slot=-1
for i in $(seq 0 $((T_QUORUM - 1))); do
if send_garbage "$((T_TEST_PORT + i))"; then
slot=$i
break
fi
done
test "$slot" -ge 0 || t_fail "no quorum port accepted"
# CLIENT_RECONNECT_TIMEOUT_MS is 20s - wait until that happens.
echo "== greeting-less connections still in reconn_wait"
for _ in $(seq 1 25); do
n=$(grep -h 'vg 0 .* rw 1' /sys/kernel/debug/scoutfs/*/connections | wc -l)
[ "$n" = 0 ] && break
sleep 1
done
test "$n" -eq 0 || t_fail "$n greeting-less conns remain in reconn_wait"
# the mount whose port we hit should be up and not disconnected now.
eval dir=\$T_D$slot
touch "$dir/portscan-after" 2>/dev/null || t_fail "fs on $dir not responsive after portscan"
t_pass