Skip to content

Releases: tame-gg/conduit-native

Conduit 1.0.3

Choose a tag to compare

@koels koels released this 14 Oct 20:14

Security fixes from a full review of the proxy: client spoofing of proxy messages, signed commands slipping past plugins, an unused channel-guard setting, and BungeeCord messaging reaching untrusted servers. Upgrading is recommended.

Security

  • Client-sent bungeecord:main / BungeeCord messages are dropped. A client could send on the proxy's own messaging channel and have backend plugins read it as the proxy speaking. Velocity drops these too.
  • 1.20.5+ signed commands raise CommandExecuteEvent. A command with a signed argument (/msg, /tell, ...) arrives as its own packet and went past the event and proxy commands. It now fires the event, runs proxy commands, and is withheld when cancelled.
  • security.channel-guard.default-action is applied to any channel not listed. It was read from the config and then ignored.
  • Forward, ForwardToPlayer and a Message/MessageRaw to one player respect messaging.trusted-servers. A backend not on the list may aim them only at players on its own server.
  • Velocity plugins that time out deny. If plugins don't finish ServerPreConnectEvent, CommandExecuteEvent, PlayerChatEvent or PluginMessageEvent in time, the action is refused instead of read as allowed.

Hardening

  • authentication.session-url is parsed: https, or http to localhost, 127.0.0.1 or ::1 only.
  • A PROXY protocol v1 address is parsed as a literal and never looked up in DNS; a port outside 0-65535 is a malformed header.
  • A source the bot filter has blocked stays tracked while its block lasts, however many new sources arrive.
  • netty 4.1.125.Final.
  • Mojang's hasJoined response decodes every JSON escape.
  • Version strings from ViaVersion's repository metadata are checked before they reach a file path.
  • A warning at start and on reload when operators or permissions.toml users are given by name in offline mode.

Fixes

  • A server switch's backend handshake carries the host the client asked for, not the backend's address.
  • A forced host skips draining and unhealthy servers.
  • The modded handshake cache never restores a Forge marker onto a client that sent none.
  • addresses.txt is appended to on a new login pairing instead of rewritten each time.
  • The 1.13-to-1.14 translator's entity tracking is bounded.
  • Per-connection console prints moved to the debug log.

Mute still covers chat only; commands such as /msg go through unless a plugin cancels CommandExecuteEvent.

Conduit 1.0.2

Choose a tag to compare

@koels koels released this 05 Oct 03:16

Proxy commands no longer show red on translated backends, a switch between two translated backends no longer drops the player, and Via follows its snapshots so a new Minecraft release is supported as soon as Via supports it.

Proxy commands in red

/conduit, /server, /hub, /send, the server shortcuts and plugin commands showed red in chat on a backend Via was translating for, while still running when sent. Three paths sent the backend's command tree to the client without the proxy's commands merged in:

  • the Via relay wrote its output as it stood;
  • the packets Via holds behind Join Game, the command tree usually among them, were flushed unmerged;
  • a 1.20.2+ client finishing Configuration was marked in Play only after the backend had been told, so a tree that came back in that gap skipped the merge.

All three merge now. CommandTreeVersionTests merges into a backend tree for every release with one, 1.13 to 26.3.

Switching between translated backends

Moving a player between two backends Via translates for (a 26.3 client on 26.2 servers, say) dropped them with "Connection reset". Releasing the old session from Via's player registry went through Via's onDisconnect, which also clears the connection's stored state; the session still relaying the old backend lost its entity trackers and failed on its next chunk. The registry entry is now removed on its own.

ViaVersion

  • Pinned to ViaVersion / ViaBackwards 5.12.1 and ViaRewind 4.2.1 snapshots, which carry 26.2 → 26.3.
  • The updater now takes the newer of the latest release and the latest snapshot in the same major version, and checks hourly instead of every 12 hours (updates.via-check-interval-hours).

Also

  • A failed switch or translation logs the exception with its cause, instead of a message that was often null or Via's "please report the full error".

Assets

conduit-1.0.2.jar is runnable on its own. conduit.toml is the commented sample. conduit-1.0.2-source.zip is the Corresponding Source for the GPL; it is not inside the jar.

Conduit 1.0.1

Choose a tag to compare

@koels koels released this 27 Sep 22:07

Conduit no longer caps how many players a network may hold, and a ChannelWriter failure now releases the connection feeding it.

