]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
Implement Server Address validation using retry packets
authorSaša Nedvědický <sashan@openssl.org>
Tue, 5 Nov 2024 21:15:55 +0000 (16:15 -0500)
committerNeil Horman <nhorman@openssl.org>
Mon, 17 Feb 2025 16:27:33 +0000 (11:27 -0500)
RFC 9000 describes a method for preforming server address validation on
QUIC using retry packets.  Based on:
https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.5.2

We do the following:
1) Client sends an Initial packet without a retry token
2) Server abandons the initial packet and responds with a retry frame
   which includes a retry token and integrity tag and new SCID
3) Client send the initial packet again, updating the encryption keys
   for the connection based on the SCID sent in (2), using it as the new
   DCID, including the retry token/tag provided in (2).
4) Server validates the token in (3) and creates a new connection using
   the updated DCID from the client to generate its encryption keys

Reviewed-by: Neil Horman <nhorman@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/25890)

include/internal/quic_channel.h
include/internal/quic_lcidm.h
ssl/quic/quic_channel.c
ssl/quic/quic_lcidm.c
ssl/quic/quic_port.c
ssl/quic/quic_wire_pkt.c
test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt
test/recipes/75-test_quicapi_data/ssltraceref.txt

index 2a741f52cf3271c937099d1930362a495219408c..c01d4a025e07c46458773e633a08707bc2d09b11 100644 (file)
@@ -448,6 +448,10 @@ uint64_t ossl_quic_channel_get_max_idle_timeout_peer_request(const QUIC_CHANNEL
 /* Get the idle timeout actually negotiated. */
 uint64_t ossl_quic_channel_get_max_idle_timeout_actual(const QUIC_CHANNEL *ch);
 
+int ossl_quic_bind_channel(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                           const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid,
+                           const QUIC_CONN_ID *odcid);
+
 # endif
 
 #endif
index 4911e042302ba9d363d4255c900cbef17894b2b5..f01b985d0047d6cb5cf119eeb7a1bd0656cc38d5 100644 (file)
@@ -252,6 +252,18 @@ int ossl_quic_lcidm_debug_add(QUIC_LCIDM *lcidm, void *opaque,
                               const QUIC_CONN_ID *lcid,
                               uint64_t seq_num);
 
+/*
+ * Obtain a local connection id which is not used yet.
+ * Returns 1 on succes, 0 on failure.
+ */
+int ossl_quic_lcidm_get_unused_cid(QUIC_LCIDM *lcidm, QUIC_CONN_ID *cid);
+
+/*
+ * Attempts to bind channel to connection id specified in `lcid`.
+ * This should be connection ID we generated during client validation.
+ */
+int ossl_quic_lcidm_bind_channel(QUIC_LCIDM *lcidm, void *opaque,
+                                 const QUIC_CONN_ID *lcid);
 # endif
 
 #endif
index 02c1213cd5737bc7e7b661817969f736ad21a817..49ae84d2cd1612694833493635527be570c3452c 100644 (file)
@@ -1736,6 +1736,21 @@ static int ch_generate_transport_params(QUIC_CHANNEL *ch)
     WPACKET wpkt;
     int wpkt_valid = 0;
     size_t buf_len = 0;
+    QUIC_CONN_ID *id_to_use = NULL;
+
+    /*
+     * We need to select which connection id to encode in the
+     * QUIC_TPARAM_ORIG_DCID transport parameter
+     * If we have an odcid, then this connection was established
+     * in response to a retry request, and we need to use the connection
+     * id sent in the first initial packet.
+     * If we don't have an odcid, then this connection was established
+     * without a retry and the init_dcid is the connection we should use
+     */
+    if (ch->odcid.id_len == 0)
+        id_to_use = &ch->init_dcid;
+    else
+        id_to_use = &ch->odcid;
 
     if (ch->local_transport_params != NULL || ch->got_local_transport_params)
         goto err;
@@ -3382,18 +3397,24 @@ static void ch_on_idle_timeout(QUIC_CHANNEL *ch)
     ch_record_state_transition(ch, QUIC_CHANNEL_STATE_TERMINATED);
 }
 
