mirror of
https://github.com/libevent/libevent.git
synced 2025-01-09 00:56:20 +08:00
Merge branch 'buffer-read-size'
And after this patch set default evbuffer max read via bufferevent is 16K not 4K. Here is some numbers for the single max read in evbuffer impact: function client() { becat "$@" | pv > /dev/null; } function server() { cat /dev/zero | becat -l "$@"; } Plain bufferevent: - 40K $ server -R $((40<<10)) & client -R $((40<<10)) 700MiB/s - 16K *default now* $ server & client 1.81GiB/s - 4K $ server -R $((4<<10)) & client -R $((4<<10)) 1.05GiB/s With OpenSSL (-S): - 40K *default now* $ server -S -R $((40<<10)) & client -S -R $((40<<10)) 900MiB/s - 16K *default now* $ server -S & client -S 745MiB/s - 4K $ server -S -R $((4<<10)) & client -S -R $((4<<10)) 593MiB/s So as you can see without openssl 16K is faster then 40K/4K, while for openssl 40K is still faster then 16K (I guess that this is due to with openssl SSL_read() more at at time, while with plain we have some allocations splits in evbuffer and maybe due to some buffer in openssl) * buffer-read-size: sample/becat: bufferevent cat, ncat/nc/telnet analog Adjust evbuffer max read for bufferevents Maximum evbuffer read configuration Fix leaks in error path of the bufferevent_init_common_()
This commit is contained in:
commit
1f4f8769c4
@ -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
|
||||
|
41
buffer.c
41
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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -380,6 +380,7 @@ AC_CHECK_FUNCS([ \
|
||||
setrlimit \
|
||||
sigaction \
|
||||
signal \
|
||||
strsignal \
|
||||
splice \
|
||||
strlcpy \
|
||||
strsep \
|
||||
|
@ -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. */
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
623
sample/becat.c
Normal file
623
sample/becat.c
Normal file
@ -0,0 +1,623 @@
|
||||
/**
|
||||
* This is an analog of nc/ncat/telnet that uses libevent's bufferevents
|
||||
*
|
||||
* TODO:
|
||||
* - optional SSL
|
||||
*/
|
||||
|
||||
#include <event2/event.h>
|
||||
#include <event2/listener.h>
|
||||
#include <event2/buffer.h>
|
||||
#include <event2/bufferevent.h>
|
||||
#include <event2/event-config.h>
|
||||
#include "../util-internal.h"
|
||||
|
||||
#include <event2/bufferevent_ssl.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#include "openssl-compat.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <getopt.h>
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#else /* _WIN32 */
|
||||
#include <sys/stat.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#endif /* !_WIN32 */
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
#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 *)"<NULL>", -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;
|
||||
}
|
@ -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 \
|
||||
|
Loading…
x
Reference in New Issue
Block a user