No player limit

Conduit has no player cap, the way Velocity has none. status.display-max-players is the number after the slash in the server list and nothing else; nothing behind it turns anyone away.

How many players a network holds is the backends' answer -- servers.<name>.max-players caps each one, and a full backend is skipped on join and refused by /server -- and what to do with the overflow belongs to a queue or routing plugin. A cap in the proxy pre-empted both, using a number an operator had to guess.

Stated plainly rather than papered over: a playing session holds no thread, so players are genuinely free, but the login phase is still a thread per connection -- virtual on Linux, platform on Windows (JDK-8334574). A flood of half-finished logins is bounded by [security.throttle] and the bot filter, both on by default and tightened by [security.attack-mode], rather than by a global cap. Put TCPShield or similar in front of a network large enough to attract one, as the large Velocity networks do.

Fixes

  • ChannelWriter.fail() weighed the low-water mark before releasing the connection feeding it. A writer fails at or above the high mark, so the one release it existed to perform never happened, and only the selector's congestion sweep found those connections.

Also

  • Backend connects and completed server switches are exposed as Prometheus histograms (conduit_backend_connect_duration_seconds, conduit_server_switch_duration_seconds) beside the existing counters, so a quantile is answerable rather than only a mean. The _seconds_total counters are unchanged.
  • New RelayBufferTests covers the selector relay's two buffers directly: the frame limits, the VarInt edge cases and both watermarks were reachable only through a whole proxy before, and so were never exercised on their own.
  • gg.tame.conduit.translate is removed. Protocol765To776Translator only ever threw and TranslationPipeline was a shell in front of ProtocolCompatibility; Via carries 765-776, and the README no longer offers a native one as future work.
  • The README's capacity section now says what to watch as a population grows -- file descriptors (two per player; the JVM raises itself to ulimit -Hn, not the 1024 a shell shows) and heap -- instead of naming a limit.

Assets

conduit-1.0.1.jar is runnable on its own. conduit.toml is the commented sample. conduit-1.0.1-source.zip is the Corresponding Source for the GPL; it is not inside the jar.

Conduit 1.0.0

Choose a tag to compare

@koels koels released this 25 Sep 00:08

Online-mode login now works on every client version Conduit has a packet table for, from 1.7 to 26.3.

Login fixes

  • 1.20.5 through 26.1 (protocols 766–775) could not log in to an online-mode proxy. Conduit's encryption request left out the Should Authenticate field that 1.20.5 added, and the client disconnected with "Failed to decode packet 'clientbound/minecraft:hello'".
  • 1.7.x clients now get the encryption request and have their response read with short-length byte arrays, the format 1.7 uses. Conduit used VarInts before, so online login on 1.7 did not work.
  • 1.19 and 1.19.2 clients with a chat-signing key answer the encryption request with a salt and a signature instead of the verify token. Conduit rejected that answer. It now keeps the profile key from Login Start and checks the signature against it. A forged signature, or a signature with no key, is refused.

A scripted login read a correct encryption request on all 41 protocol versions that have a table. New tests cover the 1.7 layout and the 1.19 signed response, including a forged signature.

1.0.0

The version skips 0.9.9. Nothing else changed: configuration, the plugin API and the pinned ViaVersion set (5.12.0 / ViaBackwards 5.12.0 / ViaRewind 4.2.0 / ViaLegacy 3.1.0) are the same as in 0.9.8.

Conduit 0.9.8

Choose a tag to compare

@koels koels released this 24 Sep 21:51

Minecraft 26.3 is supported natively, most of the Velocity API that used to throw now works, and a network gets mutes, warnings, range bans, alerts and BungeeCord/BungeeGuard forwarding.

Minecraft 26.3

  • 26.3 (protocol 777) has its own packet table, a delta on 26.2's taken from Mojang's own --reports output for 26.3. A 26.3 client on a 26.3 server goes through Conduit's direct path. A scripted client joined, changed dimension and switched between two real 26.3 servers; the official 26.3 client has been played through Conduit, but not yet validated in a scripted run.
  • ViaVersion moves to 5.12.0 (ViaBackwards 5.12.0, ViaRewind 4.2.0, ViaLegacy 3.1.0), so every other pairing with 26.3 is translated. First start installs these into lib/via, checked against the SHA-256 this build carries for each.

