|
#define _POSIX_C_SOURCE 200809L |
|
|
|
#include <time.h> |
|
#include <sys/types.h> |
|
#include <sys/socket.h> |
|
#include <netdb.h> |
|
#include <arpa/inet.h> |
|
#include <stdarg.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <stdio.h> |
|
#include <errno.h> |
|
#include <assert.h> |
|
#include <unistd.h> |
|
|
|
#include <ngtcp2/ngtcp2.h> |
|
#include <ngtcp2/ngtcp2_crypto.h> |
|
#include <ngtcp2/ngtcp2_crypto_gnutls.h> |
|
|
|
#include <gnutls/crypto.h> |
|
#include <gnutls/gnutls.h> |
|
|
|
#include <ev.h> |
|
|
|
#define REMOTE_HOST "127.0.0.1" |
|
#define REMOTE_PORT "4433" |
|
#define ALPN "hq-interop" |
|
#define MESSAGE "GET /\r\n" |
|
|
|
/* |
|
* Example 1: Handshake with www.google.com |
|
* |
|
* #define REMOTE_HOST "www.google.com" |
|
* #define REMOTE_PORT "443" |
|
* #define ALPN "h3" |
|
* |
|
* and undefine MESSAGE macro. |
|
*/ |
|
|
|
static int socket_family_for_host(const char *hostname, int preferred_family); |
|
static const char *getenv_default(const char *name, const char *fallback_name); |
|
static void gnutls_debug_log_func(int level, const char *message); |
|
static void usage(const char *progname); |
|
|
|
struct client_config { |
|
const char *host; |
|
const char *port; |
|
const char *sni; |
|
const char *alpn; |
|
const char *message; |
|
int preferred_family; |
|
int exit_on_handshake; |
|
}; |
|
|
|
static uint64_t timestamp(void) { |
|
struct timespec tp; |
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &tp) != 0) { |
|
fprintf(stderr, "clock_gettime: %s\n", strerror(errno)); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
return (uint64_t)tp.tv_sec * NGTCP2_SECONDS + (uint64_t)tp.tv_nsec; |
|
} |
|
|
|
static int create_sock(struct sockaddr *addr, socklen_t *paddrlen, |
|
const char *host, const char *port, |
|
int preferred_family) { |
|
struct addrinfo hints = {0}; |
|
struct addrinfo *res, *rp; |
|
int rv; |
|
int fd = -1; |
|
|
|
hints.ai_family = socket_family_for_host(host, preferred_family); |
|
hints.ai_socktype = SOCK_DGRAM; |
|
|
|
rv = getaddrinfo(host, port, &hints, &res); |
|
if (rv != 0) { |
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
for (rp = res; rp; rp = rp->ai_next) { |
|
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); |
|
if (fd == -1) { |
|
continue; |
|
} |
|
|
|
break; |
|
} |
|
|
|
if (fd == -1) { |
|
goto end; |
|
} |
|
|
|
*paddrlen = rp->ai_addrlen; |
|
memcpy(addr, rp->ai_addr, rp->ai_addrlen); |
|
|
|
end: |
|
freeaddrinfo(res); |
|
|
|
return fd; |
|
} |
|
|
|
static int connect_sock(struct sockaddr *local_addr, socklen_t *plocal_addrlen, |
|
int fd, const struct sockaddr *remote_addr, |
|
size_t remote_addrlen) { |
|
socklen_t len; |
|
|
|
if (connect(fd, remote_addr, (socklen_t)remote_addrlen) != 0) { |
|
fprintf(stderr, "connect: %s\n", strerror(errno)); |
|
return -1; |
|
} |
|
|
|
len = *plocal_addrlen; |
|
|
|
if (getsockname(fd, local_addr, &len) == -1) { |
|
fprintf(stderr, "getsockname: %s\n", strerror(errno)); |
|
return -1; |
|
} |
|
|
|
*plocal_addrlen = len; |
|
|
|
return 0; |
|
} |
|
|
|
struct client { |
|
struct client_config config; |
|
ngtcp2_crypto_conn_ref conn_ref; |
|
int fd; |
|
struct sockaddr_storage local_addr; |
|
socklen_t local_addrlen; |
|
gnutls_certificate_credentials_t cred; |
|
gnutls_session_t session; |
|
ngtcp2_conn *conn; |
|
|
|
struct { |
|
int64_t stream_id; |
|
const uint8_t *data; |
|
size_t datalen; |
|
size_t nwrite; |
|
} stream; |
|
|
|
ngtcp2_ccerr last_error; |
|
|
|
ev_io rev; |
|
ev_timer timer; |
|
}; |
|
|
|
static int numeric_host_family(const char *hostname, int family) { |
|
uint8_t dst[sizeof(struct in6_addr)]; |
|
return inet_pton(family, hostname, dst) == 1; |
|
} |
|
|
|
static int numeric_host(const char *hostname) { |
|
return numeric_host_family(hostname, AF_INET) || |
|
numeric_host_family(hostname, AF_INET6); |
|
} |
|
|
|
static int hook_func(gnutls_session_t session, unsigned int htype, |
|
unsigned when, unsigned int incoming, |
|
const gnutls_datum_t *msg) { |
|
(void)session; |
|
(void)htype; |
|
(void)when; |
|
(void)incoming; |
|
(void)msg; |
|
|
|
return 0; |
|
} |
|
|
|
static const char priority[] = |
|
"NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" |
|
"+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:+GROUP-X25519:" |
|
"+GROUP-SECP384R1:+GROUP-SECP521R1:%DISABLE_TLS13_COMPAT_MODE"; |
|
|
|
static int socket_family_for_host(const char *hostname, int preferred_family) { |
|
if (numeric_host_family(hostname, AF_INET)) { |
|
return AF_INET; |
|
} |
|
if (numeric_host_family(hostname, AF_INET6)) { |
|
return AF_INET6; |
|
} |
|
if (preferred_family == AF_INET || preferred_family == AF_INET6) { |
|
return preferred_family; |
|
} |
|
return AF_UNSPEC; |
|
} |
|
|
|
static const char *getenv_default(const char *name, const char *fallback_name) { |
|
const char *value = getenv(name); |
|
if (value && *value) { |
|
return value; |
|
} |
|
value = getenv(fallback_name); |
|
if (value && *value) { |
|
return value; |
|
} |
|
return NULL; |
|
} |
|
|
|
static int fill_random(void *dest, size_t destlen) { |
|
int rv = gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_rnd: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void gnutls_debug_log_func(int level, const char *message) { |
|
fprintf(stderr, "gnutls[%d]: %s", level, message); |
|
} |
|
|
|
static void usage(const char *progname) { |
|
fprintf(stderr, |
|
"Usage: %s [--host HOST] [--port PORT] [--sni NAME] [--alpn ALPN]\n" |
|
" [--ipv4]\n" |
|
" [--message TEXT | --no-message] [--exit-on-handshake]\n", |
|
progname); |
|
} |
|
|
|
static int client_load_trust_store(struct client *c) { |
|
const char *ca_file = getenv_default("NGTCP2_CA_FILE", "SSL_CERT_FILE"); |
|
const char *ca_dir = getenv_default("NGTCP2_CA_DIR", "SSL_CERT_DIR"); |
|
int rv; |
|
int loaded = 0; |
|
|
|
if (ca_file) { |
|
rv = gnutls_certificate_set_x509_trust_file(c->cred, ca_file, |
|
GNUTLS_X509_FMT_PEM); |
|
if (rv < 0) { |
|
fprintf(stderr, "gnutls_certificate_set_x509_trust_file(%s): %s\n", |
|
ca_file, gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
loaded += rv; |
|
} |
|
|
|
if (ca_dir) { |
|
rv = gnutls_certificate_set_x509_trust_dir(c->cred, ca_dir, |
|
GNUTLS_X509_FMT_PEM); |
|
if (rv < 0) { |
|
fprintf(stderr, "gnutls_certificate_set_x509_trust_dir(%s): %s\n", |
|
ca_dir, gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
loaded += rv; |
|
} |
|
|
|
if (loaded > 0) { |
|
return 0; |
|
} |
|
|
|
rv = gnutls_certificate_set_x509_system_trust(c->cred); |
|
if (rv < 0) { |
|
fprintf(stderr, "gnutls_certificate_set_x509_system_trust: %s\n", |
|
gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int client_tls_init(struct client *c) { |
|
gnutls_datum_t alpn = { |
|
.data = (unsigned char *)c->config.alpn, |
|
.size = (unsigned int)strlen(c->config.alpn), |
|
}; |
|
const char *sni = c->config.sni; |
|
int rv; |
|
|
|
rv = gnutls_certificate_allocate_credentials(&c->cred); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_certificate_allocate_credentials: %s\n", |
|
gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
if (client_load_trust_store(c) != 0) { |
|
return -1; |
|
} |
|
|
|
rv = gnutls_init(&c->session, GNUTLS_CLIENT | GNUTLS_ENABLE_EARLY_DATA | |
|
GNUTLS_NO_END_OF_EARLY_DATA); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_init: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
if (ngtcp2_crypto_gnutls_configure_client_session(c->session) != 0) { |
|
fprintf(stderr, "ngtcp2_crypto_gnutls_configure_client_session failed\n"); |
|
return -1; |
|
} |
|
|
|
rv = gnutls_priority_set_direct(c->session, priority, NULL); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_priority_set_direct: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
gnutls_handshake_set_hook_function(c->session, GNUTLS_HANDSHAKE_ANY, |
|
GNUTLS_HOOK_POST, hook_func); |
|
|
|
gnutls_session_set_ptr(c->session, &c->conn_ref); |
|
|
|
rv = gnutls_credentials_set(c->session, GNUTLS_CRD_CERTIFICATE, c->cred); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_credentials_set: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
rv = gnutls_alpn_set_protocols(c->session, &alpn, 1, GNUTLS_ALPN_MANDATORY); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_alpn_set_protocols: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
if (!sni) { |
|
if (numeric_host(c->config.host)) { |
|
sni = "localhost"; |
|
} else { |
|
sni = c->config.host; |
|
} |
|
} |
|
|
|
if (*sni) { |
|
rv = gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, sni, strlen(sni)); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_server_name_set: %s\n", gnutls_strerror(rv)); |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void rand_cb(uint8_t *dest, size_t destlen, |
|
const ngtcp2_rand_ctx *rand_ctx) { |
|
(void)rand_ctx; |
|
|
|
if (fill_random(dest, destlen) != 0) { |
|
assert(0); |
|
abort(); |
|
} |
|
} |
|
|
|
static int get_new_connection_id_cb(ngtcp2_conn *conn, ngtcp2_cid *cid, |
|
uint8_t *token, size_t cidlen, |
|
void *user_data) { |
|
(void)conn; |
|
(void)user_data; |
|
|
|
if (fill_random(cid->data, cidlen) != 0) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
|
|
cid->datalen = cidlen; |
|
|
|
if (fill_random(token, NGTCP2_STATELESS_RESET_TOKENLEN) != 0) { |
|
return NGTCP2_ERR_CALLBACK_FAILURE; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int extend_max_local_streams_bidi(ngtcp2_conn *conn, |
|
uint64_t max_streams, |
|
void *user_data) { |
|
struct client *c = user_data; |
|
int rv; |
|
int64_t stream_id; |
|
(void)max_streams; |
|
|
|
if (!c->config.message) { |
|
return 0; |
|
} |
|
|
|
if (c->stream.stream_id != -1) { |
|
return 0; |
|
} |
|
|
|
rv = ngtcp2_conn_open_bidi_stream(conn, &stream_id, NULL); |
|
if (rv != 0) { |
|
return 0; |
|
} |
|
|
|
c->stream.stream_id = stream_id; |
|
c->stream.data = (const uint8_t *)c->config.message; |
|
c->stream.datalen = strlen(c->config.message); |
|
|
|
return 0; |
|
} |
|
|
|
static int handshake_completed_cb(ngtcp2_conn *conn, void *user_data) { |
|
struct client *c = user_data; |
|
(void)conn; |
|
|
|
if (c->config.exit_on_handshake) { |
|
ev_break(EV_DEFAULT, EVBREAK_ALL); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void log_printf(void *user_data, const char *fmt, ...) { |
|
va_list ap; |
|
(void)user_data; |
|
|
|
va_start(ap, fmt); |
|
vfprintf(stderr, fmt, ap); |
|
va_end(ap); |
|
|
|
fprintf(stderr, "\n"); |
|
} |
|
|
|
static int client_quic_init(struct client *c, |
|
const struct sockaddr *remote_addr, |
|
socklen_t remote_addrlen, |
|
const struct sockaddr *local_addr, |
|
socklen_t local_addrlen) { |
|
ngtcp2_path path = { |
|
.local = |
|
{ |
|
.addr = (struct sockaddr *)local_addr, |
|
.addrlen = local_addrlen, |
|
}, |
|
.remote = |
|
{ |
|
.addr = (struct sockaddr *)remote_addr, |
|
.addrlen = remote_addrlen, |
|
}, |
|
}; |
|
ngtcp2_callbacks callbacks = { |
|
.client_initial = ngtcp2_crypto_client_initial_cb, |
|
.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb, |
|
.handshake_completed = handshake_completed_cb, |
|
.encrypt = ngtcp2_crypto_encrypt_cb, |
|
.decrypt = ngtcp2_crypto_decrypt_cb, |
|
.hp_mask = ngtcp2_crypto_hp_mask_cb, |
|
.recv_retry = ngtcp2_crypto_recv_retry_cb, |
|
.extend_max_local_streams_bidi = extend_max_local_streams_bidi, |
|
.rand = rand_cb, |
|
.get_new_connection_id = get_new_connection_id_cb, |
|
.update_key = ngtcp2_crypto_update_key_cb, |
|
.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb, |
|
.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb, |
|
.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb, |
|
.version_negotiation = ngtcp2_crypto_version_negotiation_cb, |
|
}; |
|
ngtcp2_cid dcid, scid; |
|
ngtcp2_settings settings; |
|
ngtcp2_transport_params params; |
|
int rv; |
|
|
|
dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN; |
|
if (fill_random(dcid.data, dcid.datalen) != 0) { |
|
return -1; |
|
} |
|
|
|
scid.datalen = 8; |
|
if (fill_random(scid.data, scid.datalen) != 0) { |
|
return -1; |
|
} |
|
|
|
ngtcp2_settings_default(&settings); |
|
|
|
settings.initial_ts = timestamp(); |
|
settings.log_printf = log_printf; |
|
|
|
ngtcp2_transport_params_default(¶ms); |
|
|
|
params.initial_max_streams_uni = 3; |
|
params.initial_max_stream_data_bidi_local = 128 * 1024; |
|
params.initial_max_data = 1024 * 1024; |
|
|
|
rv = |
|
ngtcp2_conn_client_new(&c->conn, &dcid, &scid, &path, NGTCP2_PROTO_VER_V1, |
|
&callbacks, &settings, ¶ms, NULL, c); |
|
if (rv != 0) { |
|
fprintf(stderr, "ngtcp2_conn_client_new: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
ngtcp2_conn_set_tls_native_handle(c->conn, c->session); |
|
|
|
return 0; |
|
} |
|
|
|
static int client_read(struct client *c) { |
|
uint8_t buf[65536]; |
|
struct sockaddr_storage addr; |
|
struct iovec iov = { |
|
.iov_base = buf, |
|
.iov_len = sizeof(buf), |
|
}; |
|
struct msghdr msg = {0}; |
|
ssize_t nread; |
|
ngtcp2_path path; |
|
ngtcp2_pkt_info pi = {0}; |
|
int rv; |
|
|
|
msg.msg_name = &addr; |
|
msg.msg_iov = &iov; |
|
msg.msg_iovlen = 1; |
|
|
|
for (;;) { |
|
msg.msg_namelen = sizeof(addr); |
|
|
|
nread = recvmsg(c->fd, &msg, MSG_DONTWAIT); |
|
|
|
if (nread == -1) { |
|
if (errno == ECONNREFUSED) { |
|
fprintf(stderr, "recvmsg: %s\n", strerror(errno)); |
|
return -1; |
|
} |
|
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK) { |
|
fprintf(stderr, "recvmsg: %s\n", strerror(errno)); |
|
} |
|
|
|
break; |
|
} |
|
|
|
path.local.addrlen = c->local_addrlen; |
|
path.local.addr = (struct sockaddr *)&c->local_addr; |
|
path.remote.addrlen = msg.msg_namelen; |
|
path.remote.addr = msg.msg_name; |
|
|
|
rv = ngtcp2_conn_read_pkt(c->conn, &path, &pi, buf, (size_t)nread, |
|
timestamp()); |
|
if (rv != 0) { |
|
fprintf(stderr, "ngtcp2_conn_read_pkt: %s\n", ngtcp2_strerror(rv)); |
|
if (!c->last_error.error_code) { |
|
if (rv == NGTCP2_ERR_CRYPTO) { |
|
ngtcp2_ccerr_set_tls_alert( |
|
&c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0); |
|
} else { |
|
ngtcp2_ccerr_set_liberr(&c->last_error, rv, NULL, 0); |
|
} |
|
} |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int client_send_packet(struct client *c, const uint8_t *data, |
|
size_t datalen) { |
|
struct iovec iov = { |
|
.iov_base = (uint8_t *)data, |
|
.iov_len = datalen, |
|
}; |
|
struct msghdr msg = {0}; |
|
ssize_t nwrite; |
|
|
|
msg.msg_iov = &iov; |
|
msg.msg_iovlen = 1; |
|
|
|
do { |
|
nwrite = sendmsg(c->fd, &msg, 0); |
|
} while (nwrite == -1 && errno == EINTR); |
|
|
|
if (nwrite == -1) { |
|
fprintf(stderr, "sendmsg: %s\n", strerror(errno)); |
|
|
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static size_t client_get_message(struct client *c, int64_t *pstream_id, |
|
int *pfin, ngtcp2_vec *datav, |
|
size_t datavcnt) { |
|
if (datavcnt == 0) { |
|
return 0; |
|
} |
|
|
|
if (c->stream.stream_id != -1 && c->stream.nwrite < c->stream.datalen) { |
|
*pstream_id = c->stream.stream_id; |
|
*pfin = 1; |
|
datav->base = (uint8_t *)c->stream.data + c->stream.nwrite; |
|
datav->len = c->stream.datalen - c->stream.nwrite; |
|
return 1; |
|
} |
|
|
|
*pstream_id = -1; |
|
*pfin = 0; |
|
datav->base = NULL; |
|
datav->len = 0; |
|
|
|
return 0; |
|
} |
|
|
|
static int client_write_streams(struct client *c) { |
|
ngtcp2_tstamp ts = timestamp(); |
|
ngtcp2_pkt_info pi; |
|
ngtcp2_ssize nwrite; |
|
uint8_t buf[1452]; |
|
ngtcp2_path_storage ps; |
|
ngtcp2_vec datav; |
|
size_t datavcnt; |
|
int64_t stream_id; |
|
ngtcp2_ssize wdatalen; |
|
uint32_t flags; |
|
int fin; |
|
|
|
ngtcp2_path_storage_zero(&ps); |
|
|
|
for (;;) { |
|
datavcnt = client_get_message(c, &stream_id, &fin, &datav, 1); |
|
|
|
flags = NGTCP2_WRITE_STREAM_FLAG_MORE; |
|
if (fin) { |
|
flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; |
|
} |
|
|
|
nwrite = ngtcp2_conn_writev_stream(c->conn, &ps.path, &pi, buf, sizeof(buf), |
|
&wdatalen, flags, stream_id, &datav, |
|
datavcnt, ts); |
|
if (nwrite < 0) { |
|
switch (nwrite) { |
|
case NGTCP2_ERR_WRITE_MORE: |
|
c->stream.nwrite += (size_t)wdatalen; |
|
continue; |
|
default: |
|
fprintf(stderr, "ngtcp2_conn_writev_stream: %s\n", |
|
ngtcp2_strerror((int)nwrite)); |
|
ngtcp2_ccerr_set_liberr(&c->last_error, (int)nwrite, NULL, 0); |
|
return -1; |
|
} |
|
} |
|
|
|
if (nwrite == 0) { |
|
return 0; |
|
} |
|
|
|
if (wdatalen > 0) { |
|
c->stream.nwrite += (size_t)wdatalen; |
|
} |
|
|
|
if (client_send_packet(c, buf, (size_t)nwrite) != 0) { |
|
break; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int client_write(struct client *c) { |
|
ngtcp2_tstamp expiry, now; |
|
ev_tstamp t; |
|
|
|
if (client_write_streams(c) != 0) { |
|
return -1; |
|
} |
|
|
|
expiry = ngtcp2_conn_get_expiry(c->conn); |
|
now = timestamp(); |
|
|
|
t = expiry < now ? 1e-9 : (ev_tstamp)(expiry - now) / NGTCP2_SECONDS; |
|
|
|
c->timer.repeat = t; |
|
ev_timer_again(EV_DEFAULT, &c->timer); |
|
|
|
return 0; |
|
} |
|
|
|
static int client_handle_expiry(struct client *c) { |
|
int rv = ngtcp2_conn_handle_expiry(c->conn, timestamp()); |
|
if (rv != 0) { |
|
fprintf(stderr, "ngtcp2_conn_handle_expiry: %s\n", ngtcp2_strerror(rv)); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void client_close(struct client *c) { |
|
ngtcp2_ssize nwrite; |
|
ngtcp2_pkt_info pi; |
|
ngtcp2_path_storage ps; |
|
uint8_t buf[1280]; |
|
|
|
if (ngtcp2_conn_in_closing_period(c->conn) || |
|
ngtcp2_conn_in_draining_period(c->conn)) { |
|
goto fin; |
|
} |
|
|
|
ngtcp2_path_storage_zero(&ps); |
|
|
|
nwrite = ngtcp2_conn_write_connection_close( |
|
c->conn, &ps.path, &pi, buf, sizeof(buf), &c->last_error, timestamp()); |
|
if (nwrite < 0) { |
|
fprintf(stderr, "ngtcp2_conn_write_connection_close: %s\n", |
|
ngtcp2_strerror((int)nwrite)); |
|
goto fin; |
|
} |
|
|
|
client_send_packet(c, buf, (size_t)nwrite); |
|
|
|
fin: |
|
ev_break(EV_DEFAULT, EVBREAK_ALL); |
|
} |
|
|
|
static void read_cb(struct ev_loop *loop, ev_io *w, int revents) { |
|
struct client *c = w->data; |
|
(void)loop; |
|
(void)revents; |
|
|
|
if (client_read(c) != 0) { |
|
client_close(c); |
|
return; |
|
} |
|
|
|
if (client_write(c) != 0) { |
|
client_close(c); |
|
} |
|
} |
|
|
|
static void timer_cb(struct ev_loop *loop, ev_timer *w, int revents) { |
|
struct client *c = w->data; |
|
(void)loop; |
|
(void)revents; |
|
|
|
if (client_handle_expiry(c) != 0) { |
|
client_close(c); |
|
return; |
|
} |
|
|
|
if (client_write(c) != 0) { |
|
client_close(c); |
|
} |
|
} |
|
|
|
static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref) { |
|
struct client *c = conn_ref->user_data; |
|
return c->conn; |
|
} |
|
|
|
static int client_init(struct client *c, const struct client_config *config) { |
|
struct sockaddr_storage remote_addr, local_addr; |
|
socklen_t remote_addrlen, local_addrlen = sizeof(local_addr); |
|
|
|
memset(c, 0, sizeof(*c)); |
|
c->config = *config; |
|
c->fd = -1; |
|
|
|
ngtcp2_ccerr_default(&c->last_error); |
|
|
|
c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen, |
|
c->config.host, c->config.port, |
|
c->config.preferred_family); |
|
if (c->fd == -1) { |
|
return -1; |
|
} |
|
|
|
if (connect_sock((struct sockaddr *)&local_addr, &local_addrlen, c->fd, |
|
(struct sockaddr *)&remote_addr, remote_addrlen) != 0) { |
|
return -1; |
|
} |
|
|
|
memcpy(&c->local_addr, &local_addr, sizeof(c->local_addr)); |
|
c->local_addrlen = local_addrlen; |
|
|
|
c->conn_ref.get_conn = get_conn; |
|
c->conn_ref.user_data = c; |
|
|
|
if (client_tls_init(c) != 0) { |
|
return -1; |
|
} |
|
|
|
if (client_quic_init(c, (struct sockaddr *)&remote_addr, remote_addrlen, |
|
(struct sockaddr *)&local_addr, local_addrlen) != 0) { |
|
return -1; |
|
} |
|
|
|
c->stream.stream_id = -1; |
|
|
|
ev_io_init(&c->rev, read_cb, c->fd, EV_READ); |
|
c->rev.data = c; |
|
ev_io_start(EV_DEFAULT, &c->rev); |
|
|
|
ev_timer_init(&c->timer, timer_cb, 0., 0.); |
|
c->timer.data = c; |
|
|
|
return 0; |
|
} |
|
|
|
static void client_free(struct client *c) { |
|
if (c->conn) { |
|
ngtcp2_conn_del(c->conn); |
|
} |
|
if (c->session) { |
|
gnutls_deinit(c->session); |
|
} |
|
if (c->cred) { |
|
gnutls_certificate_free_credentials(c->cred); |
|
} |
|
if (c->fd != -1) { |
|
close(c->fd); |
|
} |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
struct client c; |
|
struct client_config config = { |
|
.host = REMOTE_HOST, |
|
.port = REMOTE_PORT, |
|
.sni = NULL, |
|
.alpn = ALPN, |
|
.message = MESSAGE, |
|
.preferred_family = AF_UNSPEC, |
|
.exit_on_handshake = 0, |
|
}; |
|
const char *debug_level; |
|
int rv; |
|
int i; |
|
|
|
for (i = 1; i < argc; ++i) { |
|
if (strcmp(argv[i], "--host") == 0 && i + 1 < argc) { |
|
config.host = argv[++i]; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) { |
|
config.port = argv[++i]; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--sni") == 0 && i + 1 < argc) { |
|
config.sni = argv[++i]; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--alpn") == 0 && i + 1 < argc) { |
|
config.alpn = argv[++i]; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--ipv4") == 0) { |
|
config.preferred_family = AF_INET; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--message") == 0 && i + 1 < argc) { |
|
config.message = argv[++i]; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--no-message") == 0) { |
|
config.message = NULL; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--exit-on-handshake") == 0) { |
|
config.exit_on_handshake = 1; |
|
continue; |
|
} |
|
if (strcmp(argv[i], "--help") == 0) { |
|
usage(argv[0]); |
|
return 0; |
|
} |
|
|
|
usage(argv[0]); |
|
return EXIT_FAILURE; |
|
} |
|
|
|
setbuf(stderr, NULL); |
|
|
|
rv = gnutls_global_init(); |
|
if (rv != 0) { |
|
fprintf(stderr, "gnutls_global_init: %s\n", gnutls_strerror(rv)); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
debug_level = getenv("NGTCP2_GNUTLS_DEBUG"); |
|
if (debug_level && *debug_level) { |
|
gnutls_global_set_log_function(gnutls_debug_log_func); |
|
gnutls_global_set_log_level(atoi(debug_level)); |
|
} |
|
|
|
if (client_init(&c, &config) != 0) { |
|
client_free(&c); |
|
gnutls_global_deinit(); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
if (client_write(&c) != 0) { |
|
client_free(&c); |
|
gnutls_global_deinit(); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
ev_run(EV_DEFAULT, 0); |
|
|
|
client_free(&c); |
|
gnutls_global_deinit(); |
|
|
|
return 0; |
|
} |