include collections include order
include quic_types
include quic_transport_error_code
include quic_frame
include quic_transport_parameters
include ip
include quic_fsm_sending
include quic_fsm_receiving
The packet protocol has several funcitons including establishing connections and loss detection. Packets carry frames whihc implement many other funcition of QUIC.
QUIC Packets¤
This section defines the QUIC packet datatype. Packets are the basic
unit of communication between endpoints. A packet may be encoded in
either the long or the short format. There are packet types:
initial, handshake, zero_rtt and one_rtt. The zero_rtt
type is encoded in the short format, while others are encoded in the
long format. Packets have associated source cid (long format only)
and destination cid, protocol version (long format only), and a
packet sequence number. An initial packet also has a retry token, which
is a (possibly empty) sequence of bytes. The payload of the
packet consists of a sequence of frames (see frame.ivy).
Todo
retry and one_rtt packet types
Packet¤
The type quic_packet represents packet.
The fields are:
- ptype: the packet type [2]
- pversion: the protocol version, if long format, else 0 [3]
- dst_cid: the destination cid [4]
- src_cid: the source cid, if long format, else 0 [5]
- token: the retry token (see section 4.4.1) [6]
- seq_num: the packet sequence number [7]
- payload: the payload, a sequence of frames [8]
object quic_packet = {
object quic_packet_initial = { variant this of quic_packet = struct { ptype : quic_packet_type, # [2] dst_cid : cid, # [4] src_cid : cid, # [5] seq_num : pkt_num, # [7] payload : frame.arr # [8] } }
object quic_packet_handshake = { variant this of quic_packet = struct { ptype : quic_packet_type, # [2] dst_cid : cid, # [4] src_cid : cid, # [5] seq_num : pkt_num, # [7] payload : frame.arr # [8] } }
object quic_packet_one_rtt = { variant this of quic_packet = struct { ptype : quic_packet_type, # [2] dst_cid : cid, # [4] src_cid : cid, # [5] seq_num : pkt_num, # [7] payload : frame.arr # [8] } }
object quic_packet_zero_rtt = { variant this of quic_packet = struct { ptype : quic_packet_type, # [2] dst_cid : cid, # [4] src_cid : cid, # [5] seq_num : pkt_num, # [7] payload : frame.arr # [8] } }
type this = struct {
ptype : quic_packet_type, # [2]
pversion : version, # [3]
dst_cid : cid, # [4]
src_cid : cid, # [5]
token : stream_data, # [6]
seq_num : pkt_num, # [7]
payload : frame.arr # [8]
}
instance idx : unbounded_sequence
instance arr : array(idx,this)
}
object quic_packet = {
...
action long(pkt:this) returns(res:bool) = {
res := pkt.ptype ~= quic_packet_type.one_rtt;
}
}
Packet protocol events¤
The packet event¤
This event corresponds to transmission of a QUIC packet.
Parameters¤
src: the source endpointdst: the destination endpointpkt: the packet contents
action packet_event(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) = {}
action send_ack_eliciting_handshake_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) = {}
action send_ack_eliciting_initial_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) = {}
action send_ack_eliciting_application_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) = {}
Packet protocol state¤
This section defines the history variables that track the state of the packet protocol. Some of these variables are shared between protocol layers, so that the allowed interleavings of events at different layers can be specified.
Packet protocol state¤
-
For each cid C,
conn_seen(C)is true if a packet with source cid C has been transmitted. -
For each aid C,
conn_closed(C)is true if C is in the closed state. -
For each aid C,
conn_draining(C)is true if C is in the draining state. -
For each aid C,
draining_pkt_sent(C)is true if the single packet allowed to be sent in transition to the draining state has been sent. This one packet must contain a connection_close frame with error code 0. TODO: we don't check the error code. -
For each endpoint aid C, last_pkt_num(C,L) represents the number of the latest packet sent by C in encryption level L.
-
For each aid C, sent_pkt(C,L,N) is true if a packet numbered N has been sent by C in encryption level L.
-
For each aid C, acked_pkt(C,L,N) is true if a packet numbered N sent by C in encryption level L has been acknowledged.
-
For each aid C, max_acked(C,L) is the greatest packet number N such that acked_pkt(C,L,N), or zero if forall N. ~acked(C,L,N).
-
For each aid C, ack_credit(E,C) is the number of non-ack-only packets sent to C, less the number of ack-only packets sent from C.
-
For each aid C,
trans_params_set(C)is true if C has declared transport parameters. -
For each aid C, function
trans_params(C)gives the transport parameters declared by C. -
The predicate
connected(C)indicates that the peer of aidChas been determined. This occurs conceptually when a server produces a server hello frame. -
The function
connected_to(C)maps the aidCto its peer, if the peer has been determined. -
The function
nonce_cid(C)maps the client aidCto the nonce cid it has chosen for its initial packet. -
The predicate
is_client(C)indicates that aid 'C' is taking the client role. -
The predicate
conn_requested(S,D,C)indicates that a client and endpoint S has requested to open a connection with a server at endpoint D, using cid C. -
The function
hi_non_probing(C)indicates the highest packet number of a non-probing packet sent by aidC. -
The relation
hi_non_probing_endpoint(C,E)that the highest-numbered non-probing packet of aidCwas at some time sent from endpopintE. -
The relation
pkt_has_close(C,L,N)is true if packet numberNsent by aidCcontained a CONNECTION_CLOSE frame. -
The relation cid_mapped(C) is true when connection ID C has been provided as new connection ID for a connection and becomes false when the C is retired
-
If cid_mapped(C) is true, then the function cid_to_aid(C) gives the aid for which C is a new connection ID
-
If aid D created a new connection ID with sequence number S, then seqnum_to_cid(D, S) yield the new connection id.
relation conn_seen(C:cid)
relation conn_closed(C:cid)
relation conn_draining(C:cid)
relation draining_pkt_sent(C:cid)
function last_pkt_num(C:cid,L:quic_packet_type) : pkt_num
relation sent_pkt(C:cid,L:quic_packet_type,N:pkt_num)
relation acked_pkt(C:cid,L:quic_packet_type,N:pkt_num)
function max_acked(C:cid,L:quic_packet_type) : pkt_num
function ack_credit(C:cid) : pkt_num
relation trans_params_set(C:cid)
function trans_params(C:cid) : trans_params_struct
relation connected(C:cid)
function cid_to_aid(C:cid) : cid
relation cid_mapped(C:cid)
function cid_to_token(C:cid) : stream_data
function initial_token : stream_data
relation cid_mapped_token(C:cid)
function seqnum_to_cid (D : cid,S : cid_seq) : cid
function max_seq_num(C:cid) : cid_seq
function connected_to(C:cid) : cid
function nonce_cid(C:cid) : cid
relation is_client(C:cid)
relation conn_requested(S:ip.endpoint,D:ip.endpoint,C:cid)
relation issued_zero_length_cid #(S:ip.endpoint,D:ip.endpoint) TODO pass that to handle function of frames
function hi_non_probing(C:cid) : pkt_num
relation hi_non_probing_endpoint(C:cid,E:ip.endpoint)
relation pkt_has_close(C:cid,L:quic_packet_type,N:pkt_num)
function num_conn(C:cid) : stream_pos
relation tls_handshake_finished
relation migration_done
relation first_initial_send
function initial_scid : cid
function initial_dcid : cid
relation is_lost(C:cid,L:quic_packet_type,N:pkt_num)
function is_retransmitted(P:frame.arr) : pkt_num
function dst_endpoint : ip.endpoint
relation address_validated
relation path_validated
function path_validated_pkt_num: pkt_num
function anti_amplification_limit : stream_pos
relation is_not_sleeping
The history variables are initialized as follows. Initially, no connections have been seen and no packets have been sent or acknowledged.
after init {
conn_seen(C) := false;
last_pkt_num(C,L) := 0;
conn_closed(C) := false;
conn_draining(C) := false;
draining_pkt_sent(C) := false;
sent_pkt(C,L,N) := false;
acked_pkt(C,L,N) := false;
pkt_has_close(C,L,N) := false;
max_acked(C,L) := 0;
ack_credit(C) := 0;
trans_params_set(C:cid) := false;
is_client(C) := false;
conn_requested(S,D,C) := false;
hi_non_probing(C) := 0;
hi_non_probing_endpoint(C,E) := false;
cid_mapped(C) := false;
max_seq_num(C) := 0;
num_conn(C) := 1;
migration_done := false;
tls_handshake_finished := false;
first_initial_send := false;
issued_zero_length_cid := false;
is_retransmitted(P) := 0;
is_not_sleeping := true;
}
A packet event represents the transmision of a QUIC packet pkt
from source endpoint src to a QUIC destination endpoint dst
containing a sequence of queued frames.
Requirements¤
-
The packet payload may not be empty [7].
-
A sender may not re-use a packet number on a given connection [4].
-
A packet containing only ack frames and padding is ack-only. For a given cid, the number of ack-only packets sent from src to dst must not be greater than the number of non-ack-only packets sent from dst to src [5].
-
For a given connection, a server must only send packets to an address that at one time in the past sent the as packet with the highest packet numer thus far received. See notes below on migration and path challenge. [10]
-
Token Length: A variable-length integer specifying the length of the Token field, in bytes. This value is zero if no token is present. Initial packets sent by the server MUST set the Token Length field to zero; clients that receive an Initial packet with a non-zero Token Length field MUST either discard the packet or generate a connection error of type PROTOCOL_VIOLATION. [11]
Upon receiving the client's Initial packet, the server can request address validation by sending a Retry packet (Section 17.2.5) containing a token. This token MUST be repeated by the client in all Initial packets it sends for that connection after it receives the Retry packet.
Effects¤
- The
conn_seenandsent_pktrelations are updated to reflect the observed packet [1]. - The
last_pkt_numfunction is updated to indicate the observed packets as most recent for the packet's source and cid.
Notes¤
-
In the low-level packet encoding, the packet number may be encoded using a small number of bytes, in a way that loses information. At this layer of the protocol, however, the packets contain the original full packet number.
-
On seeing a packet form a new address with the highest packect number see thus far, the server detects migration of the client. It begins sending packets to this address and initiates path validation for this address. Until path validation succeeds, the server limits data sent to the new address. Currently we cannot specify this limit because we don't know the byte size of packets or the timings of packets.
An endpoint MUST treat the following as a connection error of type TRANSPORT_PARAMETER_ERROR or PROTOCOL_VIOLATION: * absence of the retry_source_connection_id transport parameter from the server after receiving a Retry packet, [1] * presence of the retry_source_connection_id transport parameter when no Retry packet was received, or [3] * a mismatch between values received from a peer in these transport parameters and the value sent in the corresponding Destination or Source Connection ID fields of Initial packets. [2] If a zero-length connection ID is selected, the corresponding transport parameter is included with a zero-length value.
import action show_is_sleep_fake_timeout(is_sleep_fake_timeout:bool)
import action show_queued_frames(scid:cid,queued_frames:frame.arr)
import action show_current_time(t:milliseconds)
around packet_event(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
call show_current_time(time_api.c_timer.now_millis_last_bp);
if _generating {
};
if ~_generating {
dst_endpoint := dst;
} else {
}
require pkt.ptype ~= quic_packet_type.zero_rtt & pkt.ptype ~= quic_packet_type.version_negociation & pkt.ptype ~= quic_packet_type.retry;
var dcid := pkt.dst_cid;
var scid := pkt.src_cid;
call show_num_ack_eliciting_pkt(num_ack_eliciting_pkt);
call show_num_ack_pkt(num_ack_pkt);
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
idle_timeout(dcid) := time_api.c_timer.now_micros_last_bp;
call show_current_idle_timeout(idle_timeout(dcid));
call show_current_idle_timeout(idle_timeout(scid));
is_retransmitted(pkt.payload) := is_retransmitted(pkt.payload) + 1;
call show_is_retransmitted(seqnum_to_streampos(pkt.seq_num),is_retransmitted(pkt.payload));
if max_idle_timeout_used > 0 {
if idle_timeout(dcid) > max_idle_timeout_used {
call show_connection_timeout(idle_timeout(dcid), max_idle_timeout_used, pto_timeout*3);
connection_timeout := true;
}
}
call show_probe_idle_timeout(pto_timeout*3);
if ~_generating & connection_timeout & is_retransmitted(pkt.payload) < 2 & idle_timeout(dcid) > pto_timeout*3 {
respect_idle_timeout := false;
call show_connection_timeout(idle_timeout(dcid), max_idle_timeout_used, pto_timeout*3);
call respect_idle_timeout_none;
}
require pkt.token.end ~= 0 -> (retry_sent(client_initial_rcid) | retry_recv(scid) | pkt.token = tls_api.upper.get_old_new_token);
if pkt.token.end ~= 0 { #
address_validated := true;
anti_amplification_limit_reached := true;
};
if (src = client_alt | dst = client_alt) & nclients = 1 {
migration_done := true;
};
if ~_generating { if ~migration_done { cid_ip_mapped(dst) := pkt.dst_cid; } else { cid_ip_mapped(dst) := pkt.dst_cid; }; };
The destination cid must represent an existing connection, except in the case of a client initial packet, when the destination cid may be the nonce cid chosen by the client for the given source cid. TODO: The standard says that on receiving the the server's cid, the clint must switch to it. However, we can't know when that server's cid has actually been received by the client. As an example, after the server sends its first initial packet, the client might still retransmit its initial packet using the nonce cid. In some cases, we can infer that the client has in fact seen the server's cid (for example, if it packet contains an ACK frame, or a CRYPTO frame that is reponsive to a server FRAME. This is trick to check, however, and the actual servers do not seem to check it.
require connected(dcid) |
pkt.ptype = quic_packet_type.initial
& is_client(scid)
& dcid = nonce_cid(scid);
if pkt.long {
require connected(dcid) -> connected_to(dcid) = scid;
} else {
scid := connected_to(dcid);
};
if retry_sent(dcid) & ~_generating & ~zero_length_token & pkt.ptype = quic_packet_type.initial {
require dcid = client_initial_rcid;
};
require pkt.ptype = queued_level(scid);
if _generating {
require pkt.seq_num = last_pkt_num(scid,pkt.ptype) + 0x1; # OK
};
if queued_ack_eliciting(scid) {
}
};
last_pkt_num(scid,pkt.ptype) := pkt.seq_num;require ~sent_pkt(scid,pkt.ptype,pkt.seq_num); # [4]sent_pkt(scid,pkt.ptype,pkt.seq_num) := true; # [1]
The payload may not be empty
require num_queued_frames(scid) > 0; # [7]
The payload must exactly match the queued frames.
if _generating {if packets_to_retransmit_end(pkt.ptype) > 0 {var paylo := packets_to_retransmit(pkt.ptype,packets_to_retransmit_end(pkt.ptype));# TODO change and put that at frame levelrequire pkt.payload = paylo;packets_to_retransmit_end(pkt.ptype) := packets_to_retransmit_end(pkt.ptype) - 1;call show_retransmit_lost_packet(paylo);}else {require pkt.payload = queued_frames(scid);};}else {call show_queued_frames(scid,queued_frames(scid));call is_generating(_generating);call show_pkt_num(pkt.seq_num);require pkt.payload = queued_frames(scid);};
TEMPORARY: don't allow client migration during handshake
require conn_seen(scid) & pkt.long & is_client(scid) -> conn_requested(src,dst,scid);
require conn_seen(dcid) -> hi_non_probing_endpoint(dcid,dst); # [10]
call show_ack_credit(scid, ack_credit(scid), queued_ack_eliciting(scid), queued_non_ack(scid), pkt.seq_num);
require ~_generating & ~queued_non_ack(scid) -> ack_credit(scid) > 0; # [5]
Question
THis sentence is from draft-18 section 13.1: "An endpoint MUST NOT send a packet containing only an ACK frame in response to a packet containing only ACK or PADDING frames, even if there are packet gaps which precede the received packet." Do we interpret this to mean that an ack-only packet cannot ack only ack-only packets? Or that an ack-only packet cannot ack any ack-only packets?
If the sender is in the draining state, this is the draining packet. Make sure that a draining packet has not previously been sent and that the packet contains a connection_close frame;
if conn_draining(scid) {
require ~draining_pkt_sent(scid) & queued_close(scid);
};
...
Temporary
The following are repeated because currently locals defined in the "before" section cannot be accessed in the "after" section.
var dcid := pkt.dst_cid;
var scid := pkt.src_cid if pkt.long else connected_to(dcid);
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
if pkt.ptype = quic_packet_type.initial {
initial_token := pkt.token;
if ~conn_seen(dcid) {
if ~zero_rtt_sent {
call show_initial_request_initial;
call tls_client_initial_request(src,dst,dcid,pkt.pversion,src_tls_id(src));
};
conn_requested(src,dst,scid) := true;
};
cid_to_token(dcid) := pkt.token;
};
conn_seen(scid) := true; # [1]
Hamid
if queued_non_ack(scid) { ack_credit(dcid) := ack_credit(dcid) + 1; } else { ack_credit(scid) := ack_credit(scid) - 1; };
if queued_ack_eliciting(scid) {
if _generating {
num_ack_eliciting_pkt := num_ack_eliciting_pkt + 1;
}
ack_eliciting_threshold_current_val(dcid) := ack_eliciting_threshold_current_val(dcid) + 1;
ack_credit(dcid) := ack_credit(dcid) + 1;
} else {
if ~_generating {
num_ack_pkt:= num_ack_pkt + 1;
}
};
if ~queued_non_ack(scid) {
ack_credit(scid) := ack_credit(scid) - 1;
};
If this is a non-probing packet, update the highest non-probing packet number seen on from this aid. QUESTION: what if two different paths send the same packet number? QUESTION: how do you compare packet numbers with different encryption levels?
if queued_non_probing(scid) {
if pkt.ptype = quic_packet_type.one_rtt {
if pkt.seq_num >= hi_non_probing(scid) {
hi_non_probing(scid) := pkt.seq_num;
hi_non_probing_endpoint(scid,src) := true;
}
} else {
hi_non_probing_endpoint(scid,src) := true;
}
};
pkt_has_close
if queued_close(scid) {
pkt_has_close(scid,pkt.ptype,pkt.seq_num) := true;
};
if conn_draining(scid) {
draining_pkt_sent(scid) := true
};
if pkt.ptype = quic_packet_type.initial & ~first_initial_send & zero_rtt_allowed {
first_initial_send := true;
initial_scid := scid;
initial_dcid := dcid;
queued_level(the_cid) := quic_packet_type.zero_rtt; # todo multiple client
} else {
first_initial_send := false;
};
queued_frames(scid) := frame.arr.empty;
queued_non_probing(scid) := false;
queued_non_ack(scid) := false;
queued_close(scid) := false;
num_queued_frames(scid) := 0;
queued_ack_eliciting(scid) := false;
}
import action show_retransmit_lost_packet(paylo:frame.arr)
import action show_connection_timeout(idle_timeout:microseconds, max_idle_timeout_used:microseconds, pto_timeout:microseconds)
around send_ack_eliciting_handshake_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
require pkt.ptype = quic_packet_type.handshake;
require need_sent_ack_eliciting_handshake_packet;
var dcid := pkt.dst_cid;
var scid := pkt.src_cid;
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
require pkt.ptype = queued_level(scid);
require pkt.token.end ~= 0 -> (retry_sent(client_initial_rcid) | retry_recv(scid) | pkt.token = tls_api.upper.get_old_new_token);
if (src = client_alt | dst = client_alt) & nclients = 1 {
migration_done := true;
};
The destination cid must represent an existing connection, except in the case of a client initial packet, when the destination cid may be the nonce cid chosen by the client for the given source cid. TODO: The standard says that on receiving the the server's cid, the clint must switch to it. However, we can't know when that server's cid has actually been received by the client. As an example, after the server sends its first initial packet, the client might still retransmit its initial packet using the nonce cid. In some cases, we can infer that the client has in fact seen the server's cid (for example, if it packet contains an ACK frame, or a CRYPTO frame that is reponsive to a server FRAME. This is trick to check, however, and the actual servers do not seem to check it.
require connected(dcid) |
pkt.ptype = quic_packet_type.initial
& is_client(scid)
& dcid = nonce_cid(scid);
if pkt.long {
require connected(dcid) -> connected_to(dcid) = scid;
} else {
scid := connected_to(dcid);
};
if retry_sent(dcid) & ~_generating & ~zero_length_token & pkt.ptype = quic_packet_type.initial {
require dcid = client_initial_rcid;
};
if _generating {
require pkt.seq_num = last_pkt_num(scid,pkt.ptype) + 0x1; # OK
};
if queued_ack_eliciting(scid) {
}
};
last_pkt_num(scid,pkt.ptype) := pkt.seq_num;require ~sent_pkt(scid,pkt.ptype,pkt.seq_num); # [4]sent_pkt(scid,pkt.ptype,pkt.seq_num) := true; # [1]
The payload may not be empty
require num_queued_frames(scid) > 0; # [7]
require pkt.payload = queued_frames(scid);
require conn_seen(scid) & pkt.long & is_client(scid) -> conn_requested(src,dst,scid);
require conn_seen(dcid) -> hi_non_probing_endpoint(dcid,dst); # [10]
call show_ack_credit(scid, ack_credit(scid), queued_ack_eliciting(scid), queued_non_ack(scid), pkt.seq_num);
require ~_generating & ~queued_non_ack(scid) -> ack_credit(scid) > 0; # [5]
Question
THis sentence is from draft-18 section 13.1: "An endpoint MUST NOT send a packet containing only an ACK frame in response to a packet containing only ACK or PADDING frames, even if there are packet gaps which precede the received packet." Do we interpret this to mean that an ack-only packet cannot ack only ack-only packets? Or that an ack-only packet cannot ack any ack-only packets?
If the sender is in the draining state, this is the draining packet. Make sure that a draining packet has not previously been sent and that the packet contains a connection_close frame;
if conn_draining(scid) {
require ~draining_pkt_sent(scid) & queued_close(scid);
};
...
Temporary
The following are repeated because currently locals defined in the "before" section cannot be accessed in the "after" section.
var dcid := pkt.dst_cid;
var scid := pkt.src_cid if pkt.long else connected_to(dcid);
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
Todo
the following should not be here
if pkt.ptype = quic_packet_type.initial {
initial_token := pkt.token;
if ~conn_seen(dcid) {
if ~zero_rtt_sent {
call show_initial_request_initial;
call tls_client_initial_request(src,dst,dcid,pkt.pversion,src_tls_id(src));
};
conn_requested(src,dst,scid) := true;
};
cid_to_token(dcid) := pkt.token;
};
conn_seen(scid) := true; # [1]
Hamid
if queued_non_ack(scid) { ack_credit(dcid) := ack_credit(dcid) + 1; } else { ack_credit(scid) := ack_credit(scid) - 1; };
if queued_ack_eliciting(scid) {
ack_credit(dcid) := ack_credit(dcid) + 1;
};
if ~queued_non_ack(scid) {
ack_credit(scid) := ack_credit(scid) - 1;
};
If this is a non-probing packet, update the highest non-probing packet number seen on from this aid. QUESTION: what if two different paths send the same packet number? QUESTION: how do you compare packet numbers with different encryption levels?
if queued_non_probing(scid) {
if pkt.ptype = quic_packet_type.one_rtt {
if pkt.seq_num >= hi_non_probing(scid) {
hi_non_probing(scid) := pkt.seq_num;
hi_non_probing_endpoint(scid,src) := true;
}
} else {
hi_non_probing_endpoint(scid,src) := true;
}
};
pkt_has_close
if queued_close(scid) {
pkt_has_close(scid,pkt.ptype,pkt.seq_num) := true;
};
if conn_draining(scid) {
draining_pkt_sent(scid) := true
};
if pkt.ptype = quic_packet_type.initial & ~first_initial_send & zero_rtt_allowed {
first_initial_send := true;
initial_scid := scid;
initial_dcid := dcid;
queued_level(the_cid) := quic_packet_type.zero_rtt; # todo multiple client
} else {
first_initial_send := false;
};
queued_frames(scid) := frame.arr.empty;
queued_non_probing(scid) := false;
queued_non_ack(scid) := false;
queued_close(scid) := false;
num_queued_frames(scid) := 0;
queued_ack_eliciting(scid) := false;
need_sent_ack_eliciting_handshake_packet := false
}
around send_ack_eliciting_application_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
if _generating {
};
require pkt.ptype = quic_packet_type.one_rtt;
require need_sent_ack_eliciting_application_packet;
var dcid := pkt.dst_cid;
var scid := pkt.src_cid;
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
require pkt.ptype = queued_level(scid);
require pkt.token.end = 0;
if (src = client_alt | dst = client_alt) & nclients = 1 {
migration_done := true;
};
The destination cid must represent an existing connection, except in the case of a client initial packet, when the destination cid may be the nonce cid chosen by the client for the given source cid. TODO: The standard says that on receiving the the server's cid, the clint must switch to it. However, we can't know when that server's cid has actually been received by the client. As an example, after the server sends its first initial packet, the client might still retransmit its initial packet using the nonce cid. In some cases, we can infer that the client has in fact seen the server's cid (for example, if it packet contains an ACK frame, or a CRYPTO frame that is reponsive to a server FRAME. This is trick to check, however, and the actual servers do not seem to check it.
require connected(dcid) |
pkt.ptype = quic_packet_type.initial
& is_client(scid)
& dcid = nonce_cid(scid);
if pkt.long {
require connected(dcid) -> connected_to(dcid) = scid;
} else {
scid := connected_to(dcid);
};
if retry_sent(dcid) & ~_generating & ~zero_length_token & pkt.ptype = quic_packet_type.initial {
require dcid = client_initial_rcid;
};
if _generating {
require pkt.seq_num = last_pkt_num(scid,pkt.ptype) + 0x1; # OK
};
if queued_ack_eliciting(scid) {
}
};
last_pkt_num(scid,pkt.ptype) := pkt.seq_num;require ~sent_pkt(scid,pkt.ptype,pkt.seq_num); # [4]sent_pkt(scid,pkt.ptype,pkt.seq_num) := true; # [1]
The payload may not be empty
require num_queued_frames(scid) > 0; # [7]
require pkt.payload = queued_frames(scid);
require conn_seen(scid) & pkt.long & is_client(scid) -> conn_requested(src,dst,scid);
require conn_seen(dcid) -> hi_non_probing_endpoint(dcid,dst); # [10]
call show_ack_credit(scid, ack_credit(scid), queued_ack_eliciting(scid), queued_non_ack(scid), pkt.seq_num);
require ~_generating & ~queued_non_ack(scid) -> ack_credit(scid) > 0; # [5]
Question
THis sentence is from draft-18 section 13.1: "An endpoint MUST NOT send a packet containing only an ACK frame in response to a packet containing only ACK or PADDING frames, even if there are packet gaps which precede the received packet." Do we interpret this to mean that an ack-only packet cannot ack only ack-only packets? Or that an ack-only packet cannot ack any ack-only packets?
If the sender is in the draining state, this is the draining packet. Make sure that a draining packet has not previously been sent and that the packet contains a connection_close frame;
if conn_draining(scid) {
require ~draining_pkt_sent(scid) & queued_close(scid);
};
...
Temporary
The following are repeated because currently locals defined in the "before" section cannot be accessed in the "after" section.
var dcid := pkt.dst_cid;
var scid := pkt.src_cid if pkt.long else connected_to(dcid);
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
Todo
the following should not be here
if pkt.ptype = quic_packet_type.initial {
initial_token := pkt.token;
if ~conn_seen(dcid) {
if ~zero_rtt_sent {
call show_initial_request_initial;
call tls_client_initial_request(src,dst,dcid,pkt.pversion,src_tls_id(src));
};
conn_requested(src,dst,scid) := true;
};
cid_to_token(dcid) := pkt.token;
};
conn_seen(scid) := true; # [1]
Hamid
if queued_non_ack(scid) { ack_credit(dcid) := ack_credit(dcid) + 1; } else { ack_credit(scid) := ack_credit(scid) - 1; };
if queued_ack_eliciting(scid) {
ack_credit(dcid) := ack_credit(dcid) + 1;
} else {
};
if ~queued_non_ack(scid) {
ack_credit(scid) := ack_credit(scid) - 1;
};
If this is a non-probing packet, update the highest non-probing packet number seen on from this aid. QUESTION: what if two different paths send the same packet number? QUESTION: how do you compare packet numbers with different encryption levels?
if queued_non_probing(scid) {
if pkt.ptype = quic_packet_type.one_rtt {
if pkt.seq_num >= hi_non_probing(scid) {
hi_non_probing(scid) := pkt.seq_num;
hi_non_probing_endpoint(scid,src) := true;
}
} else {
hi_non_probing_endpoint(scid,src) := true;
}
};
pkt_has_close
if queued_close(scid) {
pkt_has_close(scid,pkt.ptype,pkt.seq_num) := true;
};
if conn_draining(scid) {
draining_pkt_sent(scid) := true
};
if pkt.ptype = quic_packet_type.initial & ~first_initial_send & zero_rtt_allowed {
first_initial_send := true;
initial_scid := scid;
initial_dcid := dcid;
queued_level(the_cid) := quic_packet_type.zero_rtt; # todo multiple client
} else {
first_initial_send := false;
};
queued_frames(scid) := frame.arr.empty;
queued_non_probing(scid) := false;
queued_non_ack(scid) := false;
queued_close(scid) := false;
num_queued_frames(scid) := 0;
queued_ack_eliciting(scid) := false;
need_sent_ack_eliciting_application_packet := false
}
around send_ack_eliciting_initial_packet(src:ip.endpoint,dst:ip.endpoint,pkt:quic_packet) {
}; require (~is_not_sleeping -> ~_generating);
require pkt.ptype = quic_packet_type.initial;
require need_sent_ack_eliciting_initial_packet;
var dcid := pkt.dst_cid;
var scid := pkt.src_cid;
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
require pkt.ptype = queued_level(scid);
require pkt.token.end ~= 0 -> (retry_sent(client_initial_rcid) | retry_recv(scid) | pkt.token = tls_api.upper.get_old_new_token);
if (src = client_alt | dst = client_alt) & nclients = 1 {
migration_done := true;
};
The destination cid must represent an existing connection, except in the case of a client initial packet, when the destination cid may be the nonce cid chosen by the client for the given source cid. TODO: The standard says that on receiving the the server's cid, the clint must switch to it. However, we can't know when that server's cid has actually been received by the client. As an example, after the server sends its first initial packet, the client might still retransmit its initial packet using the nonce cid. In some cases, we can infer that the client has in fact seen the server's cid (for example, if it packet contains an ACK frame, or a CRYPTO frame that is reponsive to a server FRAME. This is trick to check, however, and the actual servers do not seem to check it.
require connected(dcid) |
pkt.ptype = quic_packet_type.initial
& is_client(scid)
& dcid = nonce_cid(scid);
if pkt.long {
require connected(dcid) -> connected_to(dcid) = scid;
} else {
scid := connected_to(dcid);
};
if retry_sent(dcid) & ~_generating & ~zero_length_token & pkt.ptype = quic_packet_type.initial {
require dcid = client_initial_rcid;
};
require pkt.seq_num = last_pkt_num(scid,pkt.ptype) + 0x15; # OK
if queued_ack_eliciting(scid) {
}
};
last_pkt_num(scid,pkt.ptype) := pkt.seq_num;require ~sent_pkt(scid,pkt.ptype,pkt.seq_num); # [4]sent_pkt(scid,pkt.ptype,pkt.seq_num) := true; # [1]
The payload may not be empty
require num_queued_frames(scid) > 0; # [7]
require pkt.payload = queued_frames(scid);
require conn_seen(scid) & pkt.long & is_client(scid) -> conn_requested(src,dst,scid);
require conn_seen(dcid) -> hi_non_probing_endpoint(dcid,dst); # [10]
call show_ack_credit(scid, ack_credit(scid), queued_ack_eliciting(scid), queued_non_ack(scid), pkt.seq_num);
require ~_generating & ~queued_non_ack(scid) -> ack_credit(scid) > 0; # [5]
Question
THis sentence is from draft-18 section 13.1: "An endpoint MUST NOT send a packet containing only an ACK frame in response to a packet containing only ACK or PADDING frames, even if there are packet gaps which precede the received packet." Do we interpret this to mean that an ack-only packet cannot ack only ack-only packets? Or that an ack-only packet cannot ack any ack-only packets?
If the sender is in the draining state, this is the draining packet. Make sure that a draining packet has not previously been sent and that the packet contains a connection_close frame;
if conn_draining(scid) {
require ~draining_pkt_sent(scid) & queued_close(scid);
};
...
Temporary
The following are repeated because currently locals defined in the "before" section cannot be accessed in the "after" section.
var dcid := pkt.dst_cid;
var scid := pkt.src_cid if pkt.long else connected_to(dcid);
if cid_mapped(dcid) {
dcid := cid_to_aid(dcid);
};
Todo
the following should not be here
if pkt.ptype = quic_packet_type.initial {
initial_token := pkt.token;
if ~conn_seen(dcid) {
if ~zero_rtt_sent {
call show_initial_request_initial;
call tls_client_initial_request(src,dst,dcid,pkt.pversion,src_tls_id(src));
};
conn_requested(src,dst,scid) := true;
};
cid_to_token(dcid) := pkt.token;
};
conn_seen(scid) := true; # [1]
Hamid
if queued_non_ack(scid) { ack_credit(dcid) := ack_credit(dcid) + 1; } else { ack_credit(scid) := ack_credit(scid) - 1; };
if queued_ack_eliciting(scid) {
ack_credit(dcid) := ack_credit(dcid) + 1;
};
if ~queued_non_ack(scid) {
ack_credit(scid) := ack_credit(scid) - 1;
};
If this is a non-probing packet, update the highest non-probing packet number seen on from this aid. QUESTION: what if two different paths send the same packet number? QUESTION: how do you compare packet numbers with different encryption levels?
if queued_non_probing(scid) {
if pkt.ptype = quic_packet_type.one_rtt {
if pkt.seq_num >= hi_non_probing(scid) {
hi_non_probing(scid) := pkt.seq_num;
hi_non_probing_endpoint(scid,src) := true;
}
} else {
hi_non_probing_endpoint(scid,src) := true;
}
};
pkt_has_close
if queued_close(scid) {
pkt_has_close(scid,pkt.ptype,pkt.seq_num) := true;
};
if conn_draining(scid) {
draining_pkt_sent(scid) := true
};
if pkt.ptype = quic_packet_type.initial & ~first_initial_send & zero_rtt_allowed {
first_initial_send := true;
initial_scid := scid;
initial_dcid := dcid;
queued_level(the_cid) := quic_packet_type.zero_rtt; # todo multiple client
} else {
first_initial_send := false;
};
queued_frames(scid) := frame.arr.empty;
queued_non_probing(scid) := false;
queued_non_ack(scid) := false;
queued_close(scid) := false;
num_queued_frames(scid) := 0;
queued_ack_eliciting(scid) := false;
need_sent_ack_eliciting_initial_packet := false;
}
import action show_initial_request_initial
import action show_is_retransmitted(p:stream_pos, c_time:pkt_num)
import action respect_idle_timeout_none
TLS extensions are used in the client hello and server hello
messages to carry the QUIC transport parameters, via special TLS
extension type quic_transport_parameters. This type is defined in
the reference quic_transport_parameters. Here we have procedures
that infer the transport parameters events from the TLS handshake
messages that transmit the parameters.
Todo
This inference is not really needed. It should be possible to obtain the parameters directly from the TLS API. We infer the parameters here only for historical reasons.
The handle_tls_handshake rules apply to each client_hello,
server_hello or encrypted extensions message, in order of
occurrence in the crypto data. The last parameter,
is_client_hello, is true for a client_hello message (see below).
action handle_tls_handshake(src:ip.endpoint, dst:ip.endpoint, scid:cid, dcid:cid,hs:tls.handshake) = {
if some(ch:tls.client_hello) hs *> ch {
is_client(scid) := true;
call handle_tls_extensions(src,dst,scid,ch.extensions,true);
}
else if some(sh:tls.server_hello) hs *> sh {
call handle_tls_extensions(src,dst,scid,sh.extensions,false);
}
else if some(ee:tls.encrypted_extensions) hs *> ee {
call handle_tls_extensions(src,dst,scid,ee.extensions,false);
}
if some(fh:tls.finished) hs *> fh {
tls_handshake_finished := true;
}
}
The rules in handle_client_transport_parameters apply to each
quic_transport_parameters extension instance in order of
occurrence.
action handle_tls_extensions(src:ip.endpoint,dst:ip.endpoint, scid:cid, exts:vector[tls.extension], is_client_hello:bool) = {
var idx := exts.begin;
while idx < exts.end {
var ext := exts.value(idx);
quic_transport_parameters extension...
if some (tps:quic_transport_parameters) ext *> tps {
call handle_client_transport_parameters(src,dst,scid,tps,is_client_hello);
trans_params_set(scid) := true;
};
idx := idx.next
};
}
handle_transport_parameter apply to each
transport_parameter instance a quic_transport_parameters
extension, in order of occurrence.
Requirements:
- The endpoint must issue an
initial_max_stream_datavalue [1]. [not anymore] - The endpoint must issue an
initial_max_datavalue [2]. - The endpoint must issue an
max_idle_timeoutvalue [3]. [not anymore chris] - A client must not issue an
stateless_reset_tokenvalue [4]. - The endpoint must issue an
initial_source_connection_idvalue [5]. [chris] - A server must issue an
original_destination_connection_idvalue [6].
An endpoint's min_ack_delay MUST NOT be greater than the its max_ack_delay. Endpoints that support this extension MUST treat receipt of a min_ack_delay that is greater than the received max_ack_delay as a connection error of type TRANSPORT_PARAMETER_ERROR. [7] Note that while the endpoint's max_ack_delay transport parameter is in milliseconds (Section 18.2 of [QUIC-TRANSPORT]), min_ack_delay is specified in microseconds. An endpoint MUST treat the following as a connection error of type TRANSPORT_PARAMETER_ERROR or PROTOCOL_VIOLATION: * absence of the retry_source_connection_id transport parameter from the server after receiving a Retry packet, Note:
- Setting a transport parameter requires that the parameter is not previously set.
action handle_client_transport_parameters(src:ip.endpoint,dst:ip.endpoint,scid:cid, tps:quic_transport_parameters, is_client_hello : bool) = {
var idx := tps.transport_parameters.begin;
while idx < tps.transport_parameters.end {
trans_params(scid) := tps.transport_parameters.value(idx).set(trans_params(scid));
idx := idx.next
};
if ~ _generating {
if client_non_zero_scil & ~(scid = 1) & (client_initial_version = 0x00000001 | client_initial_version = 0x00000001){ # TODO
require initial_source_connection_id.is_set(trans_params(scid)); # [5]
};
};
if is_client_hello {
require ~stateless_reset_token.is_set(trans_params(scid)); # [4]
} else {
if ~ _generating & ~(scid = 1) { # TODO
require original_destination_connection_id.is_set(trans_params(scid)); # [6]
};
};
if ~is_client_hello & (retry_recv(client_initial_rcid) | retry_sent(client_initial_rcid)) & ~zero_length_token {
require retry_source_connection_id.is_set(trans_params(scid));
require retry_source_connection_id.value(trans_params(scid)).rcid = client_initial_rcid; # todo generalize
};
if max_ack_delay.is_set(trans_params(scid)) {
max_ack_delay_tp := milliseconds_to_microseconds(max_ack_delay.value(trans_params(scid)).exponent_8);
call show_max_ack_delay(max_ack_delay_tp);
}
if ack_delay_exponent.is_set(trans_params(scid)) {
require ack_delay_exponent.value(trans_params(scid)).exponent_8 <= 20;
ack_delay_exponent_tp := ack_delay_exponent.value(trans_params(scid)).exponent_8;
call show_ack_delay_exponent(ack_delay_exponent_tp);
}
if min_ack_delay.is_set(trans_params(scid)) & max_ack_delay.is_set(trans_params(scid)) {
var min_ack_milli := min_ack_delay.value(trans_params(scid)).exponent_8; # * 1000;
require min_ack_milli < milliseconds_to_microseconds(max_ack_delay.value(trans_params(scid)).exponent_8); # [1]
};
if max_idle_timeout.is_set(trans_params(scid)) {
if is_client_hello {
max_idle_timeout_client := milliseconds_to_microseconds(max_idle_timeout.value(trans_params(scid)).seconds_16)
} else {
max_idle_timeout_server := milliseconds_to_microseconds(max_idle_timeout.value(trans_params(scid)).seconds_16)
}
if max_idle_timeout_client = 0 {
max_idle_timeout_used := max_idle_timeout_server;
} else if max_idle_timeout_server = 0 {
max_idle_timeout_used := max_idle_timeout_client;
} else if max_idle_timeout_server < max_idle_timeout_client {
max_idle_timeout_used := max_idle_timeout_server;
} else {
max_idle_timeout_used := max_idle_timeout_client;
}
call max_idle_timeout_update(max_idle_timeout_used);
}
if zero_rtt_server_test & ~is_client_hello {
if initial_max_stream_data_uni.is_set(trans_params(scid)) {
require initial_max_stream_data_uni.value(trans_params(scid)).stream_pos_32 >= initial_max_stream_data_uni_server_0rtt; # todo generalize
};
if initial_max_stream_data_bidi_remote.is_set(trans_params(scid)) {
require initial_max_stream_data_bidi_remote.value(trans_params(scid)).stream_pos_32 >= initial_max_stream_data_bidi_remote_server_0rtt; # todo generalize
};
if initial_max_data.is_set(trans_params(scid)) {
require initial_max_data.value(trans_params(scid)).stream_pos_32 >= initial_max_data_server_0rtt; # todo generalize
};
if initial_max_stream_data_bidi_local.is_set(trans_params(scid)) {
require initial_max_stream_data_bidi_local.value(trans_params(scid)).stream_pos_32 >= initial_max_stream_data_bidi_local_server_0rtt; # todo generalize
};
if initial_max_stream_id_bidi.is_set(trans_params(scid)) {
require initial_max_stream_id_bidi.value(trans_params(scid)).stream_id_16 >= initial_max_stream_id_bidi_server_0rtt; # todo generalize
};
if active_connection_id_limit.is_set(trans_params(scid)) {
require active_connection_id_limit.value(trans_params(scid)).stream_pos_32 >= active_connection_id_limit_server_0rtt; # todo generalize
};
};
if ~is_client_hello {
if initial_max_stream_data_uni.is_set(trans_params(scid)) {
call tls_api.upper.save_initial_max_stream_data_uni(initial_max_stream_data_uni.value(trans_params(scid)).stream_pos_32);
};
if initial_max_stream_data_bidi_remote.is_set(trans_params(scid)) {
call tls_api.upper.save_initial_max_stream_data_bidi_remote(initial_max_stream_data_bidi_remote.value(trans_params(scid)).stream_pos_32);
};
if initial_max_data.is_set(trans_params(scid)) {
call tls_api.upper.save_initial_max_data(initial_max_data.value(trans_params(scid)).stream_pos_32);
};
if initial_max_stream_data_bidi_local.is_set(trans_params(scid)) {
call tls_api.upper.save_initial_max_stream_data_bidi_local(initial_max_stream_data_bidi_local.value(trans_params(scid)).stream_pos_32);
};
if initial_max_stream_id_bidi.is_set(trans_params(scid)) {
call tls_api.upper.save_initial_max_stream_id_bidi(initial_max_stream_id_bidi.value(trans_params(scid)).stream_id_16);
};
if active_connection_id_limit.is_set(trans_params(scid)) {
call tls_api.upper.save_active_connection_id_limit(active_connection_id_limit.value(trans_params(scid)).stream_pos_32);
};
}
}
import action show_enc_level(e:quic_packet_type)
import action max_idle_timeout_update(e:microseconds)
import action show_current_idle_timeout(e:microseconds)
import action show_probe_idle_timeout(e:microseconds)
import action show_probing_time(e:microseconds)
import action show_max_ack_delay(e:microseconds)
import action show_ack_delay_exponent(e:microseconds)
import action sleep_event(e:microseconds)
import action show_cond(e:bool)
import action show_cid(e:cid)
import action show_pstats(scid:cid,e:quic_packet_type,pnum:pkt_num)
import action show_ack_credit(c:cid,p: pkt_num,eli: bool,non_ack: bool, pp: pkt_num)
import action show_probing(c:cid,pp: pkt_num)