Moderation and operations

  • /gmute, /gunmute and /gwarn; /gban on an address or a CIDR range; /galts from an address history.
  • [alerts] webhook-url posts backend health, attack mode and startup to one webhook (Discord and Slack field names both set). /conduit alert sends a test.
  • messaging.trusted-servers limits which backends may act on players on other servers over the BungeeCord channel.
  • Per-server max-players, a per-hostname MOTD and icon, and a permissions.toml group provider for networks without a permissions plugin.
  • Attack mode can switch itself on above auto-trip-per-second connections a second, and with known-sources-only it admits only addresses seen logging in since start. /conduit attack status says whether it was switched on automatically.
  • Prometheus gains uptime, JVM memory and attack-mode gauges. Conduit logs one line when a newer release is on GitHub; it never downloads anything ([updates] conduit = false turns it off).

Forwarding

forwarding.mode now also accepts legacy (BungeeCord IP forwarding, for Spigot/Paper with bungeecord: true) and bungeeguard (the same with the secret file's token). Legacy was checked against a real Paper 1.20.4; BungeeGuard is covered by unit tests only. Nothing signs legacy forwarding, so firewall the backends; startup and /conduit doctor warn about it.

Velocity plugins

Now working:

  • CommandMeta hints and PlayerAvailableCommandsEvent.
  • The cookie store and request events.
  • ProxyQueryEvent, through a GameSpy4 query responder behind [query] (off by default).
  • Mod info from 1.7–1.12 Forge and 1.13+ Forge/NeoForge clients, and from Forge backends' pings.
  • Guice-built injection for anything Guice can construct, and getForcedHosts().
  • Backend tab-list entries that plugins can read and edit; 1.20.1 and 1.7 clients now get the proxy's own entries.
  • Chat sessions on tab-list entries, the client's key from getIdentifiedKey() (not checked against Mojang), and signed Adventure messages shown as system chat.
  • spoofChatInput for 1.19+ clients, sent unsigned; a backend with enforce-secure-profile=true refuses it.
  • setGameProfileProperties, closeListeners, createRawRegisteredServer, closeDialog (26.2+) and sounds played from another player.

openBook and showDialog still throw. docs/VELOCITY_COMPATIBILITY.md lists every call and the test behind it.

Fixes

  • 26.x players on a translated backend saw no faces in the tab list. A 26.x client draws them only when Join Game says the server is in online mode. Conduit set that for authenticated players on the direct path only, so a 26.3 player on a 26.2 backend got the offline backend's "no". ViaJoinGameOnlineModeTests covers the join and a switch.
  • Every translated switch logged "Duplicate UUID on frontend connection". The session carrying the player now leaves Via's registry before the next backend's login and rejoins it if the switch fails.
  • The first player on a freshly started 1.19–1.20.1 backend was disconnected with a decode error. Conduit's channel registration reached the backend while it was still reading Login packets; it now waits for the backend's Join Game. ColdBackendJoinTests covers it.
  • The Gradle distribution no longer ships Via (it installs into lib/via on first start, like the jar), and fetch-via.ps1 moves Via jars that are no longer pinned aside instead of leaving two Vias on the class path.

Tested

Full test suite and a release build from a fresh clone, on Windows. CI now runs the Gradle compile, the full suite and the release build on every push.

Assets

  • conduit-0.9.8.jar: runnable on its own (java -jar), ViaVersion is installed into lib/via on first start.
  • conduit-0.9.8-source.zip: the Corresponding Source (GPL-3.0-or-later).
  • conduit.toml: the shipped configuration.

Conduit 0.9.7

Choose a tag to compare

@koels koels released this 23 Sep 01:08

Players on 26.2 and 1.20.1 now have a ping, and staff can no longer be banned while they are offline.

Fixes

  • /gban on an offline staff member went ahead. A player who may kick or ban could not do it to another who held that power, or conduit.punish.exempt, while they were online, but a permissions plugin only answers for the players it has loaded, so the same command against them once they had logged out was never refused. Conduit now records who held those nodes at each login and leave, in protected-players.txt beside the configuration, and refuses a ban by name or account on anyone in that record, or named in [permissions] operators. The console may still ban anyone. StaffPermissionTests covers the offline case.
  • Player.ping() was -1 for every 26.2 and 1.20.1 player, for the whole session. Conduit measures latency by recognising a keep-alive by its packet id, and the declared tables for protocol 776 (26.2) and 763 (1.20.1) named none, so the clock never saw one. A Velocity plugin's getPing() read the same -1, and a tab-list plugin showed it as "0 ms" or "?". 26.2 takes 26.1's published ids (0x2C to the client, 0x1C from it), as every other Play id in its table already does; 1.20.1 takes minecraft-data's (0x23 and 0x12). Every other supported release already had the pair.
  • PlayerLatencyTests now checks every registered codec, all 40 of them, for the keep-alive in both directions, so a future table cannot ship without one.

Tested

Full test suite and a release build from a fresh clone. The 26.2 ids are inferred from 26.1's published list, which every other Play id in the 26.2 table matches; no 26.2 packet report has been published to confirm them against. A native plugin was loaded, run and reloaded on the release jar from a fresh install.

Assets

  • conduit-0.9.7.jar: runnable on its own (java -jar), ViaVersion is installed into lib/via on first start.
  • conduit-0.9.7-source.zip: the Corresponding Source (GPL-3.0-or-later).
  • conduit.toml: the shipped configuration.

Conduit 0.9.6

Choose a tag to compare

@koels koels released this 22 Sep 22:12

Players now get compressed traffic, login-time security holes are closed, chat plugins see modern players, cross-version first joins through ViaVersion work again, and No Chat Reports players can see when a network is safe.

Security

  • One address could stop every online-mode login. A login held an authentication permit for as long as the client took to answer, so 33 silent connections from anywhere blocked everyone else. A permit is now held only while Conduit is talking to Mojang.
  • A slow ban plugin let banned players in. A login that Velocity plugins had not decided on within 10 seconds went through as allowed. It is now refused ("The server took too long to check your login"), and the log says it was refused. The wait can be changed with -Dconduit.velocity.waitMillis.

Performance

  • Traffic to players is compressed, as a vanilla server compresses it. listener.compression-threshold sets it (default 256; -1 turns it off). Measured with a real 1.20.4 client over the first minute of a join: 7.75 MB on the wire uncompressed, 1.23 MB compressed, about 6x smaller.
  • The accept backlog is 1024 instead of the JDK's 50, so a mass rejoin after a restart is not dropped.
  • Each relay pass sends everything it wrote in one flush instead of one per packet.
  • A connection's read and write buffers shrink back after a large burst.
  • Backend health probes run in parallel, so one dead backend no longer delays every other backend's verdict.

Fixes

  • conduit.toml repeated its own comments on every upgrade. Each time a version added or removed a setting, the "One block per backend" and "One proxy, several front doors" paragraphs were written once more above [servers.*] and [forced-hosts]. A file that already has the copies is cleaned up on the next start, and your values are kept (the old file is saved as conduit.toml.bak-5). [messaging] also moved out of the middle of the ViaVersion update notes.
  • A 26.2 client could not join a 1.20.4 backend through ViaVersion on its first join. Conduit's channel announcement reached the backend untranslated, and the backend closed the connection ("Packet configuration/2 was larger than I expected"). Switching servers already worked; the first join now does too, in both directions. A plugin sending a plugin message from PostLoginEvent had the same problem and is fixed with it.
  • A modded or plugin-channel client could be kicked after /server to a backend on another version. The channels it had registered were replayed to the new backend in the wrong state and without translation. They now go through the translator after every join and switch.
  • Upgrading an older conduit.toml could silently drop settings. [permissions] operators, [metrics] prometheus-address and [bans] values ended up under the wrong section and were ignored. They now stay where they belong. If an earlier upgrade already moved yours, check those three sections.
  • Shutdown no longer moves every player to a fallback server first. Players are told the shutdown message and disconnected, without a burst of logins at the fallback server or its world flashing up behind the message.
  • Frames over 2 MiB: instead of sending a packet no client can read, Conduit disconnects the player and says why.

Velocity plugin compatibility

  • PlayerChatEvent fires for 1.20.1, 1.20.4, 1.20.5 and 26.2 clients, so chat filters, mutes and chat bridges see modern players. Denying works for signed chat (the backend is sent a Message Acknowledgment instead, keeping its chat tracking in step). Rewriting works for unsigned messages; a rewrite of a signed message is logged and not done.
  • PostLoginEvent fires before the first server is chosen, as on Velocity. A player who gets it but joins no server gets DisconnectEvent with PRE_SERVER_JOIN.
  • Plugin-made command sources (chat bridges and similar) can run commands and receive the replies. offerBrigadierSuggestions works.
  • getModInfo, getIdentifiedKey and ProxyConfig return values instead of throwing. Titles, action bars and sounds sent to the console do nothing instead of throwing.

No Chat Reports

Players using the No Chat Reports mod can see the network marked safe in their server list. Set prevents-chat-reports = true under [status] to show the mark, as with Velocity-CTD's option of the same name. Conduit passes signed chat through unchanged, so only turn it on when your backends run No Chat Reports or strip chat signatures. With it off, the mark still shows while every backend advertises No Chat Reports in its own server-list answer.

Tested

Re-cut 2026-09-22 with the conduit.toml and No Chat Reports fixes above. Full test suite, forced Gradle compile, and a release build from a fresh clone. Checked with real vanilla 1.20.4 and 26.2 clients: joins, chat (plain, denied, rewritten), a plugin command source, the slow-ban refusal, shutdown, and cross-version first joins both ways through ViaVersion. The channel replay was reproduced and verified with a scripted 1.8 client, and the No Chat Reports mark with scripted backends. Signed chat has only the automated tests, since the real clients were offline.

Assets

  • conduit-0.9.6.jar: runnable on its own (java -jar), ViaVersion is installed into lib/via on first start.
  • conduit-0.9.6-source.zip: the Corresponding Source (GPL-3.0-or-later).
  • conduit.toml: the shipped configuration.

Conduit 0.9.5

Choose a tag to compare

@koels koels released this 22 Sep 07:05

Hub and queue plugins now actually reach the proxy on every version, a restarting or crashing backend no longer throws its players off the network, and moderation grew up: a worded ban screen, /gbanlist, Mojang-checked bans, staff alerts, and admin commands without LuckPerms.

Hub and queue plugins work, for real this time

0.9.4 taught Conduit to answer the BungeeCord channel. What it did not do was let the message arrive. A Paper or Spigot backend sends a plugin message only on a channel the player's connection has registered, and drops anything else without a word. A vanilla client never registers bungeecord:main, so every hub plugin's server selector and every backend /hub was dropped on the backend before Conduit could answer it.

  • The channel is registered with every backend a player joins, the first and each one a switch lands on, in the names the client's release uses (minecraft:register + bungeecord:main from 1.13, REGISTER + BungeeCord before). Only while [messaging] bungeecord-channel is on.
  • 26.2 and 1.20.1 could not send a plugin message to a backend at all. Their packet tables had no serverbound plugin message, so everything Conduit sends a server on a player's behalf, the registration and every BungeeCord answer included, was silently not sent. The ids are now there (0x16/0x02 for 26.2, 0x0D for 1.20.1, from the published packet lists), and a test holds every table to having them.
  • GetPlayerServer and KickPlayerRaw are answered. They were the two documented subchannels still ignored; a queue plugin asks GetPlayerServer before it moves anyone.
  • Channels a proxy plugin registers are announced to backends too. A Velocity plugin that registers a channel with the ChannelRegistrar to hear its backend half heard nothing, for the same reason. ajQueue's backend half reported "ajQueue must also be installed on the proxy" while it was. Native plugins get the same through the new ConduitProxy#listenOnChannel.

Checked end to end with real 1.20.1, 1.20.4 and 26.2 clients on official Paper: DeluxeHub's [PROXY] action and ajQueue's queue both move the player. ServerSelector, Akropolis, UltimateLobby, PistonQueue, velocity-hub and hub all load.

Backends that go away keep their players

  • A server shutting down moves its players to the next server that will take them and tells them why ("Kicked from lobby: Server closed"), instead of disconnecting them from the network. A kick no PlayerKickedFromServerEvent listener decided now walks the fallback list; a listener's own decision is obeyed as before. routing.fallback-on-kick = false restores the old behaviour for a network where a kick is meant to be final.
  • A backend that resets the connection is fallen back from as one that closes it cleanly is. A crashed or killed server resets its connections, and Conduit used to end the session on a reset.

Moderation

  • The ban screen is yours to word. A new [bans] section in conduit.toml is one template for every ban; the reason given to /gban fills {reason}, alongside {actor}, {duration}, {remaining} and {expiry}. The default now reads:
    You have been banned from this network.
    
    Reason: griefing
    Banned By: Kyle
    Duration: Permanent
    
  • /gbanlist [page] lists every ban in force: who, why, by whom, how long ago, and the time left or Permanent. It takes /gban's node.
  • Offline players are looked up at Mojang. In online mode, /gban <name> on someone not online asks Mojang who the name belongs to: a name no account has is refused, and a real one's account UUID is banned alongside the name, so a rename does not get round it. If Mojang cannot be asked, the name is still banned and the staff member is told. Offline-mode networks skip the lookup, since their players' UUIDs are not Mojang's. (0.9.4's notes said Conduit does not look names up at Mojang; it now does, for this.)
  • Unbanning works. /gban on an online player bans their name and their account; /gunban <name> lifted only the name, so the player stayed banned. It lifts both now, including bans written by 0.9.4.
  • /gban on someone already banned says so and leaves the standing ban alone, instead of replacing it and reporting a new one.
  • Staff cannot punish staff. A player may not /gkick or /gban another who holds that same command's node, or the new conduit.punish.exempt. The console always may.
  • Staff are told. Every kick, ban and unban is announced as [Staff] Kyle banned Griefer permanently: griefing to players who may kick or ban, to anyone given the new conduit.notify.moderation, and to the console.
  • Admin commands without LuckPerms. [permissions] operators = ["Name", "<uuid>"] gives those players every conduit. node until a permissions plugin is installed. Everyone else still gets only the player commands. In offline mode, use UUIDs.
  • LuckPerms can find every Conduit node. Its editor suggests only nodes it has seen checked; the first login after a permissions plugin loads now asks it about all of them.

Velocity plugin compatibility

  • A plugin that injects its own main class gets the live instance, directly, into a helper, or through the Injector, instead of a new copy. The copy was not the plugin, so EventManager.register rejected it: SignedVelocity failed to start.
  • docs/VELOCITY_COMPATIBILITY.md records both changes.

Logs for bug reports

  • logs/latest.log, beside conduit.toml, holds everything the console shows: Conduit's lines, ViaVersion's, plugins', command replies and stack traces, without the colour codes. The previous session is compressed to logs/<date>-<n>.log.gz on each start.
  • A backend plugin asking to move a player is logged, and -Dconduit.debug=true names each plugin channel a backend uses, so a hub button that does nothing can be told apart from one whose message never arrived.

Also

  • gradlew compileJava builds again; the Velocity runtime's Guava exclusion had also removed the Guava the compile needs. The release jar's contents are unchanged.

Upgrading

Drop in the new jar. conduit.toml gains the new sections, documented and commented out, with every value you set kept. Two things behave differently by default:

  • The ban screen has the new default wording above. Set [bans] message to keep your own.
  • A backend's shutdown kick now moves players rather than disconnecting them; routing.fallback-on-kick = false turns that off.

bans.txt gains an optional seventh column linking an account ban to its name. Files from 0.9.4 are read as they are.

Running it

Java 21 or newer. Put conduit-0.9.5.jar in a folder with conduit.toml and start it:

java -jar conduit-0.9.5.jar conduit.toml

An empty folder works too. The first start writes conduit.toml, creates plugins/, generates forwarding.secret and installs ViaVersion into lib/via.

conduit-0.9.5-source.zip is the Corresponding Source for this release.

Conduit 0.9.4

Choose a tag to compare

@koels koels released this 21 Sep 21:47

Hub plugins and backend queue plugins work, a network can be banned and
whitelisted from the console, and a restart no longer waits on somebody else's
server.

Hub plugins and backend queue plugins work

A plugin running on a Paper or Spigot backend has no API to reach its proxy with.
What it has is a plugin message on the channel BungeeCord, renamed
bungeecord:main when 1.13 made channel names namespaced. A hub plugin's /hub,
and a queue plugin that runs on a backend rather than on the proxy, is that
message and nothing else: it does not know what proxy it is talking to, only that
this channel answers.

Conduit answered it nowhere. The name appeared once in the whole tree, in an
unrelated check. So the message was forwarded to the client, which cannot use it,
and the plugin failed silently — the message was delivered and simply never
acted on, with nothing logged at either end. That is why a hub button did nothing
and why a backend queue never moved anybody.

