You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2020/01/14 21:00:48 UTC
[trafficserver] branch 9.0.x updated (0ccf629 -> 98f41e9)
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a change to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git.
from 0ccf629 Clear all pointers in API Hooks clear function.
new 9ebbd60 TLSv1.3 0-RTT support (#5450)
new 98f41e9 Don't sleep if ProtectedQueue.localQueue is not empty. (#6234)
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
build/crypto.m4 | 28 +++
configure.ac | 3 +
doc/admin-guide/files/records.config.en.rst | 18 ++
include/tscore/ink_config.h.in | 1 +
iocore/eventsystem/P_UnixEThread.h | 2 +-
iocore/eventsystem/UnixEThread.cc | 8 +-
iocore/net/P_SSLConfig.h | 6 +
iocore/net/P_SSLNetVConnection.h | 6 +
iocore/net/SSLConfig.cc | 13 +-
iocore/net/SSLNetVConnection.cc | 44 +++++
iocore/net/SSLSessionTicket.cc | 15 +-
iocore/net/SSLStats.cc | 12 +-
iocore/net/SSLStats.h | 1 +
iocore/net/SSLUtils.cc | 146 ++++++++++++++-
mgmt/RecordsConfig.cc | 5 +-
proxy/ProxyTransaction.cc | 4 +-
proxy/ProxyTransaction.h | 2 +-
proxy/hdrs/HTTP.h | 26 +++
proxy/hdrs/HdrToken.cc | 10 +-
proxy/hdrs/MIME.cc | 22 +--
proxy/hdrs/MIME.h | 6 +-
proxy/http/Http1ClientSession.cc | 8 +-
proxy/http/Http1ClientSession.h | 2 +
proxy/http/HttpSM.cc | 24 ++-
proxy/http/HttpSM.h | 3 +-
proxy/http/HttpTransact.cc | 14 ++
proxy/http/HttpTransact.h | 1 +
proxy/http2/Http2ClientSession.cc | 25 ++-
proxy/http2/Http2ClientSession.h | 17 +-
proxy/http2/Http2ConnectionState.cc | 25 ++-
src/traffic_quic/traffic_quic.cc | 2 +-
tests/gold_tests/tls/early_h1_get.txt | 3 +
tests/gold_tests/tls/early_h1_post.txt | 6 +
tests/gold_tests/tls/early_h2_get.txt | Bin 0 -> 77 bytes
tests/gold_tests/tls/early_h2_multi1.txt | Bin 0 -> 172 bytes
tests/gold_tests/tls/early_h2_multi2.txt | Bin 0 -> 170 bytes
tests/gold_tests/tls/early_h2_post.txt | Bin 0 -> 77 bytes
tests/gold_tests/tls/h2_early_decode.py | 257 +++++++++++++++++++++++++++
tests/gold_tests/tls/h2_early_gen.py | 185 +++++++++++++++++++
tests/gold_tests/tls/test-0rtt-s_client.py | 69 +++++++
tests/gold_tests/tls/tls_0rtt_server.test.py | 193 ++++++++++++++++++++
41 files changed, 1168 insertions(+), 44 deletions(-)
create mode 100644 tests/gold_tests/tls/early_h1_get.txt
create mode 100644 tests/gold_tests/tls/early_h1_post.txt
create mode 100644 tests/gold_tests/tls/early_h2_get.txt
create mode 100644 tests/gold_tests/tls/early_h2_multi1.txt
create mode 100644 tests/gold_tests/tls/early_h2_multi2.txt
create mode 100644 tests/gold_tests/tls/early_h2_post.txt
create mode 100755 tests/gold_tests/tls/h2_early_decode.py
create mode 100755 tests/gold_tests/tls/h2_early_gen.py
create mode 100644 tests/gold_tests/tls/test-0rtt-s_client.py
create mode 100644 tests/gold_tests/tls/tls_0rtt_server.test.py
[trafficserver] 02/02: Don't sleep if ProtectedQueue.localQueue is
not empty. (#6234)
Posted by zw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 98f41e932944a695a5ec6a20ffb8205872f86304
Author: Oknet Xu <xu...@skyguard.com.cn>
AuthorDate: Wed Nov 27 22:28:33 2019 +0800
Don't sleep if ProtectedQueue.localQueue is not empty. (#6234)
(cherry picked from commit aa5b2eafda2667621f443b455829ce3b5c3be0b0)
---
iocore/eventsystem/P_UnixEThread.h | 2 +-
iocore/eventsystem/UnixEThread.cc | 8 +++++++-
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/iocore/eventsystem/P_UnixEThread.h b/iocore/eventsystem/P_UnixEThread.h
index e5c4440..f60a8ce 100644
--- a/iocore/eventsystem/P_UnixEThread.h
+++ b/iocore/eventsystem/P_UnixEThread.h
@@ -33,7 +33,7 @@
#include "I_EThread.h"
#include "I_EventProcessor.h"
-const int DELAY_FOR_RETRY = HRTIME_MSECONDS(10);
+const ink_hrtime DELAY_FOR_RETRY = HRTIME_MSECONDS(10);
TS_INLINE Event *
EThread::schedule_imm(Continuation *cont, int callback_event, void *cookie)
diff --git a/iocore/eventsystem/UnixEThread.cc b/iocore/eventsystem/UnixEThread.cc
index dc44a83..715c7e4 100644
--- a/iocore/eventsystem/UnixEThread.cc
+++ b/iocore/eventsystem/UnixEThread.cc
@@ -264,7 +264,13 @@ EThread::execute_regular()
next_time = EventQueue.earliest_timeout();
ink_hrtime sleep_time = next_time - Thread::get_hrtime_updated();
if (sleep_time > 0) {
- sleep_time = std::min(sleep_time, HRTIME_MSECONDS(thread_max_heartbeat_mseconds));
+ if (EventQueueExternal.localQueue.empty()) {
+ sleep_time = std::min(sleep_time, HRTIME_MSECONDS(thread_max_heartbeat_mseconds));
+ } else {
+ // Because of a missed lock, Timed-Event and Negative-Event have been pushed into localQueue for retry in awhile.
+ // Therefore, we have to set the limitation of sleep time in order to handle the next retry in time.
+ sleep_time = std::min(sleep_time, DELAY_FOR_RETRY);
+ }
++(current_metric->_wait);
} else {
sleep_time = 0;
[trafficserver] 01/02: TLSv1.3 0-RTT support (#5450)
Posted by zw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
zwoop pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 9ebbd60bdf05404761742acfb90bbdcbd4c33cde
Author: Fei Deng <du...@gmail.com>
AuthorDate: Tue Jan 14 14:58:09 2020 -0600
TLSv1.3 0-RTT support (#5450)
* TLSv1.3 0-RTT support
TLSv1.3 0-RTT test
TLSv1.3 0-RTT anti-replay
TLSv1.3 0-RTT h2
(cherry picked from commit 8236813ef44e3187febe12b7996cb2cc9754f3e7)
---
build/crypto.m4 | 28 +++
configure.ac | 3 +
doc/admin-guide/files/records.config.en.rst | 18 ++
include/tscore/ink_config.h.in | 1 +
iocore/net/P_SSLConfig.h | 6 +
iocore/net/P_SSLNetVConnection.h | 6 +
iocore/net/SSLConfig.cc | 13 +-
iocore/net/SSLNetVConnection.cc | 44 +++++
iocore/net/SSLSessionTicket.cc | 15 +-
iocore/net/SSLStats.cc | 12 +-
iocore/net/SSLStats.h | 1 +
iocore/net/SSLUtils.cc | 146 ++++++++++++++-
mgmt/RecordsConfig.cc | 5 +-
proxy/ProxyTransaction.cc | 4 +-
proxy/ProxyTransaction.h | 2 +-
proxy/hdrs/HTTP.h | 26 +++
proxy/hdrs/HdrToken.cc | 10 +-
proxy/hdrs/MIME.cc | 22 +--
proxy/hdrs/MIME.h | 6 +-
proxy/http/Http1ClientSession.cc | 8 +-
proxy/http/Http1ClientSession.h | 2 +
proxy/http/HttpSM.cc | 24 ++-
proxy/http/HttpSM.h | 3 +-
proxy/http/HttpTransact.cc | 14 ++
proxy/http/HttpTransact.h | 1 +
proxy/http2/Http2ClientSession.cc | 25 ++-
proxy/http2/Http2ClientSession.h | 17 +-
proxy/http2/Http2ConnectionState.cc | 25 ++-
src/traffic_quic/traffic_quic.cc | 2 +-
tests/gold_tests/tls/early_h1_get.txt | 3 +
tests/gold_tests/tls/early_h1_post.txt | 6 +
tests/gold_tests/tls/early_h2_get.txt | Bin 0 -> 77 bytes
tests/gold_tests/tls/early_h2_multi1.txt | Bin 0 -> 172 bytes
tests/gold_tests/tls/early_h2_multi2.txt | Bin 0 -> 170 bytes
tests/gold_tests/tls/early_h2_post.txt | Bin 0 -> 77 bytes
tests/gold_tests/tls/h2_early_decode.py | 257 +++++++++++++++++++++++++++
tests/gold_tests/tls/h2_early_gen.py | 185 +++++++++++++++++++
tests/gold_tests/tls/test-0rtt-s_client.py | 69 +++++++
tests/gold_tests/tls/tls_0rtt_server.test.py | 193 ++++++++++++++++++++
39 files changed, 1160 insertions(+), 42 deletions(-)
diff --git a/build/crypto.m4 b/build/crypto.m4
index 6361955..64787b4 100644
--- a/build/crypto.m4
+++ b/build/crypto.m4
@@ -287,3 +287,31 @@ AC_DEFUN([TS_CHECK_CRYPTO_SET_CIPHERSUITES], [
TS_ARG_ENABLE_VAR([use], [tls-set-ciphersuites])
AC_SUBST(use_tls_set_ciphersuites)
])
+
+dnl
+dnl Since OpenSSL 1.1.1
+dnl
+AC_DEFUN([TS_CHECK_EARLY_DATA], [
+ _set_ciphersuites_saved_LIBS=$LIBS
+
+ TS_ADDTO(LIBS, [$OPENSSL_LIBS])
+ AC_CHECK_HEADERS(openssl/ssl.h)
+ AC_CHECK_FUNCS(
+ SSL_set_max_early_data,
+ [
+ has_tls_early_data=1
+ early_data_check=yes
+ ],
+ [
+ has_tls_early_data=0
+ early_data_check=no
+ ]
+ )
+
+ LIBS=$_set_ciphersuites_saved_LIBS
+
+ AC_MSG_CHECKING([for OpenSSL early data support])
+ AC_MSG_RESULT([$early_data_check])
+
+ AC_SUBST(has_tls_early_data)
+])
diff --git a/configure.ac b/configure.ac
index c03c6df..6b46a1c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1292,6 +1292,9 @@ TS_CHECK_CRYPTO_OCSP
# Check for SSL_CTX_set_ciphersuites call
TS_CHECK_CRYPTO_SET_CIPHERSUITES
+# Check for openssl early data support
+TS_CHECK_EARLY_DATA
+
saved_LIBS="$LIBS"
TS_ADDTO([LIBS], ["$OPENSSL_LIBS"])
diff --git a/doc/admin-guide/files/records.config.en.rst b/doc/admin-guide/files/records.config.en.rst
index 0e8743a..9bef21f 100644
--- a/doc/admin-guide/files/records.config.en.rst
+++ b/doc/admin-guide/files/records.config.en.rst
@@ -3529,6 +3529,24 @@ Client-Related Configuration
engines. This setting assumes an absolute path. An example config file is at
:ts:git:`contrib/openssl/load_engine.cnf`.
+TLS v1.3 0-RTT Configuration
+----------------------------
+
+.. note::
+ TLS v1.3 must be enabled in order to utilize 0-RTT early data.
+
+.. ts:cv:: CONFIG proxy.config.ssl.server.max_early_data INT 0
+
+ Specifies the maximum amount of early data in bytes that is permitted to be sent on a single connection.
+
+ The minimum value that enables early data, and the suggested value for this option are both 16384 (16KB).
+
+ Setting to ``0`` effectively disables 0-RTT.
+
+.. ts:cv:: CONFIG proxy.config.ssl.server.allow_early_data_params INT 0
+
+ Set to ``1`` to allow HTTP parameters on early data requests.
+
OCSP Stapling Configuration
===========================
diff --git a/include/tscore/ink_config.h.in b/include/tscore/ink_config.h.in
index e994ea0..3ff2acb 100644
--- a/include/tscore/ink_config.h.in
+++ b/include/tscore/ink_config.h.in
@@ -78,6 +78,7 @@
#define TS_USE_LINUX_NATIVE_AIO @use_linux_native_aio@
#define TS_USE_REMOTE_UNWINDING @use_remote_unwinding@
#define TS_USE_TLS_OCSP @use_tls_ocsp@
+#define TS_HAS_TLS_EARLY_DATA @has_tls_early_data@
#define TS_HAS_SO_PEERCRED @has_so_peercred@
diff --git a/iocore/net/P_SSLConfig.h b/iocore/net/P_SSLConfig.h
index 54a43dd..628eb63 100644
--- a/iocore/net/P_SSLConfig.h
+++ b/iocore/net/P_SSLConfig.h
@@ -40,6 +40,8 @@
#include "SSLSessionCache.h"
#include "YamlSNIConfig.h"
+#include "P_SSLUtils.h"
+
struct SSLCertLookup;
struct ssl_ticket_key_block;
@@ -100,6 +102,10 @@ struct SSLConfigParams : public ConfigInfo {
char *server_groups_list;
char *client_groups_list;
+ static uint32_t server_max_early_data;
+ static uint32_t server_recv_max_early_data;
+ static bool server_allow_early_data_params;
+
static int ssl_maxrecord;
static bool ssl_allow_client_renegotiation;
diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h
index 3b01405..609d6f1 100644
--- a/iocore/net/P_SSLNetVConnection.h
+++ b/iocore/net/P_SSLNetVConnection.h
@@ -409,6 +409,12 @@ public:
bool protocol_mask_set = false;
unsigned long protocol_mask;
+ // early data related stuff
+ bool early_data_finish = false;
+ MIOBuffer *early_data_buf = nullptr;
+ IOBufferReader *early_data_reader = nullptr;
+ int64_t read_from_early_data = 0;
+
// Only applies during the VERIFY certificate hooks (client and server side)
// Means to give the plugin access to the data structure passed in during the underlying
// openssl callback so the plugin can make more detailed decisions about the
diff --git a/iocore/net/SSLConfig.cc b/iocore/net/SSLConfig.cc
index a87eba3..99b3be2 100644
--- a/iocore/net/SSLConfig.cc
+++ b/iocore/net/SSLConfig.cc
@@ -41,7 +41,6 @@
#include "HttpConfig.h"
#include "P_Net.h"
-#include "P_SSLUtils.h"
#include "P_SSLClientUtils.h"
#include "P_SSLCertLookup.h"
#include "SSLDiags.h"
@@ -66,6 +65,11 @@ init_ssl_ctx_func SSLConfigParams::init_ssl_ctx_cb = nullptr;
load_ssl_file_func SSLConfigParams::load_ssl_file_cb = nullptr;
IpMap *SSLConfigParams::proxy_protocol_ipmap = nullptr;
+const uint32_t EARLY_DATA_DEFAULT_SIZE = 16384;
+uint32_t SSLConfigParams::server_max_early_data = 0;
+uint32_t SSLConfigParams::server_recv_max_early_data = EARLY_DATA_DEFAULT_SIZE;
+bool SSLConfigParams::server_allow_early_data_params = false;
+
int SSLConfigParams::async_handshake_enabled = 0;
char *SSLConfigParams::engine_conf_file = nullptr;
@@ -278,6 +282,13 @@ SSLConfigParams::initialize()
ssl_client_ctx_options |= SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION;
#endif
+ REC_ReadConfigInteger(server_max_early_data, "proxy.config.ssl.server.max_early_data");
+ REC_ReadConfigInt32(server_allow_early_data_params, "proxy.config.ssl.server.allow_early_data_params");
+
+ // According to OpenSSL the default value is 16384,
+ // we keep it unless "server_max_early_data" is higher.
+ server_recv_max_early_data = std::max(server_max_early_data, EARLY_DATA_DEFAULT_SIZE);
+
REC_ReadConfigStringAlloc(serverCertChainFilename, "proxy.config.ssl.server.cert_chain.filename");
REC_ReadConfigStringAlloc(serverCertRelativePath, "proxy.config.ssl.server.cert.path");
set_paths_helper(serverCertRelativePath, nullptr, &serverCertPathOnly, nullptr);
diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc
index 051c97a..fa59bb4 100644
--- a/iocore/net/SSLNetVConnection.cc
+++ b/iocore/net/SSLNetVConnection.cc
@@ -34,6 +34,7 @@
#include "P_Net.h"
#include "P_SSLUtils.h"
+#include "P_SSLNextProtocolSet.h"
#include "P_SSLConfig.h"
#include "P_SSLClientUtils.h"
#include "P_SSLSNI.h"
@@ -175,6 +176,38 @@ make_ssl_connection(SSL_CTX *ctx, SSLNetVConnection *netvc)
BIO *wbio = BIO_new_fd(netvc->get_socket(), BIO_NOCLOSE);
BIO_set_mem_eof_return(wbio, -1);
SSL_set_bio(ssl, rbio, wbio);
+
+#if TS_HAS_TLS_EARLY_DATA
+ // Must disable OpenSSL's internal anti-replay if external cache is used with
+ // 0-rtt, otherwise session reuse will be broken. The freshness check described
+ // in https://tools.ietf.org/html/rfc8446#section-8.3 is still performed. But we
+ // still need to implement something to try to prevent replay atacks.
+ //
+ // We are now also disabling this when using OpenSSL's internal cache, since we
+ // are calling "ssl_accept" non-blocking, it seems to be confusing the anti-replay
+ // mechanism and causing session resumption to fail.
+ SSLConfig::scoped_config params;
+ if (SSL_version(ssl) >= TLS1_3_VERSION && params->server_max_early_data > 0) {
+ bool ret1 = false;
+ bool ret2 = false;
+ if ((ret1 = SSL_set_max_early_data(ssl, params->server_max_early_data)) == 1) {
+ Debug("ssl_early_data", "SSL_set_max_early_data: success");
+ } else {
+ Debug("ssl_early_data", "SSL_set_max_early_data: failed");
+ }
+
+ if ((ret2 = SSL_set_recv_max_early_data(ssl, params->server_recv_max_early_data)) == 1) {
+ Debug("ssl_early_data", "SSL_set_recv_max_early_data: success");
+ } else {
+ Debug("ssl_early_data", "SSL_set_recv_max_early_data: failed");
+ }
+
+ if (ret1 && ret2) {
+ Debug("ssl_early_data", "Must disable anti-replay if 0-rtt is enabled.");
+ SSL_set_options(ssl, SSL_OP_NO_ANTI_REPLAY);
+ }
+ }
+#endif
}
SSLNetVCAttach(ssl, netvc);
@@ -923,6 +956,17 @@ SSLNetVConnection::free(EThread *t)
ats_free(tunnel_host);
+ if (early_data_reader != nullptr) {
+ early_data_reader->dealloc();
+ }
+
+ if (early_data_buf != nullptr) {
+ free_MIOBuffer(early_data_buf);
+ }
+
+ early_data_reader = nullptr;
+ early_data_buf = nullptr;
+
clear();
SET_CONTINUATION_HANDLER(this, (SSLNetVConnHandler)&SSLNetVConnection::startEvent);
ink_assert(con.fd == NO_FD);
diff --git a/iocore/net/SSLSessionTicket.cc b/iocore/net/SSLSessionTicket.cc
index 5610764..de7bce5 100644
--- a/iocore/net/SSLSessionTicket.cc
+++ b/iocore/net/SSLSessionTicket.cc
@@ -55,6 +55,7 @@ int
ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv, EVP_CIPHER_CTX *cipher_ctx, HMAC_CTX *hctx,
int enc)
{
+ SSLConfig::scoped_config config;
SSLCertificateConfig::scoped_config lookup;
SSLTicketKeyConfig::scoped_config params;
SSLNetVConnection &netvc = *SSLNetVCAccess(ssl);
@@ -82,7 +83,7 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
EVP_EncryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), nullptr, most_recent_key.aes_key, iv);
HMAC_Init_ex(hctx, most_recent_key.hmac_secret, sizeof(most_recent_key.hmac_secret), evp_md_func, nullptr);
- Debug("ssl", "create ticket for a new session.");
+ Debug("ssl_session_ticket", "create ticket for a new session.");
SSL_INCREMENT_DYN_STAT(ssl_total_tickets_created_stat);
return 1;
} else if (enc == 0) {
@@ -91,7 +92,7 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
EVP_DecryptInit_ex(cipher_ctx, EVP_aes_128_cbc(), nullptr, keyblock->keys[i].aes_key, iv);
HMAC_Init_ex(hctx, keyblock->keys[i].hmac_secret, sizeof(keyblock->keys[i].hmac_secret), evp_md_func, nullptr);
- Debug("ssl", "verify the ticket for an existing session.");
+ Debug("ssl_session_ticket", "verify the ticket for an existing session.");
// Increase the total number of decrypted tickets.
SSL_INCREMENT_DYN_STAT(ssl_total_tickets_verified_stat);
@@ -100,12 +101,20 @@ ssl_callback_session_ticket(SSL *ssl, unsigned char *keyname, unsigned char *iv,
}
netvc.setSSLSessionCacheHit(true);
+
+#if TS_HAS_TLS_EARLY_DATA
+ if (SSL_version(ssl) >= TLS1_3_VERSION && config->server_max_early_data > 0) {
+ Debug("ssl_session_ticket", "make sure tickets are only used once.");
+ return 2;
+ }
+#endif
+
// When we decrypt with an "older" key, encrypt the ticket again with the most recent key.
return (i == 0) ? 1 : 2;
}
}
- Debug("ssl", "keyname is not consistent.");
+ Debug("ssl_session_ticket", "keyname is not consistent.");
SSL_INCREMENT_DYN_STAT(ssl_total_tickets_not_found_stat);
return 0;
}
diff --git a/iocore/net/SSLStats.cc b/iocore/net/SSLStats.cc
index a29adc3..f9d5304 100644
--- a/iocore/net/SSLStats.cc
+++ b/iocore/net/SSLStats.cc
@@ -173,7 +173,7 @@ SSLInitializeStatistics()
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_session_cache_lock_contention", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_session_cache_lock_contention, RecRawStatSyncCount);
- /* Track dynamic record size */
+ // Track dynamic record size
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.default_record_size_count", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_total_dyn_def_tls_record_count, RecRawStatSyncSum);
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.max_record_size_count", RECD_COUNTER, RECP_PERSISTENT,
@@ -181,7 +181,7 @@ SSLInitializeStatistics()
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.redo_record_size_count", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_total_dyn_redo_tls_record_count, RecRawStatSyncCount);
- /* error stats */
+ // error stats
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_error_syscall", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_error_syscall, RecRawStatSyncCount);
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_error_read_eos", RECD_COUNTER, RECP_PERSISTENT,
@@ -191,7 +191,7 @@ SSLInitializeStatistics()
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_sni_name_set_failure", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_sni_name_set_failure, RecRawStatSyncCount);
- /* ocsp stapling stats */
+ // ocsp stapling stats
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_ocsp_revoked_cert_stat", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_ocsp_revoked_cert_stat, RecRawStatSyncCount);
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_ocsp_unknown_cert_stat", RECD_COUNTER, RECP_PERSISTENT,
@@ -201,7 +201,7 @@ SSLInitializeStatistics()
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_ocsp_refresh_cert_failure", RECD_INT, RECP_PERSISTENT,
(int)ssl_ocsp_refresh_cert_failure_stat, RecRawStatSyncCount);
- /* SSL Version stats */
+ // SSL Version stats
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_total_sslv3", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_total_sslv3, RecRawStatSyncCount);
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_total_tlsv1", RECD_COUNTER, RECP_PERSISTENT,
@@ -213,6 +213,10 @@ SSLInitializeStatistics()
RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.ssl_total_tlsv13", RECD_COUNTER, RECP_PERSISTENT,
(int)ssl_total_tlsv13, RecRawStatSyncCount);
+ // TLSv1.3 0-RTT stats
+ RecRegisterRawStat(ssl_rsb, RECT_PROCESS, "proxy.process.ssl.early_data_received", RECD_INT, RECP_PERSISTENT,
+ (int)ssl_early_data_received_count, RecRawStatSyncCount);
+
// Get and register the SSL cipher stats. Note that we are using the default SSL context to obtain
// the cipher list. This means that the set of ciphers is fixed by the build configuration and not
// filtered by proxy.config.ssl.server.cipher_suite. This keeps the set of cipher suites stable across
diff --git a/iocore/net/SSLStats.h b/iocore/net/SSLStats.h
index ccd6e93..202aa15 100644
--- a/iocore/net/SSLStats.h
+++ b/iocore/net/SSLStats.h
@@ -84,6 +84,7 @@ enum SSL_Stats {
ssl_session_cache_eviction,
ssl_session_cache_lock_contention,
ssl_session_cache_new_session,
+ ssl_early_data_received_count, // how many times we received early data
/* error stats */
ssl_error_syscall,
diff --git a/iocore/net/SSLUtils.cc b/iocore/net/SSLUtils.cc
index b1994c1..eb28242 100644
--- a/iocore/net/SSLUtils.cc
+++ b/iocore/net/SSLUtils.cc
@@ -1109,7 +1109,8 @@ SSLMultiCertConfigLoader::index_certificate(SSLCertLookup *lookup, SSLCertContex
static void
ssl_callback_info(const SSL *ssl, int where, int ret)
{
- Debug("ssl", "ssl_callback_info ssl: %p where: %d ret: %d State: %s", ssl, where, ret, SSL_state_string_long(ssl));
+ Debug("ssl", "ssl_callback_info ssl: %p, where: %d, ret: %d, State: %s", ssl, where, ret, SSL_state_string_long(ssl));
+
SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
if ((where & SSL_CB_ACCEPT_LOOP) && netvc->getSSLHandShakeComplete() == true &&
@@ -1132,6 +1133,13 @@ ssl_callback_info(const SSL *ssl, int where, int ret)
#endif
#endif
#endif
+#ifdef TLS1_3_VERSION
+ // TLSv1.3 has no renegotiation.
+ if (SSL_version(ssl) >= TLS1_3_VERSION) {
+ Debug("ssl", "TLSv1.3 has no renegotiation.");
+ return;
+ }
+#endif
netvc->setSSLClientRenegotiationAbort(true);
Debug("ssl", "ssl_callback_info trying to renegotiate from the client");
}
@@ -1237,7 +1245,6 @@ SSLMultiCertConfigLoader::init_server_ssl_ctx(std::vector<X509 *> &cert_list, co
SSL_CTX_sess_set_get_cb(ctx, ssl_get_cached_session);
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER | SSL_SESS_CACHE_NO_INTERNAL | additional_cache_flags);
-
break;
}
}
@@ -1696,7 +1703,26 @@ SSLWriteBuffer(SSL *ssl, const void *buf, int64_t nbytes, int64_t &nwritten)
return SSL_ERROR_NONE;
}
ERR_clear_error();
- int ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
+
+ int ret;
+#if TS_HAS_TLS_EARLY_DATA
+ if (SSL_version(ssl) >= TLS1_3_VERSION) {
+ if (SSL_is_init_finished(ssl)) {
+ ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
+ } else {
+ size_t nwrite;
+ ret = SSL_write_early_data(ssl, buf, static_cast<size_t>(nbytes), &nwrite);
+ if (ret == 1) {
+ ret = nwrite;
+ }
+ }
+ } else {
+ ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
+ }
+#else
+ ret = SSL_write(ssl, buf, static_cast<int>(nbytes));
+#endif
+
if (ret > 0) {
nwritten = ret;
BIO *bio = SSL_get_wbio(ssl);
@@ -1724,6 +1750,63 @@ SSLReadBuffer(SSL *ssl, void *buf, int64_t nbytes, int64_t &nread)
return SSL_ERROR_NONE;
}
ERR_clear_error();
+
+#if TS_HAS_TLS_EARLY_DATA
+ if (SSL_version(ssl) >= TLS1_3_VERSION) {
+ SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
+
+ int64_t early_data_len = 0;
+ if (netvc->early_data_reader != nullptr) {
+ early_data_len = netvc->early_data_reader->read_avail();
+ }
+
+ if (early_data_len > 0) {
+ Debug("ssl_early_data", "Reading from early data buffer.");
+ netvc->read_from_early_data += netvc->early_data_reader->read(buf, nbytes < early_data_len ? nbytes : early_data_len);
+
+ if (nbytes < early_data_len) {
+ nread = nbytes;
+ } else {
+ nread = early_data_len;
+ }
+
+ return SSL_ERROR_NONE;
+ }
+
+ if (SSLConfigParams::server_max_early_data > 0 && !netvc->early_data_finish) {
+ Debug("ssl_early_data", "More early data to read.");
+ ssl_error_t ssl_error = SSL_ERROR_NONE;
+ size_t read_bytes = 0;
+
+ int ret = SSL_read_early_data(ssl, buf, static_cast<size_t>(nbytes), &read_bytes);
+
+ if (ret == SSL_READ_EARLY_DATA_ERROR) {
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_ERROR");
+ ssl_error = SSL_get_error(ssl, ret);
+ Debug("ssl_early_data", "Error reading early data: %s", ERR_error_string(ERR_get_error(), nullptr));
+ } else {
+ if ((nread = read_bytes) > 0) {
+ netvc->read_from_early_data += read_bytes;
+ SSL_INCREMENT_DYN_STAT(ssl_early_data_received_count);
+ if (is_debug_tag_set("ssl_early_data_show_received")) {
+ std::string early_data_str(reinterpret_cast<char *>(buf), nread);
+ Debug("ssl_early_data_show_received", "Early data buffer: \n%s", early_data_str.c_str());
+ }
+ }
+
+ if (ret == SSL_READ_EARLY_DATA_FINISH) {
+ netvc->early_data_finish = true;
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_FINISH: size = %lu", nread);
+ } else {
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_SUCCESS: size = %lu", nread);
+ }
+ }
+
+ return ssl_error;
+ }
+ }
+#endif
+
int ret = SSL_read(ssl, buf, static_cast<int>(nbytes));
if (ret > 0) {
nread = ret;
@@ -1744,11 +1827,64 @@ ssl_error_t
SSLAccept(SSL *ssl)
{
ERR_clear_error();
- int ret = SSL_accept(ssl);
+
+ int ret = 0;
+ int ssl_error = SSL_ERROR_NONE;
+
+#if TS_HAS_TLS_EARLY_DATA
+ SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
+ if (SSLConfigParams::server_max_early_data > 0 && !netvc->early_data_finish) {
+ size_t nread;
+ if (netvc->early_data_buf == nullptr) {
+ netvc->early_data_buf = new_MIOBuffer(BUFFER_SIZE_INDEX_16K);
+ netvc->early_data_reader = netvc->early_data_buf->alloc_reader();
+ }
+
+ while (true) {
+ IOBufferBlock *block = new_IOBufferBlock();
+ block->alloc(BUFFER_SIZE_INDEX_16K);
+ ret = SSL_read_early_data(ssl, block->buf(), index_to_buffer_size(BUFFER_SIZE_INDEX_16K), &nread);
+
+ if (ret == SSL_READ_EARLY_DATA_ERROR) {
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_ERROR");
+ break;
+ } else {
+ if (nread > 0) {
+ block->fill(nread);
+ netvc->early_data_buf->append_block(block);
+ SSL_INCREMENT_DYN_STAT(ssl_early_data_received_count);
+
+ if (is_debug_tag_set("ssl_early_data_show_received")) {
+ std::string early_data_str(reinterpret_cast<char *>(block->buf()), nread);
+ Debug("ssl_early_data_show_received", "Early data buffer: \n%s", early_data_str.c_str());
+ }
+ }
+
+ if (ret == SSL_READ_EARLY_DATA_FINISH) {
+ netvc->early_data_finish = true;
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_FINISH: size = %lu", nread);
+
+ if (netvc->early_data_reader->read_avail() == 0) {
+ Debug("ssl_early_data", "no data in early data buffer");
+ ERR_clear_error();
+ ret = SSL_accept(ssl);
+ }
+ break;
+ }
+ Debug("ssl_early_data", "SSL_READ_EARLY_DATA_SUCCESS: size = %lu", nread);
+ }
+ }
+ } else {
+ ret = SSL_accept(ssl);
+ }
+#else
+ ret = SSL_accept(ssl);
+#endif
+
if (ret > 0) {
return SSL_ERROR_NONE;
}
- int ssl_error = SSL_get_error(ssl, ret);
+ ssl_error = SSL_get_error(ssl, ret);
if (ssl_error == SSL_ERROR_SSL && is_debug_tag_set("ssl.error.accept")) {
char buf[512];
unsigned long e = ERR_peek_last_error();
diff --git a/mgmt/RecordsConfig.cc b/mgmt/RecordsConfig.cc
index 6fa1c63..135e821 100644
--- a/mgmt/RecordsConfig.cc
+++ b/mgmt/RecordsConfig.cc
@@ -1150,7 +1150,10 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.ssl.client.groups_list", RECD_STRING, nullptr, RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
,
-
+ {RECT_CONFIG, "proxy.config.ssl.server.max_early_data", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
+ {RECT_CONFIG, "proxy.config.ssl.server.allow_early_data_params", RECD_INT, "0", RECU_RESTART_TS, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
//##############################################################################
//#
//# OCSP (Online Certificate Status Protocol) Stapling Configuration
diff --git a/proxy/ProxyTransaction.cc b/proxy/ProxyTransaction.cc
index 73843e8..8042c34 100644
--- a/proxy/ProxyTransaction.cc
+++ b/proxy/ProxyTransaction.cc
@@ -30,7 +30,7 @@
ProxyTransaction::ProxyTransaction() : VConnection(nullptr) {}
void
-ProxyTransaction::new_transaction()
+ProxyTransaction::new_transaction(bool from_early_data)
{
ink_assert(_sm == nullptr);
@@ -39,7 +39,7 @@ ProxyTransaction::new_transaction()
ink_release_assert(_proxy_ssn != nullptr);
_sm = HttpSM::allocate();
- _sm->init();
+ _sm->init(from_early_data);
HttpTxnDebug("[%" PRId64 "] Starting transaction %d using sm [%" PRId64 "]", _proxy_ssn->connection_id(),
_proxy_ssn->get_transact_count(), _sm->sm_id);
diff --git a/proxy/ProxyTransaction.h b/proxy/ProxyTransaction.h
index 60a9994..67defe3 100644
--- a/proxy/ProxyTransaction.h
+++ b/proxy/ProxyTransaction.h
@@ -37,7 +37,7 @@ public:
/// Virtual Methods
//
- virtual void new_transaction();
+ virtual void new_transaction(bool from_early_data = false);
virtual void attach_server_session(Http1ServerSession *ssession, bool transaction_done = true);
Action *adjust_thread(Continuation *cont, int event, void *data);
virtual void release(IOBufferReader *r);
diff --git a/proxy/hdrs/HTTP.h b/proxy/hdrs/HTTP.h
index c7c43e1..5acbaef 100644
--- a/proxy/hdrs/HTTP.h
+++ b/proxy/hdrs/HTTP.h
@@ -78,6 +78,7 @@ enum HTTPStatus {
HTTP_STATUS_REQUEST_URI_TOO_LONG = 414,
HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE = 415,
HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416,
+ HTTP_STATUS_TOO_EARLY = 425,
HTTP_STATUS_INTERNAL_SERVER_ERROR = 500,
HTTP_STATUS_NOT_IMPLEMENTED = 501,
@@ -506,6 +507,8 @@ public:
/// also had a port, @c false otherwise.
mutable bool m_port_in_header = false;
+ mutable bool early_data = false;
+
HTTPHdr() = default; // Force the creation of the default constructor
int valid() const;
@@ -629,6 +632,9 @@ public:
const char *reason_get(int *length);
void reason_set(const char *value, int length);
+ void mark_early_data(bool flag = true) const;
+ bool is_early_data() const;
+
ParseResult parse_req(HTTPParser *parser, const char **start, const char *end, bool eof, bool strict_uri_parsing = false,
size_t max_request_line_size = UINT16_MAX, size_t max_hdr_field_size = 131070);
ParseResult parse_resp(HTTPParser *parser, const char **start, const char *end, bool eof);
@@ -1221,6 +1227,26 @@ HTTPHdr::reason_set(const char *value, int length)
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
+inline void
+HTTPHdr::mark_early_data(bool flag) const
+{
+ ink_assert(valid());
+ early_data = flag;
+}
+
+/*-------------------------------------------------------------------------
+ -------------------------------------------------------------------------*/
+
+inline bool
+HTTPHdr::is_early_data() const
+{
+ ink_assert(valid());
+ return early_data;
+}
+
+/*-------------------------------------------------------------------------
+ -------------------------------------------------------------------------*/
+
inline ParseResult
HTTPHdr::parse_req(HTTPParser *parser, const char **start, const char *end, bool eof, bool strict_uri_parsing,
size_t max_request_line_size, size_t max_hdr_field_size)
diff --git a/proxy/hdrs/HdrToken.cc b/proxy/hdrs/HdrToken.cc
index a1ddec4..fcfc5c6 100644
--- a/proxy/hdrs/HdrToken.cc
+++ b/proxy/hdrs/HdrToken.cc
@@ -110,7 +110,10 @@ static const char *_hdrtoken_strs[] = {
"X-ID", "X-Forwarded-For", "TE", "Strict-Transport-Security", "100-continue",
// RFC-2739
- "Forwarded"};
+ "Forwarded",
+
+ // RFC-8470
+ "Early-Data"};
static HdrTokenTypeBinding _hdrtoken_strs_type_initializers[] = {
{"file", HDRTOKEN_TYPE_SCHEME},
@@ -359,7 +362,10 @@ static const char *_hdrtoken_commonly_tokenized_strs[] = {
"X-ID", "X-Forwarded-For", "TE", "Strict-Transport-Security", "100-continue",
// RFC-2739
- "Forwarded"};
+ "Forwarded",
+
+ // RFC-8470
+ "Early-Data"};
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
diff --git a/proxy/hdrs/MIME.cc b/proxy/hdrs/MIME.cc
index 7107401..75cab70 100644
--- a/proxy/hdrs/MIME.cc
+++ b/proxy/hdrs/MIME.cc
@@ -156,6 +156,7 @@ const char *MIME_FIELD_FORWARDED;
const char *MIME_FIELD_SEC_WEBSOCKET_KEY;
const char *MIME_FIELD_SEC_WEBSOCKET_VERSION;
const char *MIME_FIELD_HTTP2_SETTINGS;
+const char *MIME_FIELD_EARLY_DATA;
const char *MIME_VALUE_BYTES;
const char *MIME_VALUE_CHUNKED;
@@ -272,6 +273,7 @@ int MIME_LEN_FORWARDED;
int MIME_LEN_SEC_WEBSOCKET_KEY;
int MIME_LEN_SEC_WEBSOCKET_VERSION;
int MIME_LEN_HTTP2_SETTINGS;
+int MIME_LEN_EARLY_DATA;
int MIME_WKSIDX_ACCEPT;
int MIME_WKSIDX_ACCEPT_CHARSET;
@@ -351,6 +353,7 @@ int MIME_WKSIDX_FORWARDED;
int MIME_WKSIDX_SEC_WEBSOCKET_KEY;
int MIME_WKSIDX_SEC_WEBSOCKET_VERSION;
int MIME_WKSIDX_HTTP2_SETTINGS;
+int MIME_WKSIDX_EARLY_DATA;
/***********************************************************************
* *
@@ -745,11 +748,10 @@ mime_init()
MIME_FIELD_X_ID = hdrtoken_string_to_wks("X-ID");
MIME_FIELD_X_FORWARDED_FOR = hdrtoken_string_to_wks("X-Forwarded-For");
MIME_FIELD_FORWARDED = hdrtoken_string_to_wks("Forwarded");
-
- MIME_FIELD_SEC_WEBSOCKET_KEY = hdrtoken_string_to_wks("Sec-WebSocket-Key");
- MIME_FIELD_SEC_WEBSOCKET_VERSION = hdrtoken_string_to_wks("Sec-WebSocket-Version");
-
- MIME_FIELD_HTTP2_SETTINGS = hdrtoken_string_to_wks("HTTP2-Settings");
+ MIME_FIELD_SEC_WEBSOCKET_KEY = hdrtoken_string_to_wks("Sec-WebSocket-Key");
+ MIME_FIELD_SEC_WEBSOCKET_VERSION = hdrtoken_string_to_wks("Sec-WebSocket-Version");
+ MIME_FIELD_HTTP2_SETTINGS = hdrtoken_string_to_wks("HTTP2-Settings");
+ MIME_FIELD_EARLY_DATA = hdrtoken_string_to_wks("Early-Data");
MIME_LEN_ACCEPT = hdrtoken_wks_to_length(MIME_FIELD_ACCEPT);
MIME_LEN_ACCEPT_CHARSET = hdrtoken_wks_to_length(MIME_FIELD_ACCEPT_CHARSET);
@@ -826,11 +828,10 @@ mime_init()
MIME_LEN_X_ID = hdrtoken_wks_to_length(MIME_FIELD_X_ID);
MIME_LEN_X_FORWARDED_FOR = hdrtoken_wks_to_length(MIME_FIELD_X_FORWARDED_FOR);
MIME_LEN_FORWARDED = hdrtoken_wks_to_length(MIME_FIELD_FORWARDED);
-
- MIME_LEN_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_KEY);
- MIME_LEN_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_VERSION);
-
- MIME_LEN_HTTP2_SETTINGS = hdrtoken_wks_to_length(MIME_FIELD_HTTP2_SETTINGS);
+ MIME_LEN_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_KEY);
+ MIME_LEN_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_length(MIME_FIELD_SEC_WEBSOCKET_VERSION);
+ MIME_LEN_HTTP2_SETTINGS = hdrtoken_wks_to_length(MIME_FIELD_HTTP2_SETTINGS);
+ MIME_LEN_EARLY_DATA = hdrtoken_wks_to_length(MIME_FIELD_EARLY_DATA);
MIME_WKSIDX_ACCEPT = hdrtoken_wks_to_index(MIME_FIELD_ACCEPT);
MIME_WKSIDX_ACCEPT_CHARSET = hdrtoken_wks_to_index(MIME_FIELD_ACCEPT_CHARSET);
@@ -909,6 +910,7 @@ mime_init()
MIME_WKSIDX_SEC_WEBSOCKET_KEY = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_KEY);
MIME_WKSIDX_SEC_WEBSOCKET_VERSION = hdrtoken_wks_to_index(MIME_FIELD_SEC_WEBSOCKET_VERSION);
MIME_WKSIDX_HTTP2_SETTINGS = hdrtoken_wks_to_index(MIME_FIELD_HTTP2_SETTINGS);
+ MIME_WKSIDX_EARLY_DATA = hdrtoken_wks_to_index(MIME_FIELD_EARLY_DATA);
MIME_VALUE_BYTES = hdrtoken_string_to_wks("bytes");
MIME_VALUE_CHUNKED = hdrtoken_string_to_wks("chunked");
diff --git a/proxy/hdrs/MIME.h b/proxy/hdrs/MIME.h
index 1752ad3..a1e9dbc 100644
--- a/proxy/hdrs/MIME.h
+++ b/proxy/hdrs/MIME.h
@@ -458,6 +458,7 @@ extern const char *MIME_FIELD_FORWARDED;
extern const char *MIME_FIELD_SEC_WEBSOCKET_KEY;
extern const char *MIME_FIELD_SEC_WEBSOCKET_VERSION;
extern const char *MIME_FIELD_HTTP2_SETTINGS;
+extern const char *MIME_FIELD_EARLY_DATA;
extern const char *MIME_VALUE_BYTES;
extern const char *MIME_VALUE_CHUNKED;
@@ -559,7 +560,6 @@ extern int MIME_LEN_ATS_INTERNAL;
extern int MIME_LEN_X_ID;
extern int MIME_LEN_X_FORWARDED_FOR;
extern int MIME_LEN_FORWARDED;
-
extern int MIME_LEN_BYTES;
extern int MIME_LEN_CHUNKED;
extern int MIME_LEN_CLOSE;
@@ -582,11 +582,10 @@ extern int MIME_LEN_PROXY_REVALIDATE;
extern int MIME_LEN_PUBLIC;
extern int MIME_LEN_S_MAXAGE;
extern int MIME_LEN_NEED_REVALIDATE_ONCE;
-
extern int MIME_LEN_SEC_WEBSOCKET_KEY;
extern int MIME_LEN_SEC_WEBSOCKET_VERSION;
-
extern int MIME_LEN_HTTP2_SETTINGS;
+extern int MIME_LEN_EARLY_DATA;
extern int MIME_WKSIDX_ACCEPT;
extern int MIME_WKSIDX_ACCEPT_CHARSET;
@@ -664,6 +663,7 @@ extern int MIME_WKSIDX_X_ID;
extern int MIME_WKSIDX_SEC_WEBSOCKET_KEY;
extern int MIME_WKSIDX_SEC_WEBSOCKET_VERSION;
extern int MIME_WKSIDX_HTTP2_SETTINGS;
+extern int MIME_WKSIDX_EARLY_DATA;
/***********************************************************************
* *
diff --git a/proxy/http/Http1ClientSession.cc b/proxy/http/Http1ClientSession.cc
index d5b2269..38705df 100644
--- a/proxy/http/Http1ClientSession.cc
+++ b/proxy/http/Http1ClientSession.cc
@@ -139,6 +139,12 @@ Http1ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB
ssn_start_time = Thread::get_hrtime();
in_destroy = false;
+ SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection *>(new_vc);
+ if (ssl_vc != nullptr) {
+ read_from_early_data = ssl_vc->read_from_early_data;
+ Debug("ssl_early_data", "read_from_early_data = %ld", read_from_early_data);
+ }
+
MUTEX_TRY_LOCK(lock, mutex, this_ethread());
ink_assert(lock.is_locked());
@@ -460,7 +466,7 @@ Http1ClientSession::new_transaction()
transact_count++;
client_vc->add_to_active_queue();
- trans.new_transaction();
+ trans.new_transaction(read_from_early_data > 0 ? true : false);
}
void
diff --git a/proxy/http/Http1ClientSession.h b/proxy/http/Http1ClientSession.h
index fd9afda..4fa61c6 100644
--- a/proxy/http/Http1ClientSession.h
+++ b/proxy/http/Http1ClientSession.h
@@ -124,6 +124,8 @@ private:
int released_transactions = 0;
+ int64_t read_from_early_data = 0;
+
public:
// Link<Http1ClientSession> debug_link;
LINK(Http1ClientSession, debug_link);
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index 7f36d63..540b94c 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -296,10 +296,12 @@ HttpSM::destroy()
}
void
-HttpSM::init()
+HttpSM::init(bool from_early_data)
{
milestones[TS_MILESTONE_SM_START] = Thread::get_hrtime();
+ _from_early_data = from_early_data;
+
magic = HTTP_SM_MAGIC_ALIVE;
// Unique state machine identifier
@@ -659,7 +661,7 @@ HttpSM::state_read_client_request_header(int event, void *data)
}
// We need to handle EOS as well as READ_READY because the client
- // may have sent all of the data already followed by a fIN and that
+ // may have sent all of the data already followed by a FIN and that
// should be OK.
if (ua_raw_buffer_reader != nullptr) {
bool do_blind_tunnel = false;
@@ -750,6 +752,24 @@ HttpSM::state_read_client_request_header(int event, void *data)
case PARSE_RESULT_DONE:
SMDebug("http", "[%" PRId64 "] done parsing client request header", sm_id);
+ if (_from_early_data) {
+ // Only allow early data for safe methods defined in RFC7231 Section 4.2.1.
+ // https://tools.ietf.org/html/rfc7231#section-4.2.1
+ SMDebug("ssl_early_data", "%d", t_state.hdr_info.client_request.method_get_wksidx());
+ if (!HttpTransactHeaders::is_method_safe(t_state.hdr_info.client_request.method_get_wksidx())) {
+ SMDebug("http", "client request was from early data but is NOT safe");
+ call_transact_and_set_next_state(HttpTransact::TooEarly);
+ return 0;
+ } else if (!SSLConfigParams::server_allow_early_data_params &&
+ (t_state.hdr_info.client_request.m_http->u.req.m_url_impl->m_len_params > 0 ||
+ t_state.hdr_info.client_request.m_http->u.req.m_url_impl->m_len_query > 0)) {
+ SMDebug("http", "client request was from early data but HAS parameters");
+ call_transact_and_set_next_state(HttpTransact::TooEarly);
+ return 0;
+ }
+ t_state.hdr_info.client_request.mark_early_data();
+ }
+
ua_txn->set_session_active();
if (t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1) &&
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index 1f237ef..e357267 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -213,7 +213,7 @@ public:
HttpVCTableEntry *get_ua_entry(); // Added to get the ua_entry pointer - YTS-TEAM
HttpVCTableEntry *get_server_entry(); // Added to get the server_entry pointer
- void init();
+ void init(bool from_early_data = false);
void attach_client_session(ProxyTransaction *client_vc_arg, IOBufferReader *buffer_reader);
@@ -633,6 +633,7 @@ private:
PostDataBuffers _postbuf;
int _client_connection_id = -1, _client_transaction_id = -1;
int _client_transaction_priority_weight = -1, _client_transaction_priority_dependence = -1;
+ bool _from_early_data = false;
};
// Function to get the cache_sm object - YTS Team, yamsat
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index c83c105..34f236e 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -571,6 +571,16 @@ HttpTransact::Forbidden(State *s)
}
void
+HttpTransact::TooEarly(State *s)
+{
+ TxnDebug("http_trans", "[TooEarly]"
+ "Early Data method is not safe");
+ bootstrap_state_variables_from_request(s, &s->hdr_info.client_request);
+ build_error_response(s, HTTP_STATUS_TOO_EARLY, "Too Early", "too#early");
+ TRANSACT_RETURN(SM_ACTION_SEND_ERROR_CACHE_NOOP, nullptr);
+}
+
+void
HttpTransact::HandleBlindTunnel(State *s)
{
URL u;
@@ -7571,6 +7581,10 @@ HttpTransact::build_request(State *s, HTTPHdr *base_request, HTTPHdr *outgoing_r
TxnDebug("http_trans", "[build_request] request expect 100-continue headers removed");
}
+ if (base_request->is_early_data()) {
+ outgoing_request->value_set_int(MIME_FIELD_EARLY_DATA, MIME_LEN_EARLY_DATA, 1);
+ }
+
s->request_sent_time = ink_local_time();
s->current.now = s->request_sent_time;
diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h
index 09ddc29..f181c61 100644
--- a/proxy/http/HttpTransact.h
+++ b/proxy/http/HttpTransact.h
@@ -930,6 +930,7 @@ public:
static void HandleRequestAuthorized(State *s);
static void BadRequest(State *s);
static void Forbidden(State *s);
+ static void TooEarly(State *s);
static void PostActiveTimeoutResponse(State *s);
static void PostInactiveTimeoutResponse(State *s);
static void DecideCacheLookup(State *s);
diff --git a/proxy/http2/Http2ClientSession.cc b/proxy/http2/Http2ClientSession.cc
index 981b5a9..d703925 100644
--- a/proxy/http2/Http2ClientSession.cc
+++ b/proxy/http2/Http2ClientSession.cc
@@ -199,6 +199,12 @@ Http2ClientSession::new_connection(NetVConnection *new_vc, MIOBuffer *iobuf, IOB
this->connection_state.mutex = this->mutex;
+ SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection *>(new_vc);
+ if (ssl_vc != nullptr) {
+ this->read_from_early_data = ssl_vc->read_from_early_data;
+ Debug("ssl_early_data", "read_from_early_data = %ld", this->read_from_early_data);
+ }
+
Http2SsnDebug("session born, netvc %p", this->client_vc);
this->client_vc->set_tcp_congestion_control(CLIENT_SIDE);
@@ -443,6 +449,11 @@ Http2ClientSession::state_read_connection_preface(int event, void *edata)
return 0;
}
+ // Check whether data is read from early data
+ if (this->read_from_early_data > 0) {
+ this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data;
+ }
+
Http2SsnDebug("received connection preface");
this->_reader->consume(nbytes);
HTTP2_SET_SESSION_HANDLER(&Http2ClientSession::state_start_frame_read);
@@ -489,12 +500,19 @@ Http2ClientSession::do_start_frame_read(Http2ErrorCode &ret_error)
Http2SsnDebug("receiving frame header");
nbytes = copy_from_buffer_reader(buf, this->_reader, sizeof(buf));
+ this->cur_frame_from_early_data = false;
if (!http2_parse_frame_header(make_iovec(buf), this->current_hdr)) {
Http2SsnDebug("frame header parse failure");
this->do_io_close();
return -1;
}
+ // Check whether data is read from early data
+ if (this->read_from_early_data > 0) {
+ this->read_from_early_data -= this->read_from_early_data > nbytes ? nbytes : this->read_from_early_data;
+ this->cur_frame_from_early_data = true;
+ }
+
Http2SsnDebug("frame header length=%u, type=%u, flags=0x%x, streamid=%u", (unsigned)this->current_hdr.length,
(unsigned)this->current_hdr.type, (unsigned)this->current_hdr.flags, this->current_hdr.streamid);
@@ -552,8 +570,13 @@ Http2ClientSession::do_complete_frame_read()
// XXX parse the frame and handle it ...
ink_release_assert(this->_reader->read_avail() >= this->current_hdr.length);
- Http2Frame frame(this->current_hdr, this->_reader);
+ Http2Frame frame(this->current_hdr, this->_reader, this->cur_frame_from_early_data);
send_connection_event(&this->connection_state, HTTP2_SESSION_EVENT_RECV, &frame);
+ // Check whether data is read from early data
+ if (this->read_from_early_data > 0) {
+ this->read_from_early_data -=
+ this->read_from_early_data > this->current_hdr.length ? this->current_hdr.length : this->read_from_early_data;
+ }
this->_reader->consume(this->current_hdr.length);
++(this->_n_frame_read);
diff --git a/proxy/http2/Http2ClientSession.h b/proxy/http2/Http2ClientSession.h
index eee6a3b..c3a7209 100644
--- a/proxy/http2/Http2ClientSession.h
+++ b/proxy/http2/Http2ClientSession.h
@@ -80,8 +80,11 @@ struct Http2UpgradeContext {
class Http2Frame
{
public:
- Http2Frame(const Http2FrameHeader &h, IOBufferReader *r) : hdr(h), ioreader(r) {}
- Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags) : hdr({0, (uint8_t)type, flags, streamid}) {}
+ Http2Frame(const Http2FrameHeader &h, IOBufferReader *r, bool e = false) : hdr(h), ioreader(r), from_early_data(e) {}
+ Http2Frame(Http2FrameType type, Http2StreamId streamid, uint8_t flags, bool e = false)
+ : hdr({0, (uint8_t)type, flags, streamid}), from_early_data(e)
+ {
+ }
IOBufferReader *
reader() const
@@ -147,6 +150,12 @@ public:
}
}
+ bool
+ is_from_early_data() const
+ {
+ return this->from_early_data;
+ }
+
// noncopyable
Http2Frame(Http2Frame &) = delete;
Http2Frame &operator=(const Http2Frame &) = delete;
@@ -155,6 +164,7 @@ private:
Http2FrameHeader hdr; // frame header
Ptr<IOBufferBlock> ioblock; // frame payload
IOBufferReader *ioreader = nullptr;
+ bool from_early_data = false;
};
class Http2ClientSession : public ProxySession
@@ -264,6 +274,9 @@ private:
Event *_reenable_event = nullptr;
int _n_frame_read = 0;
+
+ int64_t read_from_early_data = 0;
+ bool cur_frame_from_early_data = false;
};
extern ClassAllocator<Http2ClientSession> http2ClientSessionAllocator;
diff --git a/proxy/http2/Http2ConnectionState.cc b/proxy/http2/Http2ConnectionState.cc
index eb23143..fed9cd2 100644
--- a/proxy/http2/Http2ConnectionState.cc
+++ b/proxy/http2/Http2ConnectionState.cc
@@ -369,7 +369,7 @@ rcv_headers_frame(Http2ConnectionState &cstate, const Http2Frame &frame)
if (!empty_request) {
SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
stream->mark_milestone(Http2StreamMilestone::START_TXN);
- stream->new_transaction();
+ stream->new_transaction(frame.is_from_early_data());
// Send request header to SM
stream->send_request(cstate);
}
@@ -925,7 +925,9 @@ rcv_continuation_frame(Http2ConnectionState &cstate, const Http2Frame &frame)
// Set up the State Machine
SCOPED_MUTEX_LOCK(stream_lock, stream->mutex, this_ethread());
stream->mark_milestone(Http2StreamMilestone::START_TXN);
- stream->new_transaction();
+ // This should be fine, need to verify whether we need to replace this with the
+ // "from_early_data" flag from the associated HEADERS frame.
+ stream->new_transaction(frame.is_from_early_data());
// Send request header to SM
stream->send_request(cstate);
} else {
@@ -1020,6 +1022,25 @@ Http2ConnectionState::main_event_handler(int event, void *edata)
break;
}
+ // We need to be careful here, certain frame types are not safe over 0-rtt, tentative for now.
+ // DATA: NO
+ // HEADERS: YES (safe http methods only, can only be checked after parsing the payload).
+ // PRIORITY: YES
+ // RST_STREAM: NO
+ // SETTINGS: YES
+ // PUSH_PROMISE: NO
+ // PING: YES
+ // GOAWAY: NO
+ // WINDOW_UPDATE: YES
+ // CONTINUATION: YES (safe http methods only, same as HEADERS frame).
+ if (frame->is_from_early_data() &&
+ (frame->header().type == HTTP2_FRAME_TYPE_DATA || frame->header().type == HTTP2_FRAME_TYPE_RST_STREAM ||
+ frame->header().type == HTTP2_FRAME_TYPE_PUSH_PROMISE || frame->header().type == HTTP2_FRAME_TYPE_GOAWAY)) {
+ Http2StreamDebug(ua_session, stream_id, "Discard a frame which is received from early data and has type=%x",
+ frame->header().type);
+ break;
+ }
+
if (frame_handlers[frame->header().type]) {
error = frame_handlers[frame->header().type](*this, *frame);
} else {
diff --git a/src/traffic_quic/traffic_quic.cc b/src/traffic_quic/traffic_quic.cc
index 1c290eb..69e2350 100644
--- a/src/traffic_quic/traffic_quic.cc
+++ b/src/traffic_quic/traffic_quic.cc
@@ -312,7 +312,7 @@ HttpSM::attach_client_session(ProxyTransaction *, IOBufferReader *)
}
void
-HttpSM::init()
+HttpSM::init(bool from_early_data)
{
ink_abort("do not call stub");
}
diff --git a/tests/gold_tests/tls/early_h1_get.txt b/tests/gold_tests/tls/early_h1_get.txt
new file mode 100644
index 0000000..047f0a3
--- /dev/null
+++ b/tests/gold_tests/tls/early_h1_get.txt
@@ -0,0 +1,3 @@
+GET /early_get HTTP/1.1
+Host: 127.0.0.1
+
diff --git a/tests/gold_tests/tls/early_h1_post.txt b/tests/gold_tests/tls/early_h1_post.txt
new file mode 100644
index 0000000..e85fd17
--- /dev/null
+++ b/tests/gold_tests/tls/early_h1_post.txt
@@ -0,0 +1,6 @@
+POST /early_post HTTP/1.1
+Host: 127.0.0.1
+Content-Length: 11
+
+knock knock
+
diff --git a/tests/gold_tests/tls/early_h2_get.txt b/tests/gold_tests/tls/early_h2_get.txt
new file mode 100644
index 0000000..6f535e8
Binary files /dev/null and b/tests/gold_tests/tls/early_h2_get.txt differ
diff --git a/tests/gold_tests/tls/early_h2_multi1.txt b/tests/gold_tests/tls/early_h2_multi1.txt
new file mode 100644
index 0000000..71c3350
Binary files /dev/null and b/tests/gold_tests/tls/early_h2_multi1.txt differ
diff --git a/tests/gold_tests/tls/early_h2_multi2.txt b/tests/gold_tests/tls/early_h2_multi2.txt
new file mode 100644
index 0000000..cdd633a
Binary files /dev/null and b/tests/gold_tests/tls/early_h2_multi2.txt differ
diff --git a/tests/gold_tests/tls/early_h2_post.txt b/tests/gold_tests/tls/early_h2_post.txt
new file mode 100644
index 0000000..ecee5c7
Binary files /dev/null and b/tests/gold_tests/tls/early_h2_post.txt differ
diff --git a/tests/gold_tests/tls/h2_early_decode.py b/tests/gold_tests/tls/h2_early_decode.py
new file mode 100755
index 0000000..7f87527
--- /dev/null
+++ b/tests/gold_tests/tls/h2_early_decode.py
@@ -0,0 +1,257 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+'''
+A simple tool to decode http2 frames for 0-rtt testing.
+'''
+
+import hpack
+import sys
+
+class Http2FrameDefs:
+
+ RESERVE_BIT_MASK = 0x7fffffff
+
+ DATA_FRAME = 0x00
+ HEADERS_FRAME = 0x01
+ PRIORITY_FRAME = 0x02
+ RST_STREAM_FRAME = 0x03
+ SETTINGS_FRAME = 0x04
+ PUSH_PROMISE_FRAME = 0x05
+ PING_FRAME = 0x06
+ GOAWAY_FRAME = 0x07
+ WINDOW_UPDATE_FRAME = 0x08
+ CONTINUATION_FRAME = 0x09
+
+ FRAME_TYPES = {
+ DATA_FRAME: 'DATA',
+ HEADERS_FRAME: 'HEADERS',
+ PRIORITY_FRAME: 'PRIORITY',
+ RST_STREAM_FRAME: 'RST_STREAM',
+ SETTINGS_FRAME: 'SETTINGS',
+ PUSH_PROMISE_FRAME: 'PUSH_PROMISE',
+ PING_FRAME: 'PING',
+ GOAWAY_FRAME: 'GOAWAY',
+ WINDOW_UPDATE_FRAME: 'WINDOW_UPDATE',
+ CONTINUATION_FRAME: 'CONTINUATION'
+ }
+
+ SETTINGS_HEADER_TABLE_SIZE = 0x01
+ SETTINGS_ENABLE_PUSH = 0x02
+ SETTINGS_MAX_CONCURRENT_STREAMS = 0x03
+ SETTINGS_INITIAL_WINDOW_SIZE = 0x04
+ SETTINGS_MAX_FRAME_SIZE = 0x05
+ SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
+
+ SETTINGS_ID = {
+ SETTINGS_HEADER_TABLE_SIZE: 'HEADER_TABLE_SIZE',
+ SETTINGS_ENABLE_PUSH: 'ENABLE_PUSH',
+ SETTINGS_MAX_CONCURRENT_STREAMS: 'MAX_CONCURRENT_STREAMS',
+ SETTINGS_INITIAL_WINDOW_SIZE: 'INITIAL_WINDOW_SIZE',
+ SETTINGS_MAX_FRAME_SIZE: 'MAX_FRAME_SIZE',
+ SETTINGS_MAX_HEADER_LIST_SIZE: 'MAX_HEADER_LIST_SIZE'
+ }
+
+ RST_STREAM_NO_ERROR = 0x0
+ RST_STREAM_PROTOCOL_ERROR = 0x1
+ RST_STREAM_INTERNAL_ERROR = 0x2
+ RST_STREAM_FLOW_CONTROL_ERROR = 0x3
+ RST_STREAM_SETTINGS_TIMEOUT = 0x4
+ RST_STREAM_STREAM_CLOSED = 0x5
+ RST_STREAM_FRAME_SIZE_ERROR = 0x6
+ RST_STREAM_REFUSED_STREAM = 0x7
+ RST_STREAM_CANCEL = 0x8
+ RST_STREAM_COMPRESSION_ERROR = 0x9
+ RST_STREAM_CONNECT_ERROR = 0xa
+ RST_STREAM_ENHANCE_YOUR_CALM = 0xb
+ RST_STREAM_INADEQUATE_SECURITY = 0xc
+ RST_STREAM_HTTP_1_1_REQUIRED = 0xd
+
+ RST_STREAM_ERROR_CODES = {
+ RST_STREAM_NO_ERROR: 'NO_ERROR',
+ RST_STREAM_PROTOCOL_ERROR: 'PROTOCOL_ERROR',
+ RST_STREAM_INTERNAL_ERROR: 'INTERNAL_ERROR',
+ RST_STREAM_FLOW_CONTROL_ERROR: 'FLOW_CONTROL_ERROR',
+ RST_STREAM_SETTINGS_TIMEOUT: 'SETTINGS_TIMEOUT',
+ RST_STREAM_STREAM_CLOSED: 'STREAM_CLOSED',
+ RST_STREAM_FRAME_SIZE_ERROR: 'FRAME_SIZE_ERROR',
+ RST_STREAM_REFUSED_STREAM: 'REFUSED_STREAM',
+ RST_STREAM_CANCEL: 'CANCEL',
+ RST_STREAM_COMPRESSION_ERROR: 'COMPRESSION_ERROR',
+ RST_STREAM_CONNECT_ERROR: 'CONNECT_ERROR',
+ RST_STREAM_ENHANCE_YOUR_CALM: 'ENHANCE_YOUR_CALM',
+ RST_STREAM_INADEQUATE_SECURITY: 'INADEQUATE_SECURITY',
+ RST_STREAM_HTTP_1_1_REQUIRED: 'HTTP_1_1_REQUIRED'
+ }
+
+
+class Http2Frame:
+ def __init__(self, length, frame_type, flags, stream_id):
+ self.length = length
+ self.frame_type = frame_type
+ self.flags = flags
+ self.stream_id = stream_id
+ self.payload = None
+ self.decode_error = None
+ return
+
+ def add_payload(self, payload):
+ self.payload = payload
+ return
+
+ def read_data(self):
+ if self.frame_type == Http2FrameDefs.DATA_FRAME:
+ return '\n' + self.payload.decode('utf-8')
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def read_headers(self):
+ if self.frame_type == Http2FrameDefs.HEADERS_FRAME:
+ try:
+ decoder = hpack.Decoder()
+ decoded_data = decoder.decode(self.payload)
+ output_str = ''
+ for header in decoded_data:
+ output_str += '\n'
+ for each in header:
+ output_str += each + ' '
+ except hpack.exceptions.InvalidTableIndex:
+ output_str = self.payload.hex()
+ output_str += '\nWarning: Decode failed: Invalid table index (not too important)'
+ return output_str
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def read_rst_stream(self):
+ if self.frame_type == Http2FrameDefs.RST_STREAM_FRAME:
+ error_code = int(self.payload.hex(), 16)
+ return '\nError Code = {0}'.format(Http2FrameDefs.RST_STREAM_ERROR_CODES[error_code])
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def read_settings(self):
+ if self.frame_type == Http2FrameDefs.SETTINGS_FRAME:
+ settings_str = ''
+ for i in range(0, self.length, 6):
+ settings_id = int(self.payload[i:i + 2].hex(), 16)
+ settings_val = int(self.payload[i + 2:i + 6].hex(), 16)
+ settings_str += '\n{0} = {1}'.format(Http2FrameDefs.SETTINGS_ID[settings_id], settings_val)
+ return settings_str
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def read_goaway(self):
+ if self.frame_type == Http2FrameDefs.GOAWAY_FRAME:
+ last_stream_id = int(self.payload[0:4].hex(), 16) & Http2FrameDefs.RESERVE_BIT_MASK
+ error_code = int(self.payload[4:8].hex(), 16)
+ debug_data = self.payload[8:].hex()
+ return '\nLast Stream ID = 0x{0:08x}\nError Code = 0x{1:08x}\nDebug Data = {2}'.format(
+ last_stream_id, error_code, debug_data
+ )
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def read_window_update(self):
+ if self.frame_type == Http2FrameDefs.WINDOW_UPDATE_FRAME:
+ window_size_increment = int(self.payload.hex(), 16) & Http2FrameDefs.RESERVE_BIT_MASK
+ return '\nWindow Size Increment = {0}'.format(window_size_increment)
+ else:
+ return '\nError: Frame type mismatch: {0}'.format(Http2FrameDefs.FRAME_TYPES[self.frame_type])
+
+ def print_payload(self):
+ if self.frame_type == Http2FrameDefs.DATA_FRAME:
+ return self.read_data()
+ elif self.frame_type == Http2FrameDefs.HEADERS_FRAME:
+ return self.read_headers()
+ elif self.frame_type == Http2FrameDefs.RST_STREAM_FRAME:
+ return self.read_rst_stream()
+ elif self.frame_type == Http2FrameDefs.SETTINGS_FRAME:
+ return self.read_settings()
+ elif self.frame_type == Http2FrameDefs.GOAWAY_FRAME:
+ return self.read_goaway()
+ elif self.frame_type == Http2FrameDefs.WINDOW_UPDATE_FRAME:
+ return self.read_window_update()
+ else:
+ return self.payload.hex()
+
+ def print(self):
+ output = 'Length: {0}\nType: {1}\nFlags: {2}\nStream ID: {3}\nPayload: {4}\n'.format(
+ self.length, Http2FrameDefs.FRAME_TYPES[self.frame_type], self.flags, self.stream_id, self.print_payload()
+ )
+ if self.decode_error is not None:
+ output += self.decode_error + '\n'
+ return output
+
+ def __str__(self):
+ return self.print()
+
+class Decoder:
+ def read_frame_header(self, data):
+ frame = Http2Frame(
+ length=int(data[0:3].hex(), 16),
+ frame_type=int(data[3:4].hex(), 16),
+ flags=int(data[4:5].hex(), 16),
+ stream_id=int(data[5:9].hex(), 16) & Http2FrameDefs.RESERVE_BIT_MASK
+ )
+ return frame
+
+ def decode(self, data):
+ temp_data = data
+ frames = []
+ while len(temp_data) >= 9:
+ frame_header = temp_data[0:9]
+ frame = self.read_frame_header(frame_header)
+ if frame.length > len(temp_data[9:]):
+ frame.decode_error = 'Error: Payload length greater than data: {0} > {1}'.format(frame.length, len(temp_data[9:]))
+ frame.add_payload(temp_data[9:])
+ frames.append(frame)
+ else:
+ frame.add_payload(temp_data[9:9 + frame.length])
+ frames.append(frame)
+ temp_data = temp_data[9 + frame.length:]
+ return frames
+
+def main():
+ # input file is output from openssl s_client.
+ # sample command to get this output:
+ # openssl s_client -bind 127.0.0.1:61991 -connect 127.0.0.1:61992 -tls1_3 -quiet -sess_out /home/duke/Dev/ats-test/sess.dat -sess_in /home/duke/Dev/ats-test/sess.dat -early_data ./gold_tests/tls/early2.txt >! _sandbox/tls_0rtt_server/early2_out.txt 2>&1
+
+ if len(sys.argv) < 2:
+ print('Error: No input file to decode.')
+ exit(1)
+
+ lines = None
+ with open(sys.argv[1], 'rb') as in_file:
+ lines = in_file.readlines()
+
+ data = b''
+ for line in lines:
+ if line.startswith(bytes('SSL_connect:', 'utf-8')) or \
+ line.startswith(bytes('SSL3 alert', 'utf-8')) or \
+ bytes('Can\'t use SSL_get_servername', 'utf-8') in line:
+ continue
+ data += line
+
+ d = Decoder()
+ frames = d.decode(data)
+ for frame in frames:
+ print(frame)
+ exit(0)
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/gold_tests/tls/h2_early_gen.py b/tests/gold_tests/tls/h2_early_gen.py
new file mode 100755
index 0000000..37e63c2
--- /dev/null
+++ b/tests/gold_tests/tls/h2_early_gen.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+'''
+A simple tool to generate some raw http2 frames for 0-rtt testing.
+'''
+
+# http2 frame format:
+# +-----------------------------------------------+
+# | Length (24) |
+# +---------------+---------------+---------------+
+# | Type (8) | Flags (8) |
+# +-+-------------+---------------+-------------------------------+
+# |R| Stream Identifier (31) |
+# +=+=============================================================+
+# | Frame Payload (0...) ...
+# +---------------------------------------------------------------+
+
+import hpack
+import os
+import sys
+
+H2_PREFACE = bytes.fromhex('505249202a20485454502f322e300d0a0d0a534d0d0a0d0a')
+
+RESERVED_BIT_MASK = 0x7FFFFFFF
+
+TYPE_HEADERS_FRAME = 0x01
+TYPE_SETTINGS_FRAME = 0x04
+TYPE_WINDOW_UPDATE_FRAME = 0x08
+
+SETTINGS_HEADER_TABLE_SIZE = 0x01
+SETTINGS_ENABLE_PUSH = 0x02
+SETTINGS_MAX_CONCURRENT_STREAMS = 0x03
+SETTINGS_INITIAL_WINDOW_SIZE = 0x04
+SETTINGS_MAX_FRAME_SIZE = 0x05
+SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
+
+HEADERS_FLAG_END_STREAM = 0x01
+HEADERS_FLAG_END_HEADERS = 0x04
+HEADERS_FLAG_END_PADDED = 0x08
+HEADERS_FLAG_END_PRIORITY = 0x20
+
+CURRENT_SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__))
+
+def encode_payload(data):
+ encoder = hpack.Encoder()
+ data_encoded = encoder.encode(data)
+ return data_encoded
+
+def make_frame(frame_length, frame_type, frame_flags, frame_stream_id, frame_payload):
+ frame_length = bytes.fromhex('{0:06x}'.format(frame_length))
+ frame_type = bytes.fromhex('{0:02x}'.format(frame_type))
+ frame_flags = bytes.fromhex('{0:02x}'.format(frame_flags))
+ frame_stream_id = bytes.fromhex('{0:08x}'.format(RESERVED_BIT_MASK & frame_stream_id))
+
+ frame = frame_length + frame_type + frame_flags + frame_stream_id
+
+ if frame_payload is not None:
+ frame += frame_payload
+
+ return frame
+
+def make_settins_frame(ack=False, empty=False):
+ payload = ''
+ if not ack and not empty:
+ payload += '{0:04x}{1:08x}'.format(SETTINGS_ENABLE_PUSH, 0)
+ payload += '{0:04x}{1:08x}'.format(SETTINGS_MAX_CONCURRENT_STREAMS, 100)
+ payload += '{0:04x}{1:08x}'.format(SETTINGS_INITIAL_WINDOW_SIZE, 1073741824)
+ payload = bytes.fromhex(payload)
+
+ frame = make_frame(
+ frame_length = len(payload),
+ frame_type = TYPE_SETTINGS_FRAME,
+ frame_flags = 1 if ack else 0,
+ frame_stream_id = 0,
+ frame_payload = payload
+ )
+
+ return frame
+
+def make_window_update_frame():
+ payload = '{0:08x}'.format(RESERVED_BIT_MASK & 1073676289)
+ payload = bytes.fromhex(payload)
+
+ frame = make_frame(
+ frame_length = len(payload),
+ frame_type = TYPE_WINDOW_UPDATE_FRAME,
+ frame_flags = 0,
+ frame_stream_id = 0,
+ frame_payload = payload
+ )
+ return frame
+
+def make_headers_frame(method, path='', stream_id=0x01):
+ headers = []
+ if method == 'get':
+ headers.append((':method', 'GET'))
+ if path != '':
+ headers.append((':path', path))
+ else:
+ headers.append((':path', '/early_get'))
+ elif method == 'post':
+ headers.append((':method', 'POST'))
+ if path != '':
+ headers.append((':path', path))
+ else:
+ headers.append((':path', '/early_post'))
+
+ headers.extend([
+ (':scheme', 'http'),
+ (':authority', '127.0.0.1'),
+ ('host', '127.0.0.1'),
+ ('accept', '*/*')
+ ])
+
+ headers_encoded = encode_payload(headers)
+
+ frame = make_frame(
+ frame_length = len(headers_encoded),
+ frame_type = TYPE_HEADERS_FRAME,
+ frame_flags = HEADERS_FLAG_END_STREAM | HEADERS_FLAG_END_HEADERS,
+ frame_stream_id = stream_id,
+ frame_payload = headers_encoded
+ )
+
+ return frame
+
+def make_h2_req(test):
+ h2_req = H2_PREFACE
+ if test == 'get' or test == 'post':
+ frames = [
+ make_settins_frame(ack=True),
+ make_headers_frame(test)
+ ]
+ for frame in frames:
+ h2_req += frame
+ elif test == 'multi1':
+ frames = [
+ make_settins_frame(ack=True),
+ make_headers_frame('get', '/early_multi_1', 1),
+ make_headers_frame('get', '/early_multi_2', 3),
+ make_headers_frame('get', '/early_multi_3', 5)
+ ]
+ for frame in frames:
+ h2_req += frame
+ elif test == 'multi2':
+ frames = [
+ make_settins_frame(ack=True),
+ make_headers_frame('get', '/early_multi_1', 1),
+ make_headers_frame('post', stream_id=3),
+ make_headers_frame('get', '/early_multi_3', 5)
+ ]
+ for frame in frames:
+ h2_req += frame
+ else:
+ pass
+ return h2_req
+
+def write_to_file(data, file_name):
+ with open(file_name, 'wb') as out_file:
+ out_file.write(data)
+ return
+
+def main():
+ test = sys.argv[1]
+ write_to_file(make_h2_req(test), os.path.join(CURRENT_SCRIPT_PATH, 'early_h2_{0}.txt'.format(test)))
+ exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/gold_tests/tls/test-0rtt-s_client.py b/tests/gold_tests/tls/test-0rtt-s_client.py
new file mode 100644
index 0000000..05c7165
--- /dev/null
+++ b/tests/gold_tests/tls/test-0rtt-s_client.py
@@ -0,0 +1,69 @@
+'''
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import subprocess
+import sys
+import os
+import shlex
+import h2_early_decode
+
+def main():
+ ats_port = sys.argv[1]
+ http_ver = sys.argv[2]
+ test = sys.argv[3]
+ sess_file_path = os.path.join(sys.argv[4], 'sess.dat')
+ early_data_file_path = os.path.join(sys.argv[4], 'early_{0}_{1}.txt'.format(http_ver, test))
+
+ s_client_cmd_1 = shlex.split('openssl s_client -connect 127.0.0.1:{0} -tls1_3 -quiet -sess_out {1}'.format(ats_port, sess_file_path))
+ s_client_cmd_2 = shlex.split('openssl s_client -connect 127.0.0.1:{0} -tls1_3 -quiet -sess_in {1} -early_data {2}'.format(ats_port, sess_file_path, early_data_file_path))
+
+ create_sess_proc = subprocess.Popen(s_client_cmd_1, env=os.environ.copy(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ try:
+ output = create_sess_proc.communicate(timeout=1)[0]
+ except subprocess.TimeoutExpired:
+ create_sess_proc.kill()
+ output = create_sess_proc.communicate()[0]
+
+ reuse_sess_proc = subprocess.Popen(s_client_cmd_2, env=os.environ.copy(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ try:
+ output = reuse_sess_proc.communicate(timeout=1)[0]
+ except subprocess.TimeoutExpired:
+ reuse_sess_proc.kill()
+ output = reuse_sess_proc.communicate()[0]
+
+ if http_ver == 'h2':
+ lines = output.split(bytes('\n', 'utf-8'))
+ data = b''
+ for line in lines:
+ line += b'\n'
+ if line.startswith(bytes('SSL_connect:', 'utf-8')) or \
+ line.startswith(bytes('SSL3 alert', 'utf-8')) or \
+ bytes('Can\'t use SSL_get_servername', 'utf-8') in line:
+ continue
+ data += line
+ d = h2_early_decode.Decoder()
+ frames = d.decode(data)
+ for frame in frames:
+ print(frame)
+ else:
+ print(output.decode('utf-8'))
+
+ exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/gold_tests/tls/tls_0rtt_server.test.py b/tests/gold_tests/tls/tls_0rtt_server.test.py
new file mode 100644
index 0000000..e438846
--- /dev/null
+++ b/tests/gold_tests/tls/tls_0rtt_server.test.py
@@ -0,0 +1,193 @@
+'''
+'''
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+Test.Summary = '''
+Test ATS TLSv1.3 0-RTT support
+'''
+
+Test.SkipUnless(Condition.HasOpenSSLVersion('1.1.1'))
+
+ts = Test.MakeATSProcess('ts', select_ports=True, enable_tls=True)
+server = Test.MakeOriginServer('server')
+
+request_header1 = {
+ 'headers': 'GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': ''
+}
+response_header1 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'curl test'
+}
+request_header2 = {
+ 'headers': 'GET /early_get HTTP/1.1\r\nHost: www.example.com\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': ''
+}
+response_header2 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'early data accepted'
+}
+request_header3 = {
+ 'headers': 'POST /early_post HTTP/1.1\r\nHost: www.example.com\r\nContent-Length: 11\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'knock knock'
+}
+response_header3 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nServer: uServer\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n',
+ 'timestamp': '1415926535.898',
+ 'body': ''
+}
+request_header4 = {
+ 'headers': 'GET /early_multi_1 HTTP/1.1\r\nHost: www.example.com\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': ''
+}
+response_header4 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'early data accepted multi_1'
+}
+request_header5 = {
+ 'headers': 'GET /early_multi_2 HTTP/1.1\r\nHost: www.example.com\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': ''
+}
+response_header5 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'early data accepted multi_2'
+}
+request_header6 = {
+ 'headers': 'GET /early_multi_3 HTTP/1.1\r\nHost: www.example.com\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': ''
+}
+response_header6 = {
+ 'headers': 'HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n',
+ 'timestamp': '1469733493.993',
+ 'body': 'early data accepted multi_3'
+}
+server.addResponse('sessionlog.json', request_header1, response_header1)
+server.addResponse('sessionlog.json', request_header2, response_header2)
+server.addResponse('sessionlog.json', request_header3, response_header3)
+server.addResponse('sessionlog.json', request_header4, response_header4)
+server.addResponse('sessionlog.json', request_header5, response_header5)
+server.addResponse('sessionlog.json', request_header6, response_header6)
+
+ts.addSSLfile('ssl/server.pem')
+ts.addSSLfile('ssl/server.key')
+
+ts.Setup.Copy('test-0rtt-s_client.py')
+ts.Setup.Copy('h2_early_decode.py')
+ts.Setup.Copy('early_h1_get.txt')
+ts.Setup.Copy('early_h1_post.txt')
+ts.Setup.Copy('early_h2_get.txt')
+ts.Setup.Copy('early_h2_post.txt')
+ts.Setup.Copy('early_h2_multi1.txt')
+ts.Setup.Copy('early_h2_multi2.txt')
+
+ts.Disk.records_config.update({
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http',
+ 'proxy.config.exec_thread.autoconfig': 0,
+ 'proxy.config.exec_thread.limit': 8,
+ 'proxy.config.http.server_ports': '{0}:proto=http2;http:ssl'.format(ts.Variables.ssl_port),
+ 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir),
+ 'proxy.config.ssl.session_cache': 2,
+ 'proxy.config.ssl.session_cache.size': 512000,
+ 'proxy.config.ssl.session_cache.timeout': 7200,
+ 'proxy.config.ssl.session_cache.num_buckets': 32768,
+ 'proxy.config.ssl.server.session_ticket.enable': 1,
+ 'proxy.config.ssl.server.max_early_data': 16384,
+ 'proxy.config.ssl.server.allow_early_data_params': 0,
+ 'proxy.config.ssl.server.cipher_suite': 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-DSS-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SH [...]
+})
+
+ts.Disk.ssl_multicert_config.AddLine(
+ 'dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key'
+)
+
+ts.Disk.remap_config.AddLine(
+ 'map / http://127.0.0.1:{0}'.format(server.Variables.Port)
+)
+
+tr = Test.AddTestRun('Basic Curl Test')
+tr.Processes.Default.Command = 'curl https://127.0.0.1:{0} -k'.format(ts.Variables.ssl_port)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.StartBefore(server)
+tr.Processes.Default.StartBefore(Test.Processes.ts, ready=When.PortOpen(ts.Variables.ssl_port))
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('curl test', 'Making sure the basics still work')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('early data accepted', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/1.1 GET)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h1', 'get', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('early data accepted', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/1.1 POST)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h1', 'post', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('HTTP/1.1 425 Too Early', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('early data accepted', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/2 GET)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h2', 'get', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('early data accepted', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/2 POST)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h2', 'post', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression(':status 425', 'Only safe methods are allowed')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('early data accepted', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/2 Multiplex)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h2', 'multi1', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('early data accepted multi_1', '')
+tr.Processes.Default.Streams.All += Testers.ContainsExpression('early data accepted multi_2', '')
+tr.Processes.Default.Streams.All += Testers.ContainsExpression('early data accepted multi_3', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')
+tr.StillRunningAfter = server
+tr.StillRunningAfter += ts
+
+tr = Test.AddTestRun('TLSv1.3 0-RTT Support (HTTP/2 Multiplex with POST)')
+tr.Processes.Default.Command = 'python3 test-0rtt-s_client.py {0} {1} {2} {3}'.format(ts.Variables.ssl_port, 'h2', 'multi2', Test.RunDirectory)
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.All = Testers.ContainsExpression('early data accepted multi_1', '')
+tr.Processes.Default.Streams.All += Testers.ContainsExpression(':status 425', 'Only safe methods are allowed')
+tr.Processes.Default.Streams.All += Testers.ContainsExpression('early data accepted multi_3', '')
+tr.Processes.Default.Streams.All += Testers.ExcludesExpression('curl test', '')