Merge pull request #47 from h2o/master

Align October 7, 2019
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 85ee333..874b9be 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,9 +4,22 @@
 PROJECT(picotls)
 
 FIND_PACKAGE(PkgConfig REQUIRED)
+INCLUDE(cmake/dtrace-utils.cmake)
+
+CHECK_DTRACE(${CMAKE_SOURCE_DIR}/picotls-probes.d)
+OPTION(WITH_DTRACE "use USDT (userspace Dtrace probes)" ${HAVE_DTRACE})
+IF (WITH_DTRACE)
+    MESSAGE(STATUS "Enabling USDT support")
+ENDIF ()
 
 SET(CMAKE_C_FLAGS "-std=c99 -Wall -O2 -g ${CC_WARNING_FLAGS} ${CMAKE_C_FLAGS}")
-INCLUDE_DIRECTORIES(deps/cifra/src/ext deps/cifra/src deps/micro-ecc deps/picotest include)
+INCLUDE_DIRECTORIES(
+    deps/cifra/src/ext
+    deps/cifra/src
+    deps/micro-ecc
+    deps/picotest
+    include
+    ${CMAKE_CURRENT_BINARY_DIR})
 SET(MINICRYPTO_LIBRARY_FILES
     deps/micro-ecc/uECC.c
     deps/cifra/src/aes.c
@@ -25,6 +38,18 @@
 SET(CORE_FILES
     lib/picotls.c
     lib/pembase64.c)
+SET(CORE_TEST_FILES
+    t/picotls.c)
+IF (WITH_DTRACE)
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPICOTLS_USE_DTRACE=1")
+    DEFINE_DTRACE_DEPENDENCIES(${CMAKE_SOURCE_DIR}/picotls-probes.d picotls)
+    LIST(APPEND CORE_FILES ${CMAKE_CURRENT_BINARY_DIR}/picotls-probes.h)
+    LIST(APPEND CORE_TEST_FILES ${CMAKE_CURRENT_BINARY_DIR}/picotls-probes.h)
+    IF (DTRACE_USES_OBJFILE)
+        LIST(APPEND CORE_FILES ${CMAKE_CURRENT_BINARY_DIR}/picotls-probes.o)
+        LIST(APPEND CORE_TEST_FILES ${CMAKE_CURRENT_BINARY_DIR}/picotls-probes.o)
+    ENDIF ()
+ENDIF ()
 
 PKG_CHECK_MODULES(BROTLI_DEC libbrotlidec)
 PKG_CHECK_MODULES(BROTLI_ENC libbrotlienc)
@@ -55,7 +80,7 @@
 ADD_EXECUTABLE(test-minicrypto.t
     ${MINICRYPTO_LIBRARY_FILES}
     deps/picotest/picotest.c
-    t/picotls.c
+    ${CORE_TEST_FILES}
     t/minicrypto.c
     lib/asn1.c
     lib/pembase64.c
@@ -70,7 +95,7 @@
 
 FIND_PACKAGE(OpenSSL)
 IF (OPENSSL_FOUND AND NOT (OPENSSL_VERSION VERSION_LESS "1.0.1"))
-    MESSAGE(WARNING "Enabling OpenSSL support")
+    MESSAGE(STATUS "  Enabling OpenSSL support")
     INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR})
     ADD_LIBRARY(picotls-openssl lib/openssl.c)
     TARGET_LINK_LIBRARIES(picotls-openssl ${OPENSSL_LIBRARIES} picotls-core ${CMAKE_DL_LIBS})
@@ -92,7 +117,7 @@
         lib/pembase64.c
         lib/ffx.c
         deps/picotest/picotest.c
-        t/picotls.c
+        ${CORE_TEST_FILES}
         t/openssl.c)
     SET_TARGET_PROPERTIES(test-openssl.t PROPERTIES COMPILE_FLAGS "-DPTLS_MEMORY_DEBUG=1")
     TARGET_LINK_LIBRARIES(test-openssl.t ${OPENSSL_LIBRARIES} ${CMAKE_DL_LIBS})