-/* Called when we, as a server, get a new incoming connection. */
-int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
-                                  const QUIC_CONN_ID *peer_scid,
-                                  const QUIC_CONN_ID *peer_dcid)
+/**
+ * @brief Common handler for initializing a new QUIC connection.
+ *
+ * This function configures a QUIC channel (`QUIC_CHANNEL *ch`) for a new
+ * connection by setting the peer address, connection IDs, and necessary
+ * callbacks. It establishes initial secrets, sets up logging, and performs
+ * required transitions for the channel state.
+ *
+ * @param ch       Pointer to the QUIC channel being initialized.
+ * @param peer     Address of the peer to which the channel connects.
+ * @param peer_scid Peer-specified source connection ID.
+ * @param peer_dcid Peer-specified destination connection ID.
+ * @return         1 on success, 0 on failure to set required elements.
+ */
+static int ch_on_new_conn_common(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                                 const QUIC_CONN_ID *peer_scid,
+                                 const QUIC_CONN_ID *peer_dcid)
 {
-    if (!ossl_assert(ch->state == QUIC_CHANNEL_STATE_IDLE && ch->is_server))
-        return 0;
-
-    /* Generate an Initial LCID we will use for the connection. */
-    if (!ossl_quic_lcidm_generate_initial(ch->lcidm, ch, &ch->cur_local_cid))
-        return 0;
-
     /* Note our newly learnt peer address and CIDs. */
     ch->cur_peer_addr   = *peer;
     ch->init_dcid       = *peer_dcid;
@@ -3432,6 +3453,43 @@ int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
     return 1;
 }
 
+/* Called when we, as a server, get a new incoming connection. */
+int ossl_quic_channel_on_new_conn(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                                  const QUIC_CONN_ID *peer_scid,
+                                  const QUIC_CONN_ID *peer_dcid)
+{
+    if (!ossl_assert(ch->state == QUIC_CHANNEL_STATE_IDLE && ch->is_server))
+        return 0;
+
+    /* Generate an Initial LCID we will use for the connection. */
+    if (!ossl_quic_lcidm_generate_initial(ch->lcidm, ch, &ch->cur_local_cid))
+        return 0;
+
+    return ch_on_new_conn_common(ch, peer, peer_scid, peer_dcid);
+}
+
+int ossl_quic_bind_channel(QUIC_CHANNEL *ch, const BIO_ADDR *peer,
+                           const QUIC_CONN_ID *peer_scid,
+                           const QUIC_CONN_ID *peer_dcid,
+                           const QUIC_CONN_ID *peer_odcid)
+{
+    if (peer_dcid == NULL)
+        return 0;
+
+    if (!ossl_assert(ch->state == QUIC_CHANNEL_STATE_IDLE && ch->is_server))
+        return 0;
+
+    ch->cur_local_cid = *peer_dcid;
+    if (!ossl_quic_lcidm_bind_channel(ch->lcidm, ch, peer_dcid))
+        return 0;
+
+    /*
+     * peer_odcid <=> is initial dst conn id chosen by peer in its
+     * first initial packet we received without token.
+     */
+    return ch_on_new_conn_common(ch, peer, peer_scid, peer_odcid);
+}
+
 SSL *ossl_quic_channel_get0_ssl(QUIC_CHANNEL *ch)
 {
     return ch->tls;
index e5948b95e90c238bc54f4a268654a981d4c42f84..ce7e354f3e0e557588f053f5f5879f8af652e56e 100644 (file)
@@ -393,6 +393,36 @@ int ossl_quic_lcidm_generate_initial(QUIC_LCIDM *lcidm,
                           initial_lcid, NULL);
 }
 
+int ossl_quic_lcidm_bind_channel(QUIC_LCIDM *lcidm, void *opaque,
+                                 const QUIC_CONN_ID *lcid)
+{
+    QUIC_LCIDM_CONN *conn;
+    QUIC_LCID *lcid_obj;
+
+    /*
+     * the plan is simple:
+     *   make sure the lcid is still unused.
+     *   do the same business as ossl_quic_lcidm_gnerate_initial() does,
+     *   except we will use lcid instead of generating a new one.
+     */
+    if (ossl_quic_lcidm_lookup(lcidm, lcid, NULL, NULL) != 0)
+        return 0;
+
+    if ((conn = lcidm_upsert_conn(lcidm, opaque)) == NULL)
+        return 0;
+
+    if ((lcid_obj = lcidm_conn_new_lcid(lcidm, conn, lcid)) == NULL) {
+        lcidm_delete_conn(lcidm, conn);
+        return 0;
+    }
+
+    lcid_obj->seq_num = conn->next_seq_num;
+    lcid_obj->type = LCID_TYPE_INITIAL;
+    conn->next_seq_num++;
+
+    return 1;
+}
+
 int ossl_quic_lcidm_generate(QUIC_LCIDM *lcidm,
                              void *opaque,
                              OSSL_QUIC_FRAME_NEW_CONN_ID *ncid_frame)
@@ -554,3 +584,16 @@ int ossl_quic_lcidm_debug_add(QUIC_LCIDM *lcidm, void *opaque,
     lcid_obj->type      = LCID_TYPE_NCID;
     return 1;
 }
+
+int ossl_quic_lcidm_get_unused_cid(QUIC_LCIDM *lcidm, QUIC_CONN_ID *cid)
+{
+    int i;
+
+    for (i = 0; i < 10; i++) {
+        if (lcidm_generate_cid(lcidm, cid)
+            && lcidm_get0_lcid(lcidm, cid) == NULL)
+            return 1; /* not found <=> radomly generated cid is unused */
+    }
+
+    return 0;
+}
index e4bafabd805b70775a94dc365e6446ff76efa92a..83b0a591c49db7dbad113519220c91818dec26aa 100644 (file)
@@ -30,6 +30,34 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
                                         const QUIC_CONN_ID *dcid);
 static void port_rx_pre(QUIC_PORT *port);
 
+/**
+ * @struct validation_token
+ * @brief Represents a validation token for secure connection handling.
+ *
+ * This struct is used to store information related to a validation token,
+ * including the token buffer, original connection ID, and an integrity tag
+ * for secure validation of QUIC connections.
+ *
+ * @var validation_token::token_buf
+ * A character array holding the token data. The size of this array is
+ * based on the length of the string "openssltoken" minus one for the null
+ * terminator.
+ *
+ * @var validation_token::token_odcid
+ * An original connection ID (`QUIC_CONN_ID`) used to identify the QUIC
+ * connection. This ID helps associate the token with a specific connection.
+ *
+ * @var validation_token::integrity_tag
+ * A character array for the integrity tag, with a length defined by
+ * `QUIC_RETRY_INTEGRITY_TAG_LEN`. This tag is used to verify the integrity
+ * of the token during the connection process.
+ */
+struct validation_token {
+    char token_buf[sizeof("openssltoken") - 1];
+    QUIC_CONN_ID token_odcid;
+    char integrity_tag[QUIC_RETRY_INTEGRITY_TAG_LEN];
+};
+
 DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
 DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL);
 DEFINE_LIST_OF_IMPL(port, QUIC_PORT);
@@ -544,28 +572,27 @@ static void port_rx_pre(QUIC_PORT *port)
  * connection from it. If a new connection is made, the new channel is written
  * to *new_ch.
  */
