Fall back to the handshake key when a packet fails to decrypt

The server encrypts initivexpand2 (COMMAND 0 and 1) with the handshake
key. If our ACK for one of those fragments is lost, its retransmit arrives
after we switched to the session key, fails with "mac check in EAX
failed", is never acknowledged and blocks the server's command stream
until it drops us. Common on lossy links such as mobile data.

Retry generation 0 packets that fail under the session key with the
handshake key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 23:21:44 +00:00
co-authored by Claude Opus 5.5
parent b335c2c511
commit 765bfb25fd
@@ -75,7 +75,8 @@ public abstract class AbstractTeamspeakClientSocket
private Identity identity = null;
private ClientConnectionState connectionState = null;
private PacketTransformation transformation = new InitPacketTransformation();
private final PacketTransformation initTransformation = new InitPacketTransformation();
private PacketTransformation transformation = initTransformation;
private PacketHandler handler;
private int clientId = 0;
@@ -543,15 +544,11 @@ public abstract class AbstractTeamspeakClientSocket
+ " generation=" + networkPacket.getHeader().getGeneration());
PacketTransformation transformation = getTransformation();
int dataLen = networkPacket.getDatagram().getLength() - networkPacket.getHeader().getSize();
try {
packetBuffer = ByteBuffer.wrap(
transformation.decrypt(
networkPacket.getHeader(),
packetBuffer,
networkPacket.getDatagram().getLength() -
networkPacket.getHeader().getSize()
)
decrypt(transformation, networkPacket.getHeader(), packetBuffer, dataLen)
).order(ByteOrder.BIG_ENDIAN);
} catch (InvalidCipherTextException e) {
throw new IOException(
@@ -604,6 +601,20 @@ public abstract class AbstractTeamspeakClientSocket
return packet;
}
/**
* Decrypts with the session key, falling back to the handshake key: the server encrypts initivexpand2 with
* the latter, so its retransmits (sent when our ACK was lost) still arrive under it after the key switch.
*/
private byte[] decrypt(PacketTransformation transformation, PacketHeader header, ByteBuffer buffer, int dataLen)
throws InvalidCipherTextException {
try {
return transformation.decrypt(header, buffer, dataLen);
} catch (InvalidCipherTextException e) {
if (transformation instanceof InitPacketTransformation || header.getGeneration() != 0) throw e;
return initTransformation.decrypt(header, buffer, dataLen);
}
}
@Override
public Packet readPacket() throws
IOException,