summaryrefslogtreecommitdiffstats
path: root/examples/gtlssimpleclient.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--examples/gtlssimpleclient.c720
1 files changed, 720 insertions, 0 deletions
diff --git a/examples/gtlssimpleclient.c b/examples/gtlssimpleclient.c
new file mode 100644
index 0000000..60c0121
--- /dev/null
+++ b/examples/gtlssimpleclient.c
@@ -0,0 +1,720 @@
+/*
+ * ngtcp2
+ *
+ * Copyright (c) 2021-2022 ngtcp2 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.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 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) {
+ struct addrinfo hints = {0};
+ struct addrinfo *res, *rp;
+ int rv;
+ int fd = -1;
+
+ hints.ai_flags = AF_UNSPEC;
+ 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 {
+ 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_connection_close_error last_error;
+
+ ev_io rev;
+ ev_timer timer;
+};
+
+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;
+ /* we could save session data here */
+
+ return 0;
+}
+
+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 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 const gnutls_datum_t alpn = {(uint8_t *)ALPN, sizeof(ALPN) - 1};
+
+static int client_gnutls_init(struct client *c) {
+ int rv = gnutls_certificate_allocate_credentials(&c->cred);
+
+ if (rv == 0)
+ rv = gnutls_certificate_set_x509_system_trust(c->cred);
+ if (rv < 0) {
+ fprintf(stderr, "cred init failed: %d: %s\n", rv, gnutls_strerror(rv));
+ 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;
+ }
+
+ gnutls_alpn_set_protocols(c->session, &alpn, 1, GNUTLS_ALPN_MANDATORY);
+
+ if (!numeric_host(REMOTE_HOST)) {
+ gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, REMOTE_HOST,
+ strlen(REMOTE_HOST));
+ } else {
+ gnutls_server_name_set(c->session, GNUTLS_NAME_DNS, "localhost",
+ strlen("localhost"));
+ }
+
+ return 0;
+}
+
+static void rand_cb(uint8_t *dest, size_t destlen,
+ const ngtcp2_rand_ctx *rand_ctx) {
+ (void)rand_ctx;
+
+ (void)gnutls_rnd(GNUTLS_RND_RANDOM, dest, destlen);
+}
+
+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 (gnutls_rnd(GNUTLS_RND_RANDOM, cid->data, cidlen) != 0) {
+ return NGTCP2_ERR_CALLBACK_FAILURE;
+ }
+
+ cid->datalen = cidlen;
+
+ if (gnutls_rnd(GNUTLS_RND_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) {
+#ifdef MESSAGE
+ struct client *c = user_data;
+ int rv;
+ int64_t stream_id;
+ (void)max_streams;
+
+ 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 *)MESSAGE;
+ c->stream.datalen = sizeof(MESSAGE) - 1;
+
+ return 0;
+#else /* !MESSAGE */
+ (void)conn;
+ (void)max_streams;
+ (void)user_data;
+
+ return 0;
+#endif /* !MESSAGE */
+}
+
+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 = {
+ {
+ (struct sockaddr *)local_addr,
+ local_addrlen,
+ },
+ {
+ (struct sockaddr *)remote_addr,
+ remote_addrlen,
+ },
+ NULL,
+ };
+ ngtcp2_callbacks callbacks = {
+ ngtcp2_crypto_client_initial_cb,
+ NULL, /* recv_client_initial */
+ ngtcp2_crypto_recv_crypto_data_cb,
+ NULL, /* handshake_completed */
+ NULL, /* recv_version_negotiation */
+ ngtcp2_crypto_encrypt_cb,
+ ngtcp2_crypto_decrypt_cb,
+ ngtcp2_crypto_hp_mask_cb,
+ NULL, /* recv_stream_data */
+ NULL, /* acked_stream_data_offset */
+ NULL, /* stream_open */
+ NULL, /* stream_close */
+ NULL, /* recv_stateless_reset */
+ ngtcp2_crypto_recv_retry_cb,
+ extend_max_local_streams_bidi,
+ NULL, /* extend_max_local_streams_uni */
+ rand_cb,
+ get_new_connection_id_cb,
+ NULL, /* remove_connection_id */
+ ngtcp2_crypto_update_key_cb,
+ NULL, /* path_validation */
+ NULL, /* select_preferred_address */
+ NULL, /* stream_reset */
+ NULL, /* extend_max_remote_streams_bidi */
+ NULL, /* extend_max_remote_streams_uni */
+ NULL, /* extend_max_stream_data */
+ NULL, /* dcid_status */
+ NULL, /* handshake_confirmed */
+ NULL, /* recv_new_token */
+ ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+ ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+ NULL, /* recv_datagram */
+ NULL, /* ack_datagram */
+ NULL, /* lost_datagram */
+ ngtcp2_crypto_get_path_challenge_data_cb,
+ NULL, /* stream_stop_sending */
+ ngtcp2_crypto_version_negotiation_cb,
+ NULL, /* recv_rx_key */
+ NULL, /* recv_tx_key */
+ NULL, /* early_data_rejected */
+ };
+ ngtcp2_cid dcid, scid;
+ ngtcp2_settings settings;
+ ngtcp2_transport_params params;
+ int rv;
+
+ dcid.datalen = NGTCP2_MIN_INITIAL_DCIDLEN;
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, dcid.data, dcid.datalen) != 0) {
+ fprintf(stderr, "gnutls_rnd failed\n");
+ return -1;
+ }
+
+ scid.datalen = 8;
+ if (gnutls_rnd(GNUTLS_RND_RANDOM, scid.data, scid.datalen) != 0) {
+ fprintf(stderr, "gnutls_rnd failed\n");
+ 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 = {buf, 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 != 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_connection_close_error_set_transport_error_tls_alert(
+ &c->last_error, ngtcp2_conn_get_tls_alert(c->conn), NULL, 0);
+ } else {
+ ngtcp2_connection_close_error_set_transport_error_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 = {(uint8_t *)data, 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[1280];
+ 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_connection_close_error_set_transport_error_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_is_in_closing_period(c->conn) ||
+ ngtcp2_conn_is_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) {
+ struct sockaddr_storage remote_addr, local_addr;
+ socklen_t remote_addrlen, local_addrlen = sizeof(local_addr);
+
+ memset(c, 0, sizeof(*c));
+
+ ngtcp2_connection_close_error_default(&c->last_error);
+
+ c->fd = create_sock((struct sockaddr *)&remote_addr, &remote_addrlen,
+ REMOTE_HOST, REMOTE_PORT);
+ 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;
+
+ if (client_gnutls_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;
+
+ c->conn_ref.get_conn = get_conn;
+ c->conn_ref.user_data = c;
+
+ 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) {
+ ngtcp2_conn_del(c->conn);
+ gnutls_deinit(c->session);
+ gnutls_certificate_free_credentials(c->cred);
+}
+
+int main(void) {
+ struct client c;
+
+ if (client_init(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (client_write(&c) != 0) {
+ exit(EXIT_FAILURE);
+ }
+
+ ev_run(EV_DEFAULT, 0);
+
+ client_free(&c);
+
+ return 0;
+}