Merge branch 'evdns-tcp-pr-1004'

@seleznevae:

  "Added support for DNS requests via TCP. By default, requests are done
   via UDP. In case truncated response is received new attempt is done
   via TCP connection. Added 2 new macros DNS_QUERY_USEVC and
   DNS_QUERY_IGNTC to force all requests to be done via TCP and to disable
   switch to TCP in case of truncated responses.

   Also added possibility for DNS server to listen and receive requests on
   TCP port. Current implementation of TCP support in DNS server seems
   rather preliminary and maybe changes after discussion and code review.

   Fallback to TCP in case of truncated DNS requests is done automatically.
   To imitate the old behaviour macros DNS_QUERY_IGNTC should be used. To
   force all DNS requests to be done via TCP one should use the flag
   DNS_QUERY_USEVC. Names DNS_QUERY_IGNTC, DNS_QUERY_USEVC were chosen to
   imitate similar flags in c-ares and glibc."

Ok, interfaces looks good, merging to avoid stalling it for too long.

* evdns-tcp-pr-1004:
  evdns: fix coding style issues
  evdns: fix trailing whitespaces
  evdns: bufferevent_setcb before bufferevent_free is redundant
  evdns: Implement dns requests via tcp
This commit is contained in:
Azat Khuzhin 2020-05-25 03:13:00 +03:00
commit 028842aacb
6 changed files with 1287 additions and 121 deletions

822
evdns.c

File diff suppressed because it is too large Load Diff

View File

