[tor-commits] [obfsproxy/master] Add a whole bunch more logging and test framework tracking. Properly distinguish BEV_EVENT_EOF from BEV_EVENT_ERROR in error_cb (flush_error_cb and pending_socks_cb are still sloppy).
nickm at torproject.org
nickm at torproject.org
Fri Sep 9 17:08:57 UTC 2011
commit dfdc4fb57b2633359d41a993d78bc3ae8f7b92fc
Author: Zack Weinberg <zackw at panix.com>
Date: Wed Jul 27 17:06:41 2011 -0700
Add a whole bunch more logging and test framework tracking. Properly distinguish BEV_EVENT_EOF from BEV_EVENT_ERROR in error_cb (flush_error_cb and pending_socks_cb are still sloppy).
---
src/main.c | 1 +
src/network.c | 91 +++++++++++++++++++------
src/network.h | 2 +
src/test/tester.py.in | 183 ++++++++++++++++++++++++++++--------------------
src/util.c | 41 +++++++++++
src/util.h | 4 +
6 files changed, 226 insertions(+), 96 deletions(-)
diff --git a/src/main.c b/src/main.c
index f6280c2..1459af3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -91,6 +91,7 @@ handle_signal_cb(evutil_socket_t fd, short what, void *arg)
void
finish_shutdown(void)
{
+ log_debug("Finishing shutdown.");
event_base_loopexit(the_event_base, NULL);
}
diff --git a/src/network.c b/src/network.c
index f05c5b8..7831a4e 100644
--- a/src/network.c
+++ b/src/network.c
@@ -92,6 +92,8 @@ static void pending_socks_cb(struct bufferevent *bev, short what, void *arg);
void
start_shutdown(int barbaric)
{
+ log_debug("Beginning %s shutdown.", barbaric ? "barbaric" : "normal");
+
if (!shutting_down)
shutting_down=1;
@@ -115,6 +117,7 @@ close_all_connections(void)
{
if (!connections)
return;
+ log_debug("Closing all connections.");
SMARTLIST_FOREACH(connections, conn_t *, conn,
{ conn_free(conn); });
smartlist_free(connections);
@@ -143,6 +146,8 @@ create_listener(struct event_base *base, protocol_params_t *params)
default: obfs_abort();
}
+ lsn->address = printable_address(params->listen_addr->ai_addr,
+ params->listen_addr->ai_addrlen);
lsn->proto_params = params;
lsn->listener =
evconnlistener_new_bind(base, callback, lsn, flags, -1,
@@ -156,6 +161,9 @@ create_listener(struct event_base *base, protocol_params_t *params)
return 0;
}
+ log_debug("Now listening on %s in mode %d, protocol %s.",
+ lsn->address, params->mode, params->vtable->name);
+
/* If we don't have a listener list, create one now. */
if (!listeners)
listeners = smartlist_create();
@@ -170,6 +178,8 @@ create_listener(struct event_base *base, protocol_params_t *params)
static void
listener_free(listener_t *lsn)
{
+ if (lsn->address)
+ free(lsn->address);
if (lsn->listener)
evconnlistener_free(lsn->listener);
if (lsn->proto_params)
@@ -206,7 +216,9 @@ simple_client_listener_cb(struct evconnlistener *evcl,
struct event_base *base;
conn_t *conn = xzalloc(sizeof(conn_t));
- log_debug("%s: connection attempt.", __func__);
+ conn->peername = printable_address(sourceaddr, socklen);
+ log_debug("%s: connection to %s from %s", __func__,
+ lsn->address, conn->peername);
conn->mode = lsn->proto_params->mode;
obfs_assert(conn->mode == LSN_SIMPLE_CLIENT);
@@ -280,7 +292,9 @@ socks_client_listener_cb(struct evconnlistener *evcl,
struct event_base *base;
conn_t *conn = xzalloc(sizeof(conn_t));
- log_debug("%s: connection attempt.", __func__);
+ conn->peername = printable_address(sourceaddr, socklen);
+ log_debug("%s: connection to %s from %s", __func__,
+ lsn->address, conn->peername);
conn->mode = lsn->proto_params->mode;
obfs_assert(conn->mode == LSN_SOCKS_CLIENT);
@@ -336,7 +350,9 @@ simple_server_listener_cb(struct evconnlistener *evcl,
struct event_base *base;
conn_t *conn = xzalloc(sizeof(conn_t));
- log_debug("%s: connection attempt.", __func__);
+ conn->peername = printable_address(sourceaddr, socklen);
+ log_debug("%s: connection to %s from %s", __func__,
+ lsn->address, conn->peername);
conn->mode = lsn->proto_params->mode;
obfs_assert(conn->mode == LSN_SIMPLE_SERVER);
@@ -403,6 +419,8 @@ simple_server_listener_cb(struct evconnlistener *evcl,
static void
conn_free(conn_t *conn)
{
+ if (conn->peername)
+ free(conn->peername);
if (conn->proto)
proto_destroy(conn->proto);
if (conn->socks_state)
@@ -423,10 +441,11 @@ static void
close_conn(conn_t *conn)
{
obfs_assert(connections);
+ log_debug("Closing connection from %s; %d remaining",
+ conn->peername, smartlist_len(connections) - 1);
+
smartlist_remove(connections, conn);
conn_free(conn);
- log_debug("Connection destroyed. "
- "We currently have %d connections!", smartlist_len(connections));
/* If this was the last connection AND we are shutting down,
finish shutdown. */
@@ -447,6 +466,7 @@ static void
close_conn_on_flush(struct bufferevent *bev, void *arg)
{
conn_t *conn = arg;
+ log_debug("%s for %s", __func__, conn->peername);
if (evbuffer_get_length(bufferevent_get_output(bev)) == 0)
close_conn(conn);
@@ -460,6 +480,7 @@ socks_read_cb(struct bufferevent *bev, void *arg)
{
conn_t *conn = arg;
enum socks_ret socks_ret;
+ log_debug("%s for %s", __func__, conn->peername);
/* socks only makes sense on the upstream side */
obfs_assert(bev == conn->upstream);
@@ -534,9 +555,9 @@ static void
upstream_read_cb(struct bufferevent *bev, void *arg)
{
conn_t *conn = arg;
+ log_debug("%s for %s", __func__, conn->peername);
obfs_assert(bev == conn->upstream);
- log_debug("Got data on upstream side");
if (proto_send(conn->proto,
bufferevent_get_input(conn->upstream),
bufferevent_get_output(conn->downstream)) < 0)
@@ -553,9 +574,9 @@ downstream_read_cb(struct bufferevent *bev, void *arg)
{
conn_t *conn = arg;
enum recv_ret r;
+ log_debug("%s for %s", __func__, conn->peername);
obfs_assert(bev == conn->downstream);
- log_debug("Got data on downstream side");
r = proto_recv(conn->proto,
bufferevent_get_input(conn->downstream),
bufferevent_get_output(conn->upstream));
@@ -576,12 +597,12 @@ static void
error_or_eof(conn_t *conn, struct bufferevent *bev_err)
{
struct bufferevent *bev_flush;
+ log_debug("%s for %s", __func__, conn->peername);
if (bev_err == conn->upstream) bev_flush = conn->downstream;
else if (bev_err == conn->downstream) bev_flush = conn->upstream;
else obfs_abort();
- log_debug("error_or_eof");
if (conn->flushing || !conn->is_open ||
evbuffer_get_length(bufferevent_get_output(bev_flush)) == 0) {
close_conn(conn);
@@ -607,17 +628,35 @@ error_or_eof(conn_t *conn, struct bufferevent *bev_err)
static void
error_cb(struct bufferevent *bev, short what, void *arg)
{
+ conn_t *conn = arg;
+ int errcode = EVUTIL_SOCKET_ERROR();
+ log_debug("%s for %s: what=%x err=%d", __func__, conn->peername,
+ what, errcode);
+
/* It should be impossible to get here with BEV_EVENT_CONNECTED. */
obfs_assert(what & (BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT));
obfs_assert(!(what & BEV_EVENT_CONNECTED));
- /* If we get EAGAIN or EINPROGRESS here, something has gone horribly
- wrong. */
- obfs_assert(EVUTIL_SOCKET_ERROR() != EAGAIN &&
- EVUTIL_SOCKET_ERROR() != EINPROGRESS);
-
- log_warn("Got error: %s",
- evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
+ if (what & BEV_EVENT_ERROR) {
+ /* If we get EAGAIN, EINTR, or EINPROGRESS here, something has
+ gone horribly wrong. */
+ obfs_assert(errcode != EAGAIN && errcode != EINTR &&
+ errcode != EINPROGRESS);
+
+ log_warn("Error on %s side of connection from %s: %s",
+ bev == conn->upstream ? "upstream" : "downstream",
+ conn->peername,
+ evutil_socket_error_to_string(errcode));
+ } else if (what & BEV_EVENT_EOF) {
+ log_info("EOF on %s side of connection from %s",
+ bev == conn->upstream ? "upstream" : "downstream",
+ conn->peername);
+ } else {
+ obfs_assert(what & BEV_EVENT_TIMEOUT);
+ log_info("Timeout on %s side of connection from %s",
+ bev == conn->upstream ? "upstream" : "downstream",
+ conn->peername);
+ }
error_or_eof(arg, bev);
}
@@ -629,6 +668,9 @@ static void
flush_error_cb(struct bufferevent *bev, short what, void *arg)
{
conn_t *conn = arg;
+ int errcode = EVUTIL_SOCKET_ERROR();
+ log_debug("%s for %s: what=%x err=%d", __func__, conn->peername,
+ what, errcode);
/* It should be impossible to get here with BEV_EVENT_CONNECTED. */
obfs_assert(what & (BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT));
@@ -636,8 +678,10 @@ flush_error_cb(struct bufferevent *bev, short what, void *arg)
obfs_assert(conn->flushing);
- log_warn("Error during flush: %s",
- evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
+ log_warn("Error during flush of %s side of connection from %s: %s",
+ bev == conn->upstream ? "upstream" : "downstream",
+ conn->peername,
+ evutil_socket_error_to_string(errcode));
close_conn(conn);
return;
}
@@ -653,6 +697,8 @@ pending_conn_cb(struct bufferevent *bev, short what, void *arg)
{
conn_t *conn = arg;
struct bufferevent *other;
+ log_debug("%s for %s", __func__, conn->peername);
+
if (bev == conn->upstream) other = conn->downstream;
else if (bev == conn->downstream) other = conn->upstream;
else obfs_abort();
@@ -663,13 +709,15 @@ pending_conn_cb(struct bufferevent *bev, short what, void *arg)
obfs_assert(!conn->flushing);
conn->is_open = 1;
- log_debug("Connection successful");
- bufferevent_enable(other, EV_READ|EV_WRITE);
+ log_debug("Successful %s connection for %s",
+ bev == conn->upstream ? "upstream" : "downstream",
+ conn->peername);
/* XXX Dirty access to bufferevent guts. There appears to be no
official API to retrieve the callback functions and/or change
just one callback while leaving the others intact. */
bufferevent_setcb(bev, bev->readcb, bev->writecb, error_cb, conn);
+ bufferevent_enable(other, EV_READ|EV_WRITE);
return;
}
@@ -686,13 +734,16 @@ static void
pending_socks_cb(struct bufferevent *bev, short what, void *arg)
{
conn_t *conn = arg;
+ log_debug("%s for %s", __func__, conn->peername);
obfs_assert(bev == conn->downstream);
obfs_assert(conn->socks_state);
/* If we got an error while in the ST_HAVE_ADDR state, chances are
that we failed connecting to the host requested by the CONNECT
call. This means that we should send a negative SOCKS reply back
- to the client and terminate the connection. */
+ to the client and terminate the connection.
+ XXX properly distinguish BEV_EVENT_EOF from BEV_EVENT_ERROR;
+ errno isn't meaningful in that case... */
if ((what & (BEV_EVENT_EOF|BEV_EVENT_ERROR|BEV_EVENT_TIMEOUT))) {
int err = EVUTIL_SOCKET_ERROR();
log_warn("Connection error: %s",
diff --git a/src/network.h b/src/network.h
index c5ef69e..4ae96fc 100644
--- a/src/network.h
+++ b/src/network.h
@@ -14,11 +14,13 @@ void start_shutdown(int barbaric);
#ifdef NETWORK_PRIVATE
typedef struct listener_t {
+ char *address;
protocol_params_t *proto_params;
struct evconnlistener *listener;
} listener_t;
typedef struct conn_t {
+ char *peername;
protocol_t *proto;
socks_state_t *socks_state;
struct bufferevent *upstream;
diff --git a/src/test/tester.py.in b/src/test/tester.py.in
index 104026c..d9040fe 100644
--- a/src/test/tester.py.in
+++ b/src/test/tester.py.in
@@ -7,16 +7,86 @@
# You need to be able to make connections to arbitrary high-numbered
# TCP ports on the loopback interface.
+import difflib
import errno
import multiprocessing
import Queue
+import re
import signal
import socket
import struct
import subprocess
import time
+import traceback
import unittest
+# Helper: generate unified-format diffs between two named strings.
+# Pythonic escaped-string syntax is used for unprintable characters.
+
+def diff(label, expected, received):
+ if expected == received:
+ return ""
+ else:
+ return (label + "\n"
+ + "\n".join(s.encode("string_escape")
+ for s in
+ difflib.unified_diff(expected.split("\n"),
+ received.split("\n"),
+ "expected", "received",
+ lineterm=""))
+ + "\n")
+
+# Helper: Run obfsproxy instances and confirm that they have
+# completed without any errors.
+
+class Obfsproxy(subprocess.Popen):
+ def __init__(self, *args, **kwargs):
+ argv = ["./obfsproxy", "--log-min-severity=debug"]
+ if len(args) == 1 and (isinstance(args[0], list) or
+ isinstance(args[0], tuple)):
+ argv.extend(args[0])
+ else:
+ argv.extend(args)
+
+ subprocess.Popen.__init__(self, argv,
+ stdin=open("/dev/null", "r"),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ **kwargs)
+
+ severe_error_re = re.compile(r"\[(?:warn|err(?:or)?)\]")
+
+ def check_completion(self, label):
+ if self.poll() is None:
+ self.send_signal(signal.SIGINT)
+
+ (out, err) = self.communicate()
+
+ report = ""
+ def indent(s):
+ return "| " + "\n| ".join(s.strip().split("\n"))
+
+ # exit status should be zero
+ if self.returncode > 0:
+ report += label + " exit code: %d\n" % self.returncode
+ elif self.returncode < 0:
+ report += label + " killed: signal %d\n" % -self.returncode
+
+ # there should be nothing on stdout
+ if out != "":
+ report += label + " stdout:\n%s\n" % indent(out)
+
+ # there will be debugging messages on stderr, but there should be
+ # no [warn], [err], or [error] messages.
+ if self.severe_error_re.search(err):
+ report += label + " stderr:\n%s\n" % indent(err)
+
+ return report
+
+ def stop(self):
+ if self.poll() is None:
+ self.terminate()
+
# Helper: Repeatedly try to connect to the specified server socket
# until either it succeeds or one full second has elapsed. (Surely
# there is a better way to do this?)
@@ -89,32 +159,14 @@ EXIT_PORT = 5001
class DirectTest(object):
def setUp(self):
self.output_reader = ReadWorker(("127.0.0.1", EXIT_PORT))
- self.obfs = subprocess.Popen(
- self.obfs_args,
- stdin=open("/dev/null", "r"),
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
+ self.obfs = Obfsproxy(self.obfs_args)
self.input_chan = connect_with_retry(("127.0.0.1", ENTRY_PORT))
self.input_chan.settimeout(1.0)
def tearDown(self):
- if self.obfs.returncode is None:
- self.obfs.terminate()
+ self.obfs.stop()
self.output_reader.stop()
-
- def checkSubprocesses(self):
- if self.obfs.poll() is None:
- self.obfs.send_signal(signal.SIGINT)
-
- (out, err) = self.obfs.communicate()
-
- if (out != "" or err != "" or self.obfs.returncode != 0):
- self.fail("obfsproxy process failure:\n"
- "\treturn code: %d\n"
- "\tstdout: %s\n"
- "\tstderr: %s\n"
- % (self.obfs.returncode, out, err))
+ self.input_chan.close()
def test_direct_transfer(self):
# Open a server and a simple client (in the same process) and
@@ -127,8 +179,11 @@ class DirectTest(object):
except Queue.Empty:
output = ""
- self.checkSubprocesses()
- self.assertEqual(output, TEST_FILE)
+ report = self.obfs.check_completion("obfsproxy")
+ report += diff("errors in transfer:", TEST_FILE, output)
+
+ if report != "":
+ self.fail("\n" + report)
# Same as above, but we use a socks client instead of a simple client,
# and the server's a separate process.
@@ -136,47 +191,14 @@ class DirectTest(object):
class SocksTest(object):
def setUp(self):
self.output_reader = ReadWorker(("127.0.0.1", EXIT_PORT))
- self.obfs_server = subprocess.Popen(
- self.server_args,
- stdin=open("/dev/null", "r"),
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- self.obfs_client = subprocess.Popen(
- self.client_args,
- stdin=open("/dev/null", "r"),
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ self.obfs_server = Obfsproxy(self.server_args)
+ self.obfs_client = Obfsproxy(self.client_args)
def tearDown(self):
- if self.obfs_server.returncode is None:
- self.obfs_server.terminate()
- if self.obfs_client.returncode is None:
- self.obfs_client.terminate()
+ self.obfs_server.stop()
+ self.obfs_client.stop()
self.output_reader.stop()
- def checkSubprocesses(self):
- if self.obfs_server.poll() is None:
- self.obfs_server.send_signal(signal.SIGINT)
- if self.obfs_client.poll() is None:
- self.obfs_client.send_signal(signal.SIGINT)
-
- (sout, serr) = self.obfs_server.communicate()
- (cout, cerr) = self.obfs_client.communicate()
-
- if (sout != "" or serr != "" or cout != "" or cerr != ""
- or self.obfs_server.returncode != 0
- or self.obfs_client.returncode != 0):
- self.fail("obfsproxy process failures:\n"
- "\tclient return code: %d\n"
- "\tserver return code: %d\n"
- "\tclient stdout: %s\n"
- "\tclient stderr: %s\n"
- "\tserver stdout: %s\n"
- "\tserver stderr: %s\n"
- % (self.obfs_client.returncode,
- self.obfs_server.returncode,
- cout, cerr, sout, serr))
-
# 'sequence' is a sequence of SOCKS[45] protocol messages
# which we will send or receive. Sends alternate with
# receives. Each entry may be a string, which is sent or
@@ -187,7 +209,7 @@ class SocksTest(object):
# the SOCKS sequence without the server having dropped the
# connection, we transmit the test file and expect to get it
# back from the far end.
- def socksTest(self, sequence):
+ def socksTestInner(self, sequence):
sending = True
good = True
input_chan = connect_with_retry(("127.0.0.1", ENTRY_PORT))
@@ -230,10 +252,25 @@ class SocksTest(object):
output = ""
input_chan.close()
- self.checkSubprocesses()
+ if good: return output
+ else: return None
- if good:
- self.assertEqual(output, TEST_FILE)
+ def socksTest(self, sequence):
+ try:
+ output = self.socksTestInner(sequence)
+ report = ""
+ except Exception:
+ output = None
+ report = traceback.format_exc()
+
+ report += self.obfs_server.check_completion("obfsproxy server")
+ report += self.obfs_client.check_completion("obfsproxy client")
+
+ if output is not None:
+ report += diff("errors in transfer:", TEST_FILE, output)
+
+ if report != "":
+ self.fail("\n" + report)
def test_illformed(self):
@@ -295,8 +332,7 @@ class SocksTest(object):
#
class DirectObfs2(DirectTest, unittest.TestCase):
- obfs_args = ("./obfsproxy", "--log-min-severity=warn",
- "obfs2",
+ obfs_args = ("obfs2",
"--dest=127.0.0.1:%d" % EXIT_PORT,
"server", "127.0.0.1:%d" % SERVER_PORT,
"+", "obfs2",
@@ -304,8 +340,7 @@ class DirectObfs2(DirectTest, unittest.TestCase):
"client", "127.0.0.1:%d" % ENTRY_PORT)
class DirectDummy(DirectTest, unittest.TestCase):
- obfs_args = ("./obfsproxy", "--log-min-severity=warn",
- "dummy", "server",
+ obfs_args = ("dummy", "server",
"127.0.0.1:%d" % SERVER_PORT,
"127.0.0.1:%d" % EXIT_PORT,
"+", "dummy", "client",
@@ -313,21 +348,17 @@ class DirectDummy(DirectTest, unittest.TestCase):
"127.0.0.1:%d" % SERVER_PORT)
class SocksObfs2(SocksTest, unittest.TestCase):
- server_args = ("./obfsproxy", "--log-min-severity=warn",
- "obfs2",
+ server_args = ("obfs2",
"--dest=127.0.0.1:%d" % EXIT_PORT,
"server", "127.0.0.1:%d" % SERVER_PORT)
- client_args = ("./obfsproxy", "--log-min-severity=warn",
- "obfs2",
+ client_args = ("obfs2",
"socks", "127.0.0.1:%d" % ENTRY_PORT)
class SocksDummy(SocksTest, unittest.TestCase):
- server_args = ("./obfsproxy", "--log-min-severity=warn",
- "dummy", "server",
+ server_args = ("dummy", "server",
"127.0.0.1:%d" % SERVER_PORT,
"127.0.0.1:%d" % EXIT_PORT)
- client_args = ("./obfsproxy", "--log-min-severity=warn",
- "dummy", "socks",
+ client_args = ("dummy", "socks",
"127.0.0.1:%d" % ENTRY_PORT)
TEST_FILE = """\
diff --git a/src/util.c b/src/util.c
index e44b131..46d4b42 100644
--- a/src/util.c
+++ b/src/util.c
@@ -10,6 +10,10 @@
#include <unistd.h>
#include <event2/dns.h>
+#include <arpa/inet.h>
+#ifdef AF_LOCAL
+#include <sys/un.h>
+#endif
/** Any size_t larger than this amount is likely to be an underflow. */
#define SIZE_T_CEILING (SIZE_MAX/2 - 16)
@@ -195,6 +199,43 @@ resolve_address_port(const char *address, int nodns, int passive,
return ai;
}
+char *
+printable_address(struct sockaddr *addr, socklen_t addrlen)
+{
+ char abuf[INET6_ADDRSTRLEN];
+ char apbuf[INET6_ADDRSTRLEN + 8]; /* []:65535 is 8 characters */
+
+ switch (addr->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *sin = (struct sockaddr_in*)addr;
+ if (!inet_ntop(AF_INET, &sin->sin_addr, abuf, INET6_ADDRSTRLEN))
+ break;
+ obfs_snprintf(apbuf, sizeof apbuf, "%s:%d", abuf, ntohs(sin->sin_port));
+ return xstrdup(apbuf);
+ }
+
+ case AF_INET6: {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)addr;
+ if (!inet_ntop(AF_INET, &sin6->sin6_addr, abuf, INET6_ADDRSTRLEN))
+ break;
+ obfs_snprintf(apbuf, sizeof apbuf, "[%s]:%d", abuf,
+ ntohs(sin6->sin6_port));
+ return xstrdup(apbuf);
+ }
+
+#ifdef AF_LOCAL
+ case AF_LOCAL:
+ return xstrdup(((struct sockaddr_un*)addr)->sun_path);
+#endif
+ default:
+ break;
+ }
+
+ obfs_snprintf(apbuf, sizeof apbuf,
+ "<addr family %d>", addr->sa_family);
+ return xstrdup(apbuf);
+}
+
static struct evdns_base *the_evdns_base = NULL;
struct evdns_base *
diff --git a/src/util.h b/src/util.h
index 4fc1a33..5108b62 100644
--- a/src/util.h
+++ b/src/util.h
@@ -93,6 +93,10 @@ struct evutil_addrinfo *resolve_address_port(const char *address,
int nodns, int passive,
const char *default_port);
+/** Produce a printable name for this sockaddr. The result is in
+ malloced memory. */
+char *printable_address(struct sockaddr *addr, socklen_t addrlen);
+
struct evdns_base *get_evdns_base(void);
int init_evdns_base(struct event_base *base);
More information about the tor-commits
mailing list