Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to avoid TIME_WAIT after TCP request? #1285

Closed
Paket236 opened this issue Jun 13, 2022 · 1 comment
Closed

How to avoid TIME_WAIT after TCP request? #1285

Paket236 opened this issue Jun 13, 2022 · 1 comment
Labels

Comments

@Paket236
Copy link

I'm facing an issue that I don't know how to solve. I have tried different versions: libevent 2.1.11 and newer. Tested on Windows Server 2008, Windows 7 and Windows 10.

If I send a TCP request to the server, then I will get the TIME_WAIT state on the server. If an attacker sends too many TCP requests, new connections may stop being created. TIME_WAIT also occurs if I execute the following code (for example, if I don't want to respond to HTTP request and just want to free up resources):
evhttp_connection_free(evhttp_request_get_connection(req));

Am I doing something wrong or is it a bug? How can TIME_WAIT be avoided?

2022-06-13_18-03-51

Below is an example to reproduce. http-server.c in samples with minor changes for Windows also reproduces this.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>

#pragma comment(lib, "lib\\libevent\\x64\\event.lib")
#pragma comment(lib, "lib\\libevent\\x64\\event_core.lib")
#pragma comment(lib, "lib\\libevent\\x64\\event_extra.lib")

#pragma comment(lib, "ws2_32.lib")

/* Compatibility for possible missing IPv6 declarations */
#include "event2_other/util-internal.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#include <signal.h>

#ifdef EVENT__HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef EVENT__HAVE_AFUNIX_H
#include <afunix.h>
#endif

#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/http.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>

#include <event2/thread.h>

#ifdef EVENT__HAVE_NETINET_IN_H
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
#  include <arpa/inet.h>
# endif
#endif

char uri_root[512] {};

struct options {
	int port;
	int iocp;
	int verbose;
	int max_body_size;

	int unlink;
	const char *unixsock;
	const char *bind;
};

static void do_term(evutil_socket_t sig, short events, void *arg)
{
	struct event_base *base = (event_base *)arg;
	event_base_loopbreak(base);
	fprintf(stderr, "Got %lld, Terminating\n", sig);
}

static int display_listen_sock(struct evhttp_bound_socket *handle)
{
	struct sockaddr_storage ss;
	evutil_socket_t fd;
	ev_socklen_t socklen = sizeof(ss);
	char addrbuf[128] {};
	void *inaddr;
	const char *addr;
	int got_port = -1;

	fd = evhttp_bound_socket_get_fd(handle);
	memset(&ss, 0, sizeof(ss));
	if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
		perror("getsockname() failed");
		return 1;
	}

	if (ss.ss_family == AF_INET) {
		got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
		inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
	} else if (ss.ss_family == AF_INET6) {
		got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
		inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
	}
	else {
		fprintf(stderr, "Weird address family %d\n",
		    ss.ss_family);
		return 1;
	}

	addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf, sizeof(addrbuf));
	if (addr) {
		printf("Listening on %s:%d\n", addr, got_port);
		evutil_snprintf(uri_root, sizeof(uri_root), "http://%s:%d", addr, got_port);
	} else {
		fprintf(stderr, "evutil_inet_ntop failed\n");
		return 1;
	}

	return 0;
}

static bufferevent* bevcb(struct event_base *base, void *arg) {
	std::cout << "bevcb()" << "\n";
	return NULL;
}

static void connection_close_cb(struct evhttp_connection *evconn, void *arg) {
	std::cout << "connection_close_cb()" << "\n";
}

static void general_request_cb(struct evhttp_request *req, void *arg)
{
	const char *cmdtype;
	struct evbuffer *evb_out;

	switch (evhttp_request_get_command(req)) {
		case EVHTTP_REQ_GET: cmdtype = "GET"; break;
		case EVHTTP_REQ_POST: cmdtype = "POST"; break;
		case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
		case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
		case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
		case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
		case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
		case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
		case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
		default: cmdtype = "unknown"; break;
	}

	printf("Received a %s request\n", cmdtype);

	u_char ch[1] {};
	ch[0] = 'A';
	evb_out = evbuffer_new();
	evbuffer_add(evb_out, ch, sizeof(ch));
	evhttp_send_reply(req, 200, "OK", evb_out);
	if(evb_out) evbuffer_free(evb_out);
}

int main(int argc, char **argv)
{
	struct event_config *cfg = NULL;
	struct event_base *base = NULL;
	struct evhttp *http = NULL;
	struct evhttp_bound_socket *handle = NULL;
	struct evconnlistener *lev = NULL;
	struct event *term = NULL;
	struct options o {};
	o.bind = "127.0.0.1";
	o.port = 80;
	o.max_body_size = 10240;
	o.iocp = 1;
	int ret = 0;

	{	WORD wVersionRequested;
		WSADATA wsaData;
		wVersionRequested = MAKEWORD(2, 2);
		WSAStartup(wVersionRequested, &wsaData);
	}

	setbuf(stdout, NULL);
	setbuf(stderr, NULL);

	// Read env like in regress
	if (o.verbose || getenv("EVENT_DEBUG_LOGGING_ALL"))
		event_enable_debug_logging(EVENT_DBG_ALL);

	cfg = event_config_new();
	if (o.iocp) {
		#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
		evthread_use_windows_threads();
		event_config_set_num_cpus_hint(cfg, 8);
		#endif
		event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP);
	}

	base = event_base_new_with_config(cfg);
	if (!base) {
		fprintf(stderr, "Couldn't create an event_base: exiting\n");
		ret = 1;
	}
	event_config_free(cfg);
	cfg = NULL;

	// Create a new evhttp object to handle requests.
	http = evhttp_new(base);
	if (!http) {
		fprintf(stderr, "couldn't create evhttp. Exiting.\n");
		ret = 1;
	}

	// We want to accept arbitrary requests, so we need to set a "generic"
	// cb. We can also add callbacks for specific paths.
	evhttp_set_gencb(http, general_request_cb, &o);

	if (o.max_body_size)
		evhttp_set_max_body_size(http, o.max_body_size);

	handle = evhttp_bind_socket_with_handle(http, o.bind, o.port);
	if (!handle) {
		fprintf(stderr, "couldn't bind to %s:%d. Exiting.\n", o.bind, o.port);
		ret = 1;
		goto err;
	}

	evhttp_set_bevcb(http, bevcb, NULL);

	if (display_listen_sock(handle)) {
		ret = 1;
		goto err;
	}

	term = evsignal_new(base, SIGINT, do_term, base);
	if (!term)
		goto err;
	if (event_add(term, NULL))
		goto err;

	event_base_dispatch(base);

	WSACleanup();

err:
	if (cfg)
		event_config_free(cfg);
	if (http)
		evhttp_free(http);
	if (term)
		event_free(term);
	if (base)
		event_base_free(base);

	return ret;
}
@azat
Copy link
Member

azat commented Jul 10, 2022

You code is fine does not leaks sockets and TIME_WAIT and a normal TCP state.
For the reference:

@azat azat closed this as completed Jul 10, 2022
@azat azat added the type:q label Jul 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants