dumpcap: Serialize machine readable interface caps as JSON

Serialize the machine readable version of the interface capability
output as JSON, using an array to support multiple interfaces.
When querying multiple interfaces, try all of them, exit with
success (unless unexpected errors occur) and report any per-interface
errors and exit codes inside the JSON rather than stopping after
the first interface with error.

Update capture_get_if_capabilities to process the JSON. It (and
sync_if_capabilities_open) still only query a single interface at,
but this will allow modification to handle multiple interfaces at
once later.

Related to #16191, #15082
This commit is contained in:
John Thacker 2023-11-21 08:31:26 -05:00 committed by AndersBroman
parent 212cfe132c
commit 0d93782443
2 changed files with 210 additions and 102 deletions

View File

@ -26,6 +26,7 @@
#include <capture/capture_ifinfo.h>
#include <wsutil/inet_addr.h>
#include <wsutil/wsjson.h>
#ifdef HAVE_PCAP_REMOTE
static GList *remote_interface_list = NULL;
@ -199,8 +200,8 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
if_capabilities_t *caps;
GList *linktype_list = NULL, *timestamp_list = NULL;
int err, i;
char *data, *primary_msg, *secondary_msg;
char **raw_list;
char *data, *primary_msg, *secondary_msg, *val_s;
jsmntok_t *tokens, *inf_tok, *array_tok, *cur_tok;
/* see if the interface is from extcap */
caps = extcap_get_if_dlts(ifname, err_primary_msg);
@ -228,23 +229,81 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
return NULL;
}
/* Split our lines */
#ifdef _WIN32
raw_list = g_strsplit(data, "\r\n", 0);
#else
raw_list = g_strsplit(data, "\n", 0);
#endif
g_free(data);
int num_tokens = json_parse(data, NULL, 0);
if (num_tokens <= 0) {
ws_warning("Capture Interface Capabilities failed with invalid JSON.");
g_free(data);
return NULL;
}
/*
* First line is 0 if monitor mode isn't supported, 1 if it is.
*/
if (raw_list[0] == NULL || *raw_list[0] == '\0') {
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 = g_strdup("Dumpcap returned no interface capability information");
}
g_strfreev(raw_list);
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
cur_tok = json_get_array_index(tokens, 0);
if (cur_tok == NULL) {
ws_warning("Capture Interface Capabilities failed with invalid JSON.");
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
inf_tok = json_get_object(data, cur_tok, ifname);
if (inf_tok == NULL) {
ws_warning("Capture Interface Capabilities failed with invalid JSON.");
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
double val_d;
if (!json_get_double(data, inf_tok, "status", &val_d)) {
ws_warning("Capture Interface Capabilities failed with invalid JSON.");
wmem_free(NULL, tokens);
g_free(data);
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);
wmem_free(NULL, tokens);
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");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap didn't return monitor-mode capability");
}
wmem_free(NULL, tokens);
g_free(data);
return NULL;
}
@ -252,75 +311,67 @@ capture_get_if_capabilities(const char *ifname, bool monitor_mode,
* Allocate the interface capabilities structure.
*/
caps = (if_capabilities_t *)g_malloc(sizeof *caps);
switch (*raw_list[0]) {
case '0':
caps->can_set_rfmon = false;
break;
case '1':
caps->can_set_rfmon = true;
break;
default:
ws_info("Capture Interface Capabilities returned bad information.");
if (err_primary_msg) {
*err_primary_msg = ws_strdup_printf("Dumpcap returned \"%s\" for monitor-mode capability",
raw_list[0]);
}
g_free(caps);
g_strfreev(raw_list);
return NULL;
}
caps->can_set_rfmon = rfmon;
/*
* The following are link-layer types.
*/
for (i = 1; raw_list[i] != NULL && *raw_list[i] != '\0'; i++) {
data_link_info_t *data_link_info;
/* ...and what if the interface name has a tab in it, Mr. Clever Programmer? */
char **lt_parts = g_strsplit(raw_list[i], "\t", 3);
if (lt_parts[0] == NULL || lt_parts[1] == NULL || lt_parts[2] == NULL) {
g_strfreev(lt_parts);
array_tok = json_get_array(data, inf_tok, "data_link_types");
if (!array_tok) {
ws_message("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) strtol(lt_parts[0], NULL, 10);
data_link_info->name = g_strdup(lt_parts[1]);
if (strcmp(lt_parts[2], "(not supported)") != 0)
data_link_info->description = g_strdup(lt_parts[2]);
else
data_link_info->description = NULL;
g_strfreev(lt_parts);
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);
}
if (raw_list[i]) { /* Oh, timestamp types! */
for (i++; raw_list[i] != NULL && *raw_list[i] != '\0'; i++) {
timestamp_info_t *timestamp_info;
char **tt_parts = g_strsplit(raw_list[i], "\t", 2);
if (tt_parts[0] == NULL || tt_parts[1] == NULL) {
g_strfreev(tt_parts);
continue;
}
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);
timestamp_info->name = g_strdup(tt_parts[0]);
timestamp_info->description = g_strdup(tt_parts[1]);
g_strfreev(tt_parts);
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);
}
}
g_strfreev(raw_list);
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;
}