diff --git a/cmake/dtrace-utils.cmake b/cmake/dtrace-utils.cmake
new file mode 100644
index 0000000..da56646
--- /dev/null
+++ b/cmake/dtrace-utils.cmake
@@ -0,0 +1,36 @@
+FUNCTION (CHECK_DTRACE d_file)
+    MESSAGE(STATUS "Detecting USDT support")
+    SET(HAVE_DTRACE "OFF" PARENT_SCOPE)
+    SET(DTRACE_USES_OBJFILE "OFF" PARENT_SCOPE)
+    IF ((CMAKE_SYSTEM_NAME STREQUAL "Darwin") OR (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
+        # USDT is not (yet) supported on platforms (e.g., FreeBSD, Solaris) that require pre-link modification of .o files
+        EXECUTE_PROCESS(
+            COMMAND dtrace -o .tmp.dprobes.h -s ${d_file} -h
+            RESULT_VARIABLE DTRACE_RESULT)
+        FILE(REMOVE .tmp.dprobes.h)
+        IF (DTRACE_RESULT EQUAL 0)
+            MESSAGE(STATUS "Detecting USDT support - found")
+            SET(HAVE_DTRACE "ON" PARENT_SCOPE)
+            IF (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+                SET(DTRACE_USES_OBJFILE "ON" PARENT_SCOPE)
+            ENDIF ()
+        ELSE ()
+            MESSAGE(STATUS "Detecting USDT support - not found")
+        ENDIF ()
+    ELSE ()
+        MESSAGE(STATUS "Detecting USDT support - disabled on this platform")
+    ENDIF ()
+ENDFUNCTION ()
+
+FUNCTION (DEFINE_DTRACE_DEPENDENCIES d_file prefix)
+    ADD_CUSTOM_COMMAND(
+        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h
+        COMMAND dtrace -o ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h -s ${d_file} -h
+        DEPENDS ${d_file})
+    IF (DTRACE_USES_OBJFILE)
+        ADD_CUSTOM_COMMAND(
+            OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o
+            COMMAND dtrace -o ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o -s ${d_file} -G
+            DEPENDS ${d_file})
+    ENDIF ()
+ENDFUNCTION ()
diff --git a/fuzz/fuzz-asn1.c b/fuzz/fuzz-asn1.c
index 001f72d..dc8b070 100644
--- a/fuzz/fuzz-asn1.c
+++ b/fuzz/fuzz-asn1.c
@@ -97,7 +97,6 @@
             }
         } else {
             ptls_minicrypto_load_private_key(&ctx, fname);
-            free(ctx.pkey_buf.base);
         }
 out2:
         close(fd);
diff --git a/include/picotls.h b/include/picotls.h
index 0fdc7c6..4181714 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -34,7 +34,19 @@
 #include <inttypes.h>
 #include <sys/types.h>
 
+#if __GNUC__ >= 3
+#define PTLS_LIKELY(x) __builtin_expect(!!(x), 1)
+#define PTLS_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+#define PTLS_LIKELY(x) (x)
+#define PTLS_UNLIKELY(x) (x)
+#endif
 
+#ifdef _WINDOWS
+#define PTLS_THREADLOCAL __declspec(thread)
+#else
+#define PTLS_THREADLOCAL __thread
+#endif
 
 #ifndef PTLS_FUZZ_HANDSHAKE
 #define PTLS_FUZZ_HANDSHAKE 0
@@ -142,6 +154,7 @@
 #define PTLS_ERROR_NOT_AVAILABLE (PTLS_ERROR_CLASS_INTERNAL + 7)
 #define PTLS_ERROR_COMPRESSION_FAILURE (PTLS_ERROR_CLASS_INTERNAL + 8)
 #define PTLS_ERROR_ESNI_RETRY (PTLS_ERROR_CLASS_INTERNAL + 8)
+#define PTLS_ERROR_REJECT_EARLY_DATA (PTLS_ERROR_CLASS_INTERNAL + 9)
 
 #define PTLS_ERROR_INCORRECT_BASE64 (PTLS_ERROR_CLASS_INTERNAL + 50)
 #define PTLS_ERROR_PEM_LABEL_NOT_FOUND (PTLS_ERROR_CLASS_INTERNAL + 51)
@@ -426,15 +439,13 @@
     ptls_iovec_t secret;
     uint8_t nonce[PTLS_ESNI_NONCE_SIZE];
     uint8_t esni_contents_hash[PTLS_MAX_DIGEST_SIZE];
-    union {
-        struct {
-            ptls_key_exchange_algorithm_t *key_share;
-            ptls_cipher_suite_t *cipher;
-            ptls_iovec_t pubkey;
-            uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
-            uint16_t padded_length;
-        } client;
-    };
+    struct {
+        ptls_key_exchange_algorithm_t *key_share;
+        ptls_cipher_suite_t *cipher;
+        ptls_iovec_t pubkey;
+        uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
+        uint16_t padded_length;
+    } client;
     uint16_t version;
 } ptls_esni_secret_t;
 
@@ -457,6 +468,10 @@
      */
     ptls_iovec_t server_name;
     /**
+     * Raw value of the client_hello message.
+     */
+    ptls_iovec_t raw_message;
+    /**
      *
      */
     struct {
@@ -478,7 +493,7 @@
     /**
      * if ESNI was used
      */
-    uint8_t esni : 1;
+    unsigned esni : 1;
 } ptls_on_client_hello_parameters_t;
 
 /**
@@ -511,7 +526,10 @@
                    int (**verify_sign)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t sign), void **verify_data,
                    ptls_iovec_t *certs, size_t num_certs);
 /**
- * encrypt-and-signs (or verify-and-decrypts) a ticket (server-only)
+ * Encrypt-and-signs (or verify-and-decrypts) a ticket (server-only).
+ * When used for encryption (i.e., is_encrypt being set), the function should return 0 if successful, or else a non-zero value.
+ * When used for decryption, the function should return 0 (successful), PTLS_ERROR_REJECT_EARLY_DATA (successful, but 0-RTT is
+ * forbidden), or any other value to indicate failure.
  */
 PTLS_CALLBACK_TYPE(int, encrypt_ticket, ptls_t *tls, int is_encrypt, ptls_buffer_t *dst, ptls_iovec_t src);
 /**
@@ -670,10 +688,6 @@
     /**
      *
      */
