#velocity
2458 messages · Page 3 of 3 (latest)
A client that stalls its FML/login handshake could enqueue plugin messages (each up to ~32 KiB serverbound) without bound in ClientPlaySessionHandler#loginPluginMessages, growing per-connection heap until an OOM kill. This PR caps the queue by both bytes (4 MiB) and count (1024), configurable via velocity.max-queued-login-plugin-message-bytes and velocity.max-queued-login-plugin-messages, and disconnects on overflow.
Building the project shows the following warning:
> Task :velocity-proxy:compileJava
warning: The `GraalVmProcessor` annotation processor is missing the recommended `log4j.graalvm.groupId` and `log4j.graalvm.artifactId` options.
To follow the GraalVM recommendations, please add the following options to your build tool:
-Alog4j.graalvm.groupId=<groupId>
-Alog4j.graalvm.artifactId=<artifactId>
This PR fixes this by adding the required options.
Depending on how long a client is actually able to stall the login phase for, this may be a scary OOM once again.
Without this fix, and at the default rate limit of 500 pps, a client would be able to fill this queue with 160MB of plugin messages every second (at 32kb/PM). That's 10GB within a minute, currently exploitable.
Of course this PR fixes that, and I'm sorry to keep bumping this but, #1786 would have greatly crippled this attack. At a limit of 5MB/s of decompressed packets per second, it would take around half an hour to fill the same 10GB in this queue. Still an OOM, if the client is actually able to stall the login phase for 30 minutes, but way less instant.
Now that we're using Java 21 throughout the project, I think we should take this opportunity to remove this try-catch block
I don't think that's likely to happen, but since it's good practice, I don't see a problem with it
I think this just hack the allowed range from -128 ~ 127 to -1~254 (255 is -1).
If modded servers register a dimension in -2 ~ -128, it may still broken.
And the second things, GTNH is not "hack this to an unsigned byte to allow dim IDs up to 255".
It hack to an int to completely fix this vanilla bug.
(https://github.com/GTNewHorizons/Hodgepodge/blob/master/src/main/java/com/mitchej123/hodgepodge/mixins/early/minecraft/packets/MixinS01PacketJoinGame_FixDimensionID.java)
If modded servers register a dimension in -2 ~ -128, it may still broken.
Not sure if this is possible, in older versions anyway. JoinGamePacket handles the dimension as a byte, regardless of its sign, it will always encode/decode as the same hex value. Though in vanilla minecraft this is definitely expected to be signed, because the nether is -1. RespawnPacket just encodes this as an integer instead, which is also expected to be signed, as the vanilla protocol expects the values -1, 0 and 1.
GTNH is not "hack this to an unsigned byte to allow dim IDs up to 255".
IMO it is a hack. The nether being -1 suggests that this value should be signed. Mods using a value over 127 would have needed to patch this bug also; RespawnPacket needs to return a signed -1 only ever for the nether, otherwise it should treat the byte as unsigned (and have a range from 0 - 255). This patch makes the range for the dimension value essentially be -1 - 254 (inclusive), as 255 is effectively mapped t...
Completely agree on both counts, this effectively won't ever happen, but it's an eye sore & bad practice in its current state.
38ff21a Fix theoretical IOOBE race (#1799) - WouterGritter
The proxy module doesn't need javadocs, and I'm generally not inclined to mix a set of dependency bumps which are fair, with a javadoc coverage thing which needs a lot of scrutinsation to deal with properly this time.
Correct me if I'm wrong, but it seems like SET_ACTION_BAR is the right action for the TitleActionbarPacket packet.
This bug doesn't surface when creating this class from the factory:
// GenericTitlePacket
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
GenericTitlePacket packet = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
packet = switch (type) {
case SET_ACTION_BAR -> new TitleActionbarPacket();
case SET_SUBTITLE -> new TitleSubtitlePacket();
case SET_TIMES -> new TitleTimesPacket();
case SET_TITLE -> new TitleTextPacket();
case HIDE, RESET -> new TitleClearPacket();
default -> throw new IllegalArgumentException("Invalid ActionType");
};
} else {
packet = new LegacyTitlePacket();
}
packet.setAction(type);
return packet;
}
Namely through:
case SET_ACTION_BAR -> new TitleActionbarPacket();
packet.setAction(type); // overrides```...
We may even enforce this contract by adding this below the switch statement in the factory method:
if (packet.getAction() != type) {
throw new AssertionError("Title packet action type mismatch!");
}
But I don't think it's worth it to assert this at runtime (and this won't even work, TitleClearPacket can be created with both HIDE and RESET).
The enforcing of this is also quite inconsistent. TitleClearPacket throws when setting its action type to anything but HIDE and RESET, but the other packets let you set the action type to anything you want. Maybe it's worth expanding this PR to enforcing this for all other packet's setAction methods as well?
Fixes #1802, this PR builds on-top of the fix proposed and explained in that PR.
This PR drops the method visibility, enforces the action types (similar pattern to the already-existing enforcement in TitleClearPacket) and cleans up some whitespace formatting.
I don't like that this is null here, but this is exactly the old behavior. Previously this was less explicit as the setAction() setter just never got called.
On the topic of v4-vs-v7, if Mojang uses it to generate Entity UUIDs, then it's alright enough.
On the performance topic, are the gains actually significant?
I actually got curious and wrote a small benchmark for that and ran on my (admittedly ancient) desktop PC on both Windows (everyday usage) and Linux (installation ISO without background services):
> Windows
Benchmark Mode Cnt Score Error Units
UuidBenchmark.uuidInsecure thrpt 6 151409.421 ± 1504.844 ops/ms
UuidBenchmark.uuidSecure thrpt 6 698.752 ± 10.978 ops/ms
> Linux
Benchmark Mode Cnt Score Error Units
UuidBenchmark.uuidInsecure thrpt 6 156989.602 ± 593.248 ops/ms
UuidBenchmark.uuidSecure thrpt 6 2846.491 ± 35.525 ops/ms
So yeah, there are good magnitude-order speedups (less good on Linux, probably due to better reseeding from the kernel CSRNG). However, the baseline of 700 (or 2800) UUIDs/ms is not something that needs extreme urgent optimization.
This seems unnecessary vs. just clearing it below. when login is sent, the client has already sent its finish configuration packet and cannot change this counter anymore. same below
UUID.randomUUID() is known to scale rather bad with more threads (see https://github.com/openjdk/jdk/pull/14135), so this might make sense. If secure random should be used, an approach similar to that in the linked PR might be possible as well.
Fair point, I completely neglected the multithreaded-ness of Velocity and cas/mutex slowdown of Java rng/csrng implementations
The referenced library also provides benchmarks: https://github.com/f4b6a3/uuid-creator/wiki/5.0.-Benchmark
Is dropping this method intentional? KeyedVelocityTabList#buildEntry (which this was previously overriding) just delegated this to one of the other buildEntry()'s that is still overridden now, but this behavior might change in the future. If it does, a change in KeyedVelocityTabList may make VelocityTabListLegacy return the wrong entry here.
Could be overkill here, but we might consider "seeding" (xorring) the nextLong's with a value from SecureRandom, example & explanation from the referenced library: https://github.com/f4b6a3/uuid-creator/blob/master/src/main/java/com/github/f4b6a3/uuid/alt/GUID.java#L796
I think this is a bit overkill for this usecase. The uuid is only used for a "fake" mapping to the name, theres no real use besides it so it would never "cross" JVMs.
b72cf26 Cap pre-join plugin-message queue size (prevents a... - WouterGritter
25fbd83 Add decompressed-bytes-per-second rate limit, upda... - WouterGritter
Since Adventure's Facet platform is now deprecated, maybe it's worth considering an alternative, similar to: GemstoneGG#883, @4drian3d?
I'd rather this was included/PR'd into adventure-api. It serves literally zero point for an implementing platform to have a type pointer when you can just instanceof.
[PaperMC/Velocity] branch deleted: cat/pps
Expected Behavior
Hey, i'm trying to use Velocity for Modded Fabric on 26.1.2.
I had tried to join the Server without Velo, and it work right, but i had used now, the Automodpack plugin with the Mod from Automodpack. and this works fine :)
Without Velocity:
Client
https://mclo.gs/ojlvXCv
Server Log: https://mclo.gs/HIjrh9B
Actual Behavior
But when i try to join the server i got an issue and we had used -Dvelocity.max-known-packs= for more packets.
But, on joining it does not work and it throws this error:
[15:46:18] [Netty epoll Worker #4/ERROR] [com.velocitypowered.proxy.connection.MinecraftConnection]: [server connection] Suerion -> survival: exception encountered in com.velocitypowered.proxy.connection.backend.BackendPlaySessionHandler@2367b702
io.netty.handler.codec.CorruptedFrameException: Error decoding class com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket Direction CLIENTBOUND Protocol 26.1 State PLAY ID 0x10
at com.velocitypowered.proxy.p```...
@electronicboy Crossstich is not available for 26.1.2 thats why i had asked here.
The mod works fine on that version
@electronicboy Then the labels are not Correct on Modrinth and Curseforge...
Had added crossstich and it work, but i got this issues on Velo:
-> Caused by: java.lang.IllegalArgumentException: Argument type identifier 58 unknown.
Velocity log
https://mclo.gs/SmmCICQ
Server Log
https://mclo.gs/50YzUOq
Client Log:
https://mclo.gs/AzeEUNv
Network Protokol
https://mclo.gs/ppm8Y5j
Should i try to disable BadPackets and NetProdis?
[17:22:04] [main/WARN]: Error loading class: net/minecraft/class_2641$class_7232 (java.lang.ClassNotFoundException: net/minecraft/class_2641$class_7232)
[17:22:04] [main/WARN]: @Mixin target net/minecraft/class_2641$class_7232 was not found crossstitch.mixins.json:command.CommandTreeSerializationMixin from mod crossstitch
I don't really know anything about fabric/modded platforms, but this makes me guess that crossstitch is not yet compatible with 26.1, probably thanks to the removal of obfuscation. There is https://github.com/PaperMC/CrossStitch/pull/23 which looks to update it, but I'm not sure what the state of that is.
If you can, give that pr a try and let us know if that resolves your problem.
if it's not working then an issue should be opened against crossstitch, velocity can't parse data it doesn't understand, crossstich is the wrapper which mitigates that on the fabric side
[17:22:04] [main/WARN]: Error loading class: net/minecraft/class_2641$class_7232 (java.lang.ClassNotFoundException: net/minecraft/class_2641$class_7232) [17:22:04] [main/WARN]: @Mixin target net/minecraft/class_2641$class_7232 was not found crossstitch.mixins.json:command.CommandTreeSerializationMixin from mod crossstitchI don't really know anything about fabric/modded platforms, but this makes me guess that crossstitch is not yet compatible with 26.1, probably thanks to the removal of obfuscation. There is PaperMC/CrossStitch#23 which looks to update it, but I'm not sure what the state of that is.
If you can, give that pr a try and let us know if that resolves your problem.
@Emilxyz
With the pull, it workes! :) had build it and added it, now it work :), i could join :) it works fine, now i could test more
<img width="1920" height="1080" alt="Image" src="https://github.com/user-attachments/assets/1de33d97-50d7-4fcf-8230-5a5634fc8243" />
@electronicboy i had said, 26....
Filter the ServerboundChatSessionUpdatePacket when player info forwarding is disabled, since the client's profile key signature will not be valid for the player's offline UUID.
See comment in ClientPlaySessionHandler.java. This is marked as draft since it's in a "works for me" state, though I'm happy to address feedback and get this merged. I'm unsure whether this behavior belongs in a separate option or whether player-info-forwarding-mode = "NONE" is a good enough heuristic for when it's needed.
My use case is a public, Mojang-authenticated gateway to an online-mode=false Vanilla server (no Paper/Fabric) that unauthenticated bots can connect to over localhost.
[17:22:04] [main/WARN]: Error loading class: net/minecraft/class_2641$class_7232 (java.lang.ClassNotFoundException: net/minecraft/class_2641$class_7232) [17:22:04] [main/WARN]: @Mixin target net/minecraft/class_2641$class_7232 was not found crossstitch.mixins.json:command.CommandTreeSerializationMixin from mod crossstitchI don't really know anything about fabric/modded platforms, but this makes me guess that crossstitch is not yet compatible with 26.1, probably thanks to the removal of obfuscation. There is PaperMC/CrossStitch#23 which looks to update it, but I'm not sure what the state of that is.
If you can, give that pr a try and let us know if that resolves your problem.
It was ready but at the time I couldn't find any real mod with a custom argument for 26.1, so I wanted to wait for that.
No problem, I'll go ahead and refactor this PR and split them up as well when I have the time! Takes a bit to reapply these changes
@evan-goode, I'd probably mark this as ready so reviewers know it's time to suggest changes
Inspired by:
- https://github.com/GemstoneGG/Velocity-CTD/commit/137e6da1d218accdc6812f4900208c1494ed9459
- https://github.com/GemstoneGG/Velocity-CTD/commit/6b6345406ba90f3e32d12712a8271080d95c1142
With this PR, plugins may dynamically decide to add commands to the client's command tree by triggering Player#sendAvailableCommands and listening for the PlayerAvailableCommandsEvent that gets fired as a result of it.
How much memory does this PR consume per player?
Let's assume 1.000 commands per player. A conservative lower bound, and a complete but educated-ish guess, would be about ~64 bytes per command, giving 64kb per player (for 500 players, this is 32MB)
Take this with a giant grain of salt, but I had Claude Opus 4.7 have a go at estimating the memory footprint this PR brings with it.
Prompt: Investigate exactly how much memory 'RootCommandNode<CommandSource> backendCommandsNode' in 'BackendPlaySessionHandler' consumes per player for 1000 commands with typical str...
I'm not too sure if we need to return a CompletableFuture here. This doesn't get returned anywhere else in the Player interface, but at the same time, when this is completed it does guarantee that all PlayerAvailableCommandsEvents have been fired and the packet is on its way to the player.
Could this be useful for plugins? Or should we just return void and rely on plugins listening for this even to figure out that the packet is (about to be) on it's way?
Approving in its current state - changes may need to be made for other considerations by authors, of course
This targets the long-lost https://github.com/PaperMC/Velocity/issues/1369 issue.
This targets https://github.com/PaperMC/Velocity/pull/1791 but with fewer side effects.
My initial thought was to put this behind add a feature flag system, which would need to be enabled by plugins on init but...
Velocity didn't have these type pointers before so the deprecation of adventure platform has absolutely nothing to do with this.
What do you mean by this exactly? Velocity does currently resolve FacetPointers.TYPE to FacetPointers.Type.PLAYER and CONSOLE through pointers().
It seems strange that this was ever the case, as net.kyori.adventure.platform.facet is an unsupported API that really should never have been used in the first place.
This makes the deprecation of platform-facet and the likes put Velocity in quite a predicament... Removing the dependency could break plugins relying on it, but continuing to use a deprecated library/API also doesn't seem like the right solution.
I'd rather this was included/PR'd into adventure-api.
Getting net.kyori.adventure.platform.facet into adventure-api does seem like the proper solution, but I don't know if they'll want that. Another solution would be to provide these classes in proxy/deprecated/adventure-facet, similar to con...
My initial thought was to put this behind add a feature flag system, which would need to be enabled by plugins on init but...
I mean we could do this intuitively.
- get rid of
Player#sendAvailableCommands - introduce
Proxy#getAvailableCommandsService
interface AvailableCommandsService {
void sendAvailableCommands(Player player);
}
Plugins would need to obtain the AvailableCommandsService which would trigger the command tree to now be saved.
The issue is however that plugins really need to do this on startup, or they'll end up missing the backend command trees from players that are already connected.
Though I don't really like this. Seems complex and unnecessary to avoid a couple megabytes of ram, realistically.
Maybe an opt-out system flag would be better. Having memory issues? Know that no plugins make use of this feature? Disable it.
Wouldn't it need to be renamed from RootBeer?
As you've already mentioned, these pointers are marked as internal by adventure-platform. They're also not actually provided as a dependency by velocity-api and only required by velocity-proxy, which is not even published. So plugins using these is extremely unlikely and also unsupported anyways.
I'm also assuming that this pr will come with a velocity version bump, due to the other breaking changes in adventure 5. So I think just doing a clean cut and dropping adventure-platform-facet should be fine. Maybe mention it in an update announcement.
This would hold callbacks for packs that don't get a terminal client response for as long as the client is connected, right?
My initial thought was to put this behind add a feature flag system, which would need to be enabled by plugins on init but...
I mean we could do this intuitively.
- get rid of
Player#sendAvailableCommands- introduce
ProxyServer#getAvailableCommandsServiceinterface AvailableCommandsService { void sendAvailableCommands(Player player); }Plugins would need to obtain the
AvailableCommandsServicewhich would trigger the command tree to now be saved.The issue is however that plugins really need to do this on startup, or they'll end up missing the backend command trees from players that are already connected.
Though I don't really like this. Seems complex and unnecessary to avoid a couple megabytes of ram, realistically. Maybe an opt-out system flag would be better. Having memory issues? Know that no plugins make use of this feature? Disable it.
We could also just expose the AvailableCommandsService in the proxy init e...
Could also be an idea to add a status() method for the event to promote the adventure status.
I also thought about marking the Velocity one as obsolete but the Adventure one for example doesn't use the same enum order as Velocity & Minecraft, which would make handling the packet more annoying if we'd replace the Velocity one in the future.
This would hold callbacks for packs that don't get a terminal client response for as long as the client is connected, right?
Do you think it's better to add a timeout to this? What if the client is just really slow though, downloading a 50MB resource pack on a 1mbit/s connection
Alternatively we could keep the method as is and throw if it was not enabled by some feature system on init.
I don't think we have any feature that works like this right? If we are going to set a precedent for "resource intensive feature that must be opt-in in an elegant way" we need to think of a proper solution, one that would also work for future features like this.
An alternative for this specific PR could be to store the raw packet instead of the decoded command tree. Decode the packet only when sendAvailableCommands is called. Trade a bit of CPU time (only when this feature is used) for a lower memory footprint. I'm kinda leaning towards this; I might do some testing to see how much this would reduce memory usage in comparison.
This would hold callbacks for packs that don't get a terminal client response for as long as the client is connected, right?
Do you think it's better to add a timeout to this? What if the client is just really slow though, downloading a 50MB resource pack on a 1mbit/s connection
Yea I'm not sure, one of the maintainers might have an idea
Alternatively we could keep the method as is and throw if it was not enabled by some feature system on init.
I don't think we have any feature that works like this right? If we are going to set a precedent for "resource intensive feature that must be opt-in in an elegant way" we need to think of a proper solution, one that would also work for future features like this.
Sure, though we also should think about the users of this API and if it is hidden behind some 'obscure' mechanism they might not find it
Yea I'm not sure, one of the maintainers might have an idea
Are clients able to trigger this flow, of adding a resource pack & thus registering a callback? I don't think so but I might be wrong.
If we can only trigger a callback being registered through a plugin, I don't think this is really a cause for concern. The lifetime of the map (and its entries if the client never responds) is only as long as the client is connected, worst case we're storing a couple stale entries but that buys us having compatibility with slow clients.
Sure, though we also should think about the users of this API and if it is hidden behind some 'obscure' mechanism they might not find it
I mean, if we don't want to hide this API, we could keep Player#sendAvailableCommands and throw with an exception saying You must call ProxyInitializeEvent#enableFeature(FeatureFlags.SEND_AVAILABLE_COMMANDS) to make use of this feature!
This should never really produce an exception at runtime, because plugin developers will (should) catch this during development.
Alternatively we could log an error or warning and have it behave like a NOP instead of throwing.
An alternative for this specific PR could be to store the raw packet instead of the decoded command tree. Decode the packet only when sendAvailableCommands is called. Trade a bit of CPU time (only when this feature is used) for a lower memory footprint. I'm kinda leaning towards this; I might do some testing to see how much this would reduce memory usage in comparison.
This is really cursed. AvailableCommandsPacket needs to store a byte[] encoded and perform encoding during the constructor, decoding during the getRootNode getter. Reading/writing the bytes into this encoded field during encode() and decode(). IMO this isn't worth it, it deviates from how other packets do things so much, but it will reduce the memory footprint by a lot. Curious what other maintainers think of this approach.
Sure, though we also should think about the users of this API and if it is hidden behind some 'obscure' mechanism they might not find it
I mean, if we don't want to hide this API, we could keep
Player#sendAvailableCommandsand throw with an exception sayingYou must call ProxyInitializeEvent#enableFeature(FeatureFlags.SEND_AVAILABLE_COMMANDS) to make use of this feature!This should never really produce an exception at runtime, because plugin developers will (should) catch this during development.
Well this is what I meant.
Alternatively we could log an error or warning and have it behave like a NOP instead of throwing.
Well I think this could be annoying, and idk but I see no benefit with this option
I agree, worth considering if this is ever extracted into a utility class, for other callers that need non-secure random UUIDs.
- Fix
PluginMessageEventinInitialConnectSessionHandlerso client -> backend plugin messages usesource = playerandtarget = serverConn, matchingClientPlaySessionHandlerandClientConfigSessionHandler. - The handler previously used the backend -> client argument order from
BackendPlaySessionHandler(introduced in #774), so client messages were exposed to plugins withServerConnectionas the source andPlayeras the target.
Fixes the values being reported on CorruptedFrameException when velocity.packet-decode-logging is true.
Likely copy/paste mistakes.
Expected Behavior
Expected Behavior
Pretty simple: instant respawn should successfully respawn the player without desync issues.
Actual Behavior
Bugged Behavior
Players who die and respawn from instant respawn become desynced at some point related to velocity (as there is no Minecraft bug related to the same issue).
Desynced players are invisible to all other players, cannot be hit or interacted with, however they can still interact with other players and the environment.
Desynced players ARE able to relog to become visible again.
Steps to Reproduce
Steps to Reproduce
- Set up velocity proxy and backend server ss per details below.
- Enable the instant respawn gamerule on the backend server
- Log in (at least) two clients
- Have one client die and be respawned
~ You should see the issue now
Plugin List
Velocity Plugins
- Velocity
- Luck Perms
- MCKotlin-Velocity
- VLobby
- Simple Voice Chat
Fabric Mods
- Fabric API
- Ferrite Core
- VMP Fabric
-...
Just tested without VMP installed on the backend and the issue was solved, indicating that VMP is the problem.
I will be closing the issue shortly, once I can put in the report on their GitHub page.
Sorry for the inconvenience and for bloating up the issues page.
also experiencing this
what hosting service do you use? I use hetzner
also experiencing this what hosting service do you use? I use hetzner
We were using Hetzner at the time, we swapped but also ended up finding a different issue from another plugin which was causing disconnects too. Now that we removed that plugin and swapped away from Hetzner we have barely experienced any issues.
This has been fixed by the referenced PR
As pointed out in https://github.com/PaperMC/Velocity/pull/1775#issuecomment-4273723975, when adding new translation keys to messages.properties, these keys are not populated into the translation files of existing setups. I think a full migration system, as suggested by the comment, for this would be overkill, we're rarely adding translations anyways, so I went with a different approach.
This pr loads the default messages.properties translations that are bundled with velocity as a "patch" on top of any existing translation files. This will prevent any missing translations from showing up as their key since the default english translations will always be available.
For a fork of Velocity, we have added a basic migration system for messages.properties.
This would take it one step further than this PR.
The tail of a messages.properties file that has been migrated by the above class looks like:
# Messages below have been added by a migration of this file at 2026-05-09 12:27:44 UTC.
velocity.command.heapdump-created=<green>Heap dump saved to <arg:0>
velocity.command.heapdump-failed=<red>Failed to write heap dump, see server log for details.
# Messages below have been added by a migration of this file at 2026-05-22 14:26:58 UTC.
velocity.command.version-offer-copy-version=<white>Click to copy version to clipboard
velocity.error.plugin-message-overflow=<red>You sent too many plugin messages before completing the connection.
Feel free to take inspiration or copy (it's licensed under...
I understand the concern that this might cause confusion for people, but I'm also not entirely happy with your approach. In the past, if users wanted to force velocity to only use a specific language, we would recommend them to delete all other translation files and rename the target translation file to messages.properties. This is something that's lost with your linked approach. So I think at most those migrations should only apply to the default messages.properties and ignore any missing keys in other translation files (or even entirely missing translation files). Curious what maintainers think about this though, before I make any changes.
IMO the default locale should try to use the system default similar to how I did it here: https://github.com/Timongcraft/TgcTranslations/blob/master/src/main/java/de/timongcraft/tgctranslations/TranslationManager.java#L240-L256
Then I don't really see the need to force a specific language as the default can be overridden via system property and thus we could migrate translations.
I agree on both comments, just for clarification: the referenced fork did away with all language .properties files and only kept the one messages.properties with english messages (in favor of maintainability of the fork itself). Ideally, if we go for an actual migration approach, it would take all of this into account of course.
For the migration we can try to keep the ordering of the properties keys (which is hard because java's properties implementation is backed by an unordered map) or just add the missing keys to the bottom (possibly with a comment like my example)
override online mode?
Looking at the src the client now uses the onlineMode boolean where it before set the Connection#isEncrypted to true when receiving the EncryptionRequestPacket (velocity) packet.
I haven't done any testing, but the old 26.1 client should only return true for Connection#isEncrypted when InitialLoginSessionHandler#generateEncryptionRequest() was called in velocity. If this indeed a drop-in-refactor on the client we should probably set this value to true iff InitialLoginSessionHandler#generateEncryptionRequest() is called
Expected Behavior
Hi, why "dev/3.0.0" is main branch, when it will be switched to dev/4 or whatever, where do you track releases as if I wish to checkout to previous release?
Actual Behavior
na
Steps to Reproduce
na
Plugin List
na
Velocity Version
na
Additional Information
No response
it would be switched to dev/4 at the point at which we moved to 4.x; We do not track releases, releases do not exist on github (you could however crossref the hash from the download system over to git)
Hi! This PR adds two cli options which allow loading additional plugin jars and directories in addition to the default plugins folder.
I decided to keep the data directory fixed rather than tying it to the plugin jar location. Paper takes the same approach, and
since this option is likely to be used together with container volumes, which are often mounted readonly, keeping mutable data separate from the plugin binaries seems like the safer choice.
For this reason, loadPlugins still keeps the directory argument so it can be used for the data directory.
4.2.13.Final fixes 13 CVEs, one of which affects epoll.
4.2.11.Final, 4.2.12.Final, and 4.2.14.Final are bug-fix releases. There are no major/breaking changes.
I have tested this change in production and did not encounter any issues.
CVE-2026-42577 does not affect Velocity, we don't make use of ALLOW_HALF_CLOSURE. Still, a netty bump would be very welcome ofc
[...] container volumes, which are often mounted readonly [...]
How will this work if #1736 gets merged? Mounting a read-only volume for plugin jars would break the plugin update feature this PR is attempting to implement.
HeaderAndFooterPacket has a similar situation, where it's never expected to be decoded:
public HeaderAndFooterPacket() {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException("Decode is not implemented");
}
I'll update this PR to match this convention.
Right now this kinda a half-implemented feature. We can send player list header/footer Components to the player from proxy plugins, and we can also get the ones last sent by the proxy through plugins, but this is misleading. It's not the current header/footer that the client is displaying, as a backend may have overridden these.
Would it be worth it to introduce an event for this as well? One that would fire both when the backend sends this packet, and also when a plugin sets these manually with ConnectedPlayer#sendPlayerListHeaderAndFooter, allowing the Components to be manipulated?
IMO it is ok as is but I think this could be nice especially with an event to block backend changes.
But currently it is not expected to carry over the backends header and footer on switch which could be confusing for existing plugins. Holding the backends state only for the server connection could be an ugly solution to that.
Seems like 1.8 also already encoded it as component, but I didn't test it.
Small nitpicks:
I believe we don't need the JD for internal silent methods as they should be self explanatory, same with the // forward comment and well the comment above that too.
The readonly thing here is only about the extra plugin paths from the new CLI options (immutable jars, e.g. mounted from a container image). #1736's update folder lives under the normal plugins/ dir, which stays writable like it is now.
So you'd just keep updatable plugins in plugins/ and put the immutable ones behind the readonly mounts. The readonly mount never touches the update folder, so nothing breaks!
~This has an added bonus (or regression?) of keeping a client's player list header/footer when they switch servers now. ConfigSessionHandler line 247 currently sends these values right after switching to PLAY.~
So I was wrong: currently, the playerlist header & footer do not survive a server switch. Looks like this is by design, as we do re-send the header/footer after server switch (as per the PR description), but before that, we clear the header/footer (see ClientPlaySessionHandler#doSwitch, line 606.
This means that right now (before and after this PR), eventhough playerListHeader and playerListFooter are stored in ConnectedPlayer, they are reset on server switch. This PR doesn't change this, it only ensures the stored values are now up to date with what the client actually sees (if the backend changes it)
Apart from an event (should we add this?), this PR should be good to go.
I don't this Velocity should care how the backend server processes the dimension ID. In that sense, I think it's better to just forward whatever GTNH attaches to/expects from a login packet (see GTNewHorizons/Hodgepodge#341). This is the patch I use:
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java
@@ -53,6 +53,8 @@ public class JoinGamePacket implements MinecraftPacket {
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
private boolean enforcesSecureChat; // 1.20.5+
+ private int dimensionId; // GTNH
+ private boolean extraDimensionId;
public int getEntityId() {
return entityId;
@@ -259,6 +261,10 @@ public class JoinGamePacket implements MinecraftPacket {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.showRespawnScreen = buf.readBoolean();
}
+ if (buf.readableBytes() >=```...
I don't this Velocity should care how the backend server processes the dimension ID. In that sense, I think it's better to just forward whatever GTNH attaches to/expects from a login packet (see GTNewHorizons/Hodgepodge#341). This is the patch I use:
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/JoinGamePacket.java
@@ -53,6 +53,8 @@ public class JoinGamePacket implements MinecraftPacket {
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
private boolean enforcesSecureChat; // 1.20.5+
+ private int dimensionId; // GTNH
+ private boolean extraDimensionId;
public int getEntityId() {
return entityId;
@@ -259,6 +261,10 @@ public class JoinGamePacket implements MinecraftPacket {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.showRespawnScreen = buf.readBoolean();
}
+ if (buf.readableBytes() >=```...
its more workaround then fix. Root cause is still unknown.
Allocating and instanly releasing bytebuf should not "leak" memory.
Also it seems these attacks are not affecting bungeecord instances at all, while it does not have any limits put here and there and still has 8MB limit for decompression buffer.
So i suspect velocity is doing something what cause it. How velocity handles compression/decompression and/or by using adaptive allocator (bungee still use pooled)
Nobody has provided any useful information on this, hence why a lot of it is all speculation and targetting specific identified issues. The pooled allocator is to my understanding more conservative about its allocations and so is potentially more risk-free here, might be worth switching back, ideally somebody would be able to test this (and either find a mitigation or report upstream)
I was able to recreate the attack with a vibe coded mod producing malicious discardedpayload and unknown packets. With my setup I was only able to OOM a proxy running on 256-512MB of ram, but I was also running on a decade old CPU, so that might be part of the problem.
The pooled allocator is to my understanding more conservative about its allocations and so is potentially more risk-free here
VeloFlame (fork of velocity ctd) is using pooled allocated and seems also was affected.
Pooled can also have fragmentation issues when allocating large buffers https://github.com/netty/netty/issues/16827, it seems similar to what these attacks doing, but not exactly. His fragmentation issue is caused by differrent lifetime of allocated bytebuf (store, proccess, release at some point later at random order, if i understand correctly) while velocity is releasing buffers immediately after allocating so it should not suffer from fragmentation, unless allocated buffer for compression somehow cause fragmentation.
I mean, plugin messages are a particular concern there given the ByteBuf they hold and the async nature of the event means that it's not immediatly released, it has to be processed elsewhere first
and the async nature of the event means that it's not immediatly released isnt it require some plugin what explicity says to process that event async?
I do not familiar with velocity's event system, but i recall all events are executed at netty threads unless plugin requests async.
That could explain it. OOM != memory leak by default. We could just be holding onto too many buffers/decompressed PMs.
In that case we may consider only reading the incoming packets when there are not too many PMs in limbo (due to events). This could explain why I was only able to recreate the issue with a small amount of memory - the network that's experiencing this has many plugins that might be doing work on plugin message events. An attacker could target specific channels that a specific plugin would need to do significant work for in their event handler.
If it is a root cause, then i think i wrote all exploits in the first post after looking at code for 10 minutes.
https://github.com/PaperMC/Velocity/issues/1742#issuecomment-4079209228
probably some plugin message packets + plugin what process PluginMessageEvent async
probably a root cause of this "decompression" attack, if async events theory is right
or abusing loginPluginMessages queue.
fixed by #1800
do not have back pressure handling
potential attack vector for some setup configurations
I was able to recreate-ish this attack from backend -> proxy by sending 50 1MB plugin messages per tick (1GB of plugin messages per second). Assuming this compresses well enough for a client to perform the same before the various commits attempting to fix this, this might have been possible by a client.
Same behavior with @Subscribe(async = true) (default) and @Subscribe(async = false).
Interestingly it didn't OOM the proxy, rather it killed the client connection with an OOM exception (differs from what we were seeing earlier):
[20:53:05 ERROR]: [server connection] wouterg_ -> paperqueue: exception encountered in com.velocitypowered.proxy.connection.backend.BackendPlaySessionHandler@27e08e88
java.lang.OutOfMemoryError: Java heap space
Other than sending the PM packets from the backend instead of the client this time, the largest difference is that I now have a plugin that's actually listening to this specific channel. This was not the case with my previous vibecoded mod tests...
I was able to recreate-ish this attack from backend -> proxy by sending 50 1MB plugin messages per tick (1GB of plugin messages per second)
How do you send 1MB? Isnt it limited to (65kb * 4 ) + 32kb?
My threory is that netty can decode multiple packets in on read() so i think potential issue is here.
Due to thenAcceptAsync it always do context switch even if it same thread ( i think). So in theory it could allocate too much these arrays if it will read too much packets in single call, but with default PM size limit(292kb) it need to decode 34k packets in single read() call to crash 10gb instance and https://github.com/PaperMC/Velocity/commit/0219993c8a9adc4bbfe97628a7c0dd40174927f0 is probably effective fix of it.
But initial report of 8MB of decompressed buffers does not allign with that threory unless you disabled PM size limits.
for that type of use case the decompressed bytes per second seems to be a valid solution
probably doing same https://github.com/PaperMC/Velocity/blob/3b142f30998ac3a1a36b1e636d67eae5ea8378b6/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java#L148 for ClientPlaySessionHandler will completly fix issue and decompressed bytes per second will not needed at all
Expected Behavior
n/a
Actual Behavior
# Specify a read timeout for connections here. The default is 30 seconds.
read-timeout = 30000
The default read-timeout of 30 seconds matches the (modern) client value exactly. When a backend server dies/stalls completely (simulate with kill -STOP <pid>), the client disconnects itself after 30 seconds. Instead of Velocity considering this a read timeout (and sending them to a fallback server, or gracefully disconnecting the client), the client hard disconnects with "Timed out".
Setting the default read-timeout to some delta below the client's timeout would be the solution here, e.g. 25000 (25 seconds). I'd argue this could be even lower, but 25 seconds seems like a reasonable value, keeping the client's timeout in mind.
One issue here is that a PR updating the default to 25 seconds won't fix existing setups. Could this warrant a migration?
Steps to Reproduce
n/a
Plugin List
n/a
Velocity Version
latest
Addi...
Note that Velocity itself sending any kind of packet to the client after the backend being killed, makes it respect Velocity's read-timeout of 30 seconds again. The client sees this packet from Velocity and doesn't timeout anymore, and Velocity will now see this 30 second timeout and handle the fallback or graceful disconnect itself.
To recreate (failing state, client times out before proxy can do something):
kill -9 <backend pid>- ensure Velocity doesn't send any packets (no commands), observe vanilla gameplay breaking (blocks not updating, obviously because the backend is dead)
- client kills connection with "Timed out"
To recreate currently working state:
kill -9 <backend pid>- make Velocity send you a packet, e.g. execute
/velocity info - observe Velocity handling read-timeout correctly
netty = "4.2.15.Final"
Netty 4.2.15.Final has been released with lots of CVE fixes once again.
Fixes #1819
Reduces the default read-timeout to 25 seconds, avoiding the client disconnecting itself (tested & verified), and adds a config migration for an unchanged old value.
Non-Unix systems might support unix domain sockets as well (https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/)
This PR doesn't support them, however.
When a player joins via a forced host whose first backend accepts the TCP connection but then never completes login (e.g. a firewall/anti-DDoS front that answers pings but black-holes logins), they get disconnected with "An internal error occurred in your connection" instead of being moved to the next server in the fallback chain. This issue surfaced on an issue on Velocity-CTD: GemstoneGG#938. I was able to recreate this failure state (simulating a "firewall" that answers pings but black-holes logins) with a Python script that does precisely this.
While the player idles in config waiting for the backend, two ReadTimeoutHandlers with the same duration race:
- backend connection timeout -> drives the fallback chain (correct)
- player connection timeout -> hard-disconnects (wrong)
The player-side handler is created first, so it fires first and wins -> disconnect instead of failover.
This is also why just lowering read-timeout didn't help (see referenced i...
All 5 other subpackages of com.velocitypowered.api.event have the package-info.java file with very similar wording. This PR cleans this up by adding this to the remaining 3 subpackages.
@WouterGritter If there's anything else I need to do, please feel free to tell me!
@WouterGritter If there's anything else I need to do, please feel free to tell me!
I'm not a maintainer with any rights on this repository, just a contributor sharing their thoughts :)
This currently crashes Fabric clients when sendAvailableCommands() is sent during non-PLAY phases: https://github.com/FabricMC/fabric-api/issues/5423 / https://github.com/GemstoneGG/Velocity-CTD/issues/959
This needs to be fixed with a simple check in sendAvailableCommands(), possibly returning a CompletableFuture<Boolean> instead?
I'd love to hear what else needs to be done to get this PR in a mergeable state!
A plugin providing a null reason to Player#disconnect will get a vague NullPointerException pointing towards Velocity's/Adventure's internals, even though the problem is a proxy plugin providing a null reason.
This PR makes this non-null contract explicit and throws with a clear NPE.
Exception:
[07:17:53 INFO]: [connected player] xDevanger (/REDACTED): kicked from server lobby: ᴍᴄꜰᴜɴɴʏ.ᴘʟ » Serwer ʟᴏʙʙʏ jest w trakcie restartu...
[07:17:53 INFO]: [server connection] xDevanger -> lobby has disconnected
[07:17:53 WARN] [io.netty.util.concurrent.AbstractEventExecutor]: A task raised an exception. Task: com.velocitypowered.proxy.connection.client.ConnectedPlayer$$Lambda/0x0000000069e10a30@61031780
java.lang.NullPointerException: like
at java.base/java.util.Objects.requireNonNull(Objects.java:246) ~[?:?]
at net.kyori.adventure.text.Component.append(Component.java:2112) ~[velocity-ctd-fatjar-3.5.0-SNAPSHOT-301.jar:3.5.0-SNAPSHOT-git-2878e6c7-```...
3b89289 Require non-null reason (#1823) - WouterGritter
bcf1bba Add missing package-info.javas for event... - WouterGritter
0cbe10e Bump netty from 4.2.10.Final to 4.2.15.F... - jonesdevelopment
team_color registration collides with color for 26.2 clients
In ArgumentPropertyRegistry, minecraft:team_color is registered at index 16, but minecraft:color is still mapped to 16 as well (its mapSet(MINECRAFT_1_19, 16) has no later override). Because ArgumentIdentifier applies each mapping to every protocol >= its version, both identifiers resolve to wire ID 16 for a 26.2 client:
writeIdentifierserializes bothcolorandteam_coloras 16readIdentifieriterates an unorderedHashMapand returns whichever of the two matches 16 first — nondeterministic
If team_color is just color renamed in 26.2 (keeping index 16), the fix is to retire color at 26.2 using the existing -1 shadow idiom (same as mob_effect/item_enchantment/entity_summon):
// minecraft:color renamed to minecraft:team_color in 26.2
empty(id("minecraft:color", mapSet(MINECRAFT_26_2, -1), mapSet(MINECRAFT_1_19, 16)));
...
empty(id("minecraft:team_color", ma_```...
Interestingly, mojang seems to be a "use the same session ID for everybody but regenerate it when the server is empty"
https://mcsrc.dev/1/26.2-rc-2/net/minecraft/server/network/ServerConnectionListener#L225
https://mcsrc.dev/1/26.2-rc-2/net/minecraft/server/network/ServerConnectionListener#L241
This is used purely for metrics on mojangs side, I guess the "good behavior" here would probably be to reproduce that logic
@electronicboy beat me by 11 minutes, but I had also dug into the duplicate registration --
tl;dr it works as-is but it's a bit fragile, and there's a cleaner way to express it.
It's not actually problematic on the wire. minecraft:color is mapSet(MINECRAFT_1_19, 16), and ArgumentIdentifier back-fills that id to every version >= 1.19, so id 16 already covers 26.2. The new team_color entry also resolves to 16 on 26.2, so both keys end up in byIdentifier and readIdentifier returns whichever the HashMap iteration happens to hit first (can differ from JVM to JVM, which isn't great...). Since both use EMPTY (no payload) and both map to 16 at 26.2, the re-encoded output is identical either way, so it's correct, but the lookup is non-deterministic, which I don't love.
It looks like this is a rename rather than an insertion too, so Mojang just renamed slot 16 color -> team_color in 26.2.
For 1.19+ the string identifier is purely cosmetic. writeIdentifier/...
Yea, I mean, on the wire it doesn't matter but can be an odd moment when debugging it, I'd rather just keep it as two seperate enteries for the sake of not having to worry about that, being able to read back the data when debugging is nice rather than tripping myself up because the id is unexpected
Yea, I mean, on the wire it doesn't matter but can be an odd moment when debugging it, I'd rather just keep it as two seperate enteries for the sake of not having to worry about that, being able to read back the data when debugging is nice rather than tripping myself up because the id is unexpected
I agree! Retiring the 1.19 - 26.1 registration in favor of this rename for 26.2+ is the canonical solution, ignore my comment :)
I'm wondering what Mojang sees as a "session" in this case. Does a new session occur the moment the last player online disconnects (or the moment the first player connects, going from 0 -> 1 players, effectively the same), or does a new session only occur when the server was "paused" due to it being empty (correct me if I'm wrong, but a vanilla server should pause processing ticks after no players have been online for x seconds (30?))
If it's the latter that's harder to match in Velocity... Though regardless, if the client expects the session ID to be the same for everyone connected at the same time, it's most likely better to match this behavior. Easiest is to just generate a session ID on boot.
(or uh, grab the session ID from the backend? is that possible here? why do we need to generate our own in velocity land?)
session ID is sent from the proxy long before we have a backend, so that's not an option;
And I guess their modal is basically single player servers where people join in the eveing, have a "play session" together, and leave, and come back in a day or a few hours or whatever. For a larger server which always had players on that would always be shared
Then again, doesn't really translate to proxies, do we just sent 0,0? I'll ask mojang...
I have a hunch the session ID will be used in the new friends feature that got dropped from the 26.2 release. I'd guess you would be able to easily add players that are on the same server as you. As this will be done through Mojang's servers, the client can just report the session UUID it got from the connected server, and Mojang can match players against the same session UUID and consider them in the same server.
This would break with the current random UUID per connection approach. Though, it might also be useful for plugins to be able to control this. I could imagine a feature where certain players want to turn this off (staff? content people?). A plugin would be able to check a permission, and override the session UUID with a random one. Though that's all assuming it will be used for the dropped social/friends feature.
0a090c8 fix(config): keep server/forced-host def... - electronicboy
Summary
Finishes the Configurate migration: the proxy's configuration moves from velocity.toml (night-config) to velocity.yml (Configurate 4 / YAML). This is a deliberately near-1:1 "yaml-ified" port — same keys, sections and documentation — with richer configuration left as a follow-up. Existing velocity.toml installs are migrated automatically on first start.
Draft / not for immediate merge. Opening this for documentation and review. It builds on the rebased
dev/3-configuratework.
What it does
ConfigurationLoader.loadConfiguration()is the new entry point, wired intoVelocityServerstartup and reload. It resolvesvelocity.yml, migrating a legacyvelocity.tomlor writing the documented default on first start.- Model mapping via Configurate
ObjectMapperover@ConfigSerializable VelocityConfiguration, withNamingSchemes.LOWER_CASE_DASHEDsocamelCasefields map tolower-case-dashedkeys.@Settingcovers the handful the sche...
As the title says.
As a bonus, we get rid of the ugly FQDN instead of an import for Component in ProxyConfig and VelocityConfig, and ProxyConfig#getMotd's javadoc is updated to correctly reflect where the MOTD is actually being used.
I added wildcard support to [forced-hosts], resolving #1587. Unit tests pass and checkstyle passes. BUT I have not tested this against a running proxy, so I'd appreciate feedback and help testing this out, especially from those on #1587 who submitted the issue.
What this does
Consider the example from #1587
[forced-hosts]
# Soul Realms
"play.soulrealms.net" = [ "soulrealms-smp-1" ]
"yt.soulrealms.net" = [ "soulrealms-smp-1" ]
"ip.soulrealms.net" = [ "soulrealms-smp-1" ]
"soulrealms.net" = [ "soulrealms-smp-1" ]
Now this can be changed to:
[forced-hosts]
# Soul Realms
"*.soulrealms.net" = [ "soulrealms-smp-1" ]
"soulrealms.net" = [ "soulrealms-smp-1" ]
How matching works
I added AddressUtil.isHostMatchingPattern(pattern, host). Patterns are matched on each label where * matches exactly one label. To be matched, patterns must have the same number of labels as the host. Matching is case insensiti...
Hi everyone, I'm drafting a PR to get this done: https://github.com/PaperMC/Velocity/pull/1826. If you could give it a test, that would be a huge help. To try it out:
- Build the proxy yourself from my repo and branch
feature/wildcard-forced-hostsOR you can use my development build velocity-proxy-3.5.0-UNOFFICIAL-wildcard-forced-host-testing-build.zip (Java 21) from commit 415d127 if it's easier. - Add a wildcard entry to
[forced-hosts]in yourvelocity.tomllike @JosTheDude described. - Connect using a few different subdomains (
play.example.com,yt.example.com, etc.) and make sure you land on the right server.
Let me know if the matching is working correctly (espe...
These should be using fictious example domains (i.e. example.com), not real domains.
Expected Behavior
Usernames accepted during offline-mode login should be validated against the vanilla character set (alphanumerics and underscore, length 1–16) to prevent log injection and identity confusion.
Actual Behavior
ServerLoginPacket.java (line 84–88) only checks for emptiness:
username = ProtocolUtils.readString(buf, 16);
if (username.isEmpty()) {
throw EMPTY_USERNAME;
}
readString(buf, 16) permits any UTF-8 content up to 16 code units. In offline mode, this name becomes the profile name and the offline UUID seed, and is forwarded to the backend as-is. Spaces, control characters (\n, \r), section signs (§), and Unicode homoglyphs are all accepted.
This only affects offline-mode or forwarding=none/legacy setups. Online-mode names come from Mojang and are already safe, and modern forwarding wraps the name in an HMAC.
Steps to Reproduce
- Run Velocity with
online-mode=false(orforwarding-mode=none). - Connect with a...
Expected Behavior
Malformed or too-short UDP datagrams sent to the GS4 query port should be silently dropped.
Actual Behavior
GameSpyQueryHandler.channelRead0 (around line 107–119) reads magic bytes, type, and sessionId via readUnsignedByte()/readByte()/readInt() without first checking readableBytes(). A datagram shorter than 7 bytes throws IndexOutOfBoundsException on every packet, caught and logged at WARNING level:
Error whilst handling query packet from <sender>
The challenge-token validation for 0x00 stat responses (line 139–143) correctly prevents reflection of the larger response — a spoofed source can't obtain a valid token. So this is just log noise and minor CPU overhead, not a reflection amplification issue.
Steps to Reproduce
- Enable
enable-query=trueinvelocity.toml. - Send a short UDP datagram to the query port:
echo -n "x" | nc -u <proxy-ip> <query-port> - Each datagram produces a WARNING-level...
The code references are accurate: ServerLoginPacket.decode() only checks for emptiness, and in offline mode the raw name flows into GameProfile.forOfflinePlayer() and the offline UUID seed with no charset validation. So the underlying observation is correct.
That said, I don't think the proposed fix is the right approach, and most of the listed harms don't really hold up:
- Log injection is the one genuinely valid concern. A username with
\r\ngets embedded unescaped inConnectedPlayer.toString()and logged atINFO ("{} has connected"), so it can forge log lines. This is worth fixing. - Identity confusion via homoglyphs isn't a real vulnerability here. The offline UUID is a deterministic hash of the exact name bytes, so distinct strings produce distinct UUIDs with no collision. Look-alike names are a social issue, not a proxy one.
- Forwarding the name as-is is by design. Modern forwarding HMAC-wraps the name, and for none/legacy the backend does its own validatio...
Every comment distinguishes different behavior. Placing each one in their own function is the canonical way to go about this. A bonus: you can drop the comment in favor of a descriptive method name
Three nits:
1 - This is a pretty large method chain that's duplicated twice, it would probably make sense to extract this out into its own method.
2 - the "or default" value is always computed, even in the case where the exact virtualHostStr mapping is present. And the .orElse(Collections.emptyList()) also always returns an empty list. Fortunately this doesn't comput or allocate anything, but this means that all three cases - exact match, pattern match, empty list - are always computed, even when an exact match could abort the latter two, which is a smell
3 - Essentially this is a giant double if-else statement:
if (/* exact forced host defined */) {
serversToTry = /* exact forced host */;
} else if (/* isHostMatchingPattern matches */) {
serversToTry = /* isHostMatchingPattern result */;
} else {
serversToTry = Collections.emptyList();
}
However this is disguised in getOrDefault (first two if and if else) and .orElse(Collections.emptyList...
patternDomains.length > i here is odd, I have never seen it written this way, I'd change this to i < patternDomains.length
Instead of naming an actual domain here, I would rather use example.com or a similar domain name
As this is a utility method that may be used elsewhere and both parameters are nullable, it's good practice to annotate both with @Nullable
Requested Feature
In the proxy module and all other modules that are licensed under the GNU GPL, I'd like to switch from the full license header (as seen in HEADER.txt) to a modern SPDX declaration.
In every file, the header would then change to:
/**
* SPDX-FileCopyrightText: Copyright (C) $YEAR Velocity Contributors
* SPDX-License-Identifier: GPL-3.0-only
*/
For more context, see https://spdx.dev/learn/handling-license-info/
The api module is so small (and licensed differently), it might not even need it, but we may consider also using the SPDX declaration there.
This could also be a good oppertunity to bump the copyright year to 2018-2026 on all files, if a substantial change has been made to the file recently (this can probably be automated).
Why is this needed?
n/a
Alternative Solutions
n/a
Additional Information
No response
Good call, I was thinking about this too. Where do you think the right place to hold this new method could be? I would have put it in ConnectedPlayer, but this same pattern is in ServerListPingHandler as well, which are two separate things from each other. Thoughts?
Another nit: try to match the test class name with the class it's actually testing. In this case it would be AddressUtil. If this test class already exists and/or these test methods would seem out of place, consider adding a nested class, e.g.
class AddressUtilTest {
class HostMatchingPatternTest {
/* isHostMatchingPattern test methods */
}
}
Some people are against this, but when doing this, I would also consider statically importing the AddressUtil.isHostMatchingPattern method.
AddressUtil seems like the right place, as you have already introduced the new isHostMatchingPattern there.
Consider making it return an Optional<String>, allowing the caller to change an empty result to an empty list (ConnectedPlayer) or server.getConfiguration().getAttemptConnectionOrder() (ServerListPingHandler). (with orElseGet() to avoid computing when the optional is present)
See 54d6816. I've implemented it using Optional<String> but not 2x .orElseGet() since I find using if/else and a for loop a bit more readable, but let me know if you think otherwise
.orElseGet(server.getConfiguration()::getAttemptConnectionOrder);
This would most likely make #1730 redundant.
Introduces a custom spotless FormatterFunc that enforces blank lines after type declarations, and fixes them.
This would likely make #1730 redundant. However, this fix is more flexible because it adds pattern matching and this fix doesn't need a config migration. That said, #1730 does consider SRV records, which I didn't fully consider yet. I can integrate this though.
Log ConnectException as WARN instead of ERROR to avoid printing full stack traces when a backend server is simply offline.
This seems should migrate to
.editOptions(builder -> builder.value(JSONOptions.EMIT_RGB, false))
(form Paper)
Expected Behavior
If I join a server that rejects my connection during the configuration phase, I expect to get kicked from that backend server as expected, velocity then would choose a new server and I get moved to a new server without problems.
Actual Behavior
If you get kicked during configuration phase, the client state gets bugged and the client thinks its receiving registry entries a second time, which Mojang doesn't allow, so the player gets kicked for an invalid network state.
I have also confirmed this on a vanilla client!
The following network crash log is generated by the client:
disconnect-2026-06-26_02.56.46-client.txt_
Steps to Reproduce
Option 1:
Run my example repo: https://github.com/davidmayr/PaperMC-Velocity-Issue-Protocol-Error-During-Config
- Contains a plugin that kicks during configuration
- Contains a docker compose file for running two...
We can't send a finish_configuration because the client isn't garaunteed to be configured, which looks like what probably happened with your client? it was sent the finish_configuration packet and blew up
Here are some screenshots of the packets that get sent to the client using a proxy in front of velocity to capture packet flow. The packet names are from minestom as this proxy layer that I just added in front of Velocity was made by someone in the minestom community.
<img width="680" height="692" alt="Image" src="https://github.com/user-attachments/assets/1bec138a-f787-404c-adbe-db5731f29db9" />
client isn't garaunteed to be configured, which looks like what probably happened with your client?
The issue isn't that the client wasn't configured; it's that the client was configured twice, and the registry data collided. I can understand that this might be a difficult fix.
I'm unaware if the client actually needs all registries to be configured, but couldn't Velocity track somehow if the client would be ready to go and do the following:
- If the client is ready to leave, kick them out of configuration and immediately kick them back in, then send them the stuff from the b...
I don't really want to tie proxy state expectations too much towards tracking the clients exact current state, etc.
Sending a transfer packet was considered for dealing with modded support but it would involve a lot of risky changes, and breaks the reliability aspect, we cannot know if your ingress address is stable enough to allow us to reuse it to connect, especially as what the client reports isn't garaunteed, i.e. if you're using SRV records to RR the DNS it will break as we get the resolved address
I also don't think it's a good idea to keep track of the full client state. But tbh, you don't even need to keep track of everything. Once a packet for e.g. registry X flows through, count that registry as sent. Not optimal, but fine enough I guess
If the server has sent all at least one packet for each registry that the client demands, count the configuration phase as being able to finish early. If it can't be finished early and there are no registry or tag packets, allow the player to continue. If there are registry packets but not all of them, abort mission and kick player. Obviously that doesn't really work on modded, but in the worst case It's probably not any worse than what we have right now.
The only state that would require is a list of Keys of sent registries and a static hardcoded list of registries the client requires for its version to compare against.
The other option is really just to kick the player for the kick reason of the server. In any case, that's b...
Thinking about it, any registry packet being sent would be enough to try to terminate early. Once that happened, conflict likely occurs anyway, so it doesn't really matter if the client errors out because we leave the config phase with invalid state or if we let the client error out with duplicates. But it would at least improve the situation, whilst keeping it just as bad as it was before in any other case
@ChipWolf Please try checking the JDK version you're using! I encountered the same issue when running Paper 1.20.6 with JDK 25 behind a Velocity proxy. After switching to JDK 21, the problem was resolved.
Our server is also experiencing this. Has there any update on this matter?
@ChipWolf Please try checking the JDK version you're using! I encountered the same issue when running Paper 1.20.6 with JDK 25 behind a Velocity proxy. After switching to JDK 21, the problem was resolved.
This was maybe 4-5 years ago, I don't suppose I've seen this particular issue recently
Expected Behavior
A slow DNS lookup for one backend shouldn't delay connecting to other backends. Sending a player to a server whose hostname is fine (cached or fast to resolve) should be quick regardless of other lookups happening at the same time.
Actual Behavior
SeparatePoolInetNameResolver runs every backend hostname lookup on a single thread, with a 30s cache:
If a lookup is slow, every other uncached resolution queues behind it on that one thread until it returns. Cached names are fine (they skip the executor), but anything not currently cached has to wait. There's also no timeout on the lookup, so one hung resolve pins the resolver thread for the full JDK resolver duration.
We hit this on a fallback. A player gets kicked and is redirected to the fallback server (defined by hostname). The connect can't sta...
Fixes #1833.
SeparatePoolInetNameResolver runs every backend hostname lookup on a single thread (Executors.newSingleThreadExecutor). Because the lookups are blocking, one slow resolve serializes all the others behind it: an unrelated stalled DNS query delays connecting players to otherwise-healthy servers until it returns or the JDK resolver times out. We saw fallback connections to a hostname-defined server hang for 10–18s while the same name resolved instantly on another thread.
This swaps the single-thread executor for a small bounded pool (ThreadPoolExecutor, 0–8 threads, 60s keep-alive, SynchronousQueue), so a slow lookup only ties up one thread instead of blocking all resolution. Threads are created on demand and reaped when idle, so it costs nothing at rest.
Since we use java 21, aren't virtual threads a better fit for this?
InetAddress.getByName resolves in a native call, which pins the carrier on 21, so a stuck lookup still holds a real OS thread and virtual threads bring back the same serialization. A dedicated bounded pool stays predictable.
Mirrors how Paper does things. Allows the user to press up to get previously executed commands, now persisting between reboots. Very useful for (plugin) development.
Always liked this idea. Looks ready for merge!
I tried implementing the Forge side of modern forwarding, seems to be working.
https://github.com/XXMA16/SprintForward
Works with ProxyCompatibleForge as well. Only issue I've found so far is that when the legacy client connects to a modern server with ViaVersion, Via drops the login query packet since it assumes the client can't handle it. At some point I'd like to make a ViaVersion extension that gets around that, but in the meantime forced-hosts work pretty well.
As Velocity builds to target java 21, there's really no need to support legacy version parsing.
Runtime.version()contains the feature version and extras
Runtime.version().toString() will produce an output like 21.0.1+12-LTS, not the 21.0.1 that System.getProperty("java.version") provides.
I'd propose the following refactor:
metrics.addCustomChart(new DrilldownPie("java_version", () -> {
Runtime.Version version = Runtime.version();
return Map.of(
"Java " + version.feature(),
Map.of(System.getProperty("java.version"), 1));
}));
but it seems a bit odd to mix the old "api" (system property) and the new Runtime.Version API here. Thoughts?
Runtime.version() contains the feature version and extras
Mixing is fine I'd say
Better than the javaVersion helper method? I'm leaning more towards that approach (the current state of the PR)
Expected Behavior
no warn?
Actual Behavior
[16:09:49 WARN] [io.netty.util.ReferenceCountUtil]: Failed to release a message: AdaptivePoolingAllocator$AdaptiveByteBuf(freed)
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
Steps to Reproduce
idk
Plugin List
....[16:55:03 INFO]: Plugins: velocity, alert, cleanstaffchat, floodgate, kaurivpn, luckperms, mckotlin-velocity, nantibot, nlogin, nuvotifier, packetevents, pl-hide-pro, skinsrestorer, vlobby
Velocity Version
....[16:54:48 INFO]: Velocity 3.5.0-SNAPSHOT (git-a7581821-b605)
Additional Information
No response
You would need the full error, but given the nature of using highly invasive plugins in the proxy, we generally couldn't provide support here
1edab14 Store historical console commands in .co... - WouterGritter
Sorry.
io.netty.channel.socket.ChannelOutputShutdownException: Channel output shutdown
at io.netty.channel.AbstractChannel$AbstractUnsafe.shutdownOutput(AbstractChannel.java:520) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.AbstractChannel$AbstractUnsafe.handleWriteError(AbstractChannel.java:816) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:796) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:619) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.handle(AbstractEpollChannel.java:477) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.EpollIoHandler$DefaultEpollIoRegistration.handle(EpollIoHandler.java:349) ~[server.jar:3.5.0-SNA...
Expected Behavior
No warn?
Actual Behavior
io.netty.channel.socket.ChannelOutputShutdownException: Channel output shutdown
at io.netty.channel.AbstractChannel$AbstractUnsafe.shutdownOutput(AbstractChannel.java:520) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.AbstractChannel$AbstractUnsafe.handleWriteError(AbstractChannel.java:816) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.AbstractChannel$AbstractUnsafe.flush0(AbstractChannel.java:796) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:619) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.handle(AbstractEpollChannel.java:477) ~[server.jar:3.5.0-SNAPSHOT (git-a7581821-b605)]
at io.netty.channel.epoll.EpollIoHandler$DefaultEpollIoRegistration.handle(E...
You are, once again, using highly invasive plugins in Velocity's networking stack. We cannot provide support here; a general guess would be to update your plugins
The error is at the base of the project, and the plugins are to blame? Man, look at what this project has turned into.
Your error is occuring in the networking stack, in which you have plugins which are fairly well established to hijack into, many of which have consistently broken on version updates. If you can reproduce the issue without plugins we can look into it, but given the highly invasive nature of those plugins, especially to support offline mode, a setup we do not support, you are on your own
Requested Feature
Add a unique, stable connection identifier to the InboundConnection interface so plugins can reliably correlate the same TCP connection across PreLoginEvent, GameProfileRequestEvent, and LoginEvent.
Something like:
public interface InboundConnection {
// existing methods...
/**
* Returns a unique identifier for this connection, stable for the
* entire lifetime of the connection.
*/
UUID getConnectionId(); // or long
}
The identifier should be:
- Generated once per TCP connection (in
HandshakeSessionHandler/InitialInboundConnectionconstructor) - Available across all login-phase events:
PreLoginEvent,GameProfileRequestEvent, and ideally also fromLoginEvent(viaConnectedPlayeror a similar accessor) - Consistent even when the
InboundConnectionwrapper object changes internally
Why is this needed?
event.getConnection() returns a LoginInboundConnection that—while the same instanc...
MinecraftConnection would be the right class to hold the UUID here, as all [Velocity]InboundConnection implementations hold a reference to a shared reference to this class.
MinecraftConnectionwould be the right class to hold the UUID here, as all[Velocity]InboundConnectionimplementations hold a reference to a shared reference to this class.MinecraftConnection正是用来存储 UUID 的合适类,因为所有的[Velocity]InboundConnection实现都包含了对这个类的引用。
Thanks for the guidance! I'm working on this in my fork and should have a PR ready soon.
Add a stable connection identifier to MinecraftConnection
A unique connectionId (AtomicLong) is held in MinecraftConnection,
the shared underlying instance across all InboundConnection wrappers,
surfaced via InboundConnection.getConnectionId().
This allows plugins to reliably track the same TCP connection across
PreLoginEvent, GameProfileRequestEvent, and LoginEvent, even when
the InboundConnection wrapper object differs between events.
Tested with both online-mode and offline-mode connections:
- [x] Same connection across all login-phase events: same connId
- [x] Different players: different connId
- [x] Premium (Mojang-authenticated) connections: connId stable
You're using FQDN's in the code instead of importing, and I'd rather like to see the use of UUIDs instead of an incrementing ID here. This way the connection IDs are also unique across reboots (useful contract to have when exposing this to plugins)
UUIDs also generally have the advantage that they're more useful should plugins want to externalise that identity for other systems, especially in a network
I initially chose long (AtomicLong) thinking it could serve dual purpose as both a connection identifier and a rough connection counter for server operators who use such metrics. But your point about UUIDs being more useful for external systems and surviving reboots makes sense, I'm going to change it
@electronicboy Done, please review again
I think we should shift from a per-connection id to a session id: one UUID that's stable for the player's entire session on the proxy, and shared across the client<->proxy connection and every proxy<->backend connection they open. So I'd name it sessionId rather than connectionId.
Right now that shared/stable property doesn't actually hold. Since every MinecraftConnection generates its own UUID.randomUUID() in the constructor:
- each serverbound connection gets a different id, regenerated on every server switch, so it's neither stable nor shared with the client side
- outgoing server-list pings (
VelocityRegisteredServer) allocate an id they never use
It happens to work for the login-phase events only because the InboundConnection wrappers all delegate to the one clientbound MinecraftConnection, but that's incidental, not a contract we could promise to plugins.
What I'd propose:
@Nullable UUID sessionIdonMinecraftConnection, passed in via th...
Interestingly, we did use Netty's async DNS resolver here in the past: 6e7c0298de14c1af2ad5aa4161f6bde054f6bb74
But this seems to come with some downsides (as the javadoc comment on SeparatePoolInetNameResolver also suggests). Wanted to suggest this as an alternative but that seems like a no-go.
One nit:
When 8 concurrent DNS requests occur, we will now throw RejectedExecutionException on the next request (SynchronousQueue has no size).
Something like
ThreadPoolExecutor executor = new ThreadPoolExecutor(
MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), // unbounded -> never rejects
new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver #%d")
.setDaemon(true)
.build());
executor.allowCoreThreadTimeOut(true);
Wouldn't throw and has the same queueing behavior as before, just with multiple threads (corePoolSize must also be MAX_RESOLVE_THREADS, ot...
Hmmm… indeed, this is a scenario I didn't test before. I only tested multiple players joining the same backend server, but I didn't consider the case of switching between backend servers. I will test this scenario and make the necessary adjustments accordingly. Thanks for pointing this out!
Expected Behavior
Without this error, the player would have continued playing on the server without disconnecting from Velocity.
Actual Behavior
If the server opens a Dialog screen during the Configuration stage, sending the client's ServerboundCustomClickActionPacket packet causes the proxy to return an error:
[16:18:57 INFO]: [connected player] krytickYT (/0.0.0.0:0) has disconnected: An internal error occurred in your connection.
[16:18:57 ERROR]: [connected player] krytickYT (/0.0.0.0:0): exception encountered in com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler@34c000a7
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.util.internal.RefCnt.throwIllegalRefCountOnRelease(RefCnt.java:239) ~[velocity-3.5.0-SNAPSHOT-6```...
I also wrote a plugin for test
[13:42:36 INFO]: Booting up Velocity 3.5.0-SNAPSHOT (git-48e7519e)...
[13:42:36 INFO]: Connections will use NIO channels, Java compression, Java ciphers
[13:42:36 INFO]: Loading localizations...
[13:42:36 INFO]: Loading plugins...
[13:42:36 INFO]: Loaded plugin cidtest 3.5.0-SNAPSHOT by xfy2412
[13:42:36 INFO]: Loaded 2 plugins
[13:42:36 INFO] [cidtest]: CIDTest Loaded — Testing the stability of getSessionId()
[13:42:36 INFO]: Listening on /[0:0:0:0:0:0:0:0]:25565
[13:42:36 INFO]: Done (0.62s)!
[13:42:54 INFO] [cidtest]: [CIDTEST] PreLogin: xfy2102 connId=e1e309f8-e7c3-424c-8b02-56153af8633f hash=1834913037
[13:42:56 INFO] [cidtest]: [CIDTEST] GameProfileRequest: xfy2102 connId=e1e309f8-e7c3-424c-8b02-56153af8633f hash=1834913037
[13:42:56 INFO]: [connected player] xfy2102 (/192.168.110.1:4564) has connected
[13:42:56 INFO] [cidtest]: [CIDTEST] Login: xfy2102 connId=e1e309f8-e7c3-424c-8b02-56153af8633f uuid=2439bef3-b313-424b-a22c```...
Netty DNS just turned out to be fairly unreliable for us in the past, worked fine for 99% of people but the 1% who'd have the odd issues with it just wasn't really acceptable; Felt kinda sad because it was fairly nice when it wasn't broken, especially as it defaulted to google DNS or something rather than relying on hosting providers DNS servers
81a5817 Resolve backend DNS on a bounded thread... - carlodrift
it defaulted to google DNS or something rather than relying on hosting providers DNS servers
Yeah that sounds bad actually. If the hosting provider uses split-horizon DNS for backends or you do it manually with docker, you really need to respect the native DNS resolver
The session ID is ultimately coming from MinecraftConnection#getSessionId, which is nullable (pings). InboundConnections javadoc guarantees this returning never {@code null}, which is currently the case by circumstances, but a regression in the future might change this. I'd recommend wrapping this with requireNonNull(...) to surface a future regression early
I would like to see a sentence here mentioning this is not to be confused with Mojang's server-wide session ID that's been introduced in 26.2 (im still not entirely sure about the naming here, too)
You're kinda sharing implementation details at a point in the code where you don't necessarily know or control this. Since it's an internal method, I'd drop the javadoc
Maybe call it ”clientConnectionId” will be better?
My understanding is that the major ABI breakage has been resolved, I think that there might be a thing or two in the pipeline pending on people actually reporting them with valid usecases, but I think that adventure is as ready to go as it's going to get in the immediate future; Anything blocking this as-is?
Will try to merge tomorrow morning or so
I think it would be a good idea to add a validation check in case someone tries to add more than 2 lines
Since the migration will only happen once, it might be a good idea to migrate the use of \n or <newline>/<br> to a new line in the configuration
My plugin is planning to rely on this PR for development, but it has been stuck for four days already. I'd like to confirm whether this feature will be merged in the future, then I could continue developing my plugin (by sessionId or other way). If there are any new comment here, I will still keep updating
I agree, though it'll be best-effort and we probably dont want to match every case. Splitting on those 3 delimiters seems reasonable (case insensitive) but afaik minimessage also supports self-closing tags (?), possibly spaces between the tag name and the self-closing /. Emulating the minimessage parsing engine seems out of scope, and deserializing+serializing would be too intrusive imo.
Why did Velocity break its public API without incrementing the major version?
Expected Behavior
Usage of the Velocity API is backward compatible for plugins compiled against previous Velocity minor versions, like 3.2 or 3.0.
Actual Behavior
Velocity 3.5.0 switched to Adventure 5 with https://github.com/PaperMC/Velocity/pull/1774. I have no idea why no one mentioned this, but this is a breaking change. Adventure 4 was a public API dependency that integrated with Velocity's APIs, and now plugins are broken because of it.
Steps to Reproduce
N/A
Plugin List
N/A
Velocity Version
Build 608, commit 97b386d86ff99f787117cb30e8a4b50c83dba6c2
Additional Information
No response
Do you have an actual compatibility error with that velocity? Could you share the stacktrace?
Yes, sure. My plugins are written against older Adventure methods, for example.
To save people from digging through an attachment to find the piece:
Caused by: java.lang.NoSuchMethodError: 'net.kyori.adventure.text.TextComponent net.kyori.adventure.text.TextComponent.ofChildren(net.kyori.adventure.text.ComponentLike[])'
For what it's worth, that method was deprecated for 5+ years -
https://github.com/PaperMC/adventure/pull/617
Although, from an outsiders' POV, it might be neat to have a velocity version bump to make the change clear
TextComponent.ofChildren(ComponentLike...) was deprecated in Adventure 4.9.0, maked as for removal.
Not relevant to this issue, and it's far from the only method that plugins can use. All of these are breaking changes.
Well technically speaking, this is a snapshot version of a future 3.5.0, which is already a version bump from the 3.4.0 released in January, with decent compatibility with plugins made for 3.4.0 version if they didn't ignore a 5 year old deprecation
Your personal opinions are irrelevant. Again, this is not about a particular API method, and I'm convinced you requested that detail from me as a red herring to the substance of this issue.
@A248 wrote:
Your personal opinions are irrelevant.
Good thing they didn't give any. Let's keep things civil, A248.
I kind of have to agree here:
As a velocity user, I should be able to update my proxy to a new build within my same version without my setup breaking, ever. Bumping a provided dependency to a new major version with a ton of breaking changes should not be done without any sort of version bump, in my opinion at least. In the past, such dependency bumps seem to have come with at least a minor version bump (e.g., 3.2.0 or 3.5.0), so I'm also a bit surprised this didn't happen here.
Well technically speaking, this is a snapshot version of a future 3.5.0, which is already a version bump from the 3.4.0 released in January, with decent compatibility with plugins made for 3.4.0 version if they didn't ignore a 5 year old deprecation
Saying "this is a snapshot of a future 3.5.0" is kind of wrong considering velocity is doing rolling releases and 3.5.0 is the only version we currently support (so it isn't really a future version).
Being honest versioning has been a sore point for a while as part of a desire to have certain things nailed down before we start going for all of the next lineup of breaking stuff; I could revert and bump out a new version and repull it back in, but it does create a weird contention point around stuff; versioning gets weird because we don't maintain two versions at once, and doing a release right now feels icky
06eb052 Revert "Adventure 5 (#1774)" - electronicboy
2b5d964 Reapply "Adventure 5 (#1774)" - electronicboy
Warcrime attempt failed as I don't have access to the release system, forgot that part wasn't automatic
Okay, further crimes commited:
Latest 3.5.0 snapshot has had adventure reverted, deployed 3.6.0 with adventure 5.x
adventure-platform is no longer maintained, and there has been some discussion in the original adventure 5 PR on what to do with adventure-platform-facet (see https://github.com/PaperMC/Velocity/pull/1774#issuecomment-4500943734 and replies).
We were only providing some internal pointers that, to my understanding, didn't do anything anyways since there is no adventure-platform for velocity. This PR drops adventure-platform-facet entirely.
ffca982 chore: drop adventure-platform-facet (#1843) - Emilxyz
a5680fc chore: Bump dependencies in alignment with paper - electronicboy
@mbax Bluntness is not incivility - you just have a different cultural background.
I don't think that users' opinions about what feels "nice" in an API should have a bearing on the discussion. Fundamentally, semver violations are about a lack of communication, and the norms of versioning that exist for a project influence its users' expectations and behavior toward each other. @Emilxyz wrote this in plain terms. It's silly to use handwaving about deprecation and vague notions of version development to dismiss these concerns.
If the Velocity maintainers want to formally abandon semver and deliver breaking changes in minor releases, that is their prerogative. If so, it should probably be clearly communicated: "Warning: This Velocity version may not be compatible with plugins built for Velocity 3.0-3.4." However, I doubt such a disclaimer is what the maintainers want, because as I think I'm driving at, there's an aesthetic want to keep versions low while also pursuing API rene...
To be honest, I'm not really too intune with Semver, especially when it comes to updating libraries;
Major releases tend to be aligned for our own set of chunky breakages which we have aligned up, this was basically my strategy of being able to update a library for alignment with the rest of the ecosystem given the demand for it, without said library bump being inaccessible bethind a large swath of internal breakages
sorry. i opened a wrong PR
Right, this is the aesthetic feel of not wanting to increment a version.
I don't see anything wrong with just bumping to Velocity 4. It's better to communicate breaking changes even if you have to increase the pretty little number.
I think that the big issue is that semver requires releases, and we haven't had releases in so long because theres always something we wanted to address before cutting a release. I'm not exactly sure what to do going forward, my understanding is that the nature of semver applying to releases is that we could to a "technically" and do a 4.x release right now and remain compliant with it, but the entire signaling around semver requires releases, not an ever-evolving roll of snapshots, which is where the issue lies.
I'm not really sure on what I should do right this second in regards to versioning here, the semver promise was always a weird one once we stopped cutting releases
[PaperMC/Velocity] New branch created: dev/4.0.0
c1cd71a Toolchain and gradle bump (Java 25+) - electronicboy
0942e16 bump version to 4.0.0-SNAPSHOT - electronicboy
@A248 wrote:
@mbax Bluntness is not incivility - you just have a different cultural background.
Incorrectly declaring a statement to be opinion, so that you can dismiss it as opinion, is uncivil. If you continue to rudely dismiss statements as invalid just because you don't like the reality they contain and then hide behind "I'm just blunt" as an excuse (along with some other rude behavior seen here and in other issues), you won't be commenting here much longer.
I have given only objective analysis of the content of this issue, but if you don't like what I say, go ahead and ban me. As we all know, giving rational arguments on a topic's substance is uncivil, but threatening to mute someone simply because you don't like their arguments is perfectly fine.
757711a Use correct max tab complete lengths for... - WouterGritter
c690b4a Strip pre-java-9 version check in Metric... - WouterGritter
729a050 Fix build-time GraalVmProcessor warning (#1801) - WouterGritter
b8d1f16 Setup encryption before possible disconn... - WouterGritter
da7427f Set TitleActionbarPacket's default actio... - WouterGritter
28c9f5a Small optimization to prevent blocking n... - Beaness
Velocity already parses and validates signed Minecraft player chat internally, but PlayerChatEvent currently exposes only the player and plaintext message to plugins.
Carry the original signed-message metadata across the public API boundary so ecosystem plugins can inspect modern Minecraft signed chat without depending on internal packet classes.
This change is backward compatible and does not alter validation, forwarding, cancellation or rewrite behavior.
@electronicboy @WouterGritter Should this PR be retargeted to the dev/4.0.0 branch? I saw the latest few PRs submitted to that branch.
Summary
Velocity parses and forwards modern Minecraft signed player chat internally,
but the public PlayerChatEvent API currently exposes only the player and
the plaintext message.
This pull request exposes the original protocol-level player chat metadata
through the public API and adds a general Velocity-managed path for delivering
server-decorated player chat to selected proxy players.
The implementation is not tied to a specific chat plugin. It provides a
general API foundation that plugins can use without accessing internal packet
classes or implementing version-specific Minecraft protocol handling.
Motivation
Modern Minecraft player chat is no longer just a plaintext string. Depending
on the protocol generation, it includes:
- sender identity
- original message body
- signed state
- signature
- timestamp
- salt
- keyed or session identity
- last-seen and message-chain metadata
Velocity already processes much of this information in...
This pull request targeted the previous development branch.
The work has since evolved significantly and has been rebased onto the current dev/4.0.0 branch.
I'm closing this PR in favor of the updated implementation, which will continue in a new pull request against dev/4.0.0.
Thank you for your time and previous review.
To be honest, I'm not really too intune with Semver, especially when it comes to updating libraries;
Major releases tend to be aligned for our own set of chunky breakages which we have aligned up, this was basically my strategy of being able to update a library for alignment with the rest of the ecosystem given the demand for it, without said library bump being inaccessible bethind a large swath of internal breakages
I know this issue is now closed and a few days old now, but I wanted to comment on something interesting here.
While yes technically Adventure is a library, it is so heavily baked into both Paper and Velocity API that I feel it is more appropriate to treat it as a component in each software's API, not just as a library. IMO this is the most sensical solution as normal library version bumps (even if major) would only affect the software internals, not its API. This would then mean semver would beg a major version bump on any breaking adventure changes, wh...
Summary
Two small defensive fixes for the proxy's network layer.
1. Guard GameSpyQueryHandler against short datagrams
GameSpyQueryHandler.channelRead0() reads magic bytes, type, and sessionId
without first checking readableBytes(). A malformed UDP datagram shorter
than 7 bytes causes IndexOutOfBoundsException on every packet, spamming
the log at WARNING level.
This adds a readableBytes() >= 7 guard at the start so short datagrams
are silently dropped instead.
2. Validate usernames in ServerLoginPacket
In offline mode, ServerLoginPacket.decode() accepts any UTF-8 content
as a username. This username becomes the offline UUID seed and is
forwarded to the backend as-is, enabling log injection and identity
confusion.
This adds a [A-Za-z0-9_]+ regex check (the vanilla Minecraft character
set), rejecting non-conforming usernames with a QuietDecoderException.
Testing
./gradlew :velocity-proxy:check passes all checks (compile, Checkstyl...
These should be seperate PRs, and the 2nd fix breaks stuff like geyser
That is, should I re-submit PR only for the first one, and the second one, as I understand it, breaks geyser?
Done — removed the username validation. This PR is now just the GS4 query fix.
Please update the PR description accordingly. And throwing the second part of this issue away could be a shame, if you think it's still relevant, I'd encourage you to open it under a second PR.
Other than "testing" it by compiling & running unit tests, were you able to verify that this indeed fixes the referenced issue? The issue contains a MRE in the form of a command to run.
One issue still:
"7" is a weird magic number here. Is there any way we could calculate this value? Most likely not, but at the very least I'd like this to be a private static field with a short javadoc explaining this is the shorted expected message and which branch reads this shortest possible message (QUERY_TYPE_HANDSHAKE / QUERY_TYPE_STAT).
What
ClientPlaySessionHandler#handle(ClientSettingsPacket) fetches
player.getConnectedServer(), null-checks it, then discards the local
variable and calls player.getConnectedServer() a second time before
calling .ensureConnected() on the result.
Between these two calls the player's connected server can change on
another thread (e.g. mid server-switch), so the second call can return
null (NPE on .ensureConnected()) or a connection whose
ensureConnected() throws IllegalStateException, crashing/kicking
the session — the same class of bug fixed for the chat/command packet
handlers in f6fbd25 ("Downgrade severity of handling several incoming
user input packet states"), which however did not touch this handler.
Fix
Reuse the already null-checked local serverConnection reference
instead of re-querying player.getConnectedServer().
Testing
Built locally with ./gradlew build; no behavioral change for the
non-racy path, since the secon...
I don't think this is an actual improvement outside of flow correction, this stuff, along with server transfer invocations, should occur on the event loop and so there is no risk of something side-sweeping in here
Allows plugins to "provide" for other plugins, similar to how it is in Paper
This just fixes an annoying bug in the Finnish language, and almost all other languages where ! would appear in a specific language. The messages.properties for localization does not even have ! for the English language, which was weird.
My IDE was also complaining about some unused variables in other languages, so I just removed them.
I just also realized there's a crowdin for velocitypowered, my bad, but this seems faster.
before:
<img width="410" height="104" alt="image" src="https://github.com/user-attachments/assets/f0d3f4c3-4ad4-451f-96d5-d132e5e5d925" />
after:
<img width="416" height="104" alt="image" src="https://github.com/user-attachments/assets/c2662d20-b1f0-463d-99a1-d091964482bd" />
Modifications need to be handled via crowdin otherwise they'll just be lost
[PaperMC/Velocity] Draft pull request opened: #1855 Fix custom click forwarding during configuration
Fixes #1841.
During configuration, a custom click packet could reach an already connected backend after the in-flight connection was cleared. That path fell through to generic forwarding without retaining the reference-counted packet, so the backend encoder and inbound decoder could over-release the same frame.
Resolve either the in-flight or connected server before forwarding and retain the packet for the backend write. The regression test recreates the shared-frame lifecycle and verifies that forwarding completes without an IllegalReferenceCountException.
Validation:
- Focused
ClientConfigSessionHandlerTest - Full
:velocity-proxy:test - Main and test Checkstyle
- Full Gradle build
All checks completed successfully.
as is, this would cause getPlugins() to return duplicate instances, should probably migrate that to using the instance map
Fixes #1841.
During configuration, a ServerboundCustomClickActionPacket arriving from a dialog screen could reach an already connected backend after the in-flight connection was cleared. The old code checked only getConnectionInFlight(), so the packet fell through to handleGeneric(). That path only retained PluginMessagePacket before writing — other reference-counted packets were forwarded without retain, causing the backend encoder and MinecraftConnection.channelRead's finally block to over-release the same frame (IllegalReferenceCountException).
Two-layer fix:
handle()now usesgetConnectionInFlightOrConnectedServer()to forward the packet withretain()regardless of whether the server is still in-flight or already connectedhandleGeneric()retains anyByteBufHolderpacket before write instead of special-casing onlyPluginMessagePacket— defense in depth against the same bug for future packets
Added ClientConfigSessionHandlerTest with 4 tests cove...
Root cause: ClientConfigSessionHandler.handle(ServerboundCustomClickActionPacket) used getConnectionInFlight(), so when the in-flight connection was already cleared (server connected), the packet fell through to handleGeneric(). That path only retained PluginMessagePacket before forwarding — other ByteBufHolder packets were not retained.
The backend encoder releases the packet after encoding, then MinecraftConnection.channelRead() finally block calls ReferenceCountUtil.release(msg) again → double-free on the same underlying ByteBuf.
Fix in #1856:
handle()now usesgetConnectionInFlightOrConnectedServer()so the packet is retained before write regardless of connection statehandleGeneric()retains anyByteBufHolder(not justPluginMessagePacket), preventing the same class of bug for future refcounted packets
All 205 proxy tests pass, Checkstyle and Spotless clean.
Fixes #1828.
GameSpyQueryHandler.channelRead0 reads magic bytes, type, and sessionId without first checking readableBytes(). A malformed UDP datagram shorter than 7 bytes throws IndexOutOfBoundsException on every packet, caught and logged at WARNING:
Error whilst handling query packet from
Fix:
- Added
MINIMUM_MESSAGE_LENGTH(7) — a named constant with javadoc explaining the 2+1+4 byte layout and which handler branch reads the shortest valid message (QUERY_TYPE_HANDSHAKE). - Silently drop any datagram shorter than
MINIMUM_MESSAGE_LENGTHbefore touching any byte. - Also guard the challenge-token read in the
QUERY_TYPE_STATbranch (4 bytes after the 7-byte header) for defence-in-depth — a 7-byte datagram with type0x00would pass the header check but fail on thereadInt()for the challenge token.
Test coverage:
- Short/empty datagrams are silently dropped (no exception)
- 7-byte STAT datagram without challenge token is silently dropped (no...
Expected Behavior
A native 26.2 client should complete the login handshake through Velocity and connect to the proxy/backend normally, the same way it does when translated through ViaVersion from 26.1. Velocity should be able to send a valid login_finished packet to a 26.2 client without the client's own decoder rejecting it.
Actual Behavior
The 26.2 client is kicked immediately during login with DecoderException: Failed to decode packet 'clientbound/minecraft:login_finished', even with no backends running and no other plugins involved.
Steps to Reproduce
Run Velocity 4.1.0-SNAPSHOT (build 8 or 9)
Set online-mode = false in velocity.toml
Start Velocity (backend servers can be stopped/offline — not required to reproduce)
Connect using a vanilla, unmodified 26.2 Java client with an offline account
Client fails at login with the DecoderException / login_finished decode error every time
To confirm it's isolated to native 26.2 handling:
Remove ViaVersion and Via...
Cannot reproduce
<img width="1130" height="710" alt="Image" src="https://github.com/user-attachments/assets/4cc02686-6684-4701-8eb0-09a69ff112ff" />