Implement new-style variable length packet number encoding

This commit is contained in:
Dirkjan Ochtman
2018-11-12 14:53:19 +01:00
parent 76cc763006
commit ac2287e6c1
3 changed files with 124 additions and 71 deletions
+26 -18
View File
@@ -5,7 +5,6 @@ use std::{cmp, io, mem};
use bytes::{Buf, Bytes, BytesMut};
use fnv::{FnvHashMap, FnvHashSet};
use rand::distributions::Distribution;
use slog::Logger;
use coding::{BufExt, BufMutExt};
@@ -346,7 +345,6 @@ impl Connection {
loc_cid: ConnectionId,
rem_cid: ConnectionId,
remote: SocketAddrV6,
initial_packet_number: u64,
client_config: Option<ClientConfig>,
tls: TlsSession,
ctx: &mut Context,
@@ -427,7 +425,7 @@ impl Connection {
largest_sent_before_rto: 0,
time_of_last_sent_retransmittable_packet: 0,
time_of_last_sent_handshake_packet: 0,
largest_sent_packet: initial_packet_number.overflowing_sub(1).0,
largest_sent_packet: 0,
largest_acked_packet: 0,
sent_packets: BTreeMap::new(),
@@ -1203,13 +1201,17 @@ impl Connection {
ctx.incoming_handshakes -= 1;
}
let n = self.get_tx_number();
debug_assert!(n < 64); // handshake_close doesn't have the connection state
// to decide on packet number encoding length; since this
// is about closing the handshake, it seems reasonable to
// assume that the packet number will fit in one byte.
ctx.io.push_back(Io::Transmit {
destination: remote,
packet: handshake_close(
&self.handshake_crypto,
&self.rem_cid,
&self.loc_cid,
n as u32,
n as u8,
state.reason.clone(),
state.alert.as_ref().map(|x| &x[..]),
),
@@ -1250,7 +1252,7 @@ impl Connection {
// Received Retry as a server
debug!(self.log, "received retry from client");
Err(TransportError::PROTOCOL_VIOLATION.into())
} else if state.clienthello_packet.unwrap() as u64 > number {
} else if state.clienthello_packet.unwrap() > number {
// Retry corresponds to an outdated Initial; must be a duplicate, so
// ignore it
Ok(State::Handshake(state))
@@ -1268,7 +1270,6 @@ impl Connection {
self.loc_cid,
rem_cid,
remote,
ctx.initial_packet_number.sample(&mut ctx.rng),
self.client_config.clone(),
tls,
ctx,
@@ -1904,21 +1905,21 @@ impl Connection {
{
if let State::Handshake(ref mut state) = self.state.as_mut().unwrap() {
if state.clienthello_packet.is_none() {
state.clienthello_packet = Some(number as u32);
state.clienthello_packet = Some(number);
}
}
Header::Initial {
src_cid: self.loc_cid,
dst_cid: self.rem_cid,
token: vec![], // TODO: determine what's needed here
number: number as u32,
number: PacketNumber::new(number, self.largest_acked_packet),
}
} else {
Header::Long {
ty: LongType::Handshake,
src_cid: self.loc_cid,
dst_cid: self.rem_cid,
number: number as u32,
number: PacketNumber::new(number, self.largest_acked_packet),
}
};
(
@@ -2148,7 +2149,11 @@ impl Connection {
}
}
if !crypto.is_1rtt() {
set_payload_length(&mut buf, header_len as usize);
let pn_len = match header {
Header::Initial { number, .. } | Header::Long { number, .. } => number.len(),
_ => panic!("invalid header for packet payload length"),
};
set_payload_length(&mut buf, header_len as usize, pn_len);
}
crypto.encrypt(number, &mut buf, header_len as usize);
(number, acks, ack_only, crypto.is_initial())
@@ -2519,7 +2524,7 @@ impl Connection {
(key_phase, number)
}
Header::Initial { number, .. } | Header::Long { number, .. } if handshake => {
(false, PacketNumber::U32(number))
(false, number)
}
_ => {
return Err(None);
@@ -2677,20 +2682,23 @@ pub fn handshake_close<R>(
crypto: &Crypto,
remote_id: &ConnectionId,
local_id: &ConnectionId,
packet_number: u32,
packet_number: u8,
reason: R,
tls_alert: Option<&[u8]>,
) -> Box<[u8]>
where
R: Into<state::CloseReason>,
{
let mut buf = Vec::<u8>::new();
Header::Long {
let number = PacketNumber::U8(packet_number);
let header = Header::Long {
ty: LongType::Handshake,
dst_cid: *remote_id,
src_cid: *local_id,
number: packet_number,
}.encode(&mut buf);
number,
};
let mut buf = Vec::<u8>::new();
header.encode(&mut buf);
let header_len = buf.len();
let max_len = MIN_MTU - header_len as u16 - AEAD_TAG_SIZE as u16;
match reason.into() {
@@ -2707,7 +2715,7 @@ where
}.encode(false, &mut buf);
}
}
set_payload_length(&mut buf, header_len);
set_payload_length(&mut buf, header_len, number.len());
crypto.encrypt(packet_number as u64, &mut buf, header_len);
buf.into()
}
@@ -2855,7 +2863,7 @@ pub mod state {
pub struct Handshake {
/// The number of the packet that first contained the latest version of the TLS
/// ClientHello. Present iff we're the client.
pub clienthello_packet: Option<u32>,
pub clienthello_packet: Option<u64>,
pub rem_cid_set: bool,
pub token: Option<BytesMut>,
}
+10 -18
View File
@@ -5,8 +5,7 @@ use std::{cmp, io};
use bytes::{Bytes, BytesMut};
use fnv::{FnvHashMap, FnvHashSet};
use rand::distributions::Distribution;
use rand::{distributions, rngs::OsRng, Rng, RngCore};
use rand::{rngs::OsRng, Rng, RngCore};
use ring::digest;
use ring::hmac::SigningKey;
use slab::Slab;
@@ -18,7 +17,10 @@ use connection::{
ReadError, State, WriteError,
};
use crypto::{self, reset_token_for, ConnectError, Crypto, ServerConfig};
use packet::{ConnectionId, Header, Packet, PacketDecodeError, PacketNumber, PartialDecode};
use packet::{
ConnectionId, Header, Packet, PacketDecodeError, PacketNumber, PartialDecode,
PACKET_NUMBER_32_MASK,
};
use {
Directionality, Side, StreamId, TransportError, MAX_CID_SIZE, MIN_CID_SIZE, MIN_INITIAL_SIZE,
RESET_TOKEN_SIZE, VERSION,
@@ -153,16 +155,9 @@ pub struct Context {
pub incoming_handshakes: usize,
pub dirty_conns: FnvHashSet<ConnectionHandle>,
pub readable_conns: FnvHashSet<ConnectionHandle>,
pub initial_packet_number: distributions::Uniform<u64>,
pub listen_keys: Option<ListenKeys>,
}
impl Context {
fn gen_initial_packet_num(&mut self) -> u32 {
self.initial_packet_number.sample(&mut self.rng) as u32
}
}
/// Information that should be preserved between restarts for server endpoints.
///
/// Keeping this around allows better behavior by clients that communicated with a previous
@@ -229,7 +224,6 @@ impl Endpoint {
config,
io: VecDeque::new(),
// session_ticket_buffer,
initial_packet_number: distributions::Uniform::from(0..2u64.pow(32) - 1024),
events: VecDeque::new(),
dirty_conns: FnvHashSet::default(),
readable_conns: FnvHashSet::default(),
@@ -418,9 +412,10 @@ impl Endpoint {
).saturating_sub(RESET_TOKEN_SIZE),
);
buf.reserve_exact(header_len + padding + RESET_TOKEN_SIZE);
let number = self.ctx.rng.gen::<u32>() & PACKET_NUMBER_32_MASK | 0x4000;
Header::Short {
dst_cid: ConnectionId::random(&mut self.ctx.rng, MAX_CID_SIZE),
number: PacketNumber::U8(self.ctx.rng.gen()),
number: PacketNumber::U32(number),
key_phase: false,
}.encode(&mut buf);
{
@@ -485,7 +480,6 @@ impl Endpoint {
client_config: Option<ClientConfig>,
) -> ConnectionHandle {
debug_assert!(!local_id.is_empty());
let packet_num = self.ctx.gen_initial_packet_num();
let conn = {
let entry = self.connections.vacant_entry();
let conn = ConnectionHandle(entry.key());
@@ -497,7 +491,6 @@ impl Endpoint {
local_id,
remote_id,
remote,
packet_num.into(),
client_config,
tls,
&mut self.ctx,
@@ -527,6 +520,7 @@ impl Endpoint {
} => (src_cid, dst_cid, number),
_ => panic!("non-initial packet in handle_initial()"),
};
let packet_number = packet_number.expand(0);
if crypto
.decrypt(packet_number as u64, &header_data, &mut payload)
@@ -541,14 +535,13 @@ impl Endpoint {
== self.ctx.config.accept_buffer as usize
{
debug!(self.log, "rejecting connection due to full accept buffer");
let n = self.ctx.gen_initial_packet_num();
self.ctx.io.push_back(Io::Transmit {
destination: remote,
packet: handshake_close(
&crypto,
&src_cid,
&loc_cid,
n,
0,
TransportError::SERVER_BUSY,
None,
),
@@ -567,14 +560,13 @@ impl Endpoint {
Ok(()) => {}
Err(e) => {
debug!(self.log, "handshake failed"; "reason" => %e);
let n = self.ctx.gen_initial_packet_num();
self.ctx.io.push_back(Io::Transmit {
destination: remote,
packet: handshake_close(
&crypto,
&src_cid,
&loc_cid,
n,
0,
TransportError::TLS_HANDSHAKE_FAILED,
None,
),
+88 -35
View File
@@ -4,7 +4,7 @@ use bytes::{BigEndian, Buf, BufMut, ByteOrder, Bytes, BytesMut};
use rand::Rng;
use slog;
use coding::{self, BufExt, BufMutExt};
use coding::{self, BufExt, BufMutExt, Codec};
use {MAX_CID_SIZE, MIN_CID_SIZE, VERSION};
// Due to packet number encryption, it is impossible to fully decode a header
@@ -64,14 +64,7 @@ impl PartialDecode {
let (payload_len, header, allow_coalesced) = match invariant_header {
InvariantHeader::Short { first, dst_cid } => {
let key_phase = first & KEY_PHASE_BIT != 0;
let number = match first & 0b11 {
0x0 => PacketNumber::U8(buf.get()?),
0x1 => PacketNumber::U16(buf.get()?),
0x2 => PacketNumber::U32(buf.get()?),
_ => {
return Err(PacketDecodeError::InvalidHeader("unknown packet type"));
}
};
let number = PacketNumber::decode(&mut buf)?;
(
buf.remaining(),
Header::Short {
@@ -124,7 +117,7 @@ impl PartialDecode {
buf.copy_to_slice(&mut token);
let len = buf.get_var()?;
let number = buf.get()?;
let number = PacketNumber::decode(&mut buf)?;
(
len as usize,
Header::Initial {
@@ -138,7 +131,7 @@ impl PartialDecode {
}
PacketType::Long(ty) => {
let len = buf.get_var()?;
let number = buf.get()?;
let number = PacketNumber::decode(&mut buf)?;
(
len as usize,
Header::Long {
@@ -190,13 +183,13 @@ pub enum Header {
src_cid: ConnectionId,
dst_cid: ConnectionId,
token: Vec<u8>,
number: u32,
number: PacketNumber,
},
Long {
ty: LongType,
src_cid: ConnectionId,
dst_cid: ConnectionId,
number: u32,
number: PacketNumber,
},
Retry {
src_cid: ConnectionId,
@@ -231,7 +224,7 @@ impl Header {
w.write_var(token.len() as u64);
w.put_slice(token);
w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length`
w.write(number);
number.encode(w);
}
Long {
ty,
@@ -243,7 +236,7 @@ impl Header {
w.write(VERSION);
Self::encode_cids(w, dst_cid, src_cid);
w.write::<u16>(0); // Placeholder for payload length; see `set_payload_length`
w.write(number);
number.encode(w);
}
Retry {
ref src_cid,
@@ -261,8 +254,7 @@ impl Header {
number,
key_phase,
} => {
let ty = number.ty() | 0x30 | if key_phase { KEY_PHASE_BIT } else { 0 };
w.write(ty);
w.write(0x30 | if key_phase { KEY_PHASE_BIT } else { 0 });
w.put_slice(dst_cid);
number.encode(w);
}
@@ -369,7 +361,7 @@ impl InvariantHeader {
}
// An encoded packet number
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PacketNumber {
U8(u8),
U16(u16),
@@ -378,9 +370,6 @@ pub enum PacketNumber {
impl PacketNumber {
pub fn new(n: u64, largest_acked: u64) -> Self {
if largest_acked == 0 {
return PacketNumber::U32(n as u32);
}
let range = (n - largest_acked) / 2;
if range < 1 << 8 {
PacketNumber::U8(n as u8)
@@ -393,21 +382,60 @@ impl PacketNumber {
}
}
fn ty(self) -> u8 {
pub fn len(self) -> usize {
use self::PacketNumber::*;
match self {
U8(_) => 0x00,
U16(_) => 0x01,
U32(_) => 0x02,
U8(_) => 1,
U16(_) => 2,
U32(_) => 4,
}
}
pub fn encode<W: BufMut>(self, w: &mut W) {
use self::PacketNumber::*;
match self {
U8(x) => w.write(x),
U16(x) => w.write(x),
U32(x) => w.write(x),
U8(x) => {
debug_assert!(x < 128);
w.write(x)
}
U16(x) => {
debug_assert!(x >= 128);
debug_assert!(x < 16384);
w.write(x | 0x8000)
}
U32(x) => {
debug_assert!(x >= 16384);
debug_assert!(x < 1073741824);
w.write(x | 0xc0000000)
}
}
}
pub fn decode<R: Buf>(r: &mut R) -> Result<PacketNumber, PacketDecodeError> {
use self::PacketNumber::*;
if r.remaining() < 1 {
return Err(coding::UnexpectedEnd.into());
}
let first = r.bytes()[0];
let len = if first < 0x80 {
1
} else if first < 0xc0 {
2
} else {
4
};
if r.remaining() < len {
return Err(coding::UnexpectedEnd.into());
}
match len {
1 => Ok(U8(r.get()?)),
2 => Ok(U16(u16::decode(r)? & PACKET_NUMBER_16_MASK)),
4 => Ok(U32(u32::decode(r)? & PACKET_NUMBER_32_MASK)),
_ => Err(PacketDecodeError::InvalidHeader(
"unable to decode packet number",
)),
}
}
@@ -415,11 +443,7 @@ impl PacketNumber {
use self::PacketNumber::*;
let t = prev + 1;
// Compute missing bits that minimize the difference from expected
let d = match self {
U8(_) => 1 << 8,
U16(_) => 1 << 16,
U32(_) => 1 << 32,
};
let d = 1 << (8 * self.len());
let x = match self {
U8(x) => x as u64,
U16(x) => x as u64,
@@ -595,12 +619,41 @@ impl slog::Value for ConnectionId {
}
}
pub fn set_payload_length(packet: &mut [u8], header_len: usize) {
pub fn set_payload_length(packet: &mut [u8], header_len: usize, pn_len: usize) {
let len = packet.len() - header_len + AEAD_TAG_SIZE;
assert!(len < 2usize.pow(14)); // Fits in reserved space
BigEndian::write_u16(&mut packet[header_len - 6..], len as u16 | 0b01 << 14);
BigEndian::write_u16(
&mut packet[header_len - pn_len - 2..],
len as u16 | 0b01 << 14,
);
}
pub const AEAD_TAG_SIZE: usize = 16;
pub const PACKET_NUMBER_16_MASK: u16 = 0x3fff;
pub const PACKET_NUMBER_32_MASK: u32 = 0x3fffffff;
const LONG_HEADER_FORM: u8 = 0x80;
const KEY_PHASE_BIT: u8 = 0x40;
#[cfg(test)]
mod tests {
use super::PacketNumber;
use std::io;
fn check_pn(typed: PacketNumber, encoded: &[u8]) {
let mut buf = Vec::new();
typed.encode(&mut buf);
assert_eq!(&buf[..encoded.len()], encoded);
let decoded = PacketNumber::decode(&mut io::Cursor::new(&buf)).unwrap();
assert_eq!(typed, decoded);
}
#[test]
fn roundtrip_packet_numbers() {
check_pn(PacketNumber::U8(127), &[0x7f]);
check_pn(PacketNumber::U16(128), &[0x80, 0x80]);
check_pn(PacketNumber::U16(16383), &[0xbf, 0xff]);
check_pn(PacketNumber::U32(16384), &[0xc0, 0x00, 0x40, 0x00]);
check_pn(PacketNumber::U32(1073741823), &[0xff, 0xff, 0xff, 0xff]);
}
}