-static void port_on_new_conn(QUIC_PORT *port, const BIO_ADDR *peer,
-                             const QUIC_CONN_ID *scid,
-                             const QUIC_CONN_ID *dcid,
-                             QUIC_CHANNEL **new_ch)
+static void port_bind_channel(QUIC_PORT *port, const BIO_ADDR *peer,
+                              const QUIC_CONN_ID *scid, const QUIC_CONN_ID *dcid,
+                              const QUIC_CONN_ID *odcid, QUIC_CHANNEL **new_ch)
 {
     QUIC_CHANNEL *ch;
 
+    /*
+     * If we're running with a simulated tserver, it will already have
+     * a dummy channel created, use that instead
+     */
     if (port->tserver_ch != NULL) {
-        /* Specially assign to existing channel */
-        if (!ossl_quic_channel_on_new_conn(port->tserver_ch, peer, scid, dcid))
-            return;
-
-        *new_ch = port->tserver_ch;
+        ch = port->tserver_ch;
         port->tserver_ch = NULL;
-        return;
+    } else {
+        ch = port_make_channel(port, NULL, /* is_server= */1);
     }
 
-    ch = port_make_channel(port, NULL, /*is_server=*/1);
     if (ch == NULL)
         return;
 
-    if (!ossl_quic_channel_on_new_conn(ch, peer, scid, dcid)) {
+    if (!ossl_quic_bind_channel(ch, peer, scid, dcid, odcid)) {
         ossl_quic_channel_free(ch);
         return;
     }
@@ -620,6 +647,158 @@ static int port_try_handle_stateless_reset(QUIC_PORT *port, const QUIC_URXE *e)
     return i > 0;
 }
 
+#define TOKEN_LEN (sizeof("openssltoken") + \
+                   QUIC_RETRY_INTEGRITY_TAG_LEN - 1 + \
+                   sizeof(unsigned char))
+
+/**
+ * @brief Sends a QUIC Retry packet to a client.
+ *
+ * This function constructs and sends a Retry packet to the specified client
+ * using the provided connection header information. The Retry packet
+ * includes a generated validation token and a new connection ID, following
+ * the QUIC protocol specifications for connection establishment.
+ *
+ * @param port        Pointer to the QUIC port from which to send the packet.
+ * @param peer        Address of the client peer receiving the packet.
+ * @param client_hdr  Header of the client's initial packet, containing
+ *                    connection IDs and other relevant information.
+ *
+ * This function performs the following steps:
+ * - Generates a validation token for the client.
+ * - Sets the destination and source connection IDs.
+ * - Calculates the integrity tag and sets the token length.
+ * - Encodes and sends the packet via the BIO network interface.
+ *
+ * Error handling is included for failures in CID generation, encoding, and
+ * network transmiss
+ */
+static void port_send_retry(QUIC_PORT *port,
+                            BIO_ADDR *peer,
+                            QUIC_PKT_HDR *client_hdr)
+{
+    BIO_MSG msg[1];
+    unsigned char buffer[512];
+    WPACKET wpkt;
+    size_t written;
+    QUIC_PKT_HDR hdr;
+    struct validation_token token;
+    size_t token_len = TOKEN_LEN;
+    unsigned char *integrity_tag;
+    int ok;
+
+    /* TODO(QUIC_SERVER): generate proper validation token */
+    memcpy(token.token_buf, "openssltoken", sizeof("openssltoken") - 1);
+
+    token.token_odcid = client_hdr->dst_conn_id;
+    token_len += token.token_odcid.id_len;
+    integrity_tag = (unsigned char *)&token.token_odcid +
+        token.token_odcid.id_len + sizeof(token.token_odcid.id_len);
+    /*
+     * 17.2.5.1 Sending a Retry packet
+     *   dst ConnId is src ConnId we got from client
+     *   src ConnId comes from local conn ID manager
+     */
+    memset(&hdr, 0, sizeof(QUIC_PKT_HDR));
+    hdr.dst_conn_id = client_hdr->src_conn_id;
+    /*
+     * this is the random connection ID, we expect client is
+     * going to send the ID with next INITIAL packet which
+     * will also come with token we generate here.
+     */
+    ok = ossl_quic_lcidm_get_unused_cid(port->lcidm, &hdr.src_conn_id);
+    if (ok == 0)
+        return;
+
+    hdr.dst_conn_id = client_hdr->src_conn_id;
+    hdr.type = QUIC_PKT_TYPE_RETRY;
+    hdr.fixed = 1;
+    hdr.version = 1;
+    hdr.len = token_len;
+    hdr.data = (unsigned char *)&token;
+    ok = ossl_quic_calculate_retry_integrity_tag(port->engine->libctx,
+                                                 port->engine->propq, &hdr,
+                                                 &client_hdr->dst_conn_id,
+                                                 integrity_tag);
+    if (ok == 0)
+        return;
+
+    hdr.token = (unsigned char *)&token;
+    hdr.token_len = token_len;
+
+    msg[0].data = buffer;
+    msg[0].peer = peer;
+    msg[0].local = NULL;
+    msg[0].flags = 0;
+
+    ok = WPACKET_init_static_len(&wpkt, buffer, sizeof(buffer), 0);
+    if (ok == 0)
+        return;
+
+    ok = ossl_quic_wire_encode_pkt_hdr(&wpkt, client_hdr->dst_conn_id.id_len,
+                                       &hdr, NULL);
+    if (ok == 0)
+        return;
+
+    ok = WPACKET_get_total_written(&wpkt, &msg[0].data_len);
+    if (ok == 0)
+        return;
+
+    ok = WPACKET_finish(&wpkt);
+    if (ok == 0)
+        return;
+
+    /*
+     * TODO(QUIC SERVER) need to retry this in the event it return EAGAIN
+     * on a non-blocking BIO
+     */
+    if (!BIO_sendmmsg(port->net_wbio, msg, sizeof(BIO_MSG), 1, 0, &written))
+        ERR_raise_data(ERR_LIB_SSL, SSL_R_QUIC_NETWORK_ERROR,
+                       "port retry send failed due to network BIO I/O error");
+
+}
+
+/**
+ * @brief Validates a received token in a QUIC packet header.
+ *
+ * This function checks the validity of a token contained in the provided
+ * QUIC packet header (`QUIC_PKT_HDR *hdr`). The validation process involves
+ * verifying that the token matches an expected format and value. If the
+ * token is valid, the function extracts the original connection ID (ODCID)
+ * and stores it in the provided `QUIC_CONN_ID *odcid`.
+ *
+ * @param hdr   Pointer to the QUIC packet header containing the token.
+ * @param odcid Pointer to the connection ID structure to store the ODCID if
+ *              the token is valid.
+ * @return      1 if the token is valid and ODCID is extracted successfully,
+ *              0 otherwise.
+ *
+ * The function performs the following checks:
+ * - Verifies that the token length meets the required minimum.
+ * - Confirms the token buffer matches the expected "openssltoken" string.
+ * -
+ */
+static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_CONN_ID *odcid)
+{
+    int valid;
+    struct validation_token *token;
+
+    memset(odcid, 0, sizeof(QUIC_CONN_ID));
+
+    token = (struct validation_token *)hdr->token;
+    if (token == NULL || hdr->token_len <= (TOKEN_LEN - QUIC_RETRY_INTEGRITY_TAG_LEN))
+        return 0;
+
+    valid = memcmp(token->token_buf, "openssltoken", sizeof("openssltoken") - 1);
+    if (valid != 0)
+        return 0;
+
+    odcid->id_len = token->token_odcid.id_len;
+    memcpy(odcid->id, token->token_odcid.id, token->token_odcid.id_len);
+
+    return 1;
+}
+
 /*
  * This is called by the demux when we get a packet not destined for any known
  * DCID.
@@ -631,6 +810,7 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
     PACKET pkt;
     QUIC_PKT_HDR hdr;
     QUIC_CHANNEL *ch = NULL, *new_ch = NULL;
+    QUIC_CONN_ID odcid;
 
     /* Don't handle anything if we are no longer running. */
     if (!ossl_quic_port_is_running(port))