Connect, ConnectOther, IP, IPOther, PlayerCount, PlayerList,
GetServers, GetServer, UUID, UUIDOther, ServerIP, Message,
MessageRaw, KickPlayer, Forward and ForwardToPlayer are now answered,
under both channel names, since a network with backends either side of 1.13 runs
both.

Connect goes through the same path /server takes, so backend health, the
switch's own rules and every event a plugin listens for still apply: a plugin
message is not a way around what a command has to obey. A message on this channel
is consumed rather than passed on, because it is addressed to the proxy and a
client has no business being handed the network's player list; plugins still see
it first and may cancel it. And it is answered only for a message that came from
a backend, never from a client, so a modded client cannot forge a
KickPlayer for somebody else.

One limit is the protocol's rather than Conduit's: a message reaches a server
through a player already on it, because that is the only connection a proxy holds
to a backend, so a server with nobody on it cannot be reached — by a named
target, by ALL or by ONLINE. BungeeCord and Velocity have the same limit.

[messaging] bungeecord-channel is on by default, because a plugin that expects
the channel and does not get it fails silently, and that is a far worse default
than answering. A proxy-side queue or hub plugin — a Velocity jar rather than a
backend one — goes through the compatibility adapter instead; what it can do is
in docs/VELOCITY_COMPATIBILITY.md.

