From 40331c131e1552e41876a9096eb4821eddffe5ac Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> Date: Tue, 26 May 2020 13:39:41 +0900 Subject: [PATCH] Implement NEW_TOKEN --- examples/client.cc | 64 +++++++- examples/client.h | 3 + examples/server.cc | 300 ++++++++++++++++++++++++++++++----- examples/server.h | 12 +- lib/includes/ngtcp2/ngtcp2.h | 55 ++++++- lib/ngtcp2_conn.c | 76 +++++++-- lib/ngtcp2_conn.h | 2 - lib/ngtcp2_log.c | 11 +- lib/ngtcp2_pkt.c | 16 +- lib/ngtcp2_pkt.h | 3 +- lib/ngtcp2_qlog.c | 12 +- tests/main.c | 2 + tests/ngtcp2_conn_test.c | 47 ++++++ tests/ngtcp2_conn_test.h | 1 + tests/ngtcp2_pkt_test.c | 10 +- 15 files changed, 520 insertions(+), 94 deletions(-) diff --git a/examples/client.cc b/examples/client.cc index 27ed356a..caf6f3e0 100644 --- a/examples/client.cc +++ b/examples/client.cc @@ -736,6 +736,22 @@ int Client::extend_max_stream_data(int64_t stream_id, uint64_t max_data) { return 0; } +namespace { +int recv_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token, + void *user_data) { + auto f = BIO_new_file(config.token_file.data(), "w"); + if (f == nullptr) { + std::cerr << "Could not write token in " << config.token_file << std::endl; + return 0; + } + + PEM_write_bio(f, "QUIC TOKEN", "", token->base, token->len); + BIO_free(f); + + return 0; +} +} // namespace + int Client::init_ssl() { if (ssl_) { SSL_free(ssl_); @@ -853,6 +869,9 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr, nullptr, // extend_max_remote_streams_bidi, nullptr, // extend_max_remote_streams_uni, ::extend_max_stream_data, + nullptr, // dcid_status + nullptr, // handshake_confirmed + ::recv_new_token, }; auto dis = std::uniform_int_distribution<uint8_t>( @@ -896,6 +915,32 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr, settings.cc_algo = config.cc == "cubic" ? NGTCP2_CC_ALGO_CUBIC : NGTCP2_CC_ALGO_RENO; settings.initial_ts = util::timestamp(loop_); + + if (!config.token_file.empty()) { + std::cerr << "Reading token file " << config.token_file << std::endl; + auto f = BIO_new_file(config.token_file.data(), "r"); + if (f == nullptr) { + std::cerr << "Could not open token file " << config.token_file + << std::endl; + } else { + char *name, *header; + unsigned char *data; + long datalen; + if (PEM_read_bio(f, &name, &header, &data, &datalen) != 1) { + std::cerr << "Could not read token file " << config.token_file + << std::endl; + } else { + settings.token.base = data; + settings.token.len = datalen; + + OPENSSL_free(name); + OPENSSL_free(header); + } + + BIO_free(f); + } + } + auto ¶ms = settings.transport_params; params.initial_max_stream_data_bidi_local = config.max_stream_data_bidi_local; params.initial_max_stream_data_bidi_remote = @@ -912,9 +957,14 @@ int Client::init(int fd, const Address &local_addr, const Address &remote_addr, reinterpret_cast<const uint8_t *>(&local_addr.su))}, {remote_addr.len, const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>( &remote_addr.su))}}; - if (auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, version, - &callbacks, &settings, nullptr, this); - rv != 0) { + auto rv = ngtcp2_conn_client_new(&conn_, &dcid, &scid, &path, version, + &callbacks, &settings, nullptr, this); + + if (settings.token.base) { + OPENSSL_free(settings.token.base); + } + + if (rv != 0) { std::cerr << "ngtcp2_conn_client_new: " << ngtcp2_strerror(rv) << std::endl; return -1; } @@ -2422,6 +2472,9 @@ Options: Disable early data. --cc=(<cubic>|<reno>) The name of congestion controller algorithm. + --token-file=<PATH> + Read/write token from/to <PATH>. Token is obtained from + NEW_TOKEN frame from server. -h, --help Display this help and exit. --- @@ -2483,6 +2536,7 @@ int main(int argc, char **argv) { {"qlog-dir", required_argument, &flag, 26}, {"cc", required_argument, &flag, 27}, {"exit-on-all-streams-close", no_argument, &flag, 28}, + {"token-file", required_argument, &flag, 29}, {nullptr, 0, nullptr, 0}, }; @@ -2700,6 +2754,10 @@ int main(int argc, char **argv) { // --exit-on-all-streams-close config.exit_on_all_streams_close = true; break; + case 29: + // --token-file + config.token_file = optarg; + break; } break; default: diff --git a/examples/client.h b/examples/client.h index f86bd585..e84434ad 100644 --- a/examples/client.h +++ b/examples/client.h @@ -151,6 +151,9 @@ struct Config { std::array<uint8_t, 32> static_secret; // cc is the congestion controller algorithm. std::string_view cc; + // token_file is a path to file to read or write token from + // NEW_TOKEN frame. + std::string_view token_file; }; struct Buffer { diff --git a/examples/server.cc b/examples/server.cc index a101ca0c..e1539a07 100644 --- a/examples/server.cc +++ b/examples/server.cc @@ -82,6 +82,21 @@ namespace { auto randgen = util::make_mt19937(); } // namespace +namespace { +// RETRY_TOKEN_MAGIC is the magic byte of Retry token. Sent in +// plaintext. +constexpr uint8_t RETRY_TOKEN_MAGIC = 0xb6; +constexpr size_t MAX_RETRY_TOKENLEN = + /* magic */ 1 + sizeof(uint64_t) + NGTCP2_MAX_CIDLEN + + /* aead tag */ 16 + TOKEN_RAND_DATALEN; + +// TOKEN_MAGIC is the magic byte of token which is sent in NEW_TOKEN +// frame. Sent in plaintext. +constexpr uint8_t TOKEN_MAGIC = 0x36; +constexpr size_t MAX_TOKENLEN = + /* magic */ 1 + sizeof(uint64_t) + /* aead tag */ 16 + TOKEN_RAND_DATALEN; +} // namespace + namespace { Config config{}; } // namespace @@ -807,6 +822,27 @@ int Handler::handshake_completed() { } } + std::array<uint8_t, MAX_TOKENLEN> tokenbuf; + size_t tokenlen = tokenbuf.size(); + + if (server_->generate_token(tokenbuf.data(), tokenlen, &remote_addr_.su.sa, + remote_addr_.len) != 0) { + if (!config.quiet) { + std::cerr << "Unable to generate token" << std::endl; + } + return 0; + } + + ngtcp2_vec token{tokenbuf.data(), tokenlen}; + + if (auto rv = ngtcp2_conn_submit_new_token(conn_, &token); rv != 0) { + if (!config.quiet) { + std::cerr << "ngtcp2_conn_submit_new_token: " << ngtcp2_strerror(rv) + << std::endl; + } + return -1; + } + return 0; } @@ -2353,11 +2389,39 @@ int Server::on_read(Endpoint &ep) { send_retry(&hd, ep, &su.sa, addrlen); continue; } - if (verify_token(&ocid, &hd, &su.sa, addrlen) != 0) { - send_stateless_connection_close(&hd, ep, &su.sa, addrlen); - continue; + + switch (hd.token[0]) { + case RETRY_TOKEN_MAGIC: + if (verify_retry_token(&ocid, &hd, &su.sa, addrlen) != 0) { + send_stateless_connection_close(&hd, ep, &su.sa, addrlen); + continue; + } + pocid = &ocid; + break; + case TOKEN_MAGIC: + if (verify_token(&hd, &su.sa, addrlen) != 0) { + if (config.validate_addr) { + send_retry(&hd, ep, &su.sa, addrlen); + continue; + } + + hd.token = nullptr; + hd.tokenlen = 0; + } + break; + default: + if (!config.quiet) { + std::cerr << "Ignore unrecognized token" << std::endl; + } + if (config.validate_addr) { + send_retry(&hd, ep, &su.sa, addrlen); + continue; + } + + hd.token = nullptr; + hd.tokenlen = 0; + break; } - pocid = &ocid; } break; case NGTCP2_PKT_0RTT: @@ -2520,11 +2584,11 @@ int Server::send_retry(const ngtcp2_pkt_hd *chd, Endpoint &ep, std::generate(scid.data, scid.data + scid.datalen, [&dis]() { return dis(randgen); }); - std::array<uint8_t, 256> token; + std::array<uint8_t, MAX_RETRY_TOKENLEN> token; size_t tokenlen = token.size(); - if (generate_token(token.data(), tokenlen, sa, salen, &scid, &chd->dcid) != - 0) { + if (generate_retry_token(token.data(), tokenlen, sa, salen, &scid, + &chd->dcid) != 0) { return -1; } @@ -2611,13 +2675,10 @@ void Server::generate_rand_data(uint8_t *buf, size_t len) { std::generate_n(buf, len, [&dis]() { return dis(randgen); }); } -// RETRY_TOKEN_MAGIC is the magic byte of Retry token. Sent in -// plaintext. -constexpr uint8_t RETRY_TOKEN_MAGIC = 0xb6; - namespace { -size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa, - socklen_t salen, const ngtcp2_cid *retry_scid) { +size_t generate_retry_token_aad(uint8_t *dest, size_t destlen, + const sockaddr *sa, socklen_t salen, + const ngtcp2_cid *retry_scid) { assert(destlen >= salen + retry_scid->datalen); auto p = std::copy_n(reinterpret_cast<const uint8_t *>(sa), salen, dest); @@ -2627,9 +2688,10 @@ size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa, } } // namespace -int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, - socklen_t salen, const ngtcp2_cid *retry_scid, - const ngtcp2_cid *ocid) { +int Server::generate_retry_token(uint8_t *token, size_t &tokenlen, + const sockaddr *sa, socklen_t salen, + const ngtcp2_cid *retry_scid, + const ngtcp2_cid *ocid) { std::array<uint8_t, 4096> plaintext; uint64_t t = std::chrono::duration_cast<std::chrono::nanoseconds>( @@ -2637,7 +2699,6 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, .count(); auto p = std::begin(plaintext); - p = std::copy_n(reinterpret_cast<const uint8_t *>(sa), salen, p); // Host byte order p = std::copy_n(reinterpret_cast<uint8_t *>(&t), sizeof(t), p); p = std::copy_n(ocid->data, ocid->datalen, p); @@ -2657,7 +2718,7 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, std::array<uint8_t, 256> aad; auto aadlen = - generate_token_aad(aad.data(), aad.size(), sa, salen, retry_scid); + generate_retry_token_aad(aad.data(), aad.size(), sa, salen, retry_scid); token[0] = RETRY_TOKEN_MAGIC; if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(), @@ -2674,8 +2735,8 @@ int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, return 0; } -int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, - const sockaddr *sa, socklen_t salen) { +int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen) { std::array<char, NI_MAXHOST> host; std::array<char, NI_MAXSERV> port; @@ -2687,12 +2748,8 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, } if (!config.quiet) { - std::cerr << "Verifying token from [" << host.data() << "]:" << port.data() - << std::endl; - } - - if (!config.quiet) { - std::cerr << "Received address validation token:" << std::endl; + std::cerr << "Verifying Retry token from [" << host.data() + << "]:" << port.data() << std::endl; util::hexdump(stderr, hd->token, hd->tokenlen); } @@ -2703,14 +2760,15 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, } return -1; } - - if (hd->token[0] != RETRY_TOKEN_MAGIC) { + if (hd->tokenlen > MAX_RETRY_TOKENLEN) { if (!config.quiet) { - std::cerr << "Token has invalid magic" << std::endl; + std::cerr << "Token is too long" << std::endl; } return -1; } + assert(hd->token[0] == RETRY_TOKEN_MAGIC); + auto rand_data = hd->token + hd->tokenlen - TOKEN_RAND_DATALEN; auto ciphertext = hd->token + 1; auto ciphertextlen = hd->tokenlen - TOKEN_RAND_DATALEN - 1; @@ -2726,9 +2784,9 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, std::array<uint8_t, 256> aad; auto aadlen = - generate_token_aad(aad.data(), aad.size(), sa, salen, &hd->dcid); + generate_retry_token_aad(aad.data(), aad.size(), sa, salen, &hd->dcid); - std::array<uint8_t, 4096> plaintext; + std::array<uint8_t, MAX_RETRY_TOKENLEN> plaintext; if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext, ciphertextlen, key.data(), iv.data(), ivlen, @@ -2742,14 +2800,14 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, assert(ciphertextlen >= ngtcp2_crypto_aead_taglen(&token_aead_)); auto plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&token_aead_); - if (plaintextlen < salen + sizeof(uint64_t)) { + if (plaintextlen < sizeof(uint64_t)) { if (!config.quiet) { std::cerr << "Bad token construction" << std::endl; } return -1; } - auto cil = plaintextlen - salen - sizeof(uint64_t); + auto cil = plaintextlen - sizeof(uint64_t); if (cil != 0 && (cil < NGTCP2_MIN_CIDLEN || cil > NGTCP2_MAX_CIDLEN)) { if (!config.quiet) { std::cerr << "Bad token construction" << std::endl; @@ -2757,29 +2815,193 @@ int Server::verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, return -1; } - if (memcmp(plaintext.data(), sa, salen) != 0) { + uint64_t t; + memcpy(&t, plaintext.data(), sizeof(uint64_t)); + + uint64_t now = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + // Allow 10 seconds window + if (t + 10ULL * NGTCP2_SECONDS < now) { + if (!config.quiet) { + std::cerr << "Token has been expired" << std::endl; + } + return -1; + } + + ngtcp2_cid_init(ocid, plaintext.data() + sizeof(uint64_t), cil); + + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } + + return 0; +} + +namespace { +size_t generate_token_aad(uint8_t *dest, size_t destlen, const sockaddr *sa, + socklen_t salen) { + const uint8_t *addr; + size_t addrlen; + + switch (sa->sa_family) { + case AF_INET: + addr = reinterpret_cast<const uint8_t *>( + &reinterpret_cast<const sockaddr_in *>(sa)->sin_addr); + addrlen = sizeof(reinterpret_cast<const sockaddr_in *>(sa)->sin_addr); + break; + case AF_INET6: + addr = reinterpret_cast<const uint8_t *>( + &reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr); + addrlen = sizeof(reinterpret_cast<const sockaddr_in6 *>(sa)->sin6_addr); + break; + default: + if (!config.quiet) { + std::cerr << "Unknown address family 0x" << std::hex << sa->sa_family + << std::dec << std::endl; + } + return -1; + } + + assert(destlen >= addrlen); + + return std::copy_n(addr, addrlen, dest) - dest; +} +} // namespace + +int Server::generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + socklen_t salen) { + std::array<uint8_t, 8> plaintext; + + uint64_t t = std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + + std::array<uint8_t, 256> aad; + auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen); + + auto p = std::begin(plaintext); + // Host byte order + p = std::copy_n(reinterpret_cast<uint8_t *>(&t), sizeof(t), p); + + std::array<uint8_t, TOKEN_RAND_DATALEN> rand_data; + std::array<uint8_t, 32> key, iv; + auto keylen = key.size(); + auto ivlen = iv.size(); + + generate_rand_data(rand_data.data(), rand_data.size()); + if (derive_token_key(key.data(), keylen, iv.data(), ivlen, rand_data.data(), + rand_data.size()) != 0) { + return -1; + } + + auto plaintextlen = std::distance(std::begin(plaintext), p); + + token[0] = TOKEN_MAGIC; + if (ngtcp2_crypto_encrypt(token + 1, &token_aead_, plaintext.data(), + plaintextlen, key.data(), iv.data(), ivlen, + aad.data(), aadlen) != 0) { + return -1; + } + + /* 1 for magic byte */ + tokenlen = 1 + plaintextlen + ngtcp2_crypto_aead_taglen(&token_aead_); + memcpy(token + tokenlen, rand_data.data(), rand_data.size()); + tokenlen += rand_data.size(); + + return 0; +} + +int Server::verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen) { + std::array<char, NI_MAXHOST> host; + std::array<char, NI_MAXSERV> port; + + if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(), + port.size(), NI_NUMERICHOST | NI_NUMERICSERV); + rv != 0) { + std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl; + return -1; + } + + if (!config.quiet) { + std::cerr << "Verifying token from [" << host.data() << "]:" << port.data() + << std::endl; + util::hexdump(stderr, hd->token, hd->tokenlen); + } + + /* 1 for TOKEN_MAGIC */ + if (hd->tokenlen < TOKEN_RAND_DATALEN + 1) { + if (!config.quiet) { + std::cerr << "Token is too short" << std::endl; + } + return -1; + } + if (hd->tokenlen > MAX_TOKENLEN) { + if (!config.quiet) { + std::cerr << "Token is too long" << std::endl; + } + return -1; + } + + assert(hd->token[0] == TOKEN_MAGIC); + + std::array<uint8_t, 256> aad; + auto aadlen = generate_token_aad(aad.data(), aad.size(), sa, salen); + + auto rand_data = hd->token + hd->tokenlen - TOKEN_RAND_DATALEN; + auto ciphertext = hd->token + 1; + auto ciphertextlen = hd->tokenlen - TOKEN_RAND_DATALEN - 1; + + std::array<uint8_t, 32> key, iv; + auto keylen = key.size(); + auto ivlen = iv.size(); + + if (derive_token_key(key.data(), keylen, iv.data(), ivlen, rand_data, + TOKEN_RAND_DATALEN) != 0) { + return -1; + } + + std::array<uint8_t, MAX_TOKENLEN> plaintext; + + if (ngtcp2_crypto_decrypt(plaintext.data(), &token_aead_, ciphertext, + ciphertextlen, key.data(), iv.data(), ivlen, + aad.data(), aadlen) != 0) { if (!config.quiet) { - std::cerr << "Client address does not match" << std::endl; + std::cerr << "Could not decrypt token" << std::endl; + } + return -1; + } + + assert(ciphertextlen >= ngtcp2_crypto_aead_taglen(&token_aead_)); + + auto plaintextlen = ciphertextlen - ngtcp2_crypto_aead_taglen(&token_aead_); + if (plaintextlen != sizeof(uint64_t)) { + if (!config.quiet) { + std::cerr << "Bad token construction" << std::endl; } return -1; } uint64_t t; - memcpy(&t, plaintext.data() + salen, sizeof(uint64_t)); + memcpy(&t, plaintext.data(), sizeof(uint64_t)); uint64_t now = std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::system_clock::now().time_since_epoch()) .count(); - // Allow 10 seconds window - if (t + 10ULL * NGTCP2_SECONDS < now) { + // Allow 1 hour window + if (t + 3600ULL * NGTCP2_SECONDS < now) { if (!config.quiet) { std::cerr << "Token has been expired" << std::endl; } return -1; } - ngtcp2_cid_init(ocid, plaintext.data() + salen + sizeof(uint64_t), cil); + if (!config.quiet) { + std::cerr << "Token was successfully validated" << std::endl; + } return 0; } diff --git a/examples/server.h b/examples/server.h index 922ab1b5..710d3401 100644 --- a/examples/server.h +++ b/examples/server.h @@ -342,11 +342,15 @@ public: socklen_t salen); int send_stateless_connection_close(const ngtcp2_pkt_hd *chd, Endpoint &ep, const sockaddr *sa, socklen_t salen); + int generate_retry_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, + socklen_t salen, const ngtcp2_cid *scid, + const ngtcp2_cid *ocid); + int verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, + const sockaddr *sa, socklen_t salen); int generate_token(uint8_t *token, size_t &tokenlen, const sockaddr *sa, - socklen_t salen, const ngtcp2_cid *scid, - const ngtcp2_cid *ocid); - int verify_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd, - const sockaddr *sa, socklen_t salen); + socklen_t salen); + int verify_token(const ngtcp2_pkt_hd *hd, const sockaddr *sa, + socklen_t salen); int send_packet(Endpoint &ep, const Address &remote_addr, const uint8_t *data, size_t datalen, size_t gso_size, ev_io *wev = nullptr); void remove(const Handler *h); diff --git a/lib/includes/ngtcp2/ngtcp2.h b/lib/includes/ngtcp2/ngtcp2.h index 4034aa80..c9e29822 100644 --- a/lib/includes/ngtcp2/ngtcp2.h +++ b/lib/includes/ngtcp2/ngtcp2.h @@ -713,10 +713,18 @@ typedef struct ngtcp2_settings { controller to compute congestion window. If it is set to 0, it defaults to NGTCP2_DEFAULT_MAX_PKTLEN. */ size_t max_udp_payload_size; - /* token is a token received in Client Initial packet and - successfully validated. Only server application may specify this - field. Server then verifies that all Client Initial packets have - this token. `ngtcp2_conn_server_new` makes a copy of token. */ + /** + * token is a token from Retry packet or NEW_TOKEN frame. + * + * Server sets this field if it received the token in Client Initial + * packet and successfully validated. + * + * Client sets this field if it intends to send token in its Initial + * packet. + * + * `ngtcp2_conn_server_new` and `ngtcp2_conn_client_new` make a copy + * of token. + */ ngtcp2_vec token; } ngtcp2_settings; @@ -1563,6 +1571,21 @@ typedef int (*ngtcp2_connection_id_status)(ngtcp2_conn *conn, int type, const uint8_t *token, void *user_data); +/** + * @functypedef + * + * :type:`ngtcp2_recv_new_token` is a callback function which is + * called when new token is received from server. + * + * |token| is the received token. + * + * The callback function must return 0 if it succeeds. Returning + * :enum:`NGTCP2_ERR_CALLBACK_FAILURE` makes the library call return + * immediately. + */ +typedef int (*ngtcp2_recv_new_token)(ngtcp2_conn *conn, const ngtcp2_vec *token, + void *user_data); + typedef struct ngtcp2_conn_callbacks { /** * client_initial is a callback function which is invoked when @@ -1744,6 +1767,11 @@ typedef struct ngtcp2_conn_callbacks { * handshake confirmation for server. */ ngtcp2_handshake_confirmed handshake_confirmed; + /** + * recv_new_token is a callback function which is invoked when new + * token is received from server. This field is ignored by server. + */ + ngtcp2_recv_new_token recv_new_token; } ngtcp2_conn_callbacks; /** @@ -2760,6 +2788,25 @@ ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, ngtcp2_crypto_level crypto_level, const uint8_t *data, const size_t datalen); +/** + * @function + * + * `ngtcp2_conn_submit_new_token` submits address validation token. + * It is sent in NEW_TOKEN frame. Only server can call this function. + * |token| must not be empty. + * + * This function makes a copy of the buffer pointed by |token|->base + * of length |token|->len. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGTCP2_ERR_NOMEM` + * Out of memory. + */ +NGTCP2_EXTERN int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, + const ngtcp2_vec *token); + /** * @function * diff --git a/lib/ngtcp2_conn.c b/lib/ngtcp2_conn.c index 9479580c..2ab1d35f 100644 --- a/lib/ngtcp2_conn.c +++ b/lib/ngtcp2_conn.c @@ -659,7 +659,7 @@ static int conn_new(ngtcp2_conn **pconn, const ngtcp2_cid *dcid, (*pconn)->local.settings = *settings; - if (server && settings->token.len) { + if (settings->token.len) { buf = ngtcp2_mem_malloc(mem, settings->token.len); if (buf == NULL) { goto fail_token; @@ -924,7 +924,6 @@ void ngtcp2_conn_del(ngtcp2_conn *conn) { ngtcp2_qlog_end(&conn->qlog); - ngtcp2_mem_free(conn->mem, conn->token.begin); ngtcp2_mem_free(conn->mem, conn->crypto.decrypt_buf.base); ngtcp2_mem_free(conn->mem, conn->local.settings.token.base); @@ -1735,9 +1734,10 @@ static ngtcp2_ssize conn_write_handshake_pkt(ngtcp2_conn *conn, uint8_t *dest, pktns->tx.last_pkt_num + 1, pktns_select_pkt_numlen(pktns), conn->version, 0); - if (type == NGTCP2_PKT_INITIAL && ngtcp2_buf_len(&conn->token)) { - hd.token = conn->token.pos; - hd.tokenlen = ngtcp2_buf_len(&conn->token); + if (!conn->server && type == NGTCP2_PKT_INITIAL && + conn->local.settings.token.len) { + hd.token = conn->local.settings.token.base; + hd.tokenlen = conn->local.settings.token.len; } ngtcp2_ppe_init(&ppe, dest, destlen, &cc); @@ -3611,12 +3611,12 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, size_t hdpktlen, const uint8_t *pkt, size_t pktlen) { int rv; ngtcp2_pkt_retry retry; - uint8_t *p; ngtcp2_pktns *in_pktns = conn->in_pktns; ngtcp2_rtb *rtb = &conn->pktns.rtb; ngtcp2_rtb *in_rtb; uint8_t cidbuf[sizeof(retry.odcid.data) * 2 + 1]; ngtcp2_frame_chain *frc = NULL; + ngtcp2_vec *token; if (!in_pktns || conn->flags & NGTCP2_CONN_FLAG_RECV_RETRY) { return 0; @@ -3688,17 +3688,19 @@ static int conn_on_retry(ngtcp2_conn *conn, const ngtcp2_pkt_hd *hd, return rv; } - assert(conn->token.begin == NULL); + token = &conn->local.settings.token; + + ngtcp2_mem_free(conn->mem, token->base); + token->base = NULL; + token->len = 0; - p = ngtcp2_mem_malloc(conn->mem, retry.tokenlen); - if (p == NULL) { + token->base = ngtcp2_mem_malloc(conn->mem, retry.tokenlen); + if (token->base == NULL) { return NGTCP2_ERR_NOMEM; } - ngtcp2_buf_init(&conn->token, p, retry.tokenlen); + token->len = retry.tokenlen; - ngtcp2_cpymem(conn->token.begin, retry.token, retry.tokenlen); - conn->token.pos = conn->token.begin; - conn->token.last = conn->token.pos + retry.tokenlen; + ngtcp2_cpymem(token->base, retry.token, retry.tokenlen); conn_reset_congestion_state(conn); @@ -5995,16 +5997,29 @@ static int conn_recv_retire_connection_id(ngtcp2_conn *conn, * This function returns 0 if it succeeds, or one of the following * negative error codes: * - * NGTCP2_ERR_FRAME_ENCODING: + * NGTCP2_ERR_FRAME_ENCODING * Token is empty + * NGTCP2_ERR_PROTO: + * Server received NEW_TOKEN. */ static int conn_recv_new_token(ngtcp2_conn *conn, const ngtcp2_new_token *fr) { - (void)conn; + int rv; + + if (conn->server) { + return NGTCP2_ERR_PROTO; + } - if (fr->tokenlen == 0) { + if (fr->token.len == 0) { return NGTCP2_ERR_FRAME_ENCODING; } - /* TODO Not implemented yet*/ + + if (conn->callbacks.recv_new_token) { + rv = conn->callbacks.recv_new_token(conn, &fr->token, conn->user_data); + if (rv != 0) { + return NGTCP2_ERR_CALLBACK_FAILURE; + } + } + return 0; } @@ -9237,6 +9252,33 @@ int ngtcp2_conn_submit_crypto_data(ngtcp2_conn *conn, return 0; } +int ngtcp2_conn_submit_new_token(ngtcp2_conn *conn, const ngtcp2_vec *token) { + int rv; + ngtcp2_frame_chain *nfrc; + uint8_t *p; + + assert(conn->server); + assert(token->base); + assert(token->len); + + rv = ngtcp2_frame_chain_extralen_new(&nfrc, token->len, conn->mem); + if (rv != 0) { + return rv; + } + + nfrc->fr.type = NGTCP2_FRAME_NEW_TOKEN; + + p = (uint8_t *)nfrc + sizeof(*nfrc); + memcpy(p, token->base, token->len); + + ngtcp2_vec_init(&nfrc->fr.new_token.token, p, token->len); + + nfrc->next = conn->pktns.tx.frq; + conn->pktns.tx.frq = nfrc; + + return 0; +} + ngtcp2_strm *ngtcp2_conn_tx_strmq_top(ngtcp2_conn *conn) { assert(!ngtcp2_pq_empty(&conn->tx.strmq)); return ngtcp2_struct_of(ngtcp2_pq_top(&conn->tx.strmq), ngtcp2_strm, pe); diff --git a/lib/ngtcp2_conn.h b/lib/ngtcp2_conn.h index b8083853..720579bf 100644 --- a/lib/ngtcp2_conn.h +++ b/lib/ngtcp2_conn.h @@ -460,8 +460,6 @@ struct ngtcp2_conn { ngtcp2_rst rst; ngtcp2_cc_algo cc_algo; ngtcp2_cc cc; - /* token is an address validation token received from server. */ - ngtcp2_buf token; const ngtcp2_mem *mem; /* idle_ts is the time instant when idle timer started. */ ngtcp2_tstamp idle_ts; diff --git a/lib/ngtcp2_log.c b/lib/ngtcp2_log.c index 1b480f6f..8846d594 100644 --- a/lib/ngtcp2_log.c +++ b/lib/ngtcp2_log.c @@ -408,17 +408,16 @@ static void log_fr_new_token(ngtcp2_log *log, const ngtcp2_pkt_hd *hd, uint8_t buf[128 + 1 + 1]; uint8_t *p; - if (fr->tokenlen > 64) { - p = ngtcp2_encode_hex(buf, fr->token, 64); + if (fr->token.len > 64) { + p = ngtcp2_encode_hex(buf, fr->token.base, 64); p[128] = '*'; p[129] = '\0'; } else { - p = ngtcp2_encode_hex(buf, fr->token, fr->tokenlen); + p = ngtcp2_encode_hex(buf, fr->token.base, fr->token.len); } log->log_printf( - log->user_data, - (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02x) token=0x%s token_len=%zu"), - NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->tokenlen); + log->user_data, (NGTCP2_LOG_PKT " NEW_TOKEN(0x%02x) token=0x%s len=%zu"), + NGTCP2_LOG_FRM_HD_FIELDS(dir), fr->type, (const char *)p, fr->token.len); } static void log_fr_retire_connection_id(ngtcp2_log *log, diff --git a/lib/ngtcp2_pkt.c b/lib/ngtcp2_pkt.c index 1a500a61..00d2d885 100644 --- a/lib/ngtcp2_pkt.c +++ b/lib/ngtcp2_pkt.c @@ -1324,10 +1324,10 @@ ngtcp2_ssize ngtcp2_pkt_decode_new_token_frame(ngtcp2_new_token *dest, len += datalen; dest->type = NGTCP2_FRAME_NEW_TOKEN; - dest->tokenlen = datalen; + dest->token.len = datalen; p += n; - dest->token = p; - p += dest->tokenlen; + dest->token.base = (uint8_t *)p; + p += dest->token.len; assert((size_t)(p - payload) == len); @@ -1856,9 +1856,11 @@ ngtcp2_ssize ngtcp2_pkt_encode_crypto_frame(uint8_t *out, size_t outlen, ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, const ngtcp2_new_token *fr) { - size_t len = 1 + ngtcp2_put_varint_len(fr->tokenlen) + fr->tokenlen; + size_t len = 1 + ngtcp2_put_varint_len(fr->token.len) + fr->token.len; uint8_t *p; + assert(fr->token.len); + if (outlen < len) { return NGTCP2_ERR_NOBUF; } @@ -1867,10 +1869,8 @@ ngtcp2_ssize ngtcp2_pkt_encode_new_token_frame(uint8_t *out, size_t outlen, *p++ = NGTCP2_FRAME_NEW_TOKEN; - p = ngtcp2_put_varint(p, fr->tokenlen); - if (fr->tokenlen) { - p = ngtcp2_cpymem(p, fr->token, fr->tokenlen); - } + p = ngtcp2_put_varint(p, fr->token.len); + p = ngtcp2_cpymem(p, fr->token.base, fr->token.len); assert((size_t)(p - out) == len); diff --git a/lib/ngtcp2_pkt.h b/lib/ngtcp2_pkt.h index 651c8630..6e9249c5 100644 --- a/lib/ngtcp2_pkt.h +++ b/lib/ngtcp2_pkt.h @@ -262,8 +262,7 @@ typedef struct { typedef struct { uint8_t type; - size_t tokenlen; - const uint8_t *token; + ngtcp2_vec token; } ngtcp2_new_token; typedef struct { diff --git a/lib/ngtcp2_qlog.c b/lib/ngtcp2_qlog.c index 07ceff74..f0abc8db 100644 --- a/lib/ngtcp2_qlog.c +++ b/lib/ngtcp2_qlog.c @@ -447,14 +447,17 @@ static uint8_t *write_new_token_frame(uint8_t *p, const ngtcp2_new_token *fr) { (void)fr; /* - * {"frame_type":"new_token"} + * {"frame_type":"new_token","length":0000000000000000000,"token":""} */ -#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 26 +#define NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD 66 *p++ = '{'; p = write_pair(p, ngtcp2_vec_lit(&name, "frame_type"), ngtcp2_vec_lit(&value, "new_token")); - /* TODO Write token here */ + *p++ = ','; + p = write_pair_number(p, ngtcp2_vec_lit(&name, "length"), fr->token.len); + *p++ = ','; + p = write_pair_hex(p, ngtcp2_vec_lit(&name, "token"), &fr->token); *p++ = '}'; return p; @@ -870,7 +873,8 @@ void ngtcp2_qlog_write_frame(ngtcp2_qlog *qlog, const ngtcp2_frame *fr) { p = write_crypto_frame(p, &fr->crypto); break; case NGTCP2_FRAME_NEW_TOKEN: - if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + 1 + + if (ngtcp2_buf_left(&qlog->buf) < NGTCP2_QLOG_NEW_TOKEN_FRAME_OVERHEAD + + fr->new_token.token.len * 2 + 1 + NGTCP2_QLOG_PKT_WRITE_END_OVERHEAD) { return; } diff --git a/tests/main.c b/tests/main.c index 1470a475..3cf5bfbc 100644 --- a/tests/main.c +++ b/tests/main.c @@ -246,6 +246,8 @@ int main() { test_ngtcp2_conn_get_active_dcid) || !CU_add_test(pSuite, "conn_recv_version_negotiation", test_ngtcp2_conn_recv_version_negotiation) || + !CU_add_test(pSuite, "conn_send_initial_token", + test_ngtcp2_conn_send_initial_token) || !CU_add_test(pSuite, "pkt_write_connection_close", test_ngtcp2_pkt_write_connection_close) || !CU_add_test(pSuite, "map", test_ngtcp2_map) || diff --git a/tests/ngtcp2_conn_test.c b/tests/ngtcp2_conn_test.c index 04388fd8..b704af1a 100644 --- a/tests/ngtcp2_conn_test.c +++ b/tests/ngtcp2_conn_test.c @@ -5082,6 +5082,53 @@ void test_ngtcp2_conn_recv_version_negotiation(void) { ngtcp2_conn_del(conn); } +void test_ngtcp2_conn_send_initial_token(void) { + ngtcp2_conn *conn; + uint8_t buf[2048]; + ngtcp2_conn_callbacks cb; + ngtcp2_settings settings; + ngtcp2_cid rcid, scid; + ngtcp2_crypto_aead retry_aead = {0}; + uint8_t token[] = "this is token"; + ngtcp2_ssize spktlen, shdlen; + ngtcp2_tstamp t = 0; + ngtcp2_pkt_hd hd; + + rcid_init(&rcid); + scid_init(&scid); + + memset(&cb, 0, sizeof(cb)); + cb.client_initial = client_initial; + cb.recv_crypto_data = recv_crypto_data; + cb.decrypt = null_decrypt; + cb.encrypt = null_encrypt; + cb.hp_mask = null_hp_mask; + cb.get_new_connection_id = get_new_connection_id; + client_default_settings(&settings); + + settings.token.base = token; + settings.token.len = sizeof(token); + + ngtcp2_conn_client_new(&conn, &rcid, &scid, &null_path, NGTCP2_PROTO_VER_MAX, + &cb, &settings, /* mem = */ NULL, NULL); + ngtcp2_conn_install_initial_key(conn, null_key, null_iv, null_hp_key, + null_key, null_iv, null_hp_key, + sizeof(null_key), sizeof(null_iv)); + ngtcp2_conn_set_retry_aead(conn, &retry_aead); + + spktlen = ngtcp2_conn_write_pkt(conn, NULL, buf, sizeof(buf), ++t); + + CU_ASSERT(spktlen > 0); + + shdlen = ngtcp2_pkt_decode_hd_long(&hd, buf, (size_t)spktlen); + + CU_ASSERT(shdlen > 0); + CU_ASSERT(sizeof(token) == hd.tokenlen); + CU_ASSERT(0 == memcmp(token, hd.token, sizeof(token))); + + ngtcp2_conn_del(conn); +} + void test_ngtcp2_pkt_write_connection_close(void) { ngtcp2_ssize spktlen; uint8_t buf[1200]; diff --git a/tests/ngtcp2_conn_test.h b/tests/ngtcp2_conn_test.h index 1bd088f5..cc992245 100644 --- a/tests/ngtcp2_conn_test.h +++ b/tests/ngtcp2_conn_test.h @@ -70,6 +70,7 @@ void test_ngtcp2_conn_recv_client_initial_retry(void); void test_ngtcp2_conn_recv_client_initial_token(void); void test_ngtcp2_conn_get_active_dcid(void); void test_ngtcp2_conn_recv_version_negotiation(void); +void test_ngtcp2_conn_send_initial_token(void); void test_ngtcp2_pkt_write_connection_close(void); #endif /* NGTCP2_CONN_TEST_H */ diff --git a/tests/ngtcp2_pkt_test.c b/tests/ngtcp2_pkt_test.c index 5aa2de7a..8946d09c 100644 --- a/tests/ngtcp2_pkt_test.c +++ b/tests/ngtcp2_pkt_test.c @@ -31,6 +31,7 @@ #include "ngtcp2_conv.h" #include "ngtcp2_cid.h" #include "ngtcp2_str.h" +#include "ngtcp2_vec.h" static int null_retry_encrypt(uint8_t *dest, const ngtcp2_crypto_aead *aead, const uint8_t *plaintext, size_t plaintextlen, @@ -1089,8 +1090,7 @@ void test_ngtcp2_pkt_encode_new_token_frame(void) { size_t framelen; fr.type = NGTCP2_FRAME_NEW_TOKEN; - fr.new_token.tokenlen = strsize(token); - fr.new_token.token = token; + ngtcp2_vec_init(&fr.new_token.token, token, strsize(token)); framelen = 1 + 1 + strsize(token); @@ -1102,9 +1102,9 @@ void test_ngtcp2_pkt_encode_new_token_frame(void) { CU_ASSERT((ngtcp2_ssize)framelen == rv); CU_ASSERT(fr.type == nfr.type); - CU_ASSERT(fr.new_token.tokenlen == nfr.new_token.tokenlen); - CU_ASSERT(0 == memcmp(fr.new_token.token, nfr.new_token.token, - fr.new_token.tokenlen)); + CU_ASSERT(fr.new_token.token.len == nfr.new_token.token.len); + CU_ASSERT(0 == memcmp(fr.new_token.token.base, nfr.new_token.token.base, + fr.new_token.token.len)); } void test_ngtcp2_pkt_encode_retire_connection_id_frame(void) { -- GitLab