@ -183,7 +183,12 @@ extern "C" {
#define DNS_PTR 2 #define DNS_PTR 2
#define DNS_IPv6_AAAA 3 #define DNS_IPv6_AAAA 3
#define DNS_QUERY_NO_SEARCH 1 /** Disable searching for the query. */
#define DNS_QUERY_NO_SEARCH 0x01
/** Use TCP connections ("virtual circuits") for queries rather than UDP datagrams. */
#define DNS_QUERY_USEVC 0x02
/** Ignore trancation flag in responses (don't fallback to TCP connections). */
#define DNS_QUERY_IGNTC 0x04
/* Allow searching */ /* Allow searching */
#define DNS_OPTION_SEARCH 1 #define DNS_OPTION_SEARCH 1
@ -197,6 +202,9 @@ extern "C" {
* - attempts: * - attempts:
* - randomize-case: * - randomize-case:
* - initial-probe-timeout: * - initial-probe-timeout:
* - tcp-idle-timeout:
* - use-vc
* - ignore-tc
*/ */
#define DNS_OPTION_MISC 4 #define DNS_OPTION_MISC 4
/* Load hosts file (i.e. "/etc/hosts") */ /* Load hosts file (i.e. "/etc/hosts") */
@ -390,7 +398,7 @@ struct evdns_request;
@param base the evdns_base to which to apply this operation @param base the evdns_base to which to apply this operation
@param name a DNS hostname @param name a DNS hostname
@param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. @param flags either 0, or combination of DNS_QUERY_* flags.
@param callback a callback function to invoke when the request is completed @param callback a callback function to invoke when the request is completed
@param ptr an argument to pass to the callback function @param ptr an argument to pass to the callback function
@return an evdns_request object if successful, or NULL if an error occurred. @return an evdns_request object if successful, or NULL if an error occurred.
@ -404,7 +412,7 @@ struct evdns_request *evdns_base_resolve_ipv4(struct evdns_base *base, const cha
@param base the evdns_base to which to apply this operation @param base the evdns_base to which to apply this operation
@param name a DNS hostname @param name a DNS hostname
@param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. @param flags either 0, or combination of DNS_QUERY_* flags.
@param callback a callback function to invoke when the request is completed @param callback a callback function to invoke when the request is completed
@param ptr an argument to pass to the callback function @param ptr an argument to pass to the callback function
@return an evdns_request object if successful, or NULL if an error occurred. @return an evdns_request object if successful, or NULL if an error occurred.
@ -421,7 +429,7 @@ struct in6_addr;
@param base the evdns_base to which to apply this operation @param base the evdns_base to which to apply this operation
@param in an IPv4 address @param in an IPv4 address
@param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. @param flags either 0, or combination of DNS_QUERY_* flags.
@param callback a callback function to invoke when the request is completed @param callback a callback function to invoke when the request is completed
@param ptr an argument to pass to the callback function @param ptr an argument to pass to the callback function
@return an evdns_request object if successful, or NULL if an error occurred. @return an evdns_request object if successful, or NULL if an error occurred.
@ -436,7 +444,7 @@ struct evdns_request *evdns_base_resolve_reverse(struct evdns_base *base, const
@param base the evdns_base to which to apply this operation @param base the evdns_base to which to apply this operation
@param in an IPv6 address @param in an IPv6 address
@param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query. @param flags either 0, or combination of DNS_QUERY_* flags.
@param callback a callback function to invoke when the request is completed @param callback a callback function to invoke when the request is completed
@param ptr an argument to pass to the callback function @param ptr an argument to pass to the callback function
@return an evdns_request object if successful, or NULL if an error occurred. @return an evdns_request object if successful, or NULL if an error occurred.
@ -462,11 +470,14 @@ void evdns_cancel_request(struct evdns_base *base, struct evdns_request *req);
ndots, timeout, max-timeouts, max-inflight, attempts, randomize-case, ndots, timeout, max-timeouts, max-inflight, attempts, randomize-case,
bind-to, initial-probe-timeout, getaddrinfo-allow-skew, bind-to, initial-probe-timeout, getaddrinfo-allow-skew,
so-rcvbuf, so-sndbuf. so-rcvbuf, so-sndbuf, tcp-idle-timeout, use-vc, ignore-tc.
In versions before Libevent 2.0.3-alpha, the option name needed to end with In versions before Libevent 2.0.3-alpha, the option name needed to end with
a colon. a colon.
In case of options without values (use-vc, ingore-tc) val should be an empty
string or NULL.
@param base the evdns_base to which to apply this operation @param base the evdns_base to which to apply this operation
@param option the name of the configuration option to be modified @param option the name of the configuration option to be modified
@param val the value to be set @param val the value to be set
@ -646,7 +657,7 @@ typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, vo
#define EVDNS_FLAGS_AA 0x400 #define EVDNS_FLAGS_AA 0x400
#define EVDNS_FLAGS_RD 0x080 #define EVDNS_FLAGS_RD 0x080
/** Create a new DNS server port. /** Create a new UDP DNS server port.
@param base The event base to handle events for the server port. @param base The event base to handle events for the server port.
@param socket A UDP socket to accept DNS requests. @param socket A UDP socket to accept DNS requests.
@ -659,10 +670,60 @@ typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, vo
*/ */
EVENT2_EXPORT_SYMBOL EVENT2_EXPORT_SYMBOL
struct evdns_server_port *evdns_add_server_port_with_base(struct event_base *base, evutil_socket_t socket, int flags, evdns_request_callback_fn_type callback, void *user_data); struct evdns_server_port *evdns_add_server_port_with_base(struct event_base *base, evutil_socket_t socket, int flags, evdns_request_callback_fn_type callback, void *user_data);
struct evconnlistener;
/** Create a new TCP DNS server port.
@param base The event base to handle events for the server port.
@param listener A TCP listener to accept DNS requests.
@param flags Always 0 for now.
@param callback A function to invoke whenever we get a DNS request
on the socket.
@param user_data Data to pass to the callback.
@return an evdns_server_port structure for this server port or NULL if
an error occurred.
*/
EVENT2_EXPORT_SYMBOL
struct evdns_server_port *evdns_add_server_port_with_listener(
struct event_base *base, struct evconnlistener *listener, int flags,
evdns_request_callback_fn_type callback, void *user_data);
/** Close down a DNS server port, and free associated structures. */ /** Close down a DNS server port, and free associated structures. */
EVENT2_EXPORT_SYMBOL EVENT2_EXPORT_SYMBOL
void evdns_close_server_port(struct evdns_server_port *port); void evdns_close_server_port(struct evdns_server_port *port);
/**
* List of configurable evdns_server_port options.
*
* @see evdns_server_port_set_option()
*/
enum evdns_server_option {
/**
* Maximum number of simultaneous tcp connections (clients)
* that server can hold. Can be set only for TCP DNS servers.
*/
EVDNS_SOPT_TCP_MAX_CLIENTS,
/**
* Idle timeout (in seconds) of incoming TCP connections.
* If client doesn't send any requests via the connection
* during this period connection is closed by the server.
* Can be set only for TCP DNS servers.
*/
EVDNS_SOPT_TCP_IDLE_TIMEOUT,
};
/**
Configure DNS server.
@param port the evdns_server_port to which to apply this operation
@param option @see evdns_server_option for the list of possible options
@param val value of the option
@return 0 if successful, or -1 if an error occurred
*/
EVENT2_EXPORT_SYMBOL
int evdns_server_port_set_option(struct evdns_server_port *port, enum evdns_server_option option, size_t value);
/** Sets some flags in a reply we're building. /** Sets some flags in a reply we're building.
Allows setting of the AA or RD flags Allows setting of the AA or RD flags
*/ */

View File

@ -81,6 +81,23 @@
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#define REPEAT_2(address) \
address "," address
#define REPEAT_4(address) \
REPEAT_2(address) "," REPEAT_2(address)
#define REPEAT_8(address) \
REPEAT_4(address) "," REPEAT_4(address)
#define REPEAT_16(address) \
REPEAT_8(address) "," REPEAT_8(address)
#define REPEAT_32(address) \
REPEAT_16(address) "," REPEAT_16(address)
#define REPEAT_64(address) \
REPEAT_32(address) "," REPEAT_32(address)
#define REPEAT_128(address) \
REPEAT_64(address) "," REPEAT_64(address)
#define REPEAT_256(address) \
REPEAT_128(address) "," REPEAT_128(address)
static int dns_ok = 0; static int dns_ok = 0;
static int dns_got_cancel = 0; static int dns_got_cancel = 0;
static int dns_err = 0; static int dns_err = 0;
@ -477,7 +494,7 @@ struct generic_dns_callback_result {
int ttl; int ttl;
size_t addrs_len; size_t addrs_len;
void *addrs; void *addrs;
char addrs_buf[256]; char addrs_buf[4096];
}; };
static void static void
@ -503,8 +520,8 @@ generic_dns_callback(int result, char type, int count, int ttl, void *addresses,
} }
if (len) { if (len) {
res->addrs_len = len; res->addrs_len = len;
if (len > 256) if (len > ARRAY_SIZE(res->addrs_buf))
len = 256; len = ARRAY_SIZE(res->addrs_buf);
memcpy(res->addrs_buf, addresses, len); memcpy(res->addrs_buf, addresses, len);
res->addrs = res->addrs_buf; res->addrs = res->addrs_buf;
} }
@ -520,6 +537,11 @@ generic_dns_callback(int result, char type, int count, int ttl, void *addresses,
} }
static struct regress_dns_server_table search_table[] = { static struct regress_dns_server_table search_table[] = {
{ "small.a.example.com", "A", REPEAT_64("11.22.33.45"), 0, 0},
{ "medium.b.example.com", "A", REPEAT_64("11.22.33.45") "," REPEAT_64("12.22.33.45"), 0, 0},
{ "large.c.example.com", "A",
REPEAT_256("11.22.33.45") "," REPEAT_256("12.22.33.45") "," REPEAT_256("13.22.33.45") "," REPEAT_256("14.22.33.45"), 0, 0},
{ "lost.request.com", "err", "67", 0, 0},
{ "host.a.example.com", "err", "3", 0, 0 }, { "host.a.example.com", "err", "3", 0, 0 },
{ "host.b.example.com", "err", "3", 0, 0 }, { "host.b.example.com", "err", "3", 0, 0 },
{ "host.c.example.com", "A", "11.22.33.44", 0, 0 }, { "host.c.example.com", "A", "11.22.33.44", 0, 0 },
@ -529,12 +551,31 @@ static struct regress_dns_server_table search_table[] = {
{ "hostn.a.example.com", "errsoa", "0", 0, 0 }, { "hostn.a.example.com", "errsoa", "0", 0, 0 },
{ "hostn.b.example.com", "errsoa", "3", 0, 0 }, { "hostn.b.example.com", "errsoa", "3", 0, 0 },
{ "hostn.c.example.com", "err", "0", 0, 0 }, { "hostn.c.example.com", "err", "0", 0, 0 },
{ "host", "err", "3", 0, 0 }, { "host", "err", "3", 0, 0 },
{ "host2", "err", "3", 0, 0 }, { "host2", "err", "3", 0, 0 },
{ "*", "err", "3", 0, 0 }, { "*", "err", "3", 0, 0 },
{ NULL, NULL, NULL, 0, 0 } { NULL, NULL, NULL, 0, 0 }
}; };
static struct regress_dns_server_table tcp_search_table[] = {
{ "small.a.example.com", "A", REPEAT_64("11.22.33.45"), 0, 0},
{ "medium.b.example.com", "A", REPEAT_64("11.22.33.45") "," REPEAT_64("12.22.33.45"), 0, 0},
{ "large.c.example.com", "A",
REPEAT_256("11.22.33.45") "," REPEAT_256("12.22.33.45") "," REPEAT_256("13.22.33.45") "," REPEAT_256("14.22.33.45"), 0, 0},
{ "lost.request.com", "err", "67", 0, 0},
{ NULL, NULL, NULL, 0, 0 }
};
#define assert_request_results(r, exp_result, exp_addresses) \
do { \
k_ = parse_csv_address_list(exp_addresses, AF_INET, addrs, ARRAY_SIZE(addrs)); \
tt_assert(r.result == exp_result); \
tt_assert(r.type == DNS_IPv4_A); \
tt_assert(r.count == k_); \
for (k_ = 0; k_ < r.count; ++k_) \
tt_int_op(((ev_uint32_t *)r.addrs)[k_], ==, addrs[k_].s_addr); \
} while (0)
static void static void
dns_search_test_impl(void *arg, int lower) dns_search_test_impl(void *arg, int lower)
{ {
@ -553,7 +594,7 @@ dns_search_test_impl(void *arg, int lower)
table[i].lower = lower; table[i].lower = lower;
} }
tt_assert(regress_dnsserver(base, &portnum, table)); tt_assert(regress_dnsserver(base, &portnum, table, NULL));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
dns = evdns_base_new(base, 0); dns = evdns_base_new(base, 0);
@ -659,7 +700,7 @@ dns_search_cancel_test(void *arg)
struct generic_dns_callback_result r1; struct generic_dns_callback_result r1;
char buf[64]; char buf[64];
port = regress_get_dnsserver(base, &portnum, NULL, port = regress_get_udp_dnsserver(base, &portnum, NULL,
search_cancel_server_cb, NULL); search_cancel_server_cb, NULL);
tt_assert(port); tt_assert(port);
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
@ -736,7 +777,7 @@ dns_retry_test_impl(void *arg, int flags)
struct generic_dns_callback_result r1; struct generic_dns_callback_result r1;
port = regress_get_dnsserver(base, &portnum, NULL, port = regress_get_udp_dnsserver(base, &portnum, NULL,
fail_server_cb, &drop_count); fail_server_cb, &drop_count);
tt_assert(port); tt_assert(port);
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
@ -833,10 +874,10 @@ dns_reissue_test_impl(void *arg, int flags)
ev_uint16_t portnum1 = 0, portnum2=0; ev_uint16_t portnum1 = 0, portnum2=0;
char buf1[64], buf2[64]; char buf1[64], buf2[64];
port1 = regress_get_dnsserver(base, &portnum1, NULL, port1 = regress_get_udp_dnsserver(base, &portnum1, NULL,
regress_dns_server_cb, internal_error_table); regress_dns_server_cb, internal_error_table);
tt_assert(port1); tt_assert(port1);
port2 = regress_get_dnsserver(base, &portnum2, NULL, port2 = regress_get_udp_dnsserver(base, &portnum2, NULL,
regress_dns_server_cb, reissue_table); regress_dns_server_cb, reissue_table);
tt_assert(port2); tt_assert(port2);
evutil_snprintf(buf1, sizeof(buf1), "127.0.0.1:%d", (int)portnum1); evutil_snprintf(buf1, sizeof(buf1), "127.0.0.1:%d", (int)portnum1);
@ -912,7 +953,7 @@ dns_inflight_test_impl(void *arg, int flags)
struct generic_dns_callback_result r[20]; struct generic_dns_callback_result r[20];
int i; int i;
dns_port = regress_get_dnsserver(base, &portnum, NULL, dns_port = regress_get_udp_dnsserver(base, &portnum, NULL,
regress_dns_server_cb, reissue_table); regress_dns_server_cb, reissue_table);
tt_assert(dns_port); tt_assert(dns_port);
if (disable_when_inactive) { if (disable_when_inactive) {
@ -977,7 +1018,7 @@ dns_disable_when_inactive_no_ns_test(void *arg)
tt_assert(inactive_base); tt_assert(inactive_base);
/** Create dns server with inactive base, to avoid replying to clients */ /** Create dns server with inactive base, to avoid replying to clients */
tt_assert(regress_dnsserver(inactive_base, &portnum, search_table)); tt_assert(regress_dnsserver(inactive_base, &portnum, search_table, NULL));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum); evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE); dns = evdns_base_new(base, EVDNS_BASE_DISABLE_WHEN_INACTIVE);
@ -1301,7 +1342,7 @@ test_bufferevent_connect_hostname(void *arg)
listener_port = regress_get_socket_port( listener_port = regress_get_socket_port(
evconnlistener_get_fd(listener)); evconnlistener_get_fd(listener));
port = regress_get_dnsserver(data->base, &dns_port, NULL, port = regress_get_udp_dnsserver(data->base, &dns_port, NULL,
be_getaddrinfo_server_cb, &n_dns); be_getaddrinfo_server_cb, &n_dns);
tt_assert(port); tt_assert(port);
tt_int_op(dns_port, >=, 0); tt_int_op(dns_port, >=, 0);
@ -1612,7 +1653,7 @@ test_getaddrinfo_async(void *arg)
/* 2. Okay, now we can actually test the asynchronous resolver. */ /* 2. Okay, now we can actually test the asynchronous resolver. */
/* Start a dummy local dns server... */ /* Start a dummy local dns server... */
port = regress_get_dnsserver(data->base, &dns_port, NULL, port = regress_get_udp_dnsserver(data->base, &dns_port, NULL,
be_getaddrinfo_server_cb, &n_dns_questions); be_getaddrinfo_server_cb, &n_dns_questions);
tt_assert(port); tt_assert(port);
tt_int_op(dns_port, >=, 0); tt_int_op(dns_port, >=, 0);
@ -2136,7 +2177,7 @@ dns_client_fail_requests_test(void *arg)
struct generic_dns_callback_result r[20]; struct generic_dns_callback_result r[20];
unsigned i; unsigned i;
dns_port = regress_get_dnsserver(base, &portnum, NULL, dns_port = regress_get_udp_dnsserver(base, &portnum, NULL,
regress_dns_server_cb, reissue_table); regress_dns_server_cb, reissue_table);
tt_assert(dns_port); tt_assert(dns_port);
@ -2184,7 +2225,7 @@ dns_client_fail_requests_getaddrinfo_test(void *arg)
struct generic_dns_callback_result r[20]; struct generic_dns_callback_result r[20];
int i; int i;
dns_port = regress_get_dnsserver(base, &portnum, NULL, dns_port = regress_get_udp_dnsserver(base, &portnum, NULL,
regress_dns_server_cb, reissue_table); regress_dns_server_cb, reissue_table);
tt_assert(dns_port); tt_assert(dns_port);
@ -2286,7 +2327,7 @@ getaddrinfo_race_gotresolve_test(void *arg)
if (evthread_make_base_notifiable(rp.base) < 0) if (evthread_make_base_notifiable(rp.base) < 0)
tt_abort_msg("Couldn't make base notifiable!"); tt_abort_msg("Couldn't make base notifiable!");
dns_port = regress_get_dnsserver(rp.base, &portnum, NULL, dns_port = regress_get_udp_dnsserver(rp.base, &portnum, NULL,
regress_dns_server_cb, reissue_table); regress_dns_server_cb, reissue_table);
tt_assert(dns_port); tt_assert(dns_port);
@ -2359,6 +2400,213 @@ end:
} }
#endif #endif
static void
test_tcp_resolve(void *arg)
{
struct basic_test_data *data = arg;
struct event_base *base = data->base;
struct evdns_base *dns = evdns_base_new(base, 0);
ev_uint16_t portnum = 0;
struct evdns_request *req = NULL;
struct generic_dns_callback_result r;
struct in_addr addrs[2048];
char buf[64];
int k_;
exit_base = base;
tt_assert(base);
tt_assert(regress_dnsserver(base, &portnum, search_table, tcp_search_table));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
tt_assert(!evdns_base_nameserver_ip_add(dns, buf));
// small table
req = evdns_base_resolve_ipv4(
dns, "small.a.example.com", 0, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
assert_request_results(r, DNS_ERR_NONE, REPEAT_64("11.22.33.45"));
tt_assert(search_table[0].seen == 1);
tt_assert(tcp_search_table[0].seen == 0);
// medium table
req = evdns_base_resolve_ipv4(
dns, "medium.b.example.com", DNS_QUERY_IGNTC, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
tt_assert(r.type != DNS_IPv4_A);
tt_assert(r.result == DNS_ERR_TRUNCATED);
tt_assert(search_table[1].seen == 1);
tt_assert(tcp_search_table[1].seen == 0);
req = evdns_base_resolve_ipv4(
dns, "medium.b.example.com", DNS_QUERY_USEVC, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
assert_request_results(r, DNS_ERR_NONE, REPEAT_64("11.22.33.45") "," REPEAT_64("12.22.33.45"));
tt_assert(search_table[1].seen == 1);
tt_assert(tcp_search_table[1].seen == 1);
// big table
req = evdns_base_resolve_ipv4(
dns, "large.c.example.com", DNS_QUERY_IGNTC, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
tt_assert(r.type != DNS_IPv4_A);
tt_assert(r.result == DNS_ERR_TRUNCATED);
tt_assert(search_table[2].seen == 1);
tt_assert(tcp_search_table[2].seen == 0);
req = evdns_base_resolve_ipv4(
dns, "large.c.example.com", 0, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
assert_request_results(r, DNS_ERR_NONE,
REPEAT_256("11.22.33.45") "," REPEAT_256("12.22.33.45") "," REPEAT_256("13.22.33.45") "," REPEAT_256("14.22.33.45"));
tt_assert(search_table[2].seen == 2);
tt_assert(tcp_search_table[2].seen == 1);
req = evdns_base_resolve_ipv4(
dns, "large.c.example.com", DNS_QUERY_USEVC, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
assert_request_results(r, DNS_ERR_NONE,
REPEAT_256("11.22.33.45") "," REPEAT_256("12.22.33.45") "," REPEAT_256("13.22.33.45") "," REPEAT_256("14.22.33.45"));
tt_assert(search_table[2].seen == 2);
tt_assert(tcp_search_table[2].seen == 2);
end:
if (dns)
evdns_base_free(dns, 0);
regress_clean_dnsserver();
}
static void
test_tcp_resolve_pipeline(void *arg)
{
struct basic_test_data *data = arg;
struct event_base *base = data->base;
struct evdns_base *dns = evdns_base_new(base, 0);
ev_uint16_t portnum = 0;
struct evdns_request *reqs[3] = {NULL, NULL, NULL};
struct generic_dns_callback_result results[3];
char buf[64];
struct in_addr addrs[2048];
int i, k_;
exit_base = base;
tt_assert(base);
tt_assert(regress_dnsserver(base, &portnum, search_table, tcp_search_table));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
tt_assert(!evdns_base_nameserver_ip_add(dns, buf));
tt_assert(!evdns_base_set_option(dns, "use-vc", NULL));
for (i = 0; i < 3; ++i) {
reqs[i] = evdns_base_resolve_ipv4(
dns, "large.c.example.com", 0, generic_dns_callback, &results[i]);
tt_assert(reqs[i]);
}
n_replies_left = 3;
event_base_dispatch(base);
for (i = 0; i < 3; ++i) {
assert_request_results(results[i], DNS_ERR_NONE,
REPEAT_256("11.22.33.45") "," REPEAT_256("12.22.33.45") "," REPEAT_256("13.22.33.45") "," REPEAT_256("14.22.33.45"));
}
tt_assert(search_table[2].seen == 0);
tt_assert(tcp_search_table[2].seen == 3);
end:
if (dns)
evdns_base_free(dns, 0);
regress_clean_dnsserver();
}
static void
test_tcp_resolve_many_clients(void *arg)
{
struct basic_test_data *data = arg;
struct event_base *base = data->base;
struct evdns_base *dns[3] = {evdns_base_new(base, 0), evdns_base_new(base, 0), evdns_base_new(base, 0)};
struct evdns_request *req[3] = {NULL, NULL, NULL};
struct generic_dns_callback_result r[3];
int k_, i;
ev_uint16_t portnum = 0;
char buf[64];
struct in_addr addrs[2048];
exit_base = base;
tt_assert(base);
tt_assert(regress_dnsserver(base, &portnum, search_table, tcp_search_table));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
for (i = 0; i < 3; ++i) {
tt_assert(!evdns_base_nameserver_ip_add(dns[i], buf));
req[i] = evdns_base_resolve_ipv4(
dns[i], "small.a.example.com", DNS_QUERY_USEVC, generic_dns_callback, &r[i]);
tt_assert(req[i]);
}
n_replies_left = 3;
event_base_dispatch(base);
for (i = 0; i < 3; ++i) {
assert_request_results(r[i], DNS_ERR_NONE, REPEAT_64("11.22.33.45"));
}
tt_assert(search_table[0].seen == 0);
tt_assert(tcp_search_table[0].seen == 3);
end:
for (i = 0; i < 3; ++i) {
if (dns[i])
evdns_base_free(dns[i], 0);
}
regress_clean_dnsserver();
}
static void
test_tcp_timeout(void *arg)
{
struct generic_dns_callback_result r;
struct basic_test_data *data = arg;
struct event_base *base = data->base;
struct evdns_base *dns = evdns_base_new(base, 0);
ev_uint16_t portnum = 0;
struct evdns_request *req = NULL;
char buf[64];
exit_base = base;
tt_assert(base);
tt_assert(!evdns_base_set_option(dns, "timeout:", "1"));
tt_assert(regress_dnsserver(base, &portnum, search_table, tcp_search_table));
evutil_snprintf(buf, sizeof(buf), "127.0.0.1:%d", (int)portnum);
tt_assert(!evdns_base_nameserver_ip_add(dns, buf));
req = evdns_base_resolve_ipv4(
dns, "lost.request.com", DNS_QUERY_USEVC, generic_dns_callback, &r);
tt_assert(req);
n_replies_left = 1;
event_base_dispatch(base);
tt_assert(DNS_ERR_TIMEOUT == r.result);
end:
if (dns)
evdns_base_free(dns, 0);
regress_clean_dnsserver();
}
static void static void
test_set_so_rcvbuf_so_sndbuf(void *arg) test_set_so_rcvbuf_so_sndbuf(void *arg)
{ {
@ -2407,6 +2655,10 @@ test_set_option(void *arg)
const char *addr_port_options[] = { const char *addr_port_options[] = {
"bind-to", "bind-to:", "bind-to", "bind-to:",
}; };
const char *options_without_values[] = {
"use-vc", "use-vc:",
"ignore-tc", "ignore-tc:",
};
dns_base = evdns_base_new(data->base, 0); dns_base = evdns_base_new(data->base, 0);
tt_assert(dns_base); tt_assert(dns_base);
@ -2437,6 +2689,13 @@ test_set_option(void *arg)
tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "foo")); tt_assert(FAIL == evdns_base_set_option(dns_base, addr_port_options[i], "foo"));
} }
for (i = 0; i < ARRAY_SIZE(options_without_values); ++i) {
tt_assert(SUCCESS == evdns_base_set_option(dns_base, options_without_values[i], NULL));
tt_assert(SUCCESS == evdns_base_set_option(dns_base, options_without_values[i], ""));
tt_assert(FAIL == evdns_base_set_option(dns_base, options_without_values[i], "1"));
tt_assert(FAIL == evdns_base_set_option(dns_base, options_without_values[i], "foo"));
}
#undef SUCCESS #undef SUCCESS
#undef FAIL #undef FAIL
end: end:
@ -2444,6 +2703,48 @@ end:
evdns_base_free(dns_base, 0); evdns_base_free(dns_base, 0);
} }
static void
test_set_server_option(void *arg)
{
#define SUCCESS 0
#define FAIL -1
struct basic_test_data *data = arg;
struct evdns_server_port *tcp_port = NULL;
struct evdns_server_port *udp_port = NULL;
evutil_socket_t udp_sock = -1;
evutil_socket_t tcp_sock = -1;
ev_uint16_t portnum;
size_t i;
enum evdns_server_option tcp_options[] = {EVDNS_SOPT_TCP_MAX_CLIENTS, EVDNS_SOPT_TCP_IDLE_TIMEOUT};
portnum = 0;
tcp_port = regress_get_tcp_dnsserver(data->base, &portnum, &tcp_sock, NULL, NULL);
tt_assert(tcp_port);
portnum = 0;
udp_port = regress_get_udp_dnsserver(data->base, &portnum, &udp_sock, NULL, NULL);
tt_assert(udp_port);
for (i = 0; i < ARRAY_SIZE(tcp_options); ++i) {
tt_assert(SUCCESS == evdns_server_port_set_option(tcp_port, tcp_options[i], 0));
tt_assert(SUCCESS == evdns_server_port_set_option(tcp_port, tcp_options[i], 1));
tt_assert(SUCCESS == evdns_server_port_set_option(tcp_port, tcp_options[i], 100));
tt_assert(FAIL == evdns_server_port_set_option(udp_port, tcp_options[i], 0));
tt_assert(FAIL == evdns_server_port_set_option(udp_port, tcp_options[i], 100));
}
#undef SUCCESS
#undef FAIL
end:
if (tcp_port)
evdns_close_server_port(tcp_port);
if (tcp_sock >= 0)
evutil_closesocket(tcp_sock);
if (udp_port)
evdns_close_server_port(udp_port);
if (udp_sock >= 0)
evutil_closesocket(udp_sock);
}
#define DNS_LEGACY(name, flags) \ #define DNS_LEGACY(name, flags) \
{ #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \ { #name, run_legacy_test_fn, flags|TT_LEGACY, &legacy_setup, \
dns_##name } dns_##name }
@ -2516,11 +2817,21 @@ struct testcase_t dns_testcases[] = {
getaddrinfo_race_gotresolve_test, getaddrinfo_race_gotresolve_test,
TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL }, TT_FORK|TT_OFF_BY_DEFAULT, NULL, NULL },
#endif #endif
{ "tcp_resolve", test_tcp_resolve,
TT_FORK | TT_NEED_BASE, &basic_setup, NULL },
{ "tcp_resolve_pipeline", test_tcp_resolve_pipeline,
TT_FORK | TT_NEED_BASE, &basic_setup, NULL },
{ "tcp_resolve_many_clients", test_tcp_resolve_many_clients,
TT_FORK | TT_NEED_BASE, &basic_setup, NULL },
{ "tcp_timeout", test_tcp_timeout,
TT_FORK | TT_NEED_BASE, &basic_setup, NULL },
{ "set_SO_RCVBUF_SO_SNDBUF", test_set_so_rcvbuf_so_sndbuf, { "set_SO_RCVBUF_SO_SNDBUF", test_set_so_rcvbuf_so_sndbuf,
TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, TT_FORK|TT_NEED_BASE, &basic_setup, NULL },
{ "set_options", test_set_option, { "set_options", test_set_option,
TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, TT_FORK|TT_NEED_BASE, &basic_setup, NULL },
{ "set_server_options", test_set_server_option,
TT_FORK|TT_NEED_BASE, &basic_setup, NULL },
END_OF_TESTCASES END_OF_TESTCASES
}; };

View File

@ -1418,7 +1418,7 @@ http_connection_async_test(void *arg)
struct evhttp *http = http_setup(&port, data->base, 0); struct evhttp *http = http_setup(&port, data->base, 0);
exit_base = data->base; exit_base = data->base;
tt_assert(regress_dnsserver(data->base, &portnum, search_table)); tt_assert(regress_dnsserver(data->base, &portnum, search_table, NULL));
dns_base = evdns_base_new(data->base, 0/* init name servers */); dns_base = evdns_base_new(data->base, 0/* init name servers */);
tt_assert(dns_base); tt_assert(dns_base);
@ -1693,7 +1693,7 @@ http_cancel_test(void *arg)
if (type & BY_HOST) { if (type & BY_HOST) {
const char *timeout = (type & NS_TIMEOUT) ? "6" : "3"; const char *timeout = (type & NS_TIMEOUT) ? "6" : "3";
tt_assert(regress_dnsserver(data->base, &portnum, search_table)); tt_assert(regress_dnsserver(data->base, &portnum, search_table, NULL));
dns_base = evdns_base_new(data->base, 0/* init name servers */); dns_base = evdns_base_new(data->base, 0/* init name servers */);
tt_assert(dns_base); tt_assert(dns_base);
@ -4126,7 +4126,7 @@ http_connection_retry_conn_address_test_impl(void *arg, int ssl)
struct evdns_base *dns_base = NULL; struct evdns_base *dns_base = NULL;
char address[64]; char address[64];
tt_assert(regress_dnsserver(data->base, &portnum, search_table)); tt_assert(regress_dnsserver(data->base, &portnum, search_table, NULL));
dns_base = evdns_base_new(data->base, 0/* init name servers */); dns_base = evdns_base_new(data->base, 0/* init name servers */);
tt_assert(dns_base); tt_assert(dns_base);
@ -4663,7 +4663,7 @@ http_ipv6_for_domain_test_impl(void *arg, int family)
ev_uint16_t portnum = 0; ev_uint16_t portnum = 0;
char address[64]; char address[64];
tt_assert(regress_dnsserver(data->base, &portnum, ipv6_search_table)); tt_assert(regress_dnsserver(data->base, &portnum, ipv6_search_table, NULL));
dns_base = evdns_base_new(data->base, 0/* init name servers */); dns_base = evdns_base_new(data->base, 0/* init name servers */);
tt_assert(dns_base); tt_assert(dns_base);

View File

@ -69,9 +69,13 @@
#include "regress.h" #include "regress.h"
#include "regress_testutils.h" #include "regress_testutils.h"
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
/* globals */ /* globals */
static struct evdns_server_port *dns_port; static struct evdns_server_port *udp_dns_port;
evutil_socket_t dns_sock = -1; evutil_socket_t udp_dns_sock = -1;
static struct evdns_server_port *tcp_dns_port;
evutil_socket_t tcp_dns_sock = -1;
/* Helper: return the port that a socket is bound on, in host order. */ /* Helper: return the port that a socket is bound on, in host order. */
int int
@ -90,7 +94,7 @@ regress_get_socket_port(evutil_socket_t fd)
} }
struct evdns_server_port * struct evdns_server_port *
regress_get_dnsserver(struct event_base *base, regress_get_udp_dnsserver(struct event_base *base,
ev_uint16_t *portnum, ev_uint16_t *portnum,
evutil_socket_t *psock, evutil_socket_t *psock,
evdns_request_callback_fn_type cb, evdns_request_callback_fn_type cb,
@ -126,16 +130,64 @@ end:
return NULL; return NULL;
} }
struct evdns_server_port *
regress_get_tcp_dnsserver(struct event_base *base,
ev_uint16_t *portnum,
evutil_socket_t *psock,
evdns_request_callback_fn_type cb,
void *arg)
{
struct evdns_server_port *port = NULL;
evutil_socket_t sock;
struct sockaddr_in my_addr;
struct evconnlistener *listener;
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(*portnum);
my_addr.sin_addr.s_addr = htonl(0x7f000001UL);
listener = evconnlistener_new_bind(base, NULL, NULL,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, 128,
(struct sockaddr*)&my_addr, sizeof(my_addr));
if (!listener)
goto end;
port = evdns_add_server_port_with_listener(base, listener, 0, cb, arg);
if (!port)
goto end;
sock = evconnlistener_get_fd(listener);
if (!*portnum)
*portnum = regress_get_socket_port(sock);
if (psock)
*psock = sock;
return port;
end:
if (listener)
evconnlistener_free(listener);
return NULL;
}
void void
regress_clean_dnsserver(void) regress_clean_dnsserver(void)
{ {
if (dns_port) { if (udp_dns_port) {
evdns_close_server_port(dns_port); evdns_close_server_port(udp_dns_port);
dns_port = NULL; udp_dns_port = NULL;
} }
if (dns_sock >= 0) { if (udp_dns_sock >= 0) {
evutil_closesocket(dns_sock); evutil_closesocket(udp_dns_sock);
dns_sock = -1; udp_dns_sock = -1;
}
if (tcp_dns_port) {
evdns_close_server_port(tcp_dns_port);
tcp_dns_port = NULL;
}
if (tcp_dns_sock >= 0) {
evutil_closesocket(tcp_dns_sock);
tcp_dns_sock = -1;
} }
} }
@ -171,7 +223,11 @@ regress_dns_server_cb(struct evdns_server_request *req, void *data)
if (!strcmp(tab->anstype, "err")) { if (!strcmp(tab->anstype, "err")) {
int err = atoi(tab->ans); int err = atoi(tab->ans);
tt_assert(! evdns_server_request_respond(req, err)); if (DNS_ERR_TIMEOUT == err) {
tt_assert(! evdns_server_request_drop(req));
} else {
tt_assert(! evdns_server_request_respond(req, err));
}
return; return;
} else if (!strcmp(tab->anstype, "errsoa")) { } else if (!strcmp(tab->anstype, "errsoa")) {
int err = atoi(tab->ans); int err = atoi(tab->ans);
@ -191,12 +247,9 @@ regress_dns_server_cb(struct evdns_server_request *req, void *data)
tt_assert(! evdns_server_request_respond(req, err)); tt_assert(! evdns_server_request_respond(req, err));
return; return;
} else if (!strcmp(tab->anstype, "A")) { } else if (!strcmp(tab->anstype, "A")) {
struct in_addr in; struct in_addr in[2048];
if (!evutil_inet_pton(AF_INET, tab->ans, &in)) { int count = parse_csv_address_list(tab->ans, AF_INET, in, ARRAY_SIZE(in));
TT_DIE(("Bad A value %s in table", tab->ans)); evdns_server_request_add_a_reply(req, question, count, in, 100);
}
evdns_server_request_add_a_reply(req, question, 1, &in.s_addr,
100);
} else if (!strcmp(tab->anstype, "AAAA")) { } else if (!strcmp(tab->anstype, "AAAA")) {
struct in6_addr in6; struct in6_addr in6;
if (!evutil_inet_pton(AF_INET6, tab->ans, &in6)) { if (!evutil_inet_pton(AF_INET6, tab->ans, &in6)) {
@ -215,11 +268,30 @@ end:
int int
regress_dnsserver(struct event_base *base, ev_uint16_t *port, regress_dnsserver(struct event_base *base, ev_uint16_t *port,
struct regress_dns_server_table *search_table) struct regress_dns_server_table *udp_seach_table,
struct regress_dns_server_table *tcp_seach_table)
{ {
dns_port = regress_get_dnsserver(base, port, &dns_sock, if (!udp_seach_table && !tcp_seach_table)
regress_dns_server_cb, search_table); goto error;
return dns_port != NULL;
if (tcp_seach_table) {
tcp_dns_port = regress_get_tcp_dnsserver(base, port, &tcp_dns_sock,
regress_dns_server_cb, tcp_seach_table);
if (!tcp_dns_port)
goto error;
}
if (udp_seach_table) {
udp_dns_port = regress_get_udp_dnsserver(base, port, &udp_dns_sock,
regress_dns_server_cb, udp_seach_table);
if (!udp_dns_port)
goto error;
}
return 1;
error:
regress_clean_dnsserver();
return 0;
} }
int int
@ -231,3 +303,29 @@ regress_get_listener_addr(struct evconnlistener *lev,
return -1; return -1;
return getsockname(s, sa, socklen); return getsockname(s, sa, socklen);
} }
int
parse_csv_address_list(const char *s, int family, void *addrs, size_t addrs_size)
{
int i = 0;
char *token;
char buf[16384];
void *next_addr;
tt_assert(family == AF_INET || family == AF_INET6);
tt_assert(strlen(s) < ARRAY_SIZE(buf));
strcpy(buf, s);
token = strtok(buf, ",");
do {
tt_assert((unsigned)i < addrs_size);
next_addr = (family == AF_INET) ? (void *)((struct in_addr*)addrs + i)
: (void *)((struct in6_addr*)addrs + i);
if (!evutil_inet_pton(AF_INET, token, next_addr)) {
TT_DIE(("Bad %s value %s in table", (family == AF_INET) ? "A" :"AAAA", token));
}
++i;
token = strtok (NULL, ",");
} while (token);
end:
return i;
}

View File

@ -32,13 +32,20 @@
struct regress_dns_server_table { struct regress_dns_server_table {
const char *q; const char *q;
const char *anstype; const char *anstype;
const char *ans; const char *ans; /* Comma-separated list of IP numbers (e.g. "1.2.3.4", "1.2.3.4,5.6.7.8") */
int seen; int seen;
int lower; int lower;
}; };
struct evdns_server_port * struct evdns_server_port *
regress_get_dnsserver(struct event_base *base, regress_get_udp_dnsserver(struct event_base *base,
ev_uint16_t *portnum,
evutil_socket_t *psock,
evdns_request_callback_fn_type cb,
void *arg);
struct evdns_server_port *
regress_get_tcp_dnsserver(struct event_base *base,
ev_uint16_t *portnum, ev_uint16_t *portnum,
evutil_socket_t *psock, evutil_socket_t *psock,
evdns_request_callback_fn_type cb, evdns_request_callback_fn_type cb,
@ -51,9 +58,12 @@ int regress_get_socket_port(evutil_socket_t fd);
void regress_dns_server_cb( void regress_dns_server_cb(
struct evdns_server_request *req, void *data); struct evdns_server_request *req, void *data);
/* globally allocates a dns server that serves from a search table */ /* Globally allocates a dns server that serves from a search table.
TCP and UDP listeners are created on the same port number. If one of the
input search tables is NULL appropriate listener is not created. */
int regress_dnsserver(struct event_base *base, ev_uint16_t *port, int regress_dnsserver(struct event_base *base, ev_uint16_t *port,
struct regress_dns_server_table *seach_table); struct regress_dns_server_table *udp_seach_table,
struct regress_dns_server_table *tcp_seach_table);
/* clean up the global dns server resources */ /* clean up the global dns server resources */
void regress_clean_dnsserver(void); void regress_clean_dnsserver(void);
@ -63,5 +73,9 @@ struct sockaddr;
int regress_get_listener_addr(struct evconnlistener *lev, int regress_get_listener_addr(struct evconnlistener *lev,
struct sockaddr *sa, ev_socklen_t *socklen); struct sockaddr *sa, ev_socklen_t *socklen);
/* Parse comma-separated list of IP addresses. */
int parse_csv_address_list(const char *s, int family,
void *addrs, size_t addrs_size);
#endif /* REGRESS_TESTUTILS_H_INCLUDED_ */ #endif /* REGRESS_TESTUTILS_H_INCLUDED_ */