/gban and /gwhitelist

Two lists an operator edits with players waiting, so neither is configuration and
neither needs a reload. They live in bans.txt and whitelist.txt beside
conduit.toml, are written the moment a command returns, and are in force on the
next login attempt.

/gban Steve griefing               permanent, with a reason
/gban Steve 7d griefing            for a week; 30m, 2h, 7d, 4w and perm are understood
/gban 198.51.100.7 open proxy      an address instead of a name
/gunban Steve
/gwhitelist on
/gwhitelist add Steve

A ban is matched on three things, so that it holds: the name, case-insensitively,
because that is how it is typed; the account, so a name change does not undo it;
and the address, so a new name from the same place is still caught. The second
word is read as a length only if it parses as one, so /gban Steve griefing
needs no punctuation to say which is which, and no length means permanent.

Banning someone who is online bans their account alongside their name and kicks
them with the message the ban will show them from then on. Banning an address
kicks everyone connected from it. A temporary ban stops matching when its time is
up, and the kick screen tells the player how long is left or that it is
permanent. An offline player's account cannot be banned, only their name:
Conduit does not look names up at Mojang, and a guessed UUID is worse than none.

The whitelist is a standing policy rather than maintenance's passing state, so it
is checked after bans and before maintenance and a player passes all three.
conduit.whitelist.bypass is how the staff who turned it on do not lock
themselves out, and taking someone off while it is on also puts them out.

Both files are one record per line and may be edited by hand while the proxy is
stopped. They are written through a temporary file and moved into place, so a
crash mid-write cannot leave a truncated list that would silently unban people,
and a reason is escaped so that one holding a tab or a newline cannot split its
record. A whitelist.txt that cannot be read leaves the whitelist off: an
unreadable file must not be the reason a whole network is shut out.

A restart no longer waits on repo.viaversion.com

The ViaVersion update check was the one thing between starting Conduit and
Conduit listening that waited on somebody else's server: about 75 ms when
there is nothing to fetch, some ten megabytes downloaded before the proxy binds
when there is, and the whole of updates.timeout-ms when the repository cannot
be reached — on every restart, for an answer that changes every few weeks.

It is now remembered. updates.via-check-interval-hours (12 by default) is how
long an answer stands for, and the time of the last successful check is kept in
lib/via/.last-update-check. A check that failed is deliberately not
remembered, so an unreachable network is retried on the next start rather than
suppressed for the interval. Set it to 0 to check on every start, as before.

For the record, the rest of the start is not slow and was not made faster: from
the jar, warm, Conduit reaches its listener in about 1.75 s, of which the JVM
is 80 ms and ViaVersion's own platform init is some 750 ms. A profile of it
catches the process idle rather than computing.

Two smaller things

Bare /conduit now names its subcommands rather than being a second spelling of
/conduit info, and it lists only the ones that source may actually run — for a
player with one node, one subcommand rather than the twenty they would be
refused. /conduit info is unchanged.

The server list's player sample is configurable. Up to twelve names have always
been sent — the hover on the player count — but the number was a constant.
status.player-sample sets it, and 0 names nobody, for a network where who
is online is not public. status.player-sample-server follows each name with the
server that player is on, which is the thing a proxy knows and a backend does
not. Left alone, the answer is unchanged byte for byte.

