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

141
dumpcap.c
View File

@ -78,6 +78,7 @@
#include "wsutil/time_util.h" #include "wsutil/time_util.h"
#include "wsutil/please_report_bug.h" #include "wsutil/please_report_bug.h"
#include "wsutil/glib-compat.h" #include "wsutil/glib-compat.h"
#include <wsutil/json_dumper.h>
#include <wsutil/ws_assert.h> #include <wsutil/ws_assert.h>
#include "capture/ws80211_utils.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! * you MUST update capture_ifinfo.c:capture_get_if_capabilities() accordingly!
*/ */
static void 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; GList *lt_entry, *ts_entry;
const gchar *desc_str; 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 (queries & CAPS_QUERY_LINK_TYPES) {
if (caps->can_set_rfmon) json_dumper_set_member_name(dumper, "rfmon");
printf("1\n"); // XXX: wsjson.h doesn't have a function to read booleans
else json_dumper_value_anyf(dumper, "%s", caps->can_set_rfmon ? "true" : "false");
printf("0\n"); json_dumper_set_member_name(dumper, "data_link_types");
json_dumper_begin_array(dumper);
for (lt_entry = caps->data_link_types; lt_entry != NULL; for (lt_entry = caps->data_link_types; lt_entry != NULL;
lt_entry = g_list_next(lt_entry)) { lt_entry = g_list_next(lt_entry)) {
data_link_info_t *data_link_info = (data_link_info_t *)lt_entry->data; 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; desc_str = data_link_info->description;
else else
desc_str = "(not supported)"; desc_str = "(not supported)";
printf("%d\t%s\t%s\n", data_link_info->dlt, data_link_info->name, json_dumper_begin_object(dumper);
desc_str); 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) { 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; for (ts_entry = caps->timestamp_types; ts_entry != NULL;
ts_entry = g_list_next(ts_entry)) { ts_entry = g_list_next(ts_entry)) {
timestamp_info_t *timestamp = (timestamp_info_t *)ts_entry->data; 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; desc_str = timestamp->description;
else else
desc_str = "(none)"; 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; gchar *open_status_str;
guint ii; guint ii;
for (ii = 0; ii < global_capture_opts.ifaces->len; ii++) { if (machine_readable) {
interface_options *interface_opts; 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); json_dumper_begin_object(&dumper);
if (caps == NULL) { json_dumper_set_member_name(&dumper, interface_opts->name);
if (capture_child) {
char *error_msg = ws_strdup_printf("The capabilities of the capture device " json_dumper_begin_object(&dumper);
"\"%s\" could not be obtained (%s)",
interface_opts->name, open_status_str); open_status = CAP_DEVICE_OPEN_NO_ERR;
sync_pipe_write_errmsgs_to_parent(2, error_msg, caps = get_if_capabilities(interface_opts, &open_status, &open_status_str);
get_pcap_failure_secondary_error_message(open_status, open_status_str)); json_dumper_set_member_name(&dumper, "status");
g_free(error_msg); 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 { json_dumper_end_object(&dumper);
cmdarg_err("The capabilities of the capture device " json_dumper_end_object(&dumper);
"\"%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_array(&dumper);
if (machine_readable) { /* tab-separated values to stdout */ if (json_dumper_finish(&dumper)) {
/* XXX: We need to change the format and adapt consumers */
print_machine_readable_if_capabilities(caps, caps_queries);
status = 0; 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 */ /* XXX: We might want to print also the interface name */
status = capture_opts_print_if_capabilities(caps, status = capture_opts_print_if_capabilities(caps,
interface_opts, interface_opts,
caps_queries); caps_queries);
free_if_capabilities(caps); free_if_capabilities(caps);
if (status != 0) if (status != 0)
break; break;
}
} }
exit_main(status); exit_main(status);
} }