@@ -691,18 +871,29 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
         goto undesirable;
 
     /*
-     * Try to process this as a valid attempt to initiate a connection.
-     *
+     * TODO(QUIC SERVER): there should be some logic similar to accounting half-open
+     * states in TCP. If we reach certain threshold, then we want to
+     * validate clients.
+     */
+    if (hdr.token == NULL) {
+        port_send_retry(port, &e->peer, &hdr);
+        goto undesirable;
+    } else if (port_validate_token(&hdr, &odcid) == 0) {
+        goto undesirable;
+    }
+
+    port_bind_channel(port, &e->peer, &hdr.src_conn_id, &hdr.dst_conn_id,
+                      &odcid, &new_ch);
+
+    /*
      * The channel will do all the LCID registration needed, but as an
      * optimization inject this packet directly into the channel's QRX for
      * processing without going through the DEMUX again.
      */
-    port_on_new_conn(port, &e->peer, &hdr.src_conn_id, &hdr.dst_conn_id,
-                     &new_ch);
-    if (new_ch != NULL)
+    if (new_ch != NULL) {
         ossl_qrx_inject_urxe(new_ch->qrx, e);
-
-    return;
+        return;
+    }
 
 undesirable:
     ossl_quic_demux_release_urxe(port->demux, e);
index 00f4afb7c082c932b37fc8ba38bf8ed030dc2406..e0018377af2ddd8cebd5d380c17dcb4ea1e44318 100644 (file)
@@ -554,8 +554,7 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
                                hdr->src_conn_id.id_len))
             return 0;
 
