Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created March 1, 2026 10:55
Show Gist options
  • Select an option

  • Save masakielastic/11d021169550613fefd1d603f19ab000 to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/11d021169550613fefd1d603f19ab000 to your computer and use it in GitHub Desktop.
ngtcp2 と GunTLS でクライアント

ngtcp2 と GunTLS でクライアント

Debian 12 上で libngtcp2-devlibngtcp2-crypto-gnutls-dev を使って ビルドできるように調整した client.c のサンプルです。

Requirements

  • Debian 12
  • libngtcp2-dev
  • libngtcp2-crypto-gnutls-dev
  • libgnutls28-dev
  • libev-dev

インストール例:

sudo apt install libngtcp2-dev libngtcp2-crypto-gnutls-dev libgnutls28-dev libev-dev

Build

cc -Wall -Wextra -std=c11 client.c \
  $(pkg-config --cflags --libs libngtcp2 libngtcp2_crypto_gnutls gnutls) \
  -lev -o client

Usage

./client [--host HOST] [--port PORT] [--sni NAME] [--alpn ALPN]
         [--ipv4]
         [--message TEXT | --no-message] [--exit-on-handshake]

主なオプション:

  • --host: 接続先ホスト名または IP アドレス
  • --port: 接続先 UDP ポート
  • --sni: TLS SNI に使うホスト名
  • --alpn: ALPN。HTTP/3 なら通常は h3
  • --ipv4: 名前解決を IPv4 に固定
  • --message: 接続後に送る文字列
  • --no-message: 送信しない
  • --exit-on-handshake: QUIC Handshake 完了時点で終了

Examples

Local gtlsserver

サーバ起動:

/usr/sbin/gtlsserver 127.0.0.1 8443 /tmp/nghttp3-localhost.key /tmp/nghttp3-localhost.crt

クライアント実行:

NGTCP2_CA_FILE=/tmp/nghttp3-localhost.crt \
./client --host localhost --port 8443 --ipv4 --sni localhost --alpn h3 \
  --no-message --exit-on-handshake

localhost::1 に解決される環境では --ipv4 を付けてください。

Google への Handshake 確認

この環境では IPv6 経路で QUIC が不安定だったため、IPv4 を明示して確認しました。

timeout 10s ./client \
  --host 142.250.192.164 --port 443 \
  --sni www.google.com --alpn h3 \
  --no-message --exit-on-handshake

Notes

  • 接続先 UDP ポートに待受がない場合、recvmsg: Connection refused で終了します。
  • 数値アドレスを指定した場合でも、必要なら --sni で証明書名を別指定してください。
  • CA 証明書は NGTCP2_CA_FILE / NGTCP2_CA_DIR を優先し、未指定なら SSL_CERT_FILE / SSL_CERT_DIR、最後に system trust を使います。
#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(&params);
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, &params, 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment