You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

440 lines
16 KiB

JSON Reply Management API - feature parity with old management interfaces (#861) * Ensure that recent code additions pass the linter * Include some of the more obviously correct lint fixes to edge_utils.c * Refactor edge JSON api into its own source file * Use shorter names for static management functions * Implement a JSON RPC way of managing the verbosity * Tidy up help display in n2nctl script * Make note of issue with implementing the stop command * Implement a JSON RPC call to fetch current community * Make n2nhttpd time value be more self-contained * Make n2nhttpd order more closely match the existing management stats output * Wire up status page to the verbosity setting * Add JSON versions of the remainder of the edge management stats * Add new file to cmake * Properly define management handler * Only update the last updated timestamp after a successful data fetch * Function and types definition cleanup * Force correct type for python scripts mgmt port * Implement initial JSON API for supernode * Fix whitespace error * Use helper function for rendering peers ip4 address * Proxy the auth requirement back out to the http client, allowing normal http auth to be used * Ensure that we do not leak the federation community * Use the same rpc method name and output for both edge and supernode for peers/edges * Allow n2nctl to show raw data returned without resorting to tricks * Make n2nctl pretty printer understandable with an empty table * Use the full name for supernodes RPC call * Use same RPC method name (but some missing fields) for getting communities from both edge and supernode * Add *_sup_broadcast stats to edge packet stats output * Refacter the stats into a packetstats method for supernode RPC * Even if I am not going to prettyprint the timestamps, at least make all the timestamps on the page the same unit * Simplify the RPC handlers by flagging some as writable and checking that in the multiplexer * Remove invalid edges data * Avoid crash on bad data to verbose RPC * Avoid showing bad or inconsistant protocol data in communities RPC * Minor clarification on when --write is handled * Make linter happy * Fix changed method name in n2nhttpd * Move mainloop stop flag into the n2n_edge_t structure, allowing access from management commands * Implement edge RPC stop command * Move mainloop stop flag into the n2n_sn_t structure, allowing access from management commands * Implement supernode RPC stop command * Allow multiple pages to be served from mini httpd * Extract common script functions into a separate URL * Handle an edge case in the python rpc class With a proper tag-based demultiplexer, this case should be a nop, but we are single-threaded and rely on the packet ordering in this library. * Add n2nhttpd support to query supernode using urls prefixed with /supernode/ * Handle missing values in javascript table print * Add another less filtering javascript key/value renderer * Add a supernode.html page to the n2nhttpd * Address lint issue * Mention the second html page on the Scripts doc * Remove purgable column from supernode edges list - it looks like it is rarely going to be set * Add a simple one-line example command at the top of the API documentation * Acknowledge that this is not the most efficient protocol, but point out that it was not supposed to be * Make it clear that the n2nctl script works for both edge and supernode * Fight with inconsistant github runner results * Turn off the /right/ coverage generator
3 years ago
/**
* (C) 2007-21 - ntop.org and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not see see <http://www.gnu.org/licenses/>
*
*/
/*
* This file has a large amount of duplication with the edge_management.c
* code. In the fullness of time, they should both be merged
*/
#include "n2n.h"
#include "edge_utils_win32.h"
int load_allowed_sn_community (n2n_sn_t *sss); /* defined in sn_utils.c */
#define FLAG_WROK 1
typedef struct n2n_mgmt_handler {
int flags;
char *cmd;
char *help;
void (*func)(n2n_sn_t *sss, char *udp_buf, struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv);
} n2n_mgmt_handler_t;
static void mgmt_error (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, char *tag, char *msg) {
size_t msg_len;
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"error\","
"\"error\":\"%s\"}\n",
tag,
msg);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_stop (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
if(type==N2N_MGMT_WRITE) {
*sss->keep_running = 0;
}
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"keep_running\":%u}\n",
tag,
*sss->keep_running);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_verbose (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
if(type==N2N_MGMT_WRITE) {
if(argv) {
setTraceLevel(strtoul(argv, NULL, 0));
}
}
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"traceLevel\":%u}\n",
tag,
getTraceLevel());
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_reload_communities (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
if(type!=N2N_MGMT_WRITE) {
mgmt_error(sss, udp_buf, sender_sock, tag, "writeonly");
return;
}
if(!sss->community_file) {
mgmt_error(sss, udp_buf, sender_sock, tag, "nofile");
return;
}
int ok = load_allowed_sn_community(sss);
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"ok\":%i}\n",
tag,
ok);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_timestamps (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"start_time\":%lu,"
"\"last_fwd\":%ld,"
"\"last_reg_super\":%ld}\n",
tag,
sss->start_time,
sss->stats.last_fwd,
sss->stats.last_reg_super);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_packetstats (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"forward\","
"\"tx_pkt\":%lu}\n",
tag,
sss->stats.fwd);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"broadcast\","
"\"tx_pkt\":%lu}\n",
tag,
sss->stats.broadcast);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"reg_super\","
"\"rx_pkt\":%lu,"
"\"nak\":%lu}\n",
tag,
sss->stats.reg_super,
sss->stats.reg_super_nak);
/* Note: reg_super_nak is not currently incremented anywhere */
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
/* Generic errors when trying to sendto() */
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"errors\","
"\"tx_pkt\":%lu}\n",
tag,
sss->stats.errors);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
static void mgmt_communities (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
struct sn_community *community, *tmp;
dec_ip_bit_str_t ip_bit_str = {'\0'};
HASH_ITER(hh, sss->communities, community, tmp) {
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"community\":\"%s\","
"\"purgeable\":%i,"
"\"is_federation\":%i,"
"\"ip4addr\":\"%s\"}\n",
tag,
(community->is_federation) ? "-/-" : community->community,
community->purgeable,
community->is_federation,
(community->auto_ip_net.net_addr == 0) ? "" : ip_subnet_to_str(ip_bit_str, &community->auto_ip_net));
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
}
static void mgmt_edges (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
struct sn_community *community, *tmp;
struct peer_info *peer, *tmpPeer;
macstr_t mac_buf;
n2n_sock_str_t sockbuf;
dec_ip_bit_str_t ip_bit_str = {'\0'};
HASH_ITER(hh, sss->communities, community, tmp) {
HASH_ITER(hh, community->edges, peer, tmpPeer) {
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"community\":\"%s\","
"\"ip4addr\":\"%s\","
"\"purgeable\":%i,"
"\"macaddr\":\"%s\","
"\"sockaddr\":\"%s\","
"\"proto\":\"%s\","
"\"desc\":\"%s\","
"\"last_seen\":%li}\n",
tag,
(community->is_federation) ? "-/-" : community->community,
(peer->dev_addr.net_addr == 0) ? "" : ip_subnet_to_str(ip_bit_str, &peer->dev_addr),
peer->purgeable,
(is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr),
sock_to_cstr(sockbuf, &(peer->sock)),
((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "UDP",
peer->dev_desc,
peer->last_seen);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
}
}
static void mgmt_unimplemented (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
mgmt_error(sss, udp_buf, sender_sock, tag, "unimplemented");
}
static void mgmt_help (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv);
n2n_mgmt_handler_t mgmt_handlers[] = {
{ .cmd = "supernodes", .help = "Reserved for edge", .func = mgmt_unimplemented},
{ .cmd = "stop", .flags = FLAG_WROK, .help = "Gracefully exit edge", .func = mgmt_stop},
{ .cmd = "verbose", .flags = FLAG_WROK, .help = "Manage verbosity level", .func = mgmt_verbose},
{ .cmd = "reload_communities", .flags = FLAG_WROK, .help = "Reloads communities and user's public keys", .func = mgmt_reload_communities},
{ .cmd = "communities", .help = "List current communities", .func = mgmt_communities},
{ .cmd = "edges", .help = "List current edges/peers", .func = mgmt_edges},
{ .cmd = "timestamps", .help = "Event timestamps", .func = mgmt_timestamps},
{ .cmd = "packetstats", .help = "Traffic statistics", .func = mgmt_packetstats},
{ .cmd = "help", .flags = FLAG_WROK, .help = "Show JSON commands", .func = mgmt_help},
{ .cmd = NULL },
};
static void mgmt_help (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) {
size_t msg_len;
n2n_mgmt_handler_t *handler;
/*
* Even though this command is readonly, we deliberately do not check
* the type - allowing help replies to both read and write requests
*/
for( handler=mgmt_handlers; handler->cmd; handler++ ) {
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"cmd\":\"%s\","
"\"help\":\"%s\"}\n",
tag,
handler->cmd,
handler->help);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
}
}
/*
* Check if the user is authorised for this command.
* - this should be more configurable!
* - for the moment we use some simple heuristics:
* Reads are not dangerous, so they are simply allowed
* Writes are possibly dangerous, so they need a fake password
*/
static int mgmt_auth (const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *auth, char *argv0, char *argv) {
if(auth) {
/* If we have an auth key, it must match */
if(0 == strcmp(auth,"CHANGEME")) {
return 1;
}
return 0;
}
/* if we dont have an auth key, we can still read */
if(type==N2N_MGMT_READ) {
return 1;
}
return 0;
}
void handleMgmtJson_sn (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock) {
char cmdlinebuf[80];
enum n2n_mgmt_type type;
char *typechar;
char *options;
char *argv0;
char *argv;
char *tag;
char *flagstr;
int flags;
char *auth;
n2n_mgmt_handler_t *handler;
size_t msg_len;
/* save a copy of the commandline before we reuse the udp_buf */
strncpy(cmdlinebuf, udp_buf, sizeof(cmdlinebuf)-1);
cmdlinebuf[sizeof(cmdlinebuf)-1] = 0;
traceEvent(TRACE_DEBUG, "mgmt json %s", cmdlinebuf);
typechar = strtok(cmdlinebuf, " \r\n");
if(!typechar) {
/* should not happen */
mgmt_error(sss, udp_buf, sender_sock, "-1", "notype");
return;
}
if(*typechar == 'r') {
type=N2N_MGMT_READ;
} else if(*typechar == 'w') {
type=N2N_MGMT_WRITE;
} else {
/* dunno how we got here */
mgmt_error(sss, udp_buf, sender_sock, "-1", "badtype");
return;
}
/* Extract the tag to use in all reply packets */
options = strtok(NULL, " \r\n");
if(!options) {
mgmt_error(sss, udp_buf, sender_sock, "-1", "nooptions");
return;
}
argv0 = strtok(NULL, " \r\n");
if(!argv0) {
mgmt_error(sss, udp_buf, sender_sock, "-1", "nocmd");
return;
}
/*
* The entire rest of the line is the argv. We apply no processing
* or arg separation so that the cmd can use it however it needs.
*/
argv = strtok(NULL, "\r\n");
/*
* There might be an auth token mixed in with the tag
*/
tag = strtok(options, ":");
flagstr = strtok(NULL, ":");
if(flagstr) {
flags = strtoul(flagstr, NULL, 16);
} else {
flags = 0;
}
/* Only 1 flag bit defined at the moment - "auth option present" */
if(flags & 1) {
auth = strtok(NULL, ":");
} else {
auth = NULL;
}
if(!mgmt_auth(sender_sock, type, auth, argv0, argv)) {
mgmt_error(sss, udp_buf, sender_sock, tag, "badauth");
return;
}
for( handler=mgmt_handlers; handler->cmd; handler++ ) {
if(0 == strcmp(handler->cmd, argv0)) {
break;
}
}
if(!handler->cmd) {
mgmt_error(sss, udp_buf, sender_sock, tag, "unknowncmd");
return;
}
if((type==N2N_MGMT_WRITE) && !(handler->flags & FLAG_WROK)) {
mgmt_error(sss, udp_buf, sender_sock, tag, "readonly");
return;
}
/*
* TODO:
* The tag provided by the requester could contain chars
* that make our JSON invalid.
* - do we care?
*/
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{\"_tag\":\"%s\",\"_type\":\"begin\",\"cmd\":\"%s\"}\n", tag, argv0);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
handler->func(sss, udp_buf, sender_sock, type, tag, argv0, argv);
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE,
"{\"_tag\":\"%s\",\"_type\":\"end\"}\n", tag);
sendto(sss->mgmt_sock, udp_buf, msg_len, 0,
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in));
return;
}