From 6995b9a864c16bcb84ea0f7a2cf856143020316b Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 16 Mar 2019 17:28:31 +0300 Subject: [PATCH 1/4] Fix leaks in error path of the bufferevent_init_common_() --- bufferevent.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/bufferevent.c b/bufferevent.c index ef7c2eaf..08c0486c 100644 --- a/bufferevent.c +++ b/bufferevent.c @@ -315,14 +315,12 @@ bufferevent_init_common_(struct bufferevent_private *bufev_private, if (!bufev->input) { if ((bufev->input = evbuffer_new()) == NULL) - return -1; + goto err; } if (!bufev->output) { - if ((bufev->output = evbuffer_new()) == NULL) { - evbuffer_free(bufev->input); - return -1; - } + if ((bufev->output = evbuffer_new()) == NULL) + goto err; } bufev_private->refcnt = 1; @@ -334,7 +332,8 @@ bufferevent_init_common_(struct bufferevent_private *bufev_private, bufev->be_ops = ops; - bufferevent_ratelim_init_(bufev_private); + if (bufferevent_ratelim_init_(bufev_private)) + goto err; /* * Set to EV_WRITE so that using bufferevent_write is going to @@ -345,20 +344,14 @@ bufferevent_init_common_(struct bufferevent_private *bufev_private, #ifndef EVENT__DISABLE_THREAD_SUPPORT if (options & BEV_OPT_THREADSAFE) { - if (bufferevent_enable_locking_(bufev, NULL) < 0) { - /* cleanup */ - evbuffer_free(bufev->input); - evbuffer_free(bufev->output); - bufev->input = NULL; - bufev->output = NULL; - return -1; - } + if (bufferevent_enable_locking_(bufev, NULL) < 0) + goto err; } #endif if ((options & (BEV_OPT_DEFER_CALLBACKS|BEV_OPT_UNLOCK_CALLBACKS)) == BEV_OPT_UNLOCK_CALLBACKS) { event_warnx("UNLOCK_CALLBACKS requires DEFER_CALLBACKS"); - return -1; + goto err; } if (options & BEV_OPT_UNLOCK_CALLBACKS) event_deferred_cb_init_( @@ -379,6 +372,17 @@ bufferevent_init_common_(struct bufferevent_private *bufev_private, evbuffer_set_parent_(bufev->output, bufev); return 0; + +err: + if (bufev->input) { + evbuffer_free(bufev->input); + bufev->input = NULL; + } + if (bufev->output) { + evbuffer_free(bufev->output); + bufev->output = NULL; + } + return -1; } void From 8c2001e92a6615cc7b4efc8e33cff58c28effd30 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 16 Mar 2019 17:09:51 +0300 Subject: [PATCH 2/4] Maximum evbuffer read configuration Before this patch evbuffer always reads 4K at a time, while this is fine most of time you can find an example when this will decrease throughput. So add an API to change default limit: - evbuffer_set_max_read() - evbuffer_get_max_read() And a notice that most of time default is sane. --- buffer.c | 41 +++++++++++++++++++++++++++++------------ evbuffer-internal.h | 2 ++ include/event2/buffer.h | 24 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/buffer.c b/buffer.c index 690154bf..11b1bdae 100644 --- a/buffer.c +++ b/buffer.c @@ -140,6 +140,8 @@ (ptr)->internal_.pos_in_chain = 0; \ } while (0) +#define EVBUFFER_MAX_READ_DEFAULT 4096 + static void evbuffer_chain_align(struct evbuffer_chain *chain); static int evbuffer_chain_should_realign(struct evbuffer_chain *chain, size_t datalen); @@ -370,6 +372,7 @@ evbuffer_new(void) LIST_INIT(&buffer->callbacks); buffer->refcnt = 1; buffer->last_with_datap = &buffer->first; + buffer->max_read = EVBUFFER_MAX_READ_DEFAULT; return (buffer); } @@ -591,6 +594,26 @@ evbuffer_free(struct evbuffer *buffer) evbuffer_decref_and_unlock_(buffer); } +int evbuffer_set_max_read(struct evbuffer *buf, size_t max) +{ + if (max > INT_MAX) { + return -1; + } + + EVBUFFER_LOCK(buf); + buf->max_read = max; + EVBUFFER_UNLOCK(buf); + return 0; +} +size_t evbuffer_get_max_read(struct evbuffer *buf) +{ + size_t result; + EVBUFFER_LOCK(buf); + result = buf->max_read; + EVBUFFER_UNLOCK(buf); + return result; +} + void evbuffer_lock(struct evbuffer *buf) { @@ -607,13 +630,9 @@ size_t evbuffer_get_length(const struct evbuffer *buffer) { size_t result; - EVBUFFER_LOCK(buffer); - - result = (buffer->total_len); - + result = buffer->total_len; EVBUFFER_UNLOCK(buffer); - return result; } @@ -2204,8 +2223,6 @@ evbuffer_expand(struct evbuffer *buf, size_t datlen) #endif #define NUM_READ_IOVEC 4 -#define EVBUFFER_MAX_READ 4096 - /** Helper function to figure out which space to use for reading data into an evbuffer. Internal use only. @@ -2261,18 +2278,18 @@ static int get_n_bytes_readable_on_socket(evutil_socket_t fd) { #if defined(FIONREAD) && defined(_WIN32) - unsigned long lng = EVBUFFER_MAX_READ; + unsigned long lng = EVBUFFER_MAX_READ_DEFAULT; if (ioctlsocket(fd, FIONREAD, &lng) < 0) return -1; /* Can overflow, but mostly harmlessly. XXXX */ return (int)lng; #elif defined(FIONREAD) - int n = EVBUFFER_MAX_READ; + int n = EVBUFFER_MAX_READ_DEFAULT; if (ioctl(fd, FIONREAD, &n) < 0) return -1; return n; #else - return EVBUFFER_MAX_READ; + return EVBUFFER_MAX_READ_DEFAULT; #endif } @@ -2300,8 +2317,8 @@ evbuffer_read(struct evbuffer *buf, evutil_socket_t fd, int howmuch) } n = get_n_bytes_readable_on_socket(fd); - if (n <= 0 || n > EVBUFFER_MAX_READ) - n = EVBUFFER_MAX_READ; + if (n <= 0 || n > (int)buf->max_read) + n = (int)buf->max_read; if (howmuch < 0 || howmuch > n) howmuch = n; diff --git a/evbuffer-internal.h b/evbuffer-internal.h index d09b4f1d..b93601b2 100644 --- a/evbuffer-internal.h +++ b/evbuffer-internal.h @@ -100,6 +100,8 @@ struct evbuffer { /** Total amount of bytes stored in all chains.*/ size_t total_len; + /** Maximum bytes per one read */ + size_t max_read; /** Number of bytes we have added to the buffer since we last tried to * invoke callbacks. */ diff --git a/include/event2/buffer.h b/include/event2/buffer.h index 468588b9..56f620af 100644 --- a/include/event2/buffer.h +++ b/include/event2/buffer.h @@ -158,6 +158,30 @@ struct evbuffer *evbuffer_new(void); EVENT2_EXPORT_SYMBOL void evbuffer_free(struct evbuffer *buf); + +/** + Set maximum read buffer size + + Default is 4096 and it works fine most of time, so before increasing the + default check carefully, since this has some negative effects (like memory + fragmentation and unfair resource distribution, i.e. some events will make + less progress than others). + + @param buf pointer to the evbuffer + @param max buffer size + @return 0 on success, -1 on failure (if @max > INT_MAX). + */ +EVENT2_EXPORT_SYMBOL +int evbuffer_set_max_read(struct evbuffer *buf, size_t max); +/** + Get maximum read buffer size + + @param buf pointer to the evbuffer + @return current maximum buffer read + */ +EVENT2_EXPORT_SYMBOL +size_t evbuffer_get_max_read(struct evbuffer *buf); + /** Enable locking on an evbuffer so that it can safely be used by multiple threads at the same time. From 5357c3d62ab3c3b654a14434ba2e573a155f8d91 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Sat, 16 Mar 2019 17:32:17 +0300 Subject: [PATCH 3/4] Adjust evbuffer max read for bufferevents --- bufferevent_ratelim.c | 7 ++++++- include/event2/bufferevent.h | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bufferevent_ratelim.c b/bufferevent_ratelim.c index 25874968..3b7ae51b 100644 --- a/bufferevent_ratelim.c +++ b/bufferevent_ratelim.c @@ -855,14 +855,16 @@ int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size) { struct bufferevent_private *bevp; + int ret = 0; BEV_LOCK(bev); bevp = BEV_UPCAST(bev); if (size == 0 || size > EV_SSIZE_MAX) bevp->max_single_read = MAX_SINGLE_READ_DEFAULT; else bevp->max_single_read = size; + ret = evbuffer_set_max_read(bev->input, bevp->max_single_read); BEV_UNLOCK(bev); - return 0; + return ret; } int @@ -1085,5 +1087,8 @@ bufferevent_ratelim_init_(struct bufferevent_private *bev) bev->max_single_read = MAX_SINGLE_READ_DEFAULT; bev->max_single_write = MAX_SINGLE_WRITE_DEFAULT; + if (evbuffer_set_max_read(bev->bev.input, bev->max_single_read)) + return -1; + return 0; } diff --git a/include/event2/bufferevent.h b/include/event2/bufferevent.h index dac34dc7..da4d3802 100644 --- a/include/event2/bufferevent.h +++ b/include/event2/bufferevent.h @@ -890,6 +890,8 @@ int bufferevent_remove_from_rate_limit_group(struct bufferevent *bev); Set to 0 for a reasonable default. Return 0 on success and -1 on failure. + + @see evbuffer_set_max_read() */ EVENT2_EXPORT_SYMBOL int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size); From d5b24cc0c80bddacee0bdb1b1758a115043fb1c9 Mon Sep 17 00:00:00 2001 From: Azat Khuzhin Date: Tue, 5 Mar 2019 21:34:31 +0300 Subject: [PATCH 4/4] sample/becat: bufferevent cat, ncat/nc/telnet analog --- CMakeLists.txt | 2 + configure.ac | 1 + event-config.h.cmake | 3 + sample/becat.c | 623 +++++++++++++++++++++++++++++++++++++++++++ sample/include.am | 5 + 5 files changed, 634 insertions(+) create mode 100644 sample/becat.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b7229f29..f1e5240b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,6 +433,7 @@ CHECK_FUNCTION_EXISTS_EX(port_create EVENT__HAVE_PORT_CREATE) CHECK_FUNCTION_EXISTS_EX(sendfile EVENT__HAVE_SENDFILE) CHECK_FUNCTION_EXISTS_EX(sigaction EVENT__HAVE_SIGACTION) CHECK_FUNCTION_EXISTS_EX(signal EVENT__HAVE_SIGNAL) +CHECK_FUNCTION_EXISTS_EX(strsignal EVENT__HAVE_STRSIGNAL) CHECK_FUNCTION_EXISTS_EX(splice EVENT__HAVE_SPLICE) CHECK_FUNCTION_EXISTS_EX(strlcpy EVENT__HAVE_STRLCPY) CHECK_FUNCTION_EXISTS_EX(strsep EVENT__HAVE_STRSEP) @@ -965,6 +966,7 @@ if (NOT EVENT__DISABLE_SAMPLES) sample/hostcheck.c) add_sample_prog(ON le-proxy sample/le-proxy.c) + add_sample_prog(ON becat sample/becat.c ${WIN32_GETOPT}) endif() set(SAMPLES_WOPT diff --git a/configure.ac b/configure.ac index 5f1a739e..b68a0acb 100644 --- a/configure.ac +++ b/configure.ac @@ -380,6 +380,7 @@ AC_CHECK_FUNCS([ \ setrlimit \ sigaction \ signal \ + strsignal \ splice \ strlcpy \ strsep \ diff --git a/event-config.h.cmake b/event-config.h.cmake index ee0df54b..c13128a0 100644 --- a/event-config.h.cmake +++ b/event-config.h.cmake @@ -253,6 +253,9 @@ /* Define to 1 if you have the `signal' function. */ #cmakedefine EVENT__HAVE_SIGNAL 1 +/* Define to 1 if you have the `strsignal' function. */ +#cmakedefine EVENT__HAVE_STRSIGNAL 1 + /* Define to 1 if you have the `splice' function. */ #cmakedefine EVENT__HAVE_SPLICE 1 diff --git a/sample/becat.c b/sample/becat.c new file mode 100644 index 00000000..4c474d6f --- /dev/null +++ b/sample/becat.c @@ -0,0 +1,623 @@ +/** + * This is an analog of nc/ncat/telnet that uses libevent's bufferevents + * + * TODO: + * - optional SSL + */ + +#include +#include +#include +#include +#include +#include "../util-internal.h" + +#include +#include +#include +#include "openssl-compat.h" + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#else /* _WIN32 */ +#include +#include +#include +#include +#endif /* !_WIN32 */ +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#define CHECK_FMT(a,b) __attribute__((format(printf, a, b))) +#else +#define CHECK_FMT(a,b) +#endif + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif + +static int verbose; + +struct addr +{ + int port; + char *address; +}; +struct options +{ + struct addr src; + struct addr dst; + + int max_read; + + struct { + int listen:1; + int keep:1; + int ssl:1; + }; +}; +struct ssl_context +{ + SSL_CTX *ctx; + EVP_PKEY *pkey; + X509 *cert; +}; +struct context +{ + struct options *opts; + struct ssl_context ssl; + + struct bufferevent *in; + struct bufferevent *out; + + FILE *fout; +}; + +static void info(const char *fmt, ...) CHECK_FMT(1,2); +static void error(const char *fmt, ...) CHECK_FMT(1,2); + +static void info(const char *fmt, ...) +{ + va_list ap; + if (!verbose) + return; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); +} +static void error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static void be_free(struct bufferevent **bevp) +{ + evutil_socket_t fd; + struct bufferevent *bev = *bevp; + + if (!bev) + return; + + fd = bufferevent_getfd(bev); + info("Freeing bufferevent with fd=%d\n", fd); + + bufferevent_free(bev); + *bevp = NULL; +} +static struct bufferevent * +be_new(struct context *ctx, struct event_base *base, evutil_socket_t fd) +{ + SSL *ssl = NULL; + struct bufferevent *bev = NULL; + int flags = BEV_OPT_CLOSE_ON_FREE; + enum bufferevent_ssl_state state = BUFFEREVENT_SSL_CONNECTING; + + if (fd != -1) + state = BUFFEREVENT_SSL_ACCEPTING; + + if (ctx->opts->ssl) { + ssl = SSL_new(ctx->ssl.ctx); + if (!ssl) + goto err; + if (SSL_use_certificate(ssl, ctx->ssl.cert) != 1) + goto err; + if (SSL_use_PrivateKey(ssl, ctx->ssl.pkey) != 1) + goto err; + bev = bufferevent_openssl_socket_new(base, fd, ssl, state, flags); + } else { + bev = bufferevent_socket_new(base, fd, flags); + } + if (ctx->opts->max_read != -1) { + if (bufferevent_set_max_single_read(bev, ctx->opts->max_read)) + goto err; + } + return bev; +err: + if (ssl) + SSL_free(ssl); + return NULL; +} + +#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ + (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) +static inline void ssl_init(void) +{ + SSL_library_init(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +} +#else /* OPENSSL_VERSION_NUMBER */ +static inline void ssl_init(void) {} +#endif /* OPENSSL_VERSION_NUMBER */ + +static void ssl_ctx_free(struct ssl_context *ssl) +{ + SSL_CTX_free(ssl->ctx); + EVP_PKEY_free(ssl->pkey); + X509_free(ssl->cert); +} +static int ssl_load_key(struct ssl_context *ssl) +{ + int err = 1; + BIGNUM *bn; + RSA *key; + + ssl->pkey = EVP_PKEY_new(); + + bn = BN_new(); + if (BN_set_word(bn, RSA_F4) != 1) + goto err; + /** Will be freed with ctx.pkey */ + key = RSA_new(); + if (RSA_generate_key_ex(key, 2048, bn, NULL) != 1) + goto err; + if (EVP_PKEY_assign_RSA(ssl->pkey, key) != 1) + goto err; + err = 0; +err: + BN_free(bn); + return err; +} +static int ssl_load_cert(struct ssl_context *ssl) +{ + X509_NAME *name; + + ssl->cert = X509_new(); + + ASN1_INTEGER_set(X509_get_serialNumber(ssl->cert), 1); + + X509_gmtime_adj(X509_get_notBefore(ssl->cert), 0); + /** 1 year lifetime */ + X509_gmtime_adj(X509_get_notAfter(ssl->cert), + (long)time(NULL) + 365 * 86400); + + X509_set_pubkey(ssl->cert, ssl->pkey); + + name = X509_get_subject_name(ssl->cert); + X509_NAME_add_entry_by_txt( + name, "C", MBSTRING_ASC, (unsigned char *)"--", -1, -1, 0); + X509_NAME_add_entry_by_txt( + name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0); + X509_NAME_add_entry_by_txt( + name, "CN", MBSTRING_ASC, (unsigned char *)"*", -1, -1, 0); + X509_set_issuer_name(ssl->cert, name); + + X509_sign(ssl->cert, ssl->pkey, EVP_sha1()); + + return 0; +} +static int ssl_ctx_init(struct ssl_context *ssl) +{ + const SSL_METHOD *method; + + ssl_init(); + + method = TLS_method(); + if (!method) + goto err; + ssl->ctx = SSL_CTX_new(method); + + SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS); + SSL_CTX_set_session_cache_mode(ssl->ctx, SSL_SESS_CACHE_OFF); + SSL_CTX_set_cipher_list(ssl->ctx, + "RC4:AES128-SHA:AES:CAMELLIA128-SHA:!ADH:!aNULL:!DH:!EDH:!eNULL:!LOW:!SSLv2:!EXP:!NULL"); + SSL_CTX_set_options(ssl->ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_COMPRESSION); + + if (ssl_load_key(ssl)) + goto err; + if (ssl_load_cert(ssl)) + goto err; + + return 0; + +err: + ssl_ctx_free(ssl); + return 1; +} + +static void print_usage(FILE *out, const char *name) +{ + fprintf(out, "Syntax: %s [ OPTS ] [hostname] [port]\n", name); + fprintf(out, + "\n" + "ncat like utility\n" + "\n" + " -p Specify source port to use\n" + " -s Specify source address to use (*does* affect -l, unlike ncat)\n" + " -l Bind and listen for incoming connections\n" + " -k Accept multiple connections in listen mode\n" + " -S Connect or listen with SSL\n" + "\n" + " -v Increase verbosity\n" + " -h Print usage\n" + ); +} +static struct options parse_opts(int argc, char **argv) +{ + struct options o; + int opt; + + memset(&o, 0, sizeof(o)); + o.src.port = o.dst.port = 10024; + o.max_read = -1; + + while ((opt = getopt(argc, argv, "p:s:R:" "lkSvh")) != -1) { + switch (opt) { + case 'p': o.src.port = atoi(optarg); break; + case 's': o.src.address = strdup("127.1"); break; + case 'R': o.max_read = atoi(optarg); break; + + case 'l': o.listen = 1; break; + case 'k': o.keep = 1; break; + case 'S': o.ssl = 1; break; + + /** + * TODO: implement other bits: + * - filters + * - pair + * - watermarks + * - ratelimits + */ + + case 'v': ++verbose; break; + case 'h': + print_usage(stdout, argv[0]); + exit(EXIT_SUCCESS); + default: + fprintf(stderr, "Unknown option -%c\n", opt); break; + exit(EXIT_FAILURE); + } + } + + if ((argc-optind) > 1) { + o.dst.address = strdup(argv[optind]); + ++optind; + } + if ((argc-optind) > 1) { + o.dst.port = atoi(optarg); + ++optind; + } + if ((argc-optind) > 1) { + print_usage(stderr, argv[0]); + exit(1); + } + + if (!o.src.address) + o.src.address = strdup("127.1"); + if (!o.dst.address) + o.dst.address = strdup("127.1"); + + return o; +} + +#ifndef EVENT__HAVE_STRSIGNAL +static inline const char* strsignal(evutil_socket_t sig) { return "Signal"; } +#endif +static void do_term(evutil_socket_t sig, short events, void *arg) +{ + struct event_base *base = arg; + event_base_loopexit(base, NULL); + fprintf(stderr, "%s(" EV_SOCK_FMT "), Terminating\n", + strsignal(sig), EV_SOCK_ARG(sig)); +} + +static ev_socklen_t +make_address(struct sockaddr_storage *ss, const char *address, ev_uint16_t port) +{ + struct evutil_addrinfo *ai = NULL; + struct evutil_addrinfo hints; + char strport[NI_MAXSERV]; + int ai_result; + ev_socklen_t ret = 0; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = EVUTIL_AI_PASSIVE|EVUTIL_AI_ADDRCONFIG; + evutil_snprintf(strport, sizeof(strport), "%d", port); + if ((ai_result = evutil_getaddrinfo(address, strport, &hints, &ai)) != 0) { + return 0; + } + if (!ai) + return 0; + if (ai->ai_addrlen > sizeof(*ss)) { + evutil_freeaddrinfo(ai); + return 0; + } + + memcpy(ss, ai->ai_addr, ai->ai_addrlen); + /** Hm.. I do not like this cast. */ + ret = (ev_socklen_t)ai->ai_addrlen; + evutil_freeaddrinfo(ai); + return ret; +} + +static void be_ssl_errors(struct bufferevent *bev) +{ + int err; + while ((err = bufferevent_get_openssl_error(bev))) { + const char *msg = ERR_reason_error_string(err); + const char *lib = ERR_lib_error_string(err); + const char *func = ERR_func_error_string(err); + error("ssl/err=%d/%s in %s %s\n", err, msg, lib, func); + } +} +static int event_cb_(struct bufferevent *bev, short what, int ssl, int stop) +{ + struct event_base *base = bufferevent_get_base(bev); + evutil_socket_t fd = bufferevent_getfd(bev); + + if (what & BEV_EVENT_CONNECTED) { + info("Connected\n"); + return 0; + } + if (ssl && what & BEV_EVENT_ERROR) { + be_ssl_errors(bev); + } + + if (stop) + event_base_loopexit(base, NULL); + + error("Got 0x%x event on fd=%d. Terminating connection\n", what, fd); + be_free(&bev); + return 1; +} + +static void read_cb(struct bufferevent *bev, void *arg) +{ + struct context *ctx = arg; + struct evbuffer *in = bufferevent_get_input(bev); + evbuffer_write(in, fileno(ctx->fout)); +} +static void write_cb(struct bufferevent *bev, void *arg) +{ + struct context *ctx = arg; + bufferevent_write_buffer(bev, bufferevent_get_input(ctx->in)); +} +static void server_event_cb(struct bufferevent *bev, short what, void *arg) +{ + struct context *ctx = arg; + EVUTIL_ASSERT(bev == ctx->out); + if (!event_cb_(bev, what, ctx->opts->ssl, !ctx->opts->keep)) + return; + ctx->out = NULL; +} + +static void +accept_cb(struct evconnlistener *listener, evutil_socket_t fd, + struct sockaddr *sa, int socklen, void *arg) +{ + char buffer[128]; + struct context *ctx = arg; + struct bufferevent *bev = NULL; + struct event_base *base = evconnlistener_get_base(listener); + + if (!ctx->opts->keep) + evconnlistener_disable(listener); + + info("Accepting %s (fd=%d)\n", + evutil_format_sockaddr_port_(sa, buffer, sizeof(buffer)-1), fd); + + bev = be_new(ctx, base, fd); + if (!bev) { + error("Cannot make bufferevent for fd=%d\n", fd); + goto err; + } + + bufferevent_setcb(bev, read_cb, write_cb, server_event_cb, ctx); + bufferevent_enable(bev, EV_READ|EV_WRITE); + + /** TODO: support multiple bevs */ + EVUTIL_ASSERT(!ctx->out); + ctx->out = bev; + + if (bufferevent_enable(ctx->in, EV_READ)) { + error("Cannot monitor EV_READ on input\n"); + goto err; + } + + return; + +err: + be_free(&bev); +} + +static void client_event_cb(struct bufferevent *bev, short what, void *arg) +{ + struct context *ctx = arg; + if (!event_cb_(bev, what, ctx->opts->ssl, 1)) + return; + ctx->out = NULL; +} + +static void in_event_cb(struct bufferevent *bev, short what, void *arg) +{ + struct context *ctx = arg; + if (!event_cb_(bev, what, ctx->opts->ssl, 1)) + return; + + ctx->in = NULL; + be_free(&ctx->out); +} + +static void trigger_bev_write_cb(struct bufferevent *bev, void *arg) +{ + struct context *ctx = arg; + if (!ctx->out) + return; + bufferevent_trigger(ctx->out, EV_WRITE, 0); +} + +int main(int argc, char **argv) +{ + struct event_base *base = NULL; + struct event *term = NULL; + struct evconnlistener *listener = NULL; + struct bufferevent *bev = NULL; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + ev_socklen_t ss_len; + char buffer[128]; + + struct context ctx; + struct options o = parse_opts(argc, argv); + int err = EXIT_SUCCESS; + + memset(&ctx, 0, sizeof(ctx)); + ctx.opts = &o; + + base = event_base_new(); + if (!base) + goto err; + + term = evsignal_new(base, SIGINT, do_term, base); + if (!term) + goto err; + if (event_add(term, NULL)) + goto err; + +#ifdef _WIN32 + { + WORD wVersionRequested; + WSADATA wsaData; + wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); + } +#else + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) + goto err; +#endif + + if (o.ssl && ssl_ctx_init(&ctx.ssl)) + goto err; + + if (o.listen) { + int flags = 0; + flags |= LEV_OPT_CLOSE_ON_FREE; + flags |= LEV_OPT_CLOSE_ON_EXEC; + flags |= LEV_OPT_REUSEABLE; + + ss_len = make_address(&ss, o.src.address, o.src.port); + if (!ss_len) { + error("Cannot make address from %s:%d\n", + o.src.address, o.src.port); + goto err; + } + info("Listening on %s\n", + evutil_format_sockaddr_port_(sa, buffer, sizeof(buffer)-1)); + listener = evconnlistener_new_bind(base, accept_cb, &ctx, flags, -1, sa, ss_len); + if (!listener) { + error("Cannot listen\n"); + goto err; + } + } else { + ss_len = make_address(&ss, o.dst.address, o.dst.port); + if (!ss_len) { + error("Cannot make address from %s:%d\n", + o.src.address, o.src.port); + goto err; + } + info("Connecting to %s\n", + evutil_format_sockaddr_port_(sa, buffer, sizeof(buffer)-1)); + + bev = be_new(&ctx, base, -1); + if (!bev) { + error("Cannot make bufferevent\n"); + goto err; + } + + bufferevent_setcb(bev, read_cb, write_cb, client_event_cb, &ctx); + if (bufferevent_enable(bev, EV_READ|EV_WRITE)) { + error("Cannot monitor EV_READ|EV_WRITE for client\n"); + goto err; + } + + if (bufferevent_socket_connect(bev, sa, ss_len)) { + info("Connection failed\n"); + goto err; + } + } + + ctx.out = bev; + ctx.fout = stdout; + + ctx.in = bufferevent_socket_new(base, STDIN_FILENO, 0); + if (o.max_read != -1) { + if (bufferevent_set_max_single_read(ctx.in, o.max_read)) + goto err; + } + if (!ctx.in) { + error("Cannot create input bufferevent\n"); + goto err; + } + bufferevent_setcb(ctx.in, trigger_bev_write_cb, NULL, in_event_cb, &ctx); + if (ctx.out && bufferevent_enable(ctx.in, EV_READ)) { + error("Cannot monitor EV_READ on input\n"); + goto err; + } + bufferevent_disable(ctx.in, EV_WRITE); + + if (!event_base_dispatch(base)) + goto out; + +err: + if (!err) + err = EXIT_FAILURE; + +out: + if (term) + event_free(term); + be_free(&ctx.in); + be_free(&ctx.out); + if (listener) + evconnlistener_free(listener); + if (base) + event_base_free(base); + + free(o.src.address); + free(o.dst.address); + + ssl_ctx_free(&ctx.ssl); + +#ifdef _WIN32 + WSACleanup(); +#endif + + return err; +} diff --git a/sample/include.am b/sample/include.am index cc003b78..266274d0 100644 --- a/sample/include.am +++ b/sample/include.am @@ -19,6 +19,11 @@ sample_le_proxy_SOURCES = sample/le-proxy.c sample_le_proxy_LDADD = libevent.la libevent_openssl.la $(OPENSSL_LIBS) $(OPENSSL_LIBADD) sample_le_proxy_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_INCS) +SAMPLES += sample/becat +sample_becat_SOURCES = sample/becat.c +sample_becat_LDADD = libevent.la libevent_openssl.la $(OPENSSL_LIBS) $(OPENSSL_LIBADD) +sample_becat_CPPFLAGS = $(AM_CPPFLAGS) $(OPENSSL_INCS) + SAMPLES += sample/https-client sample_https_client_SOURCES = \ sample/https-client.c \