141
dumpcap.c
View File

@ -78,6 +78,7 @@
#include "wsutil/time_util.h"
#include "wsutil/please_report_bug.h"
#include "wsutil/glib-compat.h"
#include <wsutil/json_dumper.h>
#include <wsutil/ws_assert.h>
#include "capture/ws80211_utils.h"
@ -1089,21 +1090,17 @@ print_machine_readable_interfaces(GList *if_list)
* you MUST update capture_ifinfo.c:capture_get_if_capabilities() accordingly!
*/
static void
print_machine_readable_if_capabilities(if_capabilities_t *caps, int queries)
print_machine_readable_if_capabilities(json_dumper *dumper, if_capabilities_t *caps, int queries)
{
GList *lt_entry, *ts_entry;
const gchar *desc_str;
if (capture_child) {
/* Let our parent know we succeeded. */
sync_pipe_write_string_msg(2, SP_SUCCESS, NULL);
}
if (queries & CAPS_QUERY_LINK_TYPES) {
if (caps->can_set_rfmon)
printf("1\n");
else
printf("0\n");
json_dumper_set_member_name(dumper, "rfmon");
// XXX: wsjson.h doesn't have a function to read booleans
json_dumper_value_anyf(dumper, "%s", caps->can_set_rfmon ? "true" : "false");
json_dumper_set_member_name(dumper, "data_link_types");
json_dumper_begin_array(dumper);
for (lt_entry = caps->data_link_types; lt_entry != NULL;
lt_entry = g_list_next(lt_entry)) {
data_link_info_t *data_link_info = (data_link_info_t *)lt_entry->data;
@ -1111,12 +1108,20 @@ print_machine_readable_if_capabilities(if_capabilities_t *caps, int queries)
desc_str = data_link_info->description;
else
desc_str = "(not supported)";
printf("%d\t%s\t%s\n", data_link_info->dlt, data_link_info->name,
desc_str);
json_dumper_begin_object(dumper);
json_dumper_set_member_name(dumper, "dlt");
json_dumper_value_anyf(dumper, "%d", data_link_info->dlt);
json_dumper_set_member_name(dumper, "name");
json_dumper_value_string(dumper, data_link_info->name);
json_dumper_set_member_name(dumper, "description");
json_dumper_value_string(dumper, desc_str);
json_dumper_end_object(dumper);
}
json_dumper_end_array(dumper);
}
printf("\n");
if (queries & CAPS_QUERY_TIMESTAMP_TYPES) {
json_dumper_set_member_name(dumper, "timestamp_types");
json_dumper_begin_array(dumper);
for (ts_entry = caps->timestamp_types; ts_entry != NULL;
ts_entry = g_list_next(ts_entry)) {
timestamp_info_t *timestamp = (timestamp_info_t *)ts_entry->data;
@ -1124,8 +1129,14 @@ print_machine_readable_if_capabilities(if_capabilities_t *caps, int queries)
desc_str = timestamp->description;
else
desc_str = "(none)";
printf("%s\t%s\n", timestamp->name, desc_str);
json_dumper_begin_object(dumper);
json_dumper_set_member_name(dumper, "name");
json_dumper_value_string(dumper, timestamp->name);
json_dumper_set_member_name(dumper, "description");
json_dumper_value_string(dumper, desc_str);
json_dumper_end_object(dumper);
}
json_dumper_end_array(dumper);
}
}
@ -5870,43 +5881,89 @@ main(int argc, char *argv[])
gchar *open_status_str;
guint ii;
for (ii = 0; ii < global_capture_opts.ifaces->len; ii++) {
interface_options *interface_opts;
if (machine_readable) {
status = 0;
json_dumper dumper = {
.output_file = stdout,
.flags = JSON_DUMPER_FLAGS_NO_DEBUG,
// Don't abort on failure
};
json_dumper_begin_array(&dumper);
for (ii = 0; ii < global_capture_opts.ifaces->len; ii++) {
interface_options *interface_opts;
interface_opts = &g_array_index(global_capture_opts.ifaces, interface_options, ii);
interface_opts = &g_array_index(global_capture_opts.ifaces, interface_options, ii);
caps = get_if_capabilities(interface_opts, &open_status, &open_status_str);
if (caps == NULL) {
if (capture_child) {
char *error_msg = ws_strdup_printf("The capabilities of the capture device "
"\"%s\" could not be obtained (%s)",
interface_opts->name, open_status_str);
sync_pipe_write_errmsgs_to_parent(2, error_msg,
get_pcap_failure_secondary_error_message(open_status, open_status_str));
g_free(error_msg);
json_dumper_begin_object(&dumper);
json_dumper_set_member_name(&dumper, interface_opts->name);
json_dumper_begin_object(&dumper);
open_status = CAP_DEVICE_OPEN_NO_ERR;
caps = get_if_capabilities(interface_opts, &open_status, &open_status_str);
json_dumper_set_member_name(&dumper, "status");
json_dumper_value_anyf(&dumper, "%i", open_status);
if (caps == NULL) {
json_dumper_set_member_name(&dumper, "primary_msg");
json_dumper_value_string(&dumper, open_status_str);
json_dumper_set_member_name(&dumper, "secondary_msg");
json_dumper_value_string(&dumper, get_pcap_failure_secondary_error_message(open_status, open_status_str));
g_free(open_status_str);
} else {
/* XXX: We need to change the format and adapt consumers */
print_machine_readable_if_capabilities(&dumper, caps, caps_queries);
free_if_capabilities(caps);
}
else {
cmdarg_err("The capabilities of the capture device "
"\"%s\" could not be obtained (%s).\n%s",
interface_opts->name, open_status_str,
get_pcap_failure_secondary_error_message(open_status, open_status_str));
}
g_free(open_status_str);
exit_main(2);
json_dumper_end_object(&dumper);
json_dumper_end_object(&dumper);
}
if (machine_readable) { /* tab-separated values to stdout */
/* XXX: We need to change the format and adapt consumers */
print_machine_readable_if_capabilities(caps, caps_queries);
json_dumper_end_array(&dumper);
if (json_dumper_finish(&dumper)) {
status = 0;
} else
if (capture_child) {
/* Let our parent know we succeeded. */
sync_pipe_write_string_msg(2, SP_SUCCESS, NULL);
}
} else {
status = 2;
if (capture_child) {
sync_pipe_write_errmsgs_to_parent(2, "Unexpected JSON error", "");
}
}
} else {
for (ii = 0; ii < global_capture_opts.ifaces->len; ii++) {
interface_options *interface_opts;
interface_opts = &g_array_index(global_capture_opts.ifaces, interface_options, ii);
caps = get_if_capabilities(interface_opts, &open_status, &open_status_str);
if (caps == NULL) {
if (capture_child) {
char *error_msg = ws_strdup_printf("The capabilities of the capture device "
"\"%s\" could not be obtained (%s)",
interface_opts->name, open_status_str);
sync_pipe_write_errmsgs_to_parent(2, error_msg,
get_pcap_failure_secondary_error_message(open_status, open_status_str));
g_free(error_msg);
}
else {
cmdarg_err("The capabilities of the capture device "
"\"%s\" could not be obtained (%s).\n%s",
interface_opts->name, open_status_str,
get_pcap_failure_secondary_error_message(open_status, open_status_str));
}
g_free(open_status_str);
exit_main(2);
}
/* XXX: We might want to print also the interface name */
status = capture_opts_print_if_capabilities(caps,
interface_opts,
caps_queries);
free_if_capabilities(caps);
if (status != 0)
break;
free_if_capabilities(caps);
if (status != 0)
break;
}
}
exit_main(status);
}