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:
parent
a8586fde3a
commit
697f37cf2b
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue