[WIP] Add QUIC to h2load
This commit is contained in:
parent
29cbf8b83f
commit
c24c7ffa06
16
configure.ac
16
configure.ac
|
@ -454,6 +454,20 @@ if test "x${request_libcares}" = "xyes" &&
|
|||
AC_MSG_ERROR([libcares was requested (--with-libcares) but not found])
|
||||
fi
|
||||
|
||||
# ngtcp2 (for src)
|
||||
PKG_CHECK_MODULES([LIBNGTCP2], [libngtcp2 >= 0.0.0], [have_libngtcp2=yes],
|
||||
[have_libngtcp2=no])
|
||||
if test "x${have_libngtcp2}" = "xno"; then
|
||||
AC_MSG_NOTICE($LIBNGTCP2_PKG_ERRORS)
|
||||
fi
|
||||
|
||||
# nghttp3 (for src)
|
||||
PKG_CHECK_MODULES([LIBNGHTTP3], [libnghttp3 >= 0.0.0], [have_libnghttp3=yes],
|
||||
[have_libnghttp3=no])
|
||||
if test "x${have_libnghttp3}" = "xno"; then
|
||||
AC_MSG_NOTICE($LIBNGHTTP3_PKT_ERRORS)
|
||||
fi
|
||||
|
||||
# libevent_openssl (for examples)
|
||||
# 2.0.8 is required because we use evconnlistener_set_error_cb()
|
||||
have_libevent_openssl=no
|
||||
|
@ -1011,6 +1025,8 @@ AC_MSG_NOTICE([summary of build options:
|
|||
Libxml2: ${have_libxml2} (CFLAGS='${LIBXML2_CFLAGS}' LIBS='${LIBXML2_LIBS}')
|
||||
Libev: ${have_libev} (CFLAGS='${LIBEV_CFLAGS}' LIBS='${LIBEV_LIBS}')
|
||||
Libc-ares: ${have_libcares} (CFLAGS='${LIBCARES_CFLAGS}' LIBS='${LIBCARES_LIBS}')
|
||||
libngtcp2: ${have_libngtcp2} (CFLAGS='${LIBNGTCP2_CFLAGS}' LIBS='${LIBNGTCP2_LIBS}')
|
||||
libnghttp3: ${have_libnghttp3} (CFLAGS='${LIBNGHTTP3_CFLAGS}' LIBS='${LIBNGHTTP3_LIBS}')
|
||||
Libevent(SSL): ${have_libevent_openssl} (CFLAGS='${LIBEVENT_OPENSSL_CFLAGS}' LIBS='${LIBEVENT_OPENSSL_LIBS}')
|
||||
Jansson: ${have_jansson} (CFLAGS='${JANSSON_CFLAGS}' LIBS='${JANSSON_LIBS}')
|
||||
Jemalloc: ${have_jemalloc} (CFLAGS='${JEMALLOC_CFLAGS}' LIBS='${JEMALLOC_LIBS}')
|
||||
|
|
|
@ -46,6 +46,8 @@ AM_CPPFLAGS = \
|
|||
@LIBEV_CFLAGS@ \
|
||||
@OPENSSL_CFLAGS@ \
|
||||
@LIBCARES_CFLAGS@ \
|
||||
@LIBNGHTTP3_CFLAGS@ \
|
||||
@LIBNGTCP2_CFLAGS@ \
|
||||
@JANSSON_CFLAGS@ \
|
||||
@ZLIB_CFLAGS@ \
|
||||
@DEFS@
|
||||
|
@ -59,6 +61,8 @@ LDADD = $(top_builddir)/lib/libnghttp2.la \
|
|||
@LIBEV_LIBS@ \
|
||||
@OPENSSL_LIBS@ \
|
||||
@LIBCARES_LIBS@ \
|
||||
@LIBNGHTTP3_LIBS@ \
|
||||
@LIBNGTCP2_LIBS@ \
|
||||
@SYSTEMD_LIBS@ \
|
||||
@JANSSON_LIBS@ \
|
||||
@ZLIB_LIBS@ \
|
||||
|
@ -97,7 +101,10 @@ h2load_SOURCES = util.cc util.h \
|
|||
tls.cc tls.h \
|
||||
h2load_session.h \
|
||||
h2load_http2_session.cc h2load_http2_session.h \
|
||||
h2load_http1_session.cc h2load_http1_session.h
|
||||
h2load_http1_session.cc h2load_http1_session.h \
|
||||
h2load_http3_session.cc h2load_http3_session.h \
|
||||
h2load_quic.cc h2load_quic.h \
|
||||
quic.cc quic.h
|
||||
|
||||
NGHTTPX_SRCS = \
|
||||
util.cc util.h http2.cc http2.h timegm.c timegm.h base64.h \
|
||||
|
|
192
src/h2load.cc
192
src/h2load.cc
|
@ -48,10 +48,14 @@
|
|||
|
||||
#include <openssl/err.h>
|
||||
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
|
||||
#include "url-parser/url_parser.h"
|
||||
|
||||
#include "h2load_http1_session.h"
|
||||
#include "h2load_http2_session.h"
|
||||
#include "h2load_http3_session.h"
|
||||
#include "h2load_quic.h"
|
||||
#include "tls.h"
|
||||
#include "http2.h"
|
||||
#include "util.h"
|
||||
|
@ -119,6 +123,9 @@ bool Config::is_rate_mode() const { return (this->rate != 0); }
|
|||
bool Config::is_timing_based_mode() const { return (this->duration > 0); }
|
||||
bool Config::has_base_uri() const { return (!this->base_uri.empty()); }
|
||||
bool Config::rps_enabled() const { return this->rps > 0.0; }
|
||||
bool Config::is_quic() const {
|
||||
return !npn_list.empty() && npn_list[0] == NGTCP2_ALPN_H3;
|
||||
}
|
||||
Config config;
|
||||
|
||||
namespace {
|
||||
|
@ -409,6 +416,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
|||
cstat{},
|
||||
worker(worker),
|
||||
ssl(nullptr),
|
||||
quic{},
|
||||
next_addr(config.addrs),
|
||||
current_addr(nullptr),
|
||||
reqidx(0),
|
||||
|
@ -420,6 +428,7 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
|||
req_done(0),
|
||||
id(id),
|
||||
fd(-1),
|
||||
local_addr{},
|
||||
new_connection_requested(false),
|
||||
final(false),
|
||||
rps_duration_started(0),
|
||||
|
@ -449,11 +458,18 @@ Client::Client(uint32_t id, Worker *worker, size_t req_todo)
|
|||
|
||||
ev_timer_init(&rps_watcher, rps_cb, 0., 0.);
|
||||
rps_watcher.data = this;
|
||||
|
||||
ev_timer_init(&quic.pkt_timer, quic_pkt_timeout_cb, 0., 0.);
|
||||
quic.pkt_timer.data = this;
|
||||
}
|
||||
|
||||
Client::~Client() {
|
||||
disconnect();
|
||||
|
||||
if (config.is_quic()) {
|
||||
quic_free();
|
||||
}
|
||||
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
}
|
||||
|
@ -466,6 +482,37 @@ int Client::do_read() { return readfn(*this); }
|
|||
int Client::do_write() { return writefn(*this); }
|
||||
|
||||
int Client::make_socket(addrinfo *addr) {
|
||||
int rv;
|
||||
|
||||
if (config.is_quic()) {
|
||||
fd = util::create_nonblock_udp_socket(addr->ai_family);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = util::bind_any_addr_udp(fd, addr->ai_family);
|
||||
if (rv != 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
socklen_t addrlen = sizeof(local_addr.su.storage);
|
||||
rv = getsockname(fd, &local_addr.su.sa, &addrlen);
|
||||
if (rv == -1) {
|
||||
return -1;
|
||||
}
|
||||
local_addr.len = addrlen;
|
||||
|
||||
if (quic_init(&local_addr.su.sa, local_addr.len, addr->ai_addr,
|
||||
addr->ai_addrlen) != 0) {
|
||||
std::cerr << "quic_init failed" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fd = util::create_nonblock_socket(addr->ai_family);
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
|
@ -475,17 +522,15 @@ int Client::make_socket(addrinfo *addr) {
|
|||
ssl = SSL_new(worker->ssl_ctx);
|
||||
}
|
||||
|
||||
auto config = worker->config;
|
||||
|
||||
if (!util::numeric_host(config->host.c_str())) {
|
||||
SSL_set_tlsext_host_name(ssl, config->host.c_str());
|
||||
}
|
||||
|
||||
SSL_set_fd(ssl, fd);
|
||||
SSL_set_connect_state(ssl);
|
||||
}
|
||||
|
||||
auto rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
|
||||
if (ssl && !util::numeric_host(config.host.c_str())) {
|
||||
SSL_set_tlsext_host_name(ssl, config.host.c_str());
|
||||
}
|
||||
|
||||
rv = ::connect(fd, addr->ai_addr, addr->ai_addrlen);
|
||||
if (rv != 0 && errno != EINPROGRESS) {
|
||||
if (ssl) {
|
||||
SSL_free(ssl);
|
||||
|
@ -542,13 +587,20 @@ int Client::connect() {
|
|||
current_addr = addr;
|
||||
}
|
||||
|
||||
writefn = &Client::connected;
|
||||
|
||||
ev_io_set(&rev, fd, EV_READ);
|
||||
ev_io_set(&wev, fd, EV_WRITE);
|
||||
|
||||
ev_io_start(worker->loop, &wev);
|
||||
|
||||
if (config.is_quic()) {
|
||||
ev_io_start(worker->loop, &rev);
|
||||
|
||||
readfn = &Client::read_quic;
|
||||
writefn = &Client::write_quic;
|
||||
} else {
|
||||
writefn = &Client::connected;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -603,6 +655,11 @@ void Client::fail() {
|
|||
void Client::disconnect() {
|
||||
record_client_end_time();
|
||||
|
||||
if (config.is_quic()) {
|
||||
quic_close_connection();
|
||||
}
|
||||
|
||||
ev_timer_stop(worker->loop, &quic.pkt_timer);
|
||||
ev_timer_stop(worker->loop, &conn_inactivity_watcher);
|
||||
ev_timer_stop(worker->loop, &conn_active_watcher);
|
||||
ev_timer_stop(worker->loop, &rps_watcher);
|
||||
|
@ -765,6 +822,9 @@ void Client::report_app_info() {
|
|||
}
|
||||
|
||||
void Client::terminate_session() {
|
||||
if (config.is_quic()) {
|
||||
quic.close_requested = true;
|
||||
}
|
||||
session->terminate();
|
||||
// http1 session needs writecb to tear down session.
|
||||
signal_write();
|
||||
|
@ -963,7 +1023,15 @@ int Client::connection_made() {
|
|||
|
||||
if (next_proto) {
|
||||
auto proto = StringRef{next_proto, next_proto_len};
|
||||
if (util::check_h2_is_selected(proto)) {
|
||||
if (config.is_quic()) {
|
||||
if (util::streq(StringRef{&NGTCP2_ALPN_H3[1]}, proto)) {
|
||||
auto s = std::make_unique<Http3Session>(this);
|
||||
if (s->init_conn() == -1) {
|
||||
return -1;
|
||||
}
|
||||
session = std::move(s);
|
||||
}
|
||||
} else if (util::check_h2_is_selected(proto)) {
|
||||
session = std::make_unique<Http2Session>(this);
|
||||
} else if (util::streq(NGHTTP2_H1_1, proto)) {
|
||||
session = std::make_unique<Http1Session>(this);
|
||||
|
@ -1285,6 +1353,15 @@ int Client::write_tls() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Client::write_udp(const sockaddr *addr, socklen_t addrlen,
|
||||
const uint8_t *data, size_t datalen) {
|
||||
auto nwrite = sendto(fd, data, datalen, MSG_DONTWAIT, addr, addrlen);
|
||||
if (nwrite < 0) {
|
||||
std::cerr << "sendto: errno=" << errno << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Client::record_request_time(RequestStat *req_stat) {
|
||||
req_stat->request_time = std::chrono::steady_clock::now();
|
||||
req_stat->request_wall_time = std::chrono::system_clock::now();
|
||||
|
@ -1698,6 +1775,79 @@ int client_select_next_proto_cb(SSL *ssl, unsigned char **out,
|
|||
} // namespace
|
||||
#endif // !OPENSSL_NO_NEXTPROTONEG
|
||||
|
||||
namespace {
|
||||
int quic_transport_params_add_cb(SSL *ssl, unsigned int ext_type,
|
||||
unsigned int content,
|
||||
const unsigned char **out, size_t *outlen,
|
||||
X509 *x, size_t chainidx, int *al,
|
||||
void *add_arg) {
|
||||
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
|
||||
auto conn = c->quic.conn;
|
||||
|
||||
ngtcp2_transport_params params;
|
||||
|
||||
ngtcp2_conn_get_local_transport_params(conn, ¶ms);
|
||||
|
||||
constexpr size_t bufsize = 128;
|
||||
auto buf = std::make_unique<uint8_t[]>(bufsize);
|
||||
|
||||
auto nwrite = ngtcp2_encode_transport_params(
|
||||
buf.get(), bufsize, NGTCP2_TRANSPORT_PARAMS_TYPE_CLIENT_HELLO, ¶ms);
|
||||
if (nwrite < 0) {
|
||||
std::cerr << "ngtcp2_encode_transport_params: " << ngtcp2_strerror(nwrite)
|
||||
<< std::endl;
|
||||
*al = SSL_AD_INTERNAL_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out = buf.release();
|
||||
*outlen = static_cast<size_t>(nwrite);
|
||||
|
||||
return 1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void quic_transport_params_free_cb(SSL *ssl, unsigned int ext_type,
|
||||
unsigned int context,
|
||||
const unsigned char *out, void *add_arg) {
|
||||
delete[] const_cast<unsigned char *>(out);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int quic_transport_params_parse_cb(SSL *ssl, unsigned int ext_type,
|
||||
unsigned int context,
|
||||
const unsigned char *in, size_t inlen,
|
||||
X509 *x, size_t chainidx, int *al,
|
||||
void *parse_arg) {
|
||||
auto c = static_cast<Client *>(SSL_get_app_data(ssl));
|
||||
auto conn = c->quic.conn;
|
||||
|
||||
int rv;
|
||||
ngtcp2_transport_params params;
|
||||
|
||||
rv = ngtcp2_decode_transport_params(
|
||||
¶ms, NGTCP2_TRANSPORT_PARAMS_TYPE_ENCRYPTED_EXTENSIONS, in, inlen);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_decode_transport_params: " << ngtcp2_strerror(rv)
|
||||
<< std::endl;
|
||||
*al = SSL_AD_ILLEGAL_PARAMETER;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = ngtcp2_conn_set_remote_transport_params(conn, ¶ms);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_conn_set_remote_transport_params: "
|
||||
<< ngtcp2_strerror(rv) << std::endl;
|
||||
*al = SSL_AD_ILLEGAL_PARAMETER;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
constexpr char UNIX_PATH_PREFIX[] = "unix:";
|
||||
} // namespace
|
||||
|
@ -2576,7 +2726,24 @@ int main(int argc, char **argv) {
|
|||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_AUTO_RETRY);
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_RELEASE_BUFFERS);
|
||||
|
||||
if (nghttp2::tls::ssl_ctx_set_proto_versions(
|
||||
if (config.is_quic()) {
|
||||
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_3_VERSION);
|
||||
|
||||
SSL_CTX_clear_options(ssl_ctx, SSL_OP_ENABLE_MIDDLEBOX_COMPAT);
|
||||
SSL_CTX_set_mode(ssl_ctx, SSL_MODE_QUIC_HACK);
|
||||
|
||||
if (SSL_CTX_add_custom_ext(
|
||||
ssl_ctx, NGTCP2_TLSEXT_QUIC_TRANSPORT_PARAMETERS,
|
||||
SSL_EXT_CLIENT_HELLO | SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS,
|
||||
quic_transport_params_add_cb, quic_transport_params_free_cb,
|
||||
nullptr, quic_transport_params_parse_cb, nullptr) != 1) {
|
||||
std::cerr << "SSL_CTX_add_custom_ext(NGTCP2_TLSEXT_QUIC_TRANSPORT_"
|
||||
"PARAMETERS) failed: "
|
||||
<< ERR_error_string(ERR_get_error(), nullptr) << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (nghttp2::tls::ssl_ctx_set_proto_versions(
|
||||
ssl_ctx, nghttp2::tls::NGHTTP2_TLS_MIN_VERSION,
|
||||
nghttp2::tls::NGHTTP2_TLS_MAX_VERSION) != 0) {
|
||||
std::cerr << "Could not set TLS versions" << std::endl;
|
||||
|
@ -2590,6 +2757,9 @@ int main(int argc, char **argv) {
|
|||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// TODO Use SSL_CTX_set_ciphersuites to set TLSv1.3 cipher list
|
||||
// TODO Use SSL_CTX_set1_groups_list to set key share
|
||||
|
||||
#ifndef OPENSSL_NO_NEXTPROTONEG
|
||||
SSL_CTX_set_next_proto_select_cb(ssl_ctx, client_select_next_proto_cb,
|
||||
nullptr);
|
||||
|
|
81
src/h2load.h
81
src/h2load.h
|
@ -45,11 +45,14 @@
|
|||
|
||||
#include <nghttp2/nghttp2.h>
|
||||
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "http2.h"
|
||||
#include "quic.h"
|
||||
#include "memchunk.h"
|
||||
#include "template.h"
|
||||
|
||||
|
@ -124,6 +127,7 @@ struct Config {
|
|||
bool is_timing_based_mode() const;
|
||||
bool has_base_uri() const;
|
||||
bool rps_enabled() const;
|
||||
bool is_quic() const;
|
||||
};
|
||||
|
||||
struct RequestStat {
|
||||
|
@ -299,6 +303,13 @@ struct Stream {
|
|||
Stream();
|
||||
};
|
||||
|
||||
struct Crypto {
|
||||
Crypto() : datalen(0), acked_offset(0) {}
|
||||
std::array<uint8_t, 1024> data;
|
||||
size_t datalen;
|
||||
size_t acked_offset;
|
||||
};
|
||||
|
||||
struct Client {
|
||||
DefaultMemchunks wb;
|
||||
std::unordered_map<int32_t, Stream> streams;
|
||||
|
@ -309,6 +320,19 @@ struct Client {
|
|||
std::function<int(Client &)> readfn, writefn;
|
||||
Worker *worker;
|
||||
SSL *ssl;
|
||||
struct {
|
||||
ev_timer pkt_timer;
|
||||
ngtcp2_conn *conn;
|
||||
quic::Error last_error;
|
||||
ngtcp2_crypto_level tx_crypto_level;
|
||||
ngtcp2_crypto_level rx_crypto_level;
|
||||
std::vector<uint8_t> server_handshake;
|
||||
size_t server_handshake_nread;
|
||||
// Client never send CRYPTO in Short packet.
|
||||
std::array<Crypto, 2> crypto;
|
||||
size_t max_pktlen;
|
||||
bool close_requested;
|
||||
} quic;
|
||||
ev_timer request_timeout_watcher;
|
||||
addrinfo *next_addr;
|
||||
// Address for the current address. When try_new_connection() is
|
||||
|
@ -332,6 +356,7 @@ struct Client {
|
|||
// The client id per worker
|
||||
uint32_t id;
|
||||
int fd;
|
||||
Address local_addr;
|
||||
ev_timer conn_active_watcher;
|
||||
ev_timer conn_inactivity_watcher;
|
||||
std::string selected_proto;
|
||||
|
@ -419,6 +444,62 @@ struct Client {
|
|||
void record_client_end_time();
|
||||
|
||||
void signal_write();
|
||||
|
||||
// QUIC
|
||||
int quic_init(const sockaddr *local_addr, socklen_t local_addrlen,
|
||||
const sockaddr *remote_addr, socklen_t remote_addrlen);
|
||||
void quic_free();
|
||||
int read_quic();
|
||||
int write_quic();
|
||||
int write_udp(const sockaddr *addr, socklen_t addrlen, const uint8_t *data,
|
||||
size_t datalen);
|
||||
void quic_close_connection();
|
||||
int quic_setup_initial_crypto();
|
||||
|
||||
int quic_client_initial();
|
||||
int quic_recv_crypto_data(ngtcp2_crypto_level crypto_level,
|
||||
const uint8_t *data, size_t datalen);
|
||||
int quic_handshake_completed();
|
||||
int quic_in_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
|
||||
size_t plaintextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen);
|
||||
int quic_in_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
|
||||
size_t ciphertextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen);
|
||||
int quic_encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
|
||||
size_t plaintextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen);
|
||||
int quic_decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
|
||||
size_t ciphertextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen);
|
||||
int quic_in_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key,
|
||||
size_t keylen, const uint8_t *sample, size_t samplelen);
|
||||
int quic_hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key,
|
||||
size_t keylen, const uint8_t *sample, size_t samplelen);
|
||||
int quic_recv_stream_data(int64_t stream_id, int fin, const uint8_t *data,
|
||||
size_t datalen);
|
||||
int quic_stream_close(int64_t stream_id, uint16_t app_error_code);
|
||||
int quic_stream_reset(int64_t stream_id, uint16_t app_error_code);
|
||||
int quic_extend_max_local_streams();
|
||||
|
||||
int quic_tls_handshake(bool initial = false);
|
||||
int quic_read_tls();
|
||||
|
||||
int quic_on_key(int name, const uint8_t *secret, size_t secretlen);
|
||||
void quic_set_tls_alert(uint8_t alert);
|
||||
|
||||
size_t quic_read_server_handshake(uint8_t *buf, size_t buflen);
|
||||
int quic_write_server_handshake(ngtcp2_crypto_level crypto_level,
|
||||
const uint8_t *data, size_t datalen);
|
||||
void quic_write_client_handshake(const uint8_t *data, size_t datalen);
|
||||
void quic_write_client_handshake(Crypto &crypto, const uint8_t *data,
|
||||
size_t datalen);
|
||||
int quic_pkt_timeout();
|
||||
void quic_restart_pkt_timer();
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
|
|
@ -0,0 +1,375 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2019 nghttp2 contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "h2load_http3_session.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
|
||||
#include "h2load.h"
|
||||
|
||||
namespace h2load {
|
||||
|
||||
Http3Session::Http3Session(Client *client)
|
||||
: client_(client), conn_(nullptr), npending_request_(0), reqidx_(0) {}
|
||||
|
||||
Http3Session::~Http3Session() { nghttp3_conn_del(conn_); }
|
||||
|
||||
void Http3Session::on_connect() {}
|
||||
|
||||
int Http3Session::submit_request() {
|
||||
if (npending_request_) {
|
||||
++npending_request_;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto config = client_->worker->config;
|
||||
reqidx_ = client_->reqidx;
|
||||
|
||||
if (++client_->reqidx == config->nva.size()) {
|
||||
client_->reqidx = 0;
|
||||
}
|
||||
|
||||
auto stream_id = submit_request_internal();
|
||||
if (stream_id < 0) {
|
||||
if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
|
||||
++npending_request_;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t Http3Session::submit_request_internal() {
|
||||
int rv;
|
||||
int64_t stream_id;
|
||||
|
||||
auto config = client_->worker->config;
|
||||
auto &nva = config->nva[reqidx_];
|
||||
|
||||
rv = ngtcp2_conn_open_bidi_stream(client_->quic.conn, &stream_id, nullptr);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = nghttp3_conn_submit_request(conn_, stream_id, nullptr,
|
||||
reinterpret_cast<nghttp3_nv *>(nva.data()),
|
||||
nva.size(), nullptr, nullptr);
|
||||
if (rv != 0) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = nghttp3_conn_end_stream(conn_, stream_id);
|
||||
assert(0 == rv);
|
||||
|
||||
client_->on_request(stream_id);
|
||||
auto req_stat = client_->get_req_stat(stream_id);
|
||||
assert(req_stat);
|
||||
client_->record_request_time(req_stat);
|
||||
|
||||
return stream_id;
|
||||
}
|
||||
|
||||
int Http3Session::on_read(const uint8_t *data, size_t len) { return -1; }
|
||||
|
||||
int Http3Session::on_write() { return -1; }
|
||||
|
||||
void Http3Session::terminate() {}
|
||||
|
||||
size_t Http3Session::max_concurrent_streams() {
|
||||
return (size_t)client_->worker->config->max_concurrent_streams;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int stream_close(nghttp3_conn *conn, int64_t stream_id, uint16_t error_code,
|
||||
void *user_data, void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
if (s->stream_close(stream_id, error_code) != 0) {
|
||||
return NGHTTP3_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Http3Session::stream_close(int64_t stream_id, uint16_t error_code) {
|
||||
client_->on_stream_close(stream_id, error_code == NGHTTP3_HTTP_NO_ERROR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int recv_data(nghttp3_conn *conn, int64_t stream_id, const uint8_t *data,
|
||||
size_t datalen, void *user_data, void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
s->recv_data(stream_id, data, datalen);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http3Session::recv_data(int64_t stream_id, const uint8_t *data,
|
||||
size_t datalen) {
|
||||
client_->record_ttfb();
|
||||
client_->worker->stats.bytes_body += datalen;
|
||||
consume(stream_id, datalen);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int deferred_consume(nghttp3_conn *conn, int64_t stream_id, size_t nconsumed,
|
||||
void *user_data, void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
s->consume(stream_id, nconsumed);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http3Session::consume(int64_t stream_id, size_t nconsumed) {
|
||||
ngtcp2_conn_extend_max_stream_offset(client_->quic.conn, stream_id,
|
||||
nconsumed);
|
||||
ngtcp2_conn_extend_max_offset(client_->quic.conn, nconsumed);
|
||||
}
|
||||
|
||||
namespace {
|
||||
int begin_headers(nghttp3_conn *conn, int64_t stream_id, void *user_data,
|
||||
void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
s->begin_headers(stream_id);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http3Session::begin_headers(int64_t stream_id) {
|
||||
auto payloadlen = nghttp3_conn_get_frame_payload_left(conn_, stream_id);
|
||||
assert(payloadlen > 0);
|
||||
|
||||
client_->worker->stats.bytes_head += payloadlen;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token,
|
||||
nghttp3_rcbuf *name, nghttp3_rcbuf *value, uint8_t flags,
|
||||
void *user_data, void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
auto k = nghttp3_rcbuf_get_buf(name);
|
||||
auto v = nghttp3_rcbuf_get_buf(value);
|
||||
s->recv_header(stream_id, &k, &v);
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Http3Session::recv_header(int64_t stream_id, const nghttp3_vec *name,
|
||||
const nghttp3_vec *value) {
|
||||
client_->on_header(stream_id, name->base, name->len, value->base, value->len);
|
||||
client_->worker->stats.bytes_head_decomp += name->len + value->len;
|
||||
}
|
||||
|
||||
namespace {
|
||||
int send_stop_sending(nghttp3_conn *conn, int64_t stream_id, void *user_data,
|
||||
void *stream_user_data) {
|
||||
auto s = static_cast<Http3Session *>(user_data);
|
||||
if (s->send_stop_sending(stream_id) != 0) {
|
||||
return NGHTTP3_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int Http3Session::send_stop_sending(int64_t stream_id) {
|
||||
auto rv = ngtcp2_conn_shutdown_stream_read(client_->quic.conn, stream_id,
|
||||
NGHTTP3_HTTP_PUSH_REFUSED);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_conn_shutdown_stream_read: " << ngtcp2_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http3Session::close_stream(int64_t stream_id, uint16_t error_code) {
|
||||
auto rv = nghttp3_conn_close_stream(conn_, stream_id, error_code);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http3Session::reset_stream(int64_t stream_id) {
|
||||
auto rv = nghttp3_conn_reset_stream(conn_, stream_id);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http3Session::extend_max_local_streams() {
|
||||
auto config = client_->worker->config;
|
||||
|
||||
for (; npending_request_; --npending_request_) {
|
||||
auto stream_id = submit_request_internal();
|
||||
if (stream_id < 0) {
|
||||
if (stream_id == NGTCP2_ERR_STREAM_ID_BLOCKED) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (++reqidx_ == config->nva.size()) {
|
||||
reqidx_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http3Session::init_conn() {
|
||||
int rv;
|
||||
|
||||
assert(conn_ == nullptr);
|
||||
|
||||
if (ngtcp2_conn_get_max_local_streams_uni(client_->quic.conn) < 3) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
nghttp3_conn_callbacks callbacks{
|
||||
nullptr, // acked_stream_data
|
||||
h2load::stream_close,
|
||||
h2load::recv_data,
|
||||
h2load::deferred_consume,
|
||||
h2load::begin_headers,
|
||||
h2load::recv_header,
|
||||
nullptr, // end_headers
|
||||
nullptr, // begin_trailers
|
||||
h2load::recv_header,
|
||||
nullptr, // end_trailers
|
||||
nullptr, // http_begin_push_promise
|
||||
nullptr, // http_recv_push_promise
|
||||
nullptr, // http_end_push_promise
|
||||
nullptr, // http_cancel_push
|
||||
h2load::send_stop_sending,
|
||||
nullptr, // push_stream,
|
||||
};
|
||||
|
||||
nghttp3_conn_settings settings;
|
||||
nghttp3_conn_settings_default(&settings);
|
||||
settings.qpack_max_table_capacity = 4096;
|
||||
settings.qpack_blocked_streams = 100;
|
||||
|
||||
auto mem = nghttp3_mem_default();
|
||||
|
||||
rv = nghttp3_conn_client_new(&conn_, &callbacks, &settings, mem, this);
|
||||
if (rv != 0) {
|
||||
std::cerr << "nghttp3_conn_client_new: " << nghttp3_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t ctrl_stream_id;
|
||||
|
||||
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &ctrl_stream_id, NULL);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = nghttp3_conn_bind_control_stream(conn_, ctrl_stream_id);
|
||||
if (rv != 0) {
|
||||
std::cerr << "nghttp3_conn_bind_control_stream: " << nghttp3_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int64_t qpack_enc_stream_id, qpack_dec_stream_id;
|
||||
|
||||
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_enc_stream_id,
|
||||
NULL);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = ngtcp2_conn_open_uni_stream(client_->quic.conn, &qpack_dec_stream_id,
|
||||
NULL);
|
||||
if (rv != 0) {
|
||||
std::cerr << "ngtcp2_conn_open_uni_stream: " << ngtcp2_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = nghttp3_conn_bind_qpack_streams(conn_, qpack_enc_stream_id,
|
||||
qpack_dec_stream_id);
|
||||
if (rv != 0) {
|
||||
std::cerr << "nghttp3_conn_bind_qpack_streams: " << nghttp3_strerror(rv)
|
||||
<< std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t Http3Session::read_stream(int64_t stream_id, const uint8_t *data,
|
||||
size_t datalen, int fin) {
|
||||
auto nconsumed =
|
||||
nghttp3_conn_read_stream(conn_, stream_id, data, datalen, fin);
|
||||
if (nconsumed < 0) {
|
||||
std::cerr << "nghttp3_conn_read_stream: " << nghttp3_strerror(nconsumed)
|
||||
<< std::endl;
|
||||
client_->quic.last_error = quic::err_application(nconsumed);
|
||||
return -1;
|
||||
}
|
||||
return nconsumed;
|
||||
}
|
||||
|
||||
ssize_t Http3Session::write_stream(int64_t &stream_id, int &fin,
|
||||
nghttp3_vec *vec, size_t veccnt) {
|
||||
auto sveccnt =
|
||||
nghttp3_conn_writev_stream(conn_, &stream_id, &fin, vec, veccnt);
|
||||
if (sveccnt < 0) {
|
||||
client_->quic.last_error = quic::err_application(sveccnt);
|
||||
return -1;
|
||||
}
|
||||
return sveccnt;
|
||||
}
|
||||
|
||||
int Http3Session::block_stream(int64_t stream_id) {
|
||||
auto rv = nghttp3_conn_block_stream(conn_, stream_id);
|
||||
if (rv != 0) {
|
||||
client_->quic.last_error = quic::err_application(rv);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Http3Session::add_write_offset(int64_t stream_id, size_t ndatalen) {
|
||||
auto rv = nghttp3_conn_add_write_offset(conn_, stream_id, ndatalen);
|
||||
if (rv != 0) {
|
||||
client_->quic.last_error = quic::err_application(rv);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace h2load
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2019 nghttp2 contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_HTTP3_SESSION_H
|
||||
#define H2LOAD_HTTP3_SESSION_H
|
||||
|
||||
#include "h2load_session.h"
|
||||
|
||||
#include <nghttp3/nghttp3.h>
|
||||
|
||||
namespace h2load {
|
||||
|
||||
struct Client;
|
||||
|
||||
class Http3Session : public Session {
|
||||
public:
|
||||
Http3Session(Client *client);
|
||||
virtual ~Http3Session();
|
||||
virtual void on_connect();
|
||||
virtual int submit_request();
|
||||
virtual int on_read(const uint8_t *data, size_t len);
|
||||
virtual int on_write();
|
||||
virtual void terminate();
|
||||
virtual size_t max_concurrent_streams();
|
||||
|
||||
int init_conn();
|
||||
int stream_close(int64_t stream_id, uint16_t error_code);
|
||||
void recv_data(int64_t stream_id, const uint8_t *data, size_t datalen);
|
||||
void consume(int64_t stream_id, size_t nconsumed);
|
||||
void begin_headers(int64_t stream_id);
|
||||
void recv_header(int64_t stream_id, const nghttp3_vec *name,
|
||||
const nghttp3_vec *value);
|
||||
int send_stop_sending(int64_t stream_id);
|
||||
|
||||
int close_stream(int64_t stream_id, uint16_t error_code);
|
||||
int reset_stream(int64_t stream_id);
|
||||
int extend_max_local_streams();
|
||||
int64_t submit_request_internal();
|
||||
|
||||
ssize_t read_stream(int64_t stream_id, const uint8_t *data, size_t datalen,
|
||||
int fin);
|
||||
ssize_t write_stream(int64_t &stream_id, int &fin, nghttp3_vec *vec,
|
||||
size_t veccnt);
|
||||
int block_stream(int64_t stream_id);
|
||||
int add_write_offset(int64_t stream_id, size_t ndatalen);
|
||||
|
||||
private:
|
||||
Client *client_;
|
||||
nghttp3_conn *conn_;
|
||||
size_t npending_request_;
|
||||
size_t reqidx_;
|
||||
};
|
||||
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_HTTP3_SESSION_H
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2019 nghttp2 contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef H2LOAD_QUIC_H
|
||||
#define H2LOAD_QUIC_H
|
||||
|
||||
#include "nghttp2_config.h"
|
||||
|
||||
#include <ev.h>
|
||||
|
||||
#include "h2load.h"
|
||||
|
||||
namespace h2load {
|
||||
void quic_pkt_timeout_cb(struct ev_loop *loop, ev_timer *w, int revents);
|
||||
} // namespace h2load
|
||||
|
||||
#endif // H2LOAD_QUIC_H
|
|
@ -0,0 +1,422 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2019 nghttp2 contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include "quic.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <openssl/kdf.h>
|
||||
|
||||
#include <ngtcp2/ngtcp2.h>
|
||||
#include <nghttp3/nghttp3.h>
|
||||
|
||||
#include "template.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
namespace quic {
|
||||
|
||||
const EVP_CIPHER *aead(SSL *ssl) {
|
||||
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
|
||||
case 0x03001301u: // TLS_AES_128_GCM_SHA256
|
||||
return EVP_aes_128_gcm();
|
||||
case 0x03001302u: // TLS_AES_256_GCM_SHA384
|
||||
return EVP_aes_256_gcm();
|
||||
case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
|
||||
return EVP_chacha20_poly1305();
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
const EVP_CIPHER *hp(SSL *ssl) {
|
||||
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
|
||||
case 0x03001301u: // TLS_AES_128_GCM_SHA256
|
||||
return EVP_aes_128_ctr();
|
||||
case 0x03001302u: // TLS_AES_256_GCM_SHA384
|
||||
return EVP_aes_256_ctr();
|
||||
case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
|
||||
return EVP_chacha20();
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
const EVP_MD *prf(SSL *ssl) {
|
||||
switch (SSL_CIPHER_get_id(SSL_get_current_cipher(ssl))) {
|
||||
case 0x03001301u: // TLS_AES_128_GCM_SHA256
|
||||
case 0x03001303u: // TLS_CHACHA20_POLY1305_SHA256
|
||||
return EVP_sha256();
|
||||
case 0x03001302u: // TLS_AES_256_GCM_SHA384
|
||||
return EVP_sha384();
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
size_t aead_key_length(const EVP_CIPHER *aead) {
|
||||
return EVP_CIPHER_key_length(aead);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
size_t aead_nonce_length(const EVP_CIPHER *aead) {
|
||||
return EVP_CIPHER_iv_length(aead);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
size_t aead_tag_length(const EVP_CIPHER *aead) {
|
||||
if (aead == EVP_aes_128_gcm() || aead == EVP_aes_256_gcm()) {
|
||||
return EVP_GCM_TLS_TAG_LEN;
|
||||
}
|
||||
if (aead == EVP_chacha20_poly1305()) {
|
||||
return EVP_CHACHAPOLY_TLS_TAG_LEN;
|
||||
}
|
||||
assert(0);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
size_t aead_max_overhead(const EVP_CIPHER *aead) {
|
||||
return aead_tag_length(aead);
|
||||
}
|
||||
|
||||
int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *salt, size_t saltlen,
|
||||
const EVP_MD *prf) {
|
||||
auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr);
|
||||
if (pctx == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto pctx_d = defer(EVP_PKEY_CTX_free, pctx);
|
||||
|
||||
if (EVP_PKEY_derive_init(pctx) != 1 ||
|
||||
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1 ||
|
||||
EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 ||
|
||||
EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, saltlen) != 1 ||
|
||||
EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 ||
|
||||
EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *info, size_t infolen,
|
||||
const EVP_MD *prf) {
|
||||
auto pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr);
|
||||
if (pctx == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto pctx_d = defer(EVP_PKEY_CTX_free, pctx);
|
||||
|
||||
if (EVP_PKEY_derive_init(pctx) != 1 ||
|
||||
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1 ||
|
||||
EVP_PKEY_CTX_set_hkdf_md(pctx, prf) != 1 ||
|
||||
EVP_PKEY_CTX_set1_hkdf_salt(pctx, "", 0) != 1 ||
|
||||
EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secretlen) != 1 ||
|
||||
EVP_PKEY_CTX_add1_hkdf_info(pctx, info, infolen) != 1 ||
|
||||
EVP_PKEY_derive(pctx, dest, &destlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *label, size_t labellen,
|
||||
const EVP_MD *prf) {
|
||||
std::array<uint8_t, 256> info;
|
||||
static constexpr const uint8_t LABEL[] = "tls13 ";
|
||||
|
||||
auto p = std::begin(info);
|
||||
*p++ = destlen / 256;
|
||||
*p++ = destlen % 256;
|
||||
*p++ = str_size(LABEL) + labellen;
|
||||
p = std::copy_n(LABEL, str_size(LABEL), p);
|
||||
p = std::copy_n(label, labellen, p);
|
||||
*p++ = 0;
|
||||
|
||||
return hkdf_expand(dest, destlen, secret, secretlen, info.data(),
|
||||
p - std::begin(info), prf);
|
||||
}
|
||||
|
||||
int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *salt,
|
||||
size_t saltlen) {
|
||||
return hkdf_extract(dest, destlen, secret, secretlen, salt, saltlen,
|
||||
EVP_sha256());
|
||||
}
|
||||
|
||||
int derive_client_initial_secret(uint8_t *dest, size_t destlen,
|
||||
const uint8_t *secret, size_t secretlen) {
|
||||
static constexpr uint8_t LABEL[] = "client in";
|
||||
return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL,
|
||||
str_size(LABEL), EVP_sha256());
|
||||
}
|
||||
|
||||
int derive_server_initial_secret(uint8_t *dest, size_t destlen,
|
||||
const uint8_t *secret, size_t secretlen) {
|
||||
static constexpr uint8_t LABEL[] = "server in";
|
||||
return hkdf_expand_label(dest, destlen, secret, secretlen, LABEL,
|
||||
str_size(LABEL), EVP_sha256());
|
||||
}
|
||||
|
||||
int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv,
|
||||
size_t &ivlen, const uint8_t *secret,
|
||||
size_t secretlen, const EVP_CIPHER *aead,
|
||||
const EVP_MD *prf) {
|
||||
int rv;
|
||||
static constexpr uint8_t KEY_LABEL[] = "quic key";
|
||||
static constexpr uint8_t IV_LABEL[] = "quic iv";
|
||||
|
||||
auto req_keylen = aead_key_length(aead);
|
||||
if (req_keylen > keylen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
keylen = req_keylen;
|
||||
rv = hkdf_expand_label(key, keylen, secret, secretlen, KEY_LABEL,
|
||||
str_size(KEY_LABEL), prf);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto req_ivlen = std::max(static_cast<size_t>(8), aead_nonce_length(aead));
|
||||
if (req_ivlen > ivlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ivlen = req_ivlen;
|
||||
rv = hkdf_expand_label(iv, ivlen, secret, secretlen, IV_LABEL,
|
||||
str_size(IV_LABEL), prf);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int derive_header_protection_key(uint8_t *key, size_t &keylen,
|
||||
const uint8_t *secret, size_t secretlen,
|
||||
const EVP_CIPHER *aead, const EVP_MD *prf) {
|
||||
int rv;
|
||||
static constexpr uint8_t LABEL[] = "quic hp";
|
||||
|
||||
auto req_keylen = aead_key_length(aead);
|
||||
if (req_keylen > keylen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
keylen = req_keylen;
|
||||
rv = hkdf_expand_label(key, keylen, secret, secretlen, LABEL, str_size(LABEL),
|
||||
prf);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
|
||||
size_t plaintextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen, const EVP_CIPHER *aead) {
|
||||
auto taglen = aead_tag_length(aead);
|
||||
|
||||
if (destlen < plaintextlen + taglen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto actx = EVP_CIPHER_CTX_new();
|
||||
if (actx == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto actx_d = defer(EVP_CIPHER_CTX_free, actx);
|
||||
|
||||
if (EVP_EncryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) !=
|
||||
1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t outlen = 0;
|
||||
int len;
|
||||
|
||||
if (EVP_EncryptUpdate(actx, nullptr, &len, ad, adlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_EncryptUpdate(actx, dest, &len, plaintext, plaintextlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
outlen = len;
|
||||
|
||||
if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
outlen += len;
|
||||
|
||||
assert(outlen + taglen <= destlen);
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_GET_TAG, taglen, dest + outlen) !=
|
||||
1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
outlen += taglen;
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
|
||||
size_t ciphertextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen, const EVP_CIPHER *aead) {
|
||||
auto taglen = aead_tag_length(aead);
|
||||
|
||||
if (taglen > ciphertextlen || destlen + taglen < ciphertextlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ciphertextlen -= taglen;
|
||||
auto tag = ciphertext + ciphertextlen;
|
||||
|
||||
auto actx = EVP_CIPHER_CTX_new();
|
||||
if (actx == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto actx_d = defer(EVP_CIPHER_CTX_free, actx);
|
||||
|
||||
if (EVP_DecryptInit_ex(actx, aead, nullptr, nullptr, nullptr) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, nullptr) !=
|
||||
1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(actx, nullptr, nullptr, key, nonce) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t outlen;
|
||||
int len;
|
||||
|
||||
if (EVP_DecryptUpdate(actx, nullptr, &len, ad, adlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptUpdate(actx, dest, &len, ciphertext, ciphertextlen) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
outlen = len;
|
||||
|
||||
if (EVP_CIPHER_CTX_ctrl(actx, EVP_CTRL_AEAD_SET_TAG, taglen,
|
||||
const_cast<uint8_t *>(tag)) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (EVP_DecryptFinal_ex(actx, dest + outlen, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
outlen += len;
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key,
|
||||
size_t keylen, const uint8_t *sample, size_t samplelen,
|
||||
const EVP_CIPHER *cipher) {
|
||||
static constexpr uint8_t PLAINTEXT[] = "\x00\x00\x00\x00\x00";
|
||||
|
||||
auto actx = EVP_CIPHER_CTX_new();
|
||||
if (actx == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto actx_d = defer(EVP_CIPHER_CTX_free, actx);
|
||||
|
||||
if (EVP_EncryptInit_ex(actx, cipher, nullptr, key, sample) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t outlen = 0;
|
||||
int len;
|
||||
|
||||
if (EVP_EncryptUpdate(actx, dest, &len, PLAINTEXT, str_size(PLAINTEXT)) !=
|
||||
1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len == 5);
|
||||
|
||||
outlen = len;
|
||||
|
||||
if (EVP_EncryptFinal_ex(actx, dest + outlen, &len) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len == 0);
|
||||
|
||||
return outlen;
|
||||
}
|
||||
|
||||
Error err_transport(int liberr) {
|
||||
if (liberr == NGTCP2_ERR_RECV_VERSION_NEGOTIATION) {
|
||||
return {ErrorType::TransportVersionNegotiation, 0};
|
||||
}
|
||||
return {ErrorType::Transport,
|
||||
ngtcp2_err_infer_quic_transport_error_code(liberr)};
|
||||
}
|
||||
|
||||
Error err_transport_tls(int alert) {
|
||||
return {ErrorType::Transport, ngtcp2_err_infer_quic_transport_error_code(
|
||||
NGTCP2_CRYPTO_ERROR | alert)};
|
||||
}
|
||||
|
||||
Error err_application(int liberr) {
|
||||
return {ErrorType::Application,
|
||||
nghttp3_err_infer_quic_app_error_code(liberr)};
|
||||
}
|
||||
|
||||
} // namespace quic
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2019 nghttp2 contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef QUIC_H
|
||||
#define QUIC_H
|
||||
|
||||
#include "nghttp2_config.h"
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
namespace quic {
|
||||
|
||||
const EVP_CIPHER *aead(SSL *ssl);
|
||||
const EVP_CIPHER *hp(SSL *ssl);
|
||||
const EVP_MD *prf(SSL *ssl);
|
||||
size_t aead_max_overhead(const EVP_CIPHER *aead);
|
||||
|
||||
int hkdf_extract(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *salt, size_t saltlen,
|
||||
const EVP_MD *prf);
|
||||
|
||||
int hkdf_expand(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *info, size_t infolen,
|
||||
const EVP_MD *prf);
|
||||
|
||||
int hkdf_expand_label(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *label, size_t labellen,
|
||||
const EVP_MD *prf);
|
||||
|
||||
int derive_initial_secret(uint8_t *dest, size_t destlen, const uint8_t *secret,
|
||||
size_t secretlen, const uint8_t *salt,
|
||||
size_t saltlen);
|
||||
|
||||
int derive_client_initial_secret(uint8_t *dest, size_t destlen,
|
||||
const uint8_t *secret, size_t secretlen);
|
||||
|
||||
int derive_server_initial_secret(uint8_t *dest, size_t destlen,
|
||||
const uint8_t *secret, size_t secretlen);
|
||||
|
||||
int derive_packet_protection_key(uint8_t *key, size_t &keylen, uint8_t *iv,
|
||||
size_t &ivlen, const uint8_t *secret,
|
||||
size_t secretlen, const EVP_CIPHER *aead,
|
||||
const EVP_MD *prf);
|
||||
|
||||
int derive_header_protection_key(uint8_t *key, size_t &keylen,
|
||||
const uint8_t *secret, size_t secretlen,
|
||||
const EVP_CIPHER *aead, const EVP_MD *prf);
|
||||
|
||||
ssize_t encrypt(uint8_t *dest, size_t destlen, const uint8_t *plaintext,
|
||||
size_t plaintextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen, const EVP_CIPHER *aead);
|
||||
|
||||
ssize_t decrypt(uint8_t *dest, size_t destlen, const uint8_t *ciphertext,
|
||||
size_t ciphertextlen, const uint8_t *key, size_t keylen,
|
||||
const uint8_t *nonce, size_t noncelen, const uint8_t *ad,
|
||||
size_t adlen, const EVP_CIPHER *aead);
|
||||
|
||||
ssize_t hp_mask(uint8_t *dest, size_t destlen, const uint8_t *key,
|
||||
size_t keylen, const uint8_t *sample, size_t samplelen,
|
||||
const EVP_CIPHER *cipher);
|
||||
|
||||
enum class ErrorType {
|
||||
Transport,
|
||||
TransportVersionNegotiation,
|
||||
Application,
|
||||
};
|
||||
|
||||
struct Error {
|
||||
Error(ErrorType type, uint16_t code) : type(type), code(code) {}
|
||||
Error() : type(ErrorType::Transport), code(0) {}
|
||||
|
||||
ErrorType type;
|
||||
uint16_t code;
|
||||
};
|
||||
|
||||
Error err_transport(int liberr);
|
||||
Error err_transport_tls(int alert);
|
||||
Error err_application(int liberr);
|
||||
|
||||
} // namespace quic
|
||||
|
||||
#endif // QUIC_H
|
50
src/util.cc
50
src/util.cc
|
@ -945,6 +945,56 @@ int create_nonblock_socket(int family) {
|
|||
return fd;
|
||||
}
|
||||
|
||||
int create_nonblock_udp_socket(int family) {
|
||||
#ifdef SOCK_NONBLOCK
|
||||
auto fd = socket(family, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
|
||||
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
#else // !SOCK_NONBLOCK
|
||||
auto fd = socket(family, SOCK_STREAM, 0);
|
||||
|
||||
if (fd == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
make_socket_nonblocking(fd);
|
||||
make_socket_closeonexec(fd);
|
||||
#endif // !SOCK_NONBLOCK
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
int bind_any_addr_udp(int fd, int family) {
|
||||
addrinfo hints{};
|
||||
addrinfo *res, *rp;
|
||||
int rv;
|
||||
|
||||
hints.ai_family = family;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
|
||||
rv = getaddrinfo(nullptr, "0", &hints, &res);
|
||||
if (rv != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
if (bind(fd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
|
||||
if (!rp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool check_socket_connected(int fd) {
|
||||
int error;
|
||||
socklen_t len = sizeof(error);
|
||||
|
|
|
@ -626,6 +626,9 @@ int make_socket_nonblocking(int fd);
|
|||
int make_socket_nodelay(int fd);
|
||||
|
||||
int create_nonblock_socket(int family);
|
||||
int create_nonblock_udp_socket(int family);
|
||||
|
||||
int bind_any_addr_udp(int fd, int family);
|
||||
|
||||
bool check_socket_connected(int fd);
|
||||
|
||||
|
|
Loading…
Reference in New Issue