shadowsocks/shadowsocks-rust · error
Packet header length mismatch
Error message
Packet header length mismatch
What it means
Panic from `<&mut Block as TryFrom<&mut [u8]>>::try_from(packet_header).expect("Packet header length mismatch")` in UDP `encrypt_message`. The packet header carrying SessionID+PacketID must be exactly one AES block (16 bytes); the conversion fails when the buffer passed as `packet_header` is not exactly 16 bytes.
Source
Thrown at crates/shadowsocks/src/relay/udprelay/aead_2022.rs:229
let cipher = get_cipher(method, key, session_id);
// Encrypt the rest of the packet with AEAD cipher (AES-*-GCM)
let (packet_header, mut message) = packet.split_at_mut(16);
let nonce = &packet_header[4..16];
if eih_len > 0 {
message = &mut message[eih_len..];
}
cipher.encrypt_packet(nonce, message);
// [SessionID + PacketID] is encrypted with AES-ECB with PSK
// No padding is required because these 2 fields are 128-bits, which is exactly the same as AES's block size
match method {
CipherKind::AEAD2022_BLAKE3_AES_128_GCM => {
let cipher = Aes128::new_from_slice(ipsk).expect("AES-128 init");
let block = <&mut Block as TryFrom<&mut [u8]>>::try_from(packet_header)
.expect("Packet header length mismatch");
cipher.encrypt_block(block);
}
CipherKind::AEAD2022_BLAKE3_AES_256_GCM => {
let cipher = Aes256::new_from_slice(ipsk).expect("AES-256 init");
let block = <&mut Block as TryFrom<&mut [u8]>>::try_from(packet_header)
.expect("Packet header length mismatch");
cipher.encrypt_block(block);
}
_ => unreachable!("{} is not an AES-*-GCM cipher", method),
}
}
_ => unreachable!("{} is not an AEAD 2022 cipher", method),
}
}
fn decrypt_message(
_context: &Context,
method: CipherKind,View on GitHub (pinned to 8eb0f0a65b)
Solutions
- Build the packet header as exactly 16 bytes: 8-byte session ID + 8-byte packet ID, and pass only that slice.
- Verify the split of the outgoing buffer: header = first 16 bytes, remainder is the AEAD-encrypted message.
- Check the serialization of SessionID/PacketID field widths against the shadowsocks-2022 spec.
- Add a length check `packet_header.len() == 16` with a clear error before the TryFrom.
Example fix
// before
let block = <&mut Block as TryFrom<&mut [u8]>>::try_from(packet_header)
.expect("Packet header length mismatch");
// after
assert_eq!(packet_header.len(), 16, "UDP AEAD-2022 header (session_id + packet_id) must be 16 bytes");
let block = <&mut Block as TryFrom<&mut [u8]>>::try_from(packet_header)
.expect("Packet header length mismatch"); Defensive patterns
Strategy: validation
Validate before calling
if packet_header.len() != 16 {
return Err(format!("UDP AEAD-2022 header must be 16 bytes, got {}", packet_header.len()));
} Type guard
fn is_valid_header(buf: &[u8]) -> bool { buf.len() == 16 } Prevention
- Serialize session ID (8 bytes) + packet ID (8 bytes) to get a 16-byte header.
- Split outgoing datagrams correctly: 16-byte header, then encrypted payload.
- Unit-test packet builders against the shadowsocks-2022 layout.
When it happens
Trigger: Calling `encrypt_message` (or `encrypt_client_payload_aead_2022` / `encrypt_server_payload_aead_2022`) with a `packet_header` slice that is not 16 bytes — e.g. a header built with wrong field sizes (SessionID or PacketID of unexpected width) or an offset/split error on the payload buffer.
Common situations: Hand-rolled packet builders using 4-byte or 8-byte session/packet ID fields instead of the AEAD-2022 sizes; passing the whole datagram instead of the 16-byte header prefix; protocol fuzzing/custom interop with a non-conformant peer format.
Related errors
AI-assisted analysis of shadowsocks/shadowsocks-rust@8eb0f0a65b (2026-09-09).
Data as JSON: /api/errors/077291d423862185.
Report an issue: GitHub.