Running it

Java 21 or newer. Put conduit-0.9.4.jar in a folder with conduit.toml and
start it:

java -jar conduit-0.9.4.jar conduit.toml

An empty folder works too — the first start writes conduit.toml, creates
plugins/, generates forwarding.secret and installs ViaVersion into lib/via.
An existing conduit.toml gains this release's settings in their proper sections
with every value you set kept, and the file you had is saved beside it.

conduit-0.9.4-source.zip is the Corresponding Source for this release.

Conduit 0.9.3

Choose a tag to compare

@koels koels released this 21 Sep 20:28

Three changes to what a packet costs on its way through the proxy, and one to what a
player costs while they are not sending any.

Compression stops being the expensive part

Every packet leaving the proxy is deflated, and deflated again for each player it goes
to. Conduit did that at zlib's default level 6, which is the level for a file written
once and read many times. A proxy is the other case: a packet is compressed once,
travels once and is thrown away. On chunk-shaped data level 4 reaches the same size in
half the time -- 8192 bytes down to 2957 bytes at 83.5 MiB/s, against 2956 bytes
at 46.5 MiB/s
-- so level 6 was buying one byte in eight thousand at half the
throughput. It is now level 4.

The allocation around it cost as much as the deflate did on a small packet. Every
compressed packet made a 512-byte buffer, two ByteArrayOutputStreams and a
toByteArray copy, so the compressed bytes were written once and copied three more
times. A packet is now deflated straight in behind its own size VarInt, into a buffer
the connection keeps for the next one, and handed back without another copy.

A packet under the compression threshold is not compressed at all -- movement, keep
alives, chat, most of what a player sends -- and it went through those same two streams
to gain a single zero byte in front of it. That is now an array and an arraycopy.

A frame's length is one write

The length VarInt in front of every frame was sent a byte per write. On an encrypted
connection each of those is its own AES/CFB8 call, and the call overhead is most of what
a byte costs there: the same cipher measures 44.3 MiB/s in bulk and 26.4 MiB/s
driven a byte at a time. A short packet spent as long on its two-byte length as on its
body, and every packet leaving the proxy paid it. The prefix is now built once and
written once. The bytes on the wire are identical.

A player costs no thread, on every platform

A playing session is read by a small set of selector threads rather than by two threads
parked in blocking reads. That was one of two paths: a selector that would not open, or
a connection that was not channel-backed, left the session on a thread per socket, and
the proxy logged one line and carried on. What a connection cost between packets -- a
thread and its stack, or a file descriptor and its buffers -- therefore depended on
something decided at the first join, possibly hours earlier. That is not a thing to find
out at a thousand players.

The selector is now opened at start, and a proxy that cannot open one does not start: a
Selector that will not open means this JDK cannot do non-blocking I/O at all, which is
not a state to serve players in. With that settled the second path has no reason to
exist, and readClient, readBackend and the thread that ran them are gone. Both
sockets Conduit owns are channel-backed -- the listener accepts on a
ServerSocketChannel, a backend is dialled on a SocketChannel -- so the checks that
used to select the fallback could not fail anyway; they now end the session with a
reason instead.

Between packets a session costs no thread on every platform, rather than on the
platforms where the fallback happened not to be taken.

What is not in this release

Conduit's AES/CFB8 still runs at about 45 MiB/s, and it is now the slowest thing on a
busy encrypted connection. That is a JCE limit rather than a Conduit one: CFB8 needs one
AES block operation per byte, and what a byte costs is the call around it. A CFB8 written
by hand over the same provider was measured at 51.8 MiB/s and byte-exact against JCE --
15%, which is not enough to be worth hand-writing a cipher for. The order-of-magnitude
answer is AES-NI through the FFM API, which needs a toolchain newer than this build
targets. It is not in 0.9.3.

Running it

Java 21 or newer. Put conduit-0.9.3.jar in a folder with conduit.toml and start it:

java -jar conduit-0.9.3.jar conduit.toml

An empty folder works too -- the first start writes conduit.toml, creates plugins/,
generates forwarding.secret and installs ViaVersion into lib/via. ViaVersion is not
in the jar; a first start that can reach repo.viaversion.com fetches it, and one that
cannot says so and carries on with cross-version play off.

conduit-0.9.3-source.zip is the Corresponding Source for this release.