-    ptls_iovec_t pkey_buf;
-    /**
-     *
-     */
     ptls_on_extension_t *on_extension;
 };
 
@@ -1010,6 +1024,14 @@
  */
 void **ptls_get_data_ptr(ptls_t *tls);
 /**
+ *
+ */
+int ptls_skip_tracing(ptls_t *tls);
+/**
+ *
+ */
+void ptls_set_skip_tracing(ptls_t *tls, int skip_tracing);
+/**
  * proceeds with the handshake, optionally taking some input from peer. The function returns zero in case the handshake completed
  * successfully. PTLS_ERROR_IN_PROGRESS is returned in case the handshake is incomplete. Otherwise, an error value is returned. The
  * contents of sendbuf should be sent to the client, regardless of whether if an error is returned. inlen is an argument used for
@@ -1188,20 +1210,26 @@
  *
  */
 void ptls_esni_dispose_context(ptls_esni_context_t *esni);
-
 /**
  * Obtain the ESNI secrets negotiated during the handshake.
  */
 ptls_esni_secret_t *ptls_get_esni_secret(ptls_t *ctx);
-
 /**
  *
  */
-void ptls_hexdump(char *dst, const void *src, size_t len);
+char *ptls_hexdump(char *dst, const void *src, size_t len);
 /**
  * the default get_time callback
  */
 extern ptls_get_time_t ptls_get_time;
+#if PICOTLS_USE_DTRACE
+/**
+ *
+ */
+extern PTLS_THREADLOCAL unsigned ptls_default_skip_tracing;
+#else
+#define ptls_default_skip_tracing 0
+#endif
 
 /* inline functions */
 
diff --git a/lib/cifra/random.c b/lib/cifra/random.c
index 4dc3a55..9d26634 100644
--- a/lib/cifra/random.c
+++ b/lib/cifra/random.c
@@ -111,11 +111,7 @@
 
 void ptls_minicrypto_random_bytes(void *buf, size_t len)
 {
-#ifdef _WINDOWS
-    static __declspec(thread) cf_hash_drbg_sha256 ctx;
-#else
-    static __thread cf_hash_drbg_sha256 ctx;
-#endif
+    static PTLS_THREADLOCAL cf_hash_drbg_sha256 ctx;
 
     if (cf_hash_drbg_sha256_needs_reseed(&ctx)) {
         uint8_t entropy[256];
diff --git a/lib/minicrypto-pem.c b/lib/minicrypto-pem.c
index 7c52b20..9d6c499 100644
--- a/lib/minicrypto-pem.c
+++ b/lib/minicrypto-pem.c
@@ -211,7 +211,6 @@
     uint8_t *ecdsa_key_data = NULL;
     uint32_t ecdsa_key_data_length = 0;
     size_t ecdsa_key_data_last = 0;
-    ctx->pkey_buf = pkey->vec;
 
     /* We expect the parameters to include just the curve ID */
 
@@ -335,15 +334,17 @@
      * In theory, we could add support for RSA keys at some point.
      */
     if (pkey.algorithm_length != sizeof(ptls_asn1_algorithm_ecdsa) ||
-        memcmp(pkey.vec.base + pkey.algorithm_index, ptls_asn1_algorithm_ecdsa, sizeof(ptls_asn1_algorithm_ecdsa)) != 0)
+        memcmp(pkey.vec.base + pkey.algorithm_index, ptls_asn1_algorithm_ecdsa, sizeof(ptls_asn1_algorithm_ecdsa)) != 0) {
+        ret = -1;
         goto err;
+    }
 
-    return ptls_set_ecdsa_private_key(ctx, &pkey, NULL);
+    ret = ptls_set_ecdsa_private_key(ctx, &pkey, NULL);
 
 err:
     if (pkey.vec.base) {
         ptls_clear_memory(pkey.vec.base, pkey.vec.len);
         free(pkey.vec.base);
     }
-    return -1;
+    return ret;
 }
diff --git a/lib/picotls.c b/lib/picotls.c
index d79d96c..5b4e060 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -31,6 +31,9 @@
 #include <sys/time.h>
 #endif
 #include "picotls.h"
+#if PICOTLS_USE_DTRACE
+#include "picotls-probes.h"
+#endif
 
 #define PTLS_MAX_PLAINTEXT_RECORD_SIZE 16384
 #define PTLS_MAX_ENCRYPTED_RECORD_SIZE (16384 + 256)
@@ -89,6 +92,25 @@
 #define PTLS_MEMORY_DEBUG 0
 #endif
 
+#if PICOTLS_USE_DTRACE
+#define PTLS_SHOULD_PROBE(LABEL, tls) (PTLS_UNLIKELY(PICOTLS_##LABEL##_ENABLED()) && !(tls)->skip_tracing)
+#define PTLS_PROBE0(LABEL, tls)                                                                                                    \
+    do {                                                                                                                           \
+        ptls_t *_tls = (tls);                                                                                                      \
+        if (PTLS_SHOULD_PROBE(LABEL, _tls))                                                                                        \
+            PICOTLS_##LABEL(_tls);                                                                                                 \
+    } while (0)
+#define PTLS_PROBE(LABEL, tls, ...)                                                                                                \
+    do {                                                                                                                           \
+        ptls_t *_tls = (tls);                                                                                                      \
+        if (PTLS_SHOULD_PROBE(LABEL, _tls))                                                                                        \
+            PICOTLS_##LABEL(_tls, __VA_ARGS__);                                                                                    \
+    } while (0)
+#else
+#define PTLS_PROBE0(LABEL, tls)
+#define PTLS_PROBE(LABEL, tls, ...)
+#endif
+
 /**
  * list of supported versions in the preferred order
  */
@@ -208,6 +230,7 @@
     unsigned send_change_cipher_spec : 1;
     unsigned needs_key_update : 1;
     unsigned key_update_send_request : 1;
+    unsigned skip_tracing : 1;
     /**
      * misc.
      */
@@ -775,11 +798,12 @@
 
 static void log_secret(ptls_t *tls, const char *type, ptls_iovec_t secret)
 {
-    if (tls->ctx->log_event != NULL) {
-        char hexbuf[PTLS_MAX_DIGEST_SIZE * 2 + 1];
-        ptls_hexdump(hexbuf, secret.base, secret.len);
-        tls->ctx->log_event->cb(tls->ctx->log_event, tls, type, "%s", hexbuf);
-    }
+    char hexbuf[PTLS_MAX_DIGEST_SIZE * 2 + 1];
+
+    PTLS_PROBE(NEW_SECRET, tls, type, ptls_hexdump(hexbuf, secret.base, secret.len));
+
+    if (tls->ctx->log_event != NULL)
+        tls->ctx->log_event->cb(tls->ctx->log_event, tls, type, "%s", ptls_hexdump(hexbuf, secret.base, secret.len));
 }
 
 static void key_schedule_free(ptls_key_schedule_t *sched)
@@ -1170,6 +1194,12 @@
     return setup_traffic_protection(tls, is_enc, NULL, 2, 1);
 }
 
+static inline void log_client_random(ptls_t *tls)
+{
+    PTLS_PROBE(CLIENT_RANDOM, tls,
+               ptls_hexdump(alloca(sizeof(tls->client_random) * 2 + 1), tls->client_random, sizeof(tls->client_random)));
+}
+
 #define SESSION_IDENTIFIER_MAGIC "ptls0001" /* the number should be changed upon incompatible format change */
 #define SESSION_IDENTIFIER_MAGIC_SIZE (sizeof(SESSION_IDENTIFIER_MAGIC) - 1)
 
