feat(velocity): let 1.8.x players through the modern-forwarding proxy

Felis pins player-info-forwarding-mode = "modern", and Velocity's
HandshakeSessionHandler#handleLogin refuses anything below 1.13 outright: it reads
handshake.getProtocolVersion() and disconnects with
velocity.error.modern-forwarding-needs-new-client before the backend is ever
contacted. A player pinned to 1.8.9 never reaches the login gate, never sees the
onboarding link, and gets an error string that tells them to upgrade their client.

install_via_plugins now stages ViaVersion, ViaBackwards and ViaRewind into
/opt/felis/velocity/plugins alongside felis-velocity.jar. Nothing else moves: same
forwarding mode, same secret, no backend patched and no backend downgraded. The 1.13
floor turns out to be a property of the unassisted proxy pipeline rather than of the
forwarding protocol, so lifting it costs three jars and no source change.

This was measured, not assumed. Felis-Legacy's FL-007 probe stands a protocol-47
client in front of a stock Paper 1.21.11 backend behind a modern-forwarding proxy and
watches it join. The proof is the join itself rather than the log line: that backend
runs velocity.enabled with a shared secret, and Paper in that state rejects any login
not carrying forwarding data signed with a matching HMAC. The control cell without
Via is rejected before the backend is contacted, so Via is the only difference. A
second cell re-runs the same join with force-key-authentication = true, the way Felis
sets it, because a result that only holds under a config Felis does not run is not a
result about Felis; the 1.19+ signed chat key a protocol-47 client cannot produce is
never demanded, and it cannot be, since the pre-1.19 wire format has no player-key
field to decode.

The jars are pinned by sha256 and not by a moving tag. They sit in front of every
packet on the proxy and they are the exact bytes FL-007 measured; "latest" would
quietly make this an unmeasured configuration. The digests come from the GitHub
releases FL-006 locked, which is deliberate — Hangar's VELOCITY/download endpoint
serves different bytes for the same version numbers, so an installer that only
checked for HTTP 200 would ship artifacts nothing has tested. A version bump means a
digest bump here.

Two limits are worth writing down. Only protocol 47 was measured; the rest of Via's
documented 1.7-1.12 range is inference from that one point. And the probe runs
online-mode = false because it has no Mojang account, so Felis's online-mode = true
is untested — the untested part is the Mojang auth handshake specifically, which puts
the residual in Via's own login handling rather than in forwarding or in the
hasJoined multiplexer, whose request is protocol-independent.

Checks: a fresh install lands all three jars at the pinned digests and sizes with no
temp files left behind; a re-run downloads nothing; a tampered jar is restored to the
pinned bytes; and a deliberately wrong digest aborts without installing anything.
Existing installs pick this up by re-running bootstrap, which re-enters
install_velocity because bootstrap.done is written but never read as an early exit.
This commit is contained in:
flyemoji committed 2026-07-22 17:03:01 +09:00
1 parent 0e69ade1b3
commit f532684249
1 file changed
+54
+54
View File
@@ -1313,6 +1313,59 @@ build_velocity_plugin() {
atomic_install_file "${jars[0]}" "${VELOCITY_DIR}/plugins/felis-velocity.jar" 0644 root root atomic_install_file "${jars[0]}" "${VELOCITY_DIR}/plugins/felis-velocity.jar" 0644 root root
} }
# install_via_plugins stages ViaVersion + ViaBackwards + ViaRewind so players on clients
# older than the proxy can still join.
#
# Modern forwarding nominally refuses anything below 1.13: HandshakeSessionHandler#handleLogin
# reads the handshake protocol version and disconnects with
# velocity.error.modern-forwarding-needs-new-client. That gate stops firing once Via is
# present — it logs "Replacing channel initializers" during startup, so the version reaching
# the check is plausibly already the rewritten one. The 1.13 floor is a property of the
# UNASSISTED proxy pipeline, not of the forwarding protocol, so nothing here changes
# player-info-forwarding-mode and no backend is patched or downgraded.
#
# Measured end to end rather than assumed (Felis-Legacy FL-007, cell modern-via121): a
# protocol-47 client joined a stock Paper 1.21.11 backend through a modern-forwarding proxy.
# The proof is the join itself, not the log line — that backend ran velocity.enabled=true with
# a shared secret, and Paper in that state rejects any login not carrying forwarding data
# signed with a matching HMAC. Only protocol 47 was measured; the rest of Via's 1.7-1.12 range
# is its own documented support.
#
# Pinned by hash and not by "latest" on purpose. These three jars sit in front of every packet
# on the proxy, and they are the exact bytes FL-007 measured — a moving tag would quietly make
# this an unmeasured configuration. Bumping a version means bumping its checksum here.
install_via_plugins() {
prepare_velocity_layout
local name version want target url tmp have
while read -r name version want; do
[ -n "$name" ] || continue
target="${VELOCITY_DIR}/plugins/${name}.jar"
# Hash stdin, never the path: sha256sum escapes its output line when the filename
# carries a backslash or a newline, and a leading "\" on the digest silently fails
# every comparison below.
have=""
[ -f "$target" ] && have="$(sha256sum <"$target" | cut -d' ' -f1)"
if [ "$have" = "$want" ]; then
ok "${name} ${version} already staged"
continue
fi
url="https://github.com/ViaVersion/${name}/releases/download/${version}/${name}-${version}.jar"
log "downloading ${name} ${version}"
tmp="$(mktemp "${VELOCITY_DIR}/.${name}.jar.XXXXXX")"
remember_temp "$tmp"
curl -fsSL "$url" -o "$tmp" || die "failed to download ${name} ${version}: ${url}"
have="$(sha256sum <"$tmp" | cut -d' ' -f1)"
[ "$have" = "$want" ] \
|| die "${name} ${version} checksum mismatch: got ${have}, expected ${want}"
atomic_install_file "$tmp" "$target" 0644 root root
done <<'EOF'
ViaVersion 5.11.0 18d19e90fc9467d68128c076630ae8700449c901402a3ef421837ce006bc8cae
ViaBackwards 5.11.0 b21983d561e3f92df257683f0133ab6c68ec68175e8acfd82c6231723bf83587
ViaRewind 4.1.2 88f413eb1a5c302cf0fdd32bf11051bbb65485cbf6012921dbcfedab3772f341
EOF
ok "Via staged; clients from 1.8 up can join under modern forwarding"
}
install_jre() { install_jre() {
local arch url local arch url
if [ -x "${JRE_DIR}/bin/java" ]; then if [ -x "${JRE_DIR}/bin/java" ]; then
@@ -1352,6 +1405,7 @@ install_velocity() {
curl -fsSL "$url" -o "$tmp" || die "failed to download Velocity: ${url}" curl -fsSL "$url" -o "$tmp" || die "failed to download Velocity: ${url}"
atomic_install_file "$tmp" "${VELOCITY_DIR}/velocity.jar" 0644 root root atomic_install_file "$tmp" "${VELOCITY_DIR}/velocity.jar" 0644 root root
install_via_plugins
write_velocity_config write_velocity_config
install_velocity_service install_velocity_service
configure_velocity_firewall configure_velocity_firewall