Capture: Get our capabilities list in one dumpcap call

Instead of calling dumpcap separately for each interface in
the list, make one dumpcap call.

There's still two calls, one to get the list of interfaces and
one to get the capabilities, which is partly because interfaces
that support monitor mode can indicate support for different
link-layer types depending on whether monitor mode is enabled,
and we have to check per-interface preferences for the name to
see if we want monitor mode.

This roughly doubles the speed to add interfaces at startup
in my testing on Windows and Linux, and should massively
reduce the number of UAC pop-ups when npcap is installed with
restrictions to administrative access.

Fix #16191. Related to #15082 (it improves the number of UACs,
but perhaps they could be reduced even further by having dumpcap
stay open for all the calls in the life of the program.)
This commit is contained in:
John Thacker 2023-11-23 07:22:31 -05:00
parent a8586fde3a
commit 697f37cf2b
6 changed files with 288 additions and 97 deletions

View File

@ -189,8 +189,122 @@ capture_interface_list(int *err, char **err_str, void (*update_cb)(void))
return if_list;
}
/* XXX - We parse simple text output to get our interface list. Should
* we use "real" data serialization instead, e.g. via XML? */
static if_capabilities_t *
deserialize_if_capability(char* data, jsmntok_t *inf_tok,
char **err_primary_msg, char **err_secondary_msg)
{
if_capabilities_t *caps;
GList *linktype_list = NULL, *timestamp_list = NULL;
int err, i;
char *primary_msg, *secondary_msg, *val_s;
jsmntok_t *array_tok, *cur_tok;
if (inf_tok == NULL) {
ws_info("Capture Interface Capabilities failed with invalid JSON.");
return NULL;
}
double val_d;
if (!json_get_double(data, inf_tok, "status", &val_d)) {
ws_info("Capture Interface Capabilities failed with invalid JSON.");
return NULL;
}
err = (int)val_d;
if (err != 0) {
primary_msg = json_get_string(data, inf_tok, "primary_msg");
if (primary_msg) {
primary_msg = g_strdup(primary_msg);
}
secondary_msg = json_get_string(data, inf_tok, "secondary_msg");
if (secondary_msg) {
secondary_msg = g_strdup(secondary_msg);
}
ws_info("Capture Interface Capabilities failed. Error %d, %s",
err, primary_msg ? primary_msg : "no message");
if (err_primary_msg)
*err_primary_msg = primary_msg;
else
g_free(primary_msg);
if (err_secondary_msg)
*err_secondary_msg = secondary_msg;
else
g_free(secondary_msg);
return NULL;
}
bool rfmon;
if (!json_get_boolean(data, inf_tok, "rfmon", &rfmon)) {
ws_message("Capture Interface Capabilities returned bad information.");
ws_message("Didn't return monitor-mode cap");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap didn't return monitor-mode capability");
}
return NULL;
}
/*
* Allocate the interface capabilities structure.
*/
caps = (if_capabilities_t *)g_malloc(sizeof *caps);
caps->can_set_rfmon = rfmon;
/*
* The following are link-layer types.
*/
array_tok = json_get_array(data, inf_tok, "data_link_types");
if (!array_tok) {
ws_info("Capture Interface Capabilities returned bad data_link information.");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap didn't return data link types capability");
}
g_free(caps);
return NULL;
}
for (i = 0; i < json_get_array_len(array_tok); i++) {
cur_tok = json_get_array_index(array_tok, i);
if (!json_get_double(data, cur_tok, "dlt", &val_d)) {
continue;
}
data_link_info_t *data_link_info;
data_link_info = g_new(data_link_info_t,1);
data_link_info->dlt = (int)val_d;
val_s = json_get_string(data, cur_tok, "name");
data_link_info->name = val_s ? g_strdup(val_s) : NULL;
val_s = json_get_string(data, cur_tok, "description");
if (!val_s || strcmp(val_s, "(not supported)") == 0) {
data_link_info->description = NULL;
} else {
data_link_info->description = g_strdup(val_s);
}
linktype_list = g_list_append(linktype_list, data_link_info);
}
array_tok = json_get_array(data, inf_tok, "timestamp_types");
if (array_tok) {
for (i = 0; i < json_get_array_len(array_tok); i++) {
cur_tok = json_get_array_index(array_tok, i);
timestamp_info_t *timestamp_info;
timestamp_info = g_new(timestamp_info_t,1);
val_s = json_get_string(data, cur_tok, "name");
timestamp_info->name = val_s ? g_strdup(val_s) : NULL;
val_s = json_get_string(data, cur_tok, "description");
timestamp_info->description = val_s ? g_strdup(val_s) : NULL;
timestamp_list = g_list_append(timestamp_list, timestamp_info);
}
}
caps->data_link_types = linktype_list;
/* Might be NULL. Not all systems report timestamp types */
caps->timestamp_types = timestamp_list;
return caps;
}
if_capabilities_t *
capture_get_if_capabilities(const char *ifname, bool monitor_mode,
const char *auth_string,
@ -198,10 +312,9 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
void (*update_cb)(void))
{
if_capabilities_t *caps;
GList *linktype_list = NULL, *timestamp_list = NULL;
int err, i;
char *data, *primary_msg, *secondary_msg, *val_s;
jsmntok_t *tokens, *inf_tok, *array_tok, *cur_tok;
int err;
char *data, *primary_msg, *secondary_msg;
jsmntok_t *tokens, *inf_tok;
/* see if the interface is from extcap */
caps = extcap_get_if_dlts(ifname, err_primary_msg);
@ -252,30 +365,48 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
inf_tok = json_get_object(data, inf_tok, ifname);
}
if (inf_tok == NULL) {
ws_info("Capture Interface Capabilities failed with invalid JSON.");
wmem_free(NULL, tokens);
g_free(data);
return NULL;
caps = deserialize_if_capability(data, inf_tok, err_primary_msg, err_secondary_msg);
wmem_free(NULL, tokens);
g_free(data);
return caps;
}
GHashTable*
capture_get_if_list_capabilities(GList *if_cap_queries,
char **err_primary_msg, char **err_secondary_msg,
void (*update_cb)(void))
{
if_cap_query_t *query;
if_capabilities_t *caps;
GHashTable *caps_hash;
GList *local_queries = NULL;
int err, i;
char *data, *primary_msg, *secondary_msg;
jsmntok_t *tokens, *inf_tok;
caps_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
for (GList *li = if_cap_queries; li != NULL; li = g_list_next(li)) {
query = (if_cap_query_t *)li->data;
/* see if the interface is from extcap */
caps = extcap_get_if_dlts(query->name, err_primary_msg);
/* if the extcap interface generated an error, it was from extcap */
if (caps != NULL || (err_primary_msg != NULL && *err_primary_msg != NULL)) {
g_hash_table_replace(caps_hash, g_strdup(query->name), caps);
} else {
local_queries = g_list_prepend(local_queries, query);
}
}
double val_d;
if (!json_get_double(data, inf_tok, "status", &val_d)) {
ws_info("Capture Interface Capabilities failed with invalid JSON.");
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
err = (int)val_d;
local_queries = g_list_reverse(local_queries);
/* Try to get our interface list */
err = sync_if_list_capabilities_open(local_queries, &data,
&primary_msg, &secondary_msg, update_cb);
g_list_free(local_queries);
if (err != 0) {
primary_msg = json_get_string(data, inf_tok, "primary_msg");
if (primary_msg) {
primary_msg = g_strdup(primary_msg);
}
secondary_msg = json_get_string(data, inf_tok, "secondary_msg");
if (secondary_msg) {
secondary_msg = g_strdup(secondary_msg);
}
ws_info("Capture Interface Capabilities failed. Error %d, %s",
err, primary_msg ? primary_msg : "no message");
if (err_primary_msg)
@ -286,89 +417,44 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
*err_secondary_msg = secondary_msg;
else
g_free(secondary_msg);
wmem_free(NULL, tokens);
return NULL;
}
int num_tokens = json_parse(data, NULL, 0);
if (num_tokens <= 0) {
ws_info("Capture Interface Capabilities failed with invalid JSON.");
g_free(data);
return NULL;
}
bool rfmon;
if (!json_get_boolean(data, inf_tok, "rfmon", &rfmon)) {
ws_message("Capture Interface Capabilities returned bad information.");
ws_message("Didn't return monitor-mode cap");
tokens = wmem_alloc_array(NULL, jsmntok_t, num_tokens);
if (json_parse(data, tokens, num_tokens) <= 0) {
ws_info("Capture Interface Capabilities returned no information.");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap didn't return monitor-mode capability");
*err_primary_msg = g_strdup("Dumpcap returned no interface capability information");
}
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
/*
* Allocate the interface capabilities structure.
*/
caps = (if_capabilities_t *)g_malloc(sizeof *caps);
caps->can_set_rfmon = rfmon;
/*
* The following are link-layer types.
*/
array_tok = json_get_array(data, inf_tok, "data_link_types");
if (!array_tok) {
ws_info("Capture Interface Capabilities returned bad data_link information.");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap didn't return data link types capability");
}
wmem_free(NULL, tokens);
g_free(data);
g_free(caps);
return NULL;
}
for (i = 0; i < json_get_array_len(array_tok); i++) {
cur_tok = json_get_array_index(array_tok, i);
if (!json_get_double(data, cur_tok, "dlt", &val_d)) {
continue;
}
data_link_info_t *data_link_info;
data_link_info = g_new(data_link_info_t,1);
data_link_info->dlt = (int)val_d;
val_s = json_get_string(data, cur_tok, "name");
data_link_info->name = val_s ? g_strdup(val_s) : NULL;
val_s = json_get_string(data, cur_tok, "description");
if (!val_s || strcmp(val_s, "(not supported)") == 0) {
data_link_info->description = NULL;
} else {
data_link_info->description = g_strdup(val_s);
}
linktype_list = g_list_append(linktype_list, data_link_info);
}
array_tok = json_get_array(data, inf_tok, "timestamp_types");
if (array_tok) {
for (i = 0; i < json_get_array_len(array_tok); i++) {
cur_tok = json_get_array_index(array_tok, i);
timestamp_info_t *timestamp_info;
timestamp_info = g_new(timestamp_info_t,1);
val_s = json_get_string(data, cur_tok, "name");
timestamp_info->name = val_s ? g_strdup(val_s) : NULL;
val_s = json_get_string(data, cur_tok, "description");
timestamp_info->description = val_s ? g_strdup(val_s) : NULL;
timestamp_list = g_list_append(timestamp_list, timestamp_info);
char *ifname;
for (i = 0; i < json_get_array_len(tokens); i++) {
inf_tok = json_get_array_index(tokens, i);
if (inf_tok && inf_tok->type == JSMN_OBJECT) {
inf_tok++; // Key
ifname = g_strndup(&data[inf_tok->start], inf_tok->end - inf_tok->start);
if (!json_decode_string_inplace(ifname)) continue;
inf_tok++;
caps = deserialize_if_capability(data, inf_tok, err_primary_msg, err_secondary_msg);
g_hash_table_replace(caps_hash, ifname, caps);
}
}
caps->data_link_types = linktype_list;
/* Might be NULL. Not all systems report timestamp types */
caps->timestamp_types = timestamp_list;
wmem_free(NULL, tokens);
g_free(data);
return caps;
return caps_hash;
}
#ifdef HAVE_PCAP_REMOTE

View File

@ -105,6 +105,12 @@ typedef struct {
GList *timestamp_types; /* GList of timestamp_info_t's */
} if_capabilities_t;
typedef struct {
const char *name;
bool monitor_mode;
const char *auth;
} if_cap_query_t;
/*
* Information about data link types.
*/
@ -131,6 +137,14 @@ capture_get_if_capabilities(const char *devname, bool monitor_mode,
char **err_primary_msg, char **err_secondary_msg,
void (*update_cb)(void));
/**
* Fetch the linktype list for the specified interface from a child process.
*/
extern GHashTable *
capture_get_if_list_capabilities(GList *if_cap_queries,
char **err_primary_msg, char **err_secondary_msg,
void (*update_cb)(void));
void free_if_capabilities(if_capabilities_t *caps);
void add_interface_to_remote_list(if_info_t *if_info);

View File

@ -1357,6 +1357,51 @@ sync_if_capabilities_open(const char *ifname, bool monitor_mode, const char* aut
return ret;
}
int
sync_if_list_capabilities_open(GList *if_queries,
char **data, char **primary_msg,
char **secondary_msg, void (*update_cb)(void))
{
int argc;
char **argv;
int ret;
if_cap_query_t *if_cap_query;
ws_debug("sync_if_list_capabilities_open");
argv = init_pipe_args(&argc);
if (!argv) {
*primary_msg = g_strdup("We don't know where to find dumpcap.");
*secondary_msg = NULL;
*data = NULL;
return -1;
}
for (GList *li = if_queries; li != NULL; li = g_list_next(li)) {
if_cap_query = (if_cap_query_t*)li->data;
/* Ask for the interface capabilities */
argv = sync_pipe_add_arg(argv, &argc, "-i");
argv = sync_pipe_add_arg(argv, &argc, if_cap_query->name);
if (if_cap_query->monitor_mode)
argv = sync_pipe_add_arg(argv, &argc, "-I");
if (if_cap_query->auth) {
argv = sync_pipe_add_arg(argv, &argc, "-A");
argv = sync_pipe_add_arg(argv, &argc, if_cap_query->auth);
}
}
argv = sync_pipe_add_arg(argv, &argc, "-L");
argv = sync_pipe_add_arg(argv, &argc, "--list-time-stamp-types");
#ifndef DEBUG_CHILD
/* Run dumpcap in capture child mode */
argv = sync_pipe_add_arg(argv, &argc, "-Z");
argv = sync_pipe_add_arg(argv, &argc, SIGNAL_PIPE_CTRL_ID_NONE);
#endif
ret = sync_pipe_run_command(argv, data, primary_msg, secondary_msg, update_cb);
free_argv(argv, argc);
return ret;
}
/*
* Start getting interface statistics using dumpcap. On success, read_fd
* contains the file descriptor for the pipe's stdout, *msg is unchanged,

View File

@ -94,6 +94,11 @@ sync_if_capabilities_open(const char *ifname, bool monitor_mode, const char* aut
char **data, char **primary_msg,
char **secondary_msg, void (*update_cb)(void));
extern int
sync_if_list_capabilities_open(GList *ifqueries,
char **data, char **primary_msg,
char **secondary_msg, void (*update_cb)(void));
/** Start getting interface statistics using dumpcap. */
extern int
sync_interface_stats_open(int *read_fd, ws_process_id *fork_child, char **msg, void (*update_cb)(void));

View File

@ -93,6 +93,10 @@ The following features are new (or have been significantly updated) since versio
last session, instead of every remote host that the current profile has ever
connected to. wsbuglink:17484[]
* Adding interfaces at startup is about twice as fast, and has many fewer
UAC pop-ups when npcap is installed with access restricted to Administrators
on Windows
//=== Removed Features and Support
// === Removed Dissectors

View File

@ -198,12 +198,48 @@ scan_local_interfaces_filtered(GList * allowed_types, void (*update_cb)(void))
if_list = capture_interface_list(&global_capture_opts.ifaces_err,
&global_capture_opts.ifaces_err_info,
update_cb);
count = 0;
/*
* For each discovered interface name, look up its list of capabilities.
* (if it supports monitor mode, supported DLTs, assigned IP addresses).
* Do this all at once to reduce the number of spawned privileged dumpcap
* processes.
* It might be even better to get this information when getting the list,
* but some devices can support different DLTs depending on whether
* monitor mode is enabled, and we have to look up the monitor mode pref.
*/
GList *if_cap_queries = NULL;
if_cap_query_t *if_cap_query;
GHashTable *capability_hash;
for (if_entry = if_list; if_entry != NULL; if_entry = g_list_next(if_entry)) {
if_info = (if_info_t *)if_entry->data;
if (strstr(if_info->name, "rpcap:")) {
continue;
}
/* Filter out all interfaces which are not allowed to be scanned */
if (allowed_types != NULL)
{
if(g_list_find(allowed_types, GUINT_TO_POINTER((guint) if_info->type)) == NULL) {
continue;
}
}
if_cap_query = g_new(if_cap_query_t, 1);
if_cap_query->name = if_info->name;
if_cap_query->monitor_mode = prefs_capture_device_monitor_mode(if_info->name);
if_cap_query->auth = NULL;
if_cap_queries = g_list_prepend(if_cap_queries, if_cap_query);
}
if_cap_queries = g_list_reverse(if_cap_queries);
capability_hash = capture_get_if_list_capabilities(if_cap_queries, NULL, NULL, update_cb);
/* The if_info->name are not copied, so we can just free the
* if_cap_query_t's and not their members. */
g_list_free_full(if_cap_queries, g_free);
/*
* For each discovered interface name, create a new device and add extra
* information (like supported DLTs, assigned IP addresses).
* information (including the capabilities we retrieved above).
*/
count = 0;
for (if_entry = if_list; if_entry != NULL; if_entry = g_list_next(if_entry)) {
memset(&device, 0, sizeof(device));
if_info = (if_info_t *)if_entry->data;
@ -211,7 +247,7 @@ scan_local_interfaces_filtered(GList * allowed_types, void (*update_cb)(void))
if (strstr(if_info->name, "rpcap:")) {
continue;
}
/* Filter out all interfaces, which are not allowed to be scanned */
/* Filter out all interfaces which are not allowed to be scanned */
if (allowed_types != NULL)
{
if(g_list_find(allowed_types, GUINT_TO_POINTER((guint) if_info->type)) == NULL) {
@ -242,7 +278,7 @@ scan_local_interfaces_filtered(GList * allowed_types, void (*update_cb)(void))
}
device.type = if_info->type;
monitor_mode = prefs_capture_device_monitor_mode(if_info->name);
caps = capture_get_if_capabilities(if_info->name, monitor_mode, NULL, NULL, NULL, update_cb);
caps = g_hash_table_lookup(capability_hash, if_info->name);
ip_str = g_string_new("");
for (; (curr_addr = g_slist_nth(if_info->addrs, ips)) != NULL; ips++) {
temp_addr = g_new0(if_addr_t, 1);
@ -376,6 +412,7 @@ scan_local_interfaces_filtered(GList * allowed_types, void (*update_cb)(void))
count++;
}
g_hash_table_destroy(capability_hash);
free_interface_list(if_list);
/*