Do IP address resolution synchronously before printing in TShark.

Otherwise, the first N packets printed, for a value of N dependent on
various factors, won't get IP addresses resolved to names, even if the
user wants them resolved.  Unlike Wireshark, which, when a name is
resolved in asynchronously, can go back and fix up the displayed packet
when the asynchronous operation completes, once TShark has written
packet data to the standard output, it can't go back and fix it if it
did name resolution asynchronously.

Bug: 14711
Change-Id: I8ebbd83103e5780c903b5560e01b7b92fa39c924
Reviewed-on: https://code.wireshark.org/review/27668
Reviewed-by: Guy Harris <guy@alum.mit.edu>
This commit is contained in:
Guy Harris 2018-05-20 01:44:15 -07:00
parent 0f1f1d0ab8
commit 0542c5b700
3 changed files with 234 additions and 32 deletions

View File

@ -293,6 +293,7 @@ e_addr_resolve gbl_resolv_flags = {
};
#ifdef HAVE_C_ARES
static guint name_resolve_concurrency = 500;
static gboolean resolve_synchronously = FALSE;
#endif
/*
@ -318,7 +319,7 @@ gchar *g_penterprises_path = NULL; /* personal enterprises file */
/* c-ares */
#ifdef HAVE_C_ARES
/*
* Submitted queries trigger a callback (c_ares_ghba_cb()).
* Submitted asynchronous queries trigger a callback (c_ares_ghba_cb()).
* Queries are added to c_ares_queue_head. During processing, queries are
* popped off the front of c_ares_queue_head and submitted using
* ares_gethostbyaddr().
@ -339,12 +340,171 @@ typedef struct _async_hostent {
void *addrp;
} async_hostent_t;
/*
* Submitted synchronous queries trigger a callback (c_ares_ghba_sync_cb()).
* The callback processes the response, sets completed to TRUE if
* completed is non-NULL, then frees the request.
*/
typedef struct _sync_dns_data
{
union {
guint32 ip4;
ws_in6_addr ip6;
} addr;
int family;
gboolean *completed;
} sync_dns_data_t;
static ares_channel ghba_chan; /* ares_gethostbyaddr -- Usually non-interactive, no timeout */
static ares_channel ghbn_chan; /* ares_gethostbyname -- Usually interactive, timeout */
static gboolean async_dns_initialized = FALSE;
static guint async_dns_in_flight = 0;
static wmem_list_t *async_dns_queue_head = NULL;
static void
c_ares_ghba_sync_cb(void *arg, int status, int timeouts _U_, struct hostent *he) {
sync_dns_data_t *sdd = (sync_dns_data_t *)arg;
char **p;
if (status == ARES_SUCCESS) {
for (p = he->h_addr_list; *p != NULL; p++) {
switch(sdd->family) {
case AF_INET:
add_ipv4_name(sdd->addr.ip4, he->h_name);
break;
case AF_INET6:
add_ipv6_name(&sdd->addr.ip6, he->h_name);
break;
default:
/* Throw an exception? */
break;
}
}
}
/*
* Let our caller know that this is complete.
*/
*sdd->completed = TRUE;
/*
* Free the structure for this call.
*/
g_free(sdd);
}
static void
wait_for_sync_resolv(gboolean *completed) {
int nfds;
fd_set rfds, wfds;
struct timeval tv;
while (!*completed) {
/*
* Not yet resolved; wait for something to show up on the
* address-to-name C-ARES channel.
*
* To quote the source code for ares_timeout() as of C-ARES
* 1.12.0, "WARNING: Beware that this is linear in the number
* of outstanding requests! You are probably far better off
* just calling ares_process() once per second, rather than
* calling ares_timeout() to figure out when to next call
* ares_process().", although we should have only one request
* outstanding.
*
* And, yes, we have to reset it each time, as select(), in
* some OSes modifies the timeout to reflect the time remaining
* (e.g., Linux) and select() in other OSes doesn't (most if not
* all other UN*Xes, Windows?), so we can't rely on *either*
* behavior.
*/
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
nfds = ares_fds(ghba_chan, &rfds, &wfds);
if (nfds > 0) {
if (select(nfds, &rfds, &wfds, NULL, &tv) == -1) { /* call to select() failed */
fprintf(stderr, "Warning: call to select() failed, error is %s\n", g_strerror(errno));
return;
}
ares_process(ghba_chan, &rfds, &wfds);
}
}
}
static void
sync_lookup_ip4(const guint32 addr)
{
gboolean completed = FALSE;
sync_dns_data_t *sdd;
if (!async_dns_initialized) {
/*
* c-ares not initialized. Bail out.
*/
return;
}
/*
* Start the request.
*/
sdd = g_new(sync_dns_data_t, 1);
sdd->family = AF_INET;
sdd->addr.ip4 = addr;
sdd->completed = &completed;
ares_gethostbyaddr(ghba_chan, &addr, sizeof(guint32), AF_INET,
c_ares_ghba_sync_cb, sdd);
/*
* Now wait for it to finish.
*/
wait_for_sync_resolv(&completed);
}
static void
sync_lookup_ip6(const ws_in6_addr *addr)
{
gboolean completed = FALSE;
sync_dns_data_t *sdd;
if (!async_dns_initialized) {
/*
* c-ares not initialized. Bail out.
*/
return;
}
/*
* Start the request.
*/
sdd = g_new(sync_dns_data_t, 1);
sdd->family = AF_INET6;
memcpy(&sdd->addr.ip6, addr, sizeof(sdd->addr.ip6));
sdd->completed = &completed;
ares_gethostbyaddr(ghba_chan, &addr, sizeof(ws_in6_addr), AF_INET6,
c_ares_ghba_sync_cb, sdd);
/*
* Now wait for it to finish.
*/
wait_for_sync_resolv(&completed);
}
void
set_resolution_synchrony(gboolean synchronous)
{
resolve_synchronously = synchronous;
}
#else
void
set_resolution_synchrony(gboolean synchronous _U_)
{
/* Nothing to set. */
}
#endif /* HAVE_C_ARES */
typedef struct {
@ -849,9 +1009,6 @@ static hashipv4_t *
host_lookup(const guint addr)
{
hashipv4_t * volatile tp;
#ifdef HAVE_C_ARES
async_dns_queue_msg_t *caqm;
#endif
tp = (hashipv4_t *)wmem_map_lookup(ipv4_hash_table, GUINT_TO_POINTER(addr));
if (tp == NULL) {
@ -878,11 +1035,28 @@ host_lookup(const guint addr)
tp->flags |= TRIED_RESOLVE_ADDRESS;
#ifdef HAVE_C_ARES
if (async_dns_initialized && name_resolve_concurrency > 0) {
caqm = wmem_new(wmem_epan_scope(), async_dns_queue_msg_t);
caqm->family = AF_INET;
caqm->addr.ip4 = addr;
wmem_list_append(async_dns_queue_head, (gpointer) caqm);
if (async_dns_initialized) {
/* c-ares is initialized, so we can use it */
if (resolve_synchronously || name_resolve_concurrency == 0) {
/*
* Either all names are to be resolved synchronously or
* the concurrencly level is 0; do the resolution
* synchronously.
*/
sync_lookup_ip4(addr);
} else {
/*
* Names are to be resolved asynchronously, and we
* allow at least one asynchronous request in flight;
* post an asynchronous request.
*/
async_dns_queue_msg_t *caqm;
caqm = wmem_new(wmem_epan_scope(), async_dns_queue_msg_t);
caqm->family = AF_INET;
caqm->addr.ip4 = addr;
wmem_list_append(async_dns_queue_head, (gpointer) caqm);
}
}
#endif
}
@ -908,9 +1082,6 @@ static hashipv6_t *
host_lookup6(const ws_in6_addr *addr)
{
hashipv6_t * volatile tp;
#ifdef HAVE_C_ARES
async_dns_queue_msg_t *caqm;
#endif
tp = (hashipv6_t *)wmem_map_lookup(ipv6_hash_table, addr);
if (tp == NULL) {
@ -939,12 +1110,30 @@ host_lookup6(const ws_in6_addr *addr)
if (gbl_resolv_flags.use_external_net_name_resolver) {
tp->flags |= TRIED_RESOLVE_ADDRESS;
#ifdef HAVE_C_ARES
if (async_dns_initialized && name_resolve_concurrency > 0) {
caqm = wmem_new(wmem_epan_scope(), async_dns_queue_msg_t);
caqm->family = AF_INET6;
memcpy(&caqm->addr.ip6, addr, sizeof(caqm->addr.ip6));
wmem_list_append(async_dns_queue_head, (gpointer) caqm);
if (async_dns_initialized) {
/* c-ares is initialized, so we can use it */
if (resolve_synchronously || name_resolve_concurrency == 0) {
/*
* Either all names are to be resolved synchronously or
* the concurrencly level is 0; do the resolution
* synchronously.
*/
sync_lookup_ip6(addr);
} else {
/*
* Names are to be resolved asynchronously, and we
* allow at least one asynchronous request in flight;
* post an asynchronous request.
*/
async_dns_queue_msg_t *caqm;
caqm = wmem_new(wmem_epan_scope(), async_dns_queue_msg_t);
caqm->family = AF_INET6;
memcpy(&caqm->addr.ip6, addr, sizeof(caqm->addr.ip6));
wmem_list_append(async_dns_queue_head, (gpointer) caqm);
}
}
#endif
}

View File

@ -349,6 +349,14 @@ wmem_map_t *get_ipv4_hash_table(void);
WS_DLL_PUBLIC
wmem_map_t *get_ipv6_hash_table(void);
/*
* XXX - if we ever have per-session host name etc. information, we
* should probably have the "resolve synchronously or asynchronously"
* flag be per-session, set with an epan API.
*/
WS_DLL_PUBLIC
void set_resolution_synchrony(gboolean synchronous);
/*
* private functions (should only be called by epan directly)
*/

View File

@ -2466,6 +2466,13 @@ capture(void)
if (!ret)
return FALSE;
/*
* Force synchronous resolution of IP addresses; we're doing only
* one pass, so we can't do it in the background and fix up past
* dissections.
*/
set_resolution_synchrony(TRUE);
/* the actual capture loop
*
* XXX - glib doesn't seem to provide any event based loop handling.
@ -2905,11 +2912,6 @@ process_packet_first_pass(capture_file *cf, epan_dissect_t *edt,
from the dissection or running taps on the packet; if we're doing
any of that, we'll do it in the second pass.) */
if (edt) {
if (gbl_resolv_flags.mac_name || gbl_resolv_flags.network_name ||
gbl_resolv_flags.transport_name)
/* Grab any resolved addresses */
host_name_lookup_process();
/* If we're running a read filter, prime the epan_dissect_t with that
filter. */
if (cf->rfcode)
@ -2986,11 +2988,6 @@ process_packet_second_pass(capture_file *cf, epan_dissect_t *edt,
passes over the packets; that's the pass where we print
packet information or run taps.) */
if (edt) {
if (gbl_resolv_flags.mac_name || gbl_resolv_flags.network_name ||
gbl_resolv_flags.transport_name)
/* Grab any resolved addresses */
host_name_lookup_process();
/* If we're running a display filter, prime the epan_dissect_t with that
filter. */
if (cf->dfcode)
@ -3275,6 +3272,12 @@ process_cap_file(capture_file *cf, char *save_file, int out_file_type,
edt = epan_dissect_new(cf->epan, create_proto_tree, print_packet_info && print_details);
}
/*
* Force synchronous resolution of IP addresses; in this pass, we
* can't do it in the background and fix up past dissections.
*/
set_resolution_synchrony(TRUE);
for (framenum = 1; err == 0 && framenum <= cf->count; framenum++) {
fdata = frame_data_sequence_find(cf->provider.frames, framenum);
if (wtap_seek_read(cf->provider.wth, fdata->file_off, &rec, &buf, &err,
@ -3357,6 +3360,13 @@ process_cap_file(capture_file *cf, char *save_file, int out_file_type,
edt = epan_dissect_new(cf->epan, create_proto_tree, print_packet_info && print_details);
}
/*
* Force synchronous resolution of IP addresses; we're doing only
* one pass, so we can't do it in the background and fix up past
* dissections.
*/
set_resolution_synchrony(TRUE);
while (wtap_read(cf->provider.wth, &err, &err_info, &data_offset)) {
framenum++;
@ -3498,11 +3508,6 @@ process_packet_single_pass(capture_file *cf, epan_dissect_t *edt, gint64 offset,
over the packets, so, if we'll be printing packet information
or running taps, we'll be doing it here.) */
if (edt) {
if (print_packet_info && (gbl_resolv_flags.mac_name || gbl_resolv_flags.network_name ||
gbl_resolv_flags.transport_name))
/* Grab any resolved addresses */
host_name_lookup_process();
/* If we're running a filter, prime the epan_dissect_t with that
filter. */
if (cf->dfcode)