-        if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG
-            || hdr->type == QUIC_PKT_TYPE_RETRY) {
+        if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG) {
             if (hdr->len > 0 && !WPACKET_reserve_bytes(pkt, hdr->len, NULL))
                 return 0;
 
@@ -568,6 +567,12 @@ int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
                 return 0;
         }
 
+        if (hdr->type == QUIC_PKT_TYPE_RETRY) {
+            if (!WPACKET_memcpy(pkt, hdr->token, hdr->token_len))
+                return 0;
+            return 1;
+        }
+
         if (!WPACKET_quic_write_vlint(pkt, hdr->len + hdr->pn_len)
             || !WPACKET_get_total_written(pkt, &off_pn)
             || !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
index 6249195ee3644198e3344af8bc5af1c23b84eb0f..9ab460994aa0ddf1a23a26125202978344e4cb5f 100644 (file)
@@ -84,6 +84,22 @@ Sent Packet
   Packet Number: 0x00000000
 Sent Datagram
   Length: 1200
+Received Datagram
+  Length: 52
+Sent Frame: Crypto
+    Offset: 0
+    Len: 263
+Sent Frame: Padding
+Sent Packet
+  Packet Type: Initial
+  Version: 0x00000001
+  Destination Conn Id: 0x????????????????
+  Source Conn Id: <zero length id>
+  Payload length: 1157
+  Token: ??????????????????????????????????????????
+  Packet Number: 0x00000001
+Sent Datagram
+  Length: 1200
 Received Datagram
   Length: 1200
 Received Datagram
@@ -97,7 +113,7 @@ Received Packet
   Token: <zero length token>
   Packet Number: 0x00000000
 Received Frame: Ack  (without ECN)
-    Largest acked: 0
+    Largest acked: 1
     Ack delay (raw) 0
     Ack range count: 0
     First ack range: 0
@@ -291,9 +307,9 @@ Sent Packet
   Version: 0x00000001
   Destination Conn Id: 0x????????????????
   Source Conn Id: <zero length id>
-  Payload length: 1097
-  Token: <zero length token>
-  Packet Number: 0x00000001
+  Payload length: 1076
+  Token: ??????????????????????????????????????????
+  Packet Number: 0x00000002
 Sent Packet
   Packet Type: Handshake
   Version: 0x00000001
index 177677d64dcccb904507fd379f4ce42d5d5486cb..c241d3755959defc99514f6afb5f6d84c53b7a66 100644 (file)
@@ -82,6 +82,22 @@ Sent Packet
   Packet Number: 0x00000000
 Sent Datagram
   Length: 1200
+Received Datagram
+  Length: 52
+Sent Frame: Crypto
+    Offset: 0
+    Len: 256
+Sent Frame: Padding
+Sent Packet
+  Packet Type: Initial
+  Version: 0x00000001
+  Destination Conn Id: 0x????????????????
+  Source Conn Id: <zero length id>
+  Payload length: 1157
+  Token: ??????????????????????????????????????????
+  Packet Number: 0x00000001
+Sent Datagram
+  Length: 1200
 Received Datagram
   Length: 1200
 Received Datagram
@@ -95,7 +111,7 @@ Received Packet
   Token: <zero length token>
   Packet Number: 0x00000000
 Received Frame: Ack  (without ECN)
-    Largest acked: 0
+    Largest acked: 1
     Ack delay (raw) 0
     Ack range count: 0
     First ack range: 0
@@ -289,9 +305,9 @@ Sent Packet
   Version: 0x00000001
   Destination Conn Id: 0x????????????????
   Source Conn Id: <zero length id>
-  Payload length: 1097
-  Token: <zero length token>
-  Packet Number: 0x00000001
+  Payload length: 1076
+  Token: ??????????????????????????????????????????
+  Packet Number: 0x00000002
 Sent Packet
   Packet Type: Handshake
   Version: 0x00000001