@@ -3368,9 +3398,17 @@
     for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) {
         struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index;
         /* decrypt and decode */
+        int can_accept_early_data = 1;
         decbuf.off = 0;
-        if ((tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) != 0)
+        switch (tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) {
+        case 0: /* decrypted */
+            break;
+        case PTLS_ERROR_REJECT_EARLY_DATA: /* decrypted, but early data is rejected */
+            can_accept_early_data = 0;
+            break;
+        default: /* decryption failure */
             continue;
+        }
         if (decode_session_identifier(&issue_at, &ticket_psk, &age_add, &ticket_server_name, &ticket_key_exchange_id, &ticket_csid,
                                       &ticket_negotiated_protocol, decbuf.base, decbuf.base + decbuf.off) != 0)
             continue;
@@ -3380,7 +3418,7 @@
         if (now - issue_at > (uint64_t)tls->ctx->ticket_lifetime * 1000)
             continue;
         *accept_early_data = 0;
-        if (ch->psk.early_data_indication) {
+        if (ch->psk.early_data_indication && can_accept_early_data) {
             /* accept early-data if abs(diff) between the reported age and the actual age is within += 10 seconds */
             int64_t delta = (now - issue_at) - (identity->obfuscated_ticket_age - age_add);
             if (delta < 0)
@@ -3553,6 +3591,7 @@
     /* handle client_random, SNI, ESNI */
     if (!is_second_flight) {
         memcpy(tls->client_random, ch.random_bytes, sizeof(tls->client_random));
+        log_client_random(tls);
         ptls_iovec_t server_name = {NULL};
         int is_esni = 0;
         if (ch.esni.cipher != NULL && tls->ctx->esni != NULL) {
@@ -3568,7 +3607,7 @@
             server_name = ch.server_name;
         }
         if (tls->ctx->on_client_hello != NULL) {
-            ptls_on_client_hello_parameters_t params = {server_name,
+            ptls_on_client_hello_parameters_t params = {server_name, message,
                                                         {ch.alpn.list, ch.alpn.count},
                                                         {ch.signature_algorithms.list, ch.signature_algorithms.count},
                                                         {ch.cert_compression_algos.list, ch.cert_compression_algos.count},
@@ -4105,20 +4144,24 @@
     *tls = (ptls_t){ctx};
     tls->is_server = is_server;
     tls->send_change_cipher_spec = ctx->send_change_cipher_spec;
+    tls->skip_tracing = ptls_default_skip_tracing;
     if (!is_server) {
         tls->state = PTLS_STATE_CLIENT_HANDSHAKE_START;
         tls->ctx->random_bytes(tls->client_random, sizeof(tls->client_random));
+        log_client_random(tls);
         tls->ctx->random_bytes(tls->client.legacy_session_id, sizeof(tls->client.legacy_session_id));
     } else {
         tls->state = PTLS_STATE_SERVER_EXPECT_CLIENT_HELLO;
         tls->server.early_data_skipped_bytes = UINT32_MAX;
     }
 
+    PTLS_PROBE(NEW, tls, is_server);
     return tls;
 }
 
 void ptls_free(ptls_t *tls)
 {
+    PTLS_PROBE0(FREE, tls);
     ptls_buffer_dispose(&tls->recvbuf.rec);
     ptls_buffer_dispose(&tls->recvbuf.mess);
     free_exporter_master_secret(tls, 1);
@@ -4238,6 +4281,16 @@
     return &tls->data_ptr;
 }
 
+int ptls_skip_tracing(ptls_t *tls)
+{
+    return tls->skip_tracing;
+}
+
+void ptls_set_skip_tracing(ptls_t *tls, int skip_tracing)
+{
+    tls->skip_tracing = skip_tracing;
+}
+
 static int handle_handshake_message(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message, int is_end_of_record,
                                     ptls_handshake_properties_t *properties)
 {
@@ -4358,6 +4411,9 @@
         break;
     }
 
+    PTLS_PROBE(RECEIVE_MESSAGE, tls, message.base[0], message.base + PTLS_HANDSHAKE_HEADER_SIZE,
+               message.len - PTLS_HANDSHAKE_HEADER_SIZE, ret);
+
     return ret;
 }
 
@@ -5015,6 +5071,9 @@
 }
 
 ptls_get_time_t ptls_get_time = {get_time};
+#if PICOTLS_USE_DTRACE
+PTLS_THREADLOCAL unsigned ptls_default_skip_tracing = 0;
+#endif
 
 int ptls_is_server(ptls_t *tls)
 {
@@ -5247,8 +5306,9 @@
     return 0;
 }
 
-void ptls_hexdump(char *dst, const void *_src, size_t len)
+char *ptls_hexdump(char *buf, const void *_src, size_t len)
 {
+    char *dst = buf;
     const uint8_t *src = _src;
     size_t i;
 
@@ -5257,4 +5317,5 @@
         *dst++ = "0123456789abcdef"[src[i] & 0xf];
     }
     *dst++ = '\0';
+    return buf;
 }
diff --git a/misc/dtrace/bpftrace.d b/misc/dtrace/bpftrace.d
new file mode 100644
index 0000000..84ea53a
--- /dev/null
+++ b/misc/dtrace/bpftrace.d
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2019 Fastly, Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Below is an example bpftrace script that logs the events in JSON-logging
+ * format.  The script can be invoked like:
+ *
+ * % sudo bpftrace -p $(pidof cli) /mydev/picotls/misc/dtrace/bpftrace.d
+ */
+
+usdt::picotls_new {
+    printf("{\"addr\": \"%p\", \"event\": \"new\", \"is_server\": %d}\n", arg0, arg1);
+}
+usdt::picotls_free {
+    printf("{\"addr\": \"%p\", \"event\": \"free\"}\n", arg0);
+}
+usdt::picotls_client_random {
+    printf("{\"addr\": \"%p\", \"event\": \"client_random\"", arg0);
+    printf(", \"bytes\": \"%s\"}\n", str(arg1));
+}
+usdt::picotls_new_secret {
+    printf("\"addr\": \"%p\", \"event\": \"new_secret\"", arg0);
+    printf(", \"label\": \"%s\", \"secret\": \"%s\"}\n", str(arg1), str(arg2));
+}
diff --git a/misc/dtrace/dtrace.d b/misc/dtrace/dtrace.d
new file mode 100644
index 0000000..de34d38
--- /dev/null
+++ b/misc/dtrace/dtrace.d
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2019 Fastly, Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ *
+ * Below is an example bpftrace script that logs the events in JSON-logging
+ * format.  The script can be invoked like:
+ *
+ * % sudo dtrace -c './cli 127.0.0.1 4433' -s misc/dtrace/dtrace.d
+ */
+
+picotls$target:::picotls_new {
+    printf("\n{\"addr\": \"0x%p\", \"event\": \"new\", \"is_server\": %d}", arg0, arg1);
+}
+picotls$target:::picotls_free {
+    printf("\n{\"addr\": \"0x%p\", \"event\": \"free\"}", arg0);
+}
+picotls$target:::picotls_client_random {
+    printf("\n{\"addr\": \"0x%p\", \"event\": \"client_random\", \"bytes\": \"%s\"}", arg0, copyinstr(arg1));
+}
+picotls$target:::picotls_new_secret {
+    printf("\n{\"addr\": \"0x%p\", \"event\": \"new_secret\", \"label\": \"%s\", \"secret\": \"%s\"}", arg0, copyinstr(arg1), copyinstr(arg2));
+}
+picotls$target:::picotls_receive_message {
+    printf("\n{\"addr\": \"0x%p\", \"event\": \"receive_message\", \"type\": %d, \"ret\": %d}\n", arg0, arg1, arg4);
+    tracemem(copyin(arg2, arg3), 65535, arg3);
+}
diff --git a/picotls-probes.d b/picotls-probes.d
new file mode 100644
index 0000000..614a571
--- /dev/null
+++ b/picotls-probes.d
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2019 Fastly, Kazuho Oku
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+provider picotls {
+    probe new(struct st_ptls_t *tls, int is_server);
+    probe free(struct st_ptls_t *tls);
+    probe client_random(struct st_ptls_t *tls, const void *bytes);
+    probe receive_message(struct st_ptls_t *tls, uint8_t message, const void *bytes, size_t len, int result);
+    probe new_secret(struct st_ptls_t *tls, const char *label, const char *secret_hex);
+};
diff --git a/picotls.xcodeproj/project.pbxproj b/picotls.xcodeproj/project.pbxproj
index 921682e..519a807 100644
--- a/picotls.xcodeproj/project.pbxproj
+++ b/picotls.xcodeproj/project.pbxproj
@@ -91,6 +91,7 @@
 		10EACB131DCEAF0F00CA0341 /* curve25519.h in Headers */ = {isa = PBXBuildFile; fileRef = 105900361DC8D44E00FB4085 /* curve25519.h */; };
 		10EACB1A1DCEC2A300CA0341 /* libpicotls-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 106530DA1D9B3E6F005B2C60 /* libpicotls-core.a */; };
 		E949EF282073629300511ECA /* minicrypto-pem.c in Sources */ = {isa = PBXBuildFile; fileRef = E949EF272073629300511ECA /* minicrypto-pem.c */; };
+		E95E95382290456B00215ACD /* picotls-probes.d in Sources */ = {isa = PBXBuildFile; fileRef = E95EBCC0227B71170022C32D /* picotls-probes.d */; };
 		E97577012212405300D1EF74 /* ffx.h in Headers */ = {isa = PBXBuildFile; fileRef = E97577002212405300D1EF74 /* ffx.h */; };
 		E97577032212405D00D1EF74 /* ffx.c in Sources */ = {isa = PBXBuildFile; fileRef = E97577022212405D00D1EF74 /* ffx.c */; };
 		E97577042212407900D1EF74 /* ffx.c in Sources */ = {isa = PBXBuildFile; fileRef = E97577022212405D00D1EF74 /* ffx.c */; };
@@ -249,6 +250,11 @@
 		106530FE1DAD8A3C005B2C60 /* cli.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cli.c; sourceTree = "<group>"; };
 		10EACB171DCEAF0F00CA0341 /* libpicotls-minicrypto.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libpicotls-minicrypto.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		E949EF272073629300511ECA /* minicrypto-pem.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "minicrypto-pem.c"; sourceTree = "<group>"; };
+		E95EBCC0227B71170022C32D /* picotls-probes.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = "picotls-probes.d"; sourceTree = "<group>"; };
+		E95EBCC3227E82E00022C32D /* dump-github-repository.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = "dump-github-repository.pl"; sourceTree = "<group>"; };
+		E95EBCC5227E82FF0022C32D /* bpftrace.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = bpftrace.d; sourceTree = "<group>"; };
+		E95EBCC6227E82FF0022C32D /* dtrace.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = dtrace.d; sourceTree = "<group>"; };
+		E95EBCCA227EA0180022C32D /* dtrace-utils.cmake */ = {isa = PBXFileReference; lastKnownFileType = text; path = "dtrace-utils.cmake"; sourceTree = "<group>"; };
 		E97577002212405300D1EF74 /* ffx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ffx.h; sourceTree = "<group>"; };
 		E97577022212405D00D1EF74 /* ffx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffx.c; sourceTree = "<group>"; };
 		E97577072213148800D1EF74 /* e2e.t */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = e2e.t; sourceTree = "<group>"; };
@@ -380,10 +386,13 @@
 		106530A91D9985E0005B2C60 = {
 			isa = PBXGroup;
 			children = (
+				E95EBCC9227E9FF30022C32D /* cmake */,
 				E9E4B12C2181927900514B47 /* CMakeLists.txt */,
 				106530E11D9B4000005B2C60 /* deps */,
 				106530BC1D998616005B2C60 /* include */,
 				106530BD1D998624005B2C60 /* lib */,
+				E95EBCC2227E82BA0022C32D /* misc */,
+				E95EBCC0227B71170022C32D /* picotls-probes.d */,
 				E992F79920E99A080008154D /* src */,
 				106530C41D9B1A0E005B2C60 /* t */,
 				106530B31D9985E0005B2C60 /* Products */,
@@ -474,6 +483,32 @@
 			path = picotls;
 			sourceTree = "<group>";
 		};
+		E95EBCC2227E82BA0022C32D /* misc */ = {
+			isa = PBXGroup;
+			children = (
+				E95EBCC4227E82F20022C32D /* dtrace */,
+				E95EBCC3227E82E00022C32D /* dump-github-repository.pl */,
+			);
+			path = misc;
+			sourceTree = "<group>";
+		};
+		E95EBCC4227E82F20022C32D /* dtrace */ = {
+			isa = PBXGroup;
+			children = (
+				E95EBCC5227E82FF0022C32D /* bpftrace.d */,
+				E95EBCC6227E82FF0022C32D /* dtrace.d */,
+			);
+			path = dtrace;
+			sourceTree = "<group>";
+		};
+		E95EBCC9227E9FF30022C32D /* cmake */ = {
+			isa = PBXGroup;
+			children = (
+				E95EBCCA227EA0180022C32D /* dtrace-utils.cmake */,
+			);
+			path = cmake;
+			sourceTree = "<group>";
+		};
 		E992F79920E99A080008154D /* src */ = {
 			isa = PBXGroup;
 			children = (
@@ -800,6 +835,7 @@
 			files = (
 				E99B75E01F5CDDB500CF503E /* asn1.c in Sources */,
 				E99B75E11F5CDDB500CF503E /* pembase64.c in Sources */,
+				E95E95382290456B00215ACD /* picotls-probes.d in Sources */,
 				E97577032212405D00D1EF74 /* ffx.c in Sources */,
 				106530EB1D9B7C5C005B2C60 /* picotls.c in Sources */,
 				E9E4B12B2180530400514B47 /* certificate_compression.c in Sources */,
@@ -880,7 +916,7 @@
 		105900491DC8D57000FB4085 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				OTHER_LDFLAGS = "";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
@@ -889,7 +925,7 @@
 		1059004A1DC8D57000FB4085 /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				OTHER_LDFLAGS = "";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
@@ -899,6 +935,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
@@ -912,6 +949,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
@@ -952,6 +990,7 @@
 				GCC_OPTIMIZATION_LEVEL = 0;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"DEBUG=1",
+					"PICOTLS_USE_DTRACE=1",
 					"$(inherited)",
 				);
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
@@ -999,6 +1038,7 @@
 				ENABLE_STRICT_OBJC_MSGSEND = YES;
 				GCC_C_LANGUAGE_STANDARD = gnu99;
 				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_PREPROCESSOR_DEFINITIONS = "PICOTLS_USE_DTRACE=1";
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
 				GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -1019,7 +1059,7 @@
 		106530D11D9B3D45005B2C60 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
@@ -1033,7 +1073,7 @@
 		106530D21D9B3D45005B2C60 /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
@@ -1048,7 +1088,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Debug;
@@ -1057,7 +1097,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Release;
@@ -1065,7 +1105,10 @@
 		106530FA1DAD8985005B2C60 /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "PICOTLS_USE_BROTLI=1";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"PICOTLS_USE_BROTLI=1",
+					"$(inherited)",
+				);
 				HEADER_SEARCH_PATHS = (
 					include,
 					"/usr/local/openssl-1.1.0/include",
@@ -1088,7 +1131,10 @@
 		106530FB1DAD8985005B2C60 /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
-				GCC_PREPROCESSOR_DEFINITIONS = "PICOTLS_USE_BROTLI=1";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"PICOTLS_USE_BROTLI=1",
+					"$(inherited)",
+				);
 				HEADER_SEARCH_PATHS = (
 					include,
 					"/usr/local/openssl-1.1.0/include",
@@ -1112,7 +1158,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Debug;
@@ -1121,7 +1167,7 @@
 			isa = XCBuildConfiguration;
 			buildSettings = {
 				EXECUTABLE_PREFIX = lib;
-				GCC_PREPROCESSOR_DEFINITIONS = "";
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				PRODUCT_NAME = "$(TARGET_NAME)";
 			};
 			name = Release;
@@ -1129,6 +1175,7 @@
 		E992F7A720E99A7C0008154D /* Debug */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
@@ -1142,6 +1189,7 @@
 		E992F7A820E99A7C0008154D /* Release */ = {
 			isa = XCBuildConfiguration;
 			buildSettings = {
+				GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
 				HEADER_SEARCH_PATHS = (
 					"/usr/local/openssl-1.1.0/include",
 					include,
diff --git a/t/cli.c b/t/cli.c
index e502d14..0a90dca 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -64,7 +64,7 @@
 }
 
 static int handle_connection(int sockfd, ptls_context_t *ctx, const char *server_name, const char *input_file,
-                             ptls_handshake_properties_t *hsprop, int request_key_update)
+                             ptls_handshake_properties_t *hsprop, int request_key_update, int keep_sender_open)
 {
     ptls_t *tls = ptls_new(ctx, server_name == NULL);
     ptls_buffer_t rbuf, encbuf, ptbuf;
@@ -224,18 +224,19 @@
 
         /* close the sender side when necessary */
         if (state == IN_1RTT && inputfd == -1) {
-            ptls_buffer_t wbuf;
-            uint8_t wbuf_small[32];
-            ptls_buffer_init(&wbuf, wbuf_small, sizeof(wbuf_small));
-            if ((ret = ptls_send_alert(tls, &wbuf,
-                       PTLS_ALERT_LEVEL_WARNING, PTLS_ALERT_CLOSE_NOTIFY)) != 0) {
-                fprintf(stderr, "ptls_send_alert:%d\n", ret);
+            if (!keep_sender_open) {
+                ptls_buffer_t wbuf;
+                uint8_t wbuf_small[32];
+                ptls_buffer_init(&wbuf, wbuf_small, sizeof(wbuf_small));
+                if ((ret = ptls_send_alert(tls, &wbuf,
+                           PTLS_ALERT_LEVEL_WARNING, PTLS_ALERT_CLOSE_NOTIFY)) != 0) {
+                    fprintf(stderr, "ptls_send_alert:%d\n", ret);
+                }
+                if (wbuf.off != 0)
+                    (void)write(sockfd, wbuf.base, wbuf.off);
+                ptls_buffer_dispose(&wbuf);
+                shutdown(sockfd, SHUT_WR);
             }
-            if (wbuf.off != 0) {
-                (void)write(sockfd, wbuf.base, wbuf.off);
-            }
-            ptls_buffer_dispose(&wbuf);
-            shutdown(sockfd, SHUT_WR);
             state = IN_SHUTDOWN;
         }
     }
@@ -278,14 +279,14 @@
     while (1) {
         fprintf(stderr, "waiting for connections\n");
         if ((conn_fd = accept(listen_fd, NULL, 0)) != -1)
-            handle_connection(conn_fd, ctx, NULL, input_file, hsprop, request_key_update);
+            handle_connection(conn_fd, ctx, NULL, input_file, hsprop, request_key_update, 0);
     }
 
     return 0;
 }
 
 static int run_client(struct sockaddr *sa, socklen_t salen, ptls_context_t *ctx, const char *server_name, const char *input_file,
-                      ptls_handshake_properties_t *hsprop, int request_key_update)
+                      ptls_handshake_properties_t *hsprop, int request_key_update, int keep_sender_open)
 {
     int fd;
 
@@ -300,7 +301,7 @@
         return 1;
     }
 
-    int ret = handle_connection(fd, ctx, server_name, input_file, hsprop, request_key_update);
+    int ret = handle_connection(fd, ctx, server_name, input_file, hsprop, request_key_update, keep_sender_open);
     free(hsprop->client.esni_keys.base);
     return ret;
 }
@@ -317,6 +318,7 @@
            "  -C certificate-file  certificate chain used for client authentication\n"
            "  -c certificate-file  certificate chain used for server authentication\n"
            "  -i file              a file to read from and send to the peer (default: stdin)\n"
+           "  -I                   keep send side open after sending all data (client-only)\n"
            "  -k key-file          specifies the credentials for signing the certificate\n"
            "  -l log-file          file to log events (incl. traffic secrets)\n"
            "  -n                   negotiates the key exchange method (i.e. wait for HRR)\n"
@@ -365,12 +367,12 @@
         ptls_key_exchange_context_t *elements[16];
         size_t count;
     } esni_key_exchanges;
-    int is_server = 0, use_early_data = 0, request_key_update = 0, ch;
+    int is_server = 0, use_early_data = 0, request_key_update = 0, keep_sender_open = 0, ch;
     struct sockaddr_storage sa;
     socklen_t salen;
     int family = 0;
 
-    while ((ch = getopt(argc, argv, "46abC:c:i:k:nN:es:SE:K:l:vh")) != -1) {
+    while ((ch = getopt(argc, argv, "46abC:c:i:Ik:nN:es:SE:K:l:vh")) != -1) {
         switch (ch) {
         case '4':
             family = AF_INET;
@@ -401,6 +403,9 @@
         case 'i':
             file = optarg;
             break;
+        case 'I':
+            keep_sender_open = 1;
+            break;
         case 'k':
             load_private_key(&ctx, optarg);
             break;
@@ -531,6 +536,6 @@
     if (is_server) {
         return run_server((struct sockaddr *)&sa, salen, &ctx, file, &hsprop, request_key_update);
     } else {
-        return run_client((struct sockaddr *)&sa, salen, &ctx, host, file, &hsprop, request_key_update);
+        return run_client((struct sockaddr *)&sa, salen, &ctx, host, file, &hsprop, request_key_update, keep_sender_open);
     }
 }