/**
* (C) 2007-22 - 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
*
*/
/*
* 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"
#include "strbuf.h"
#include "management.h"
int load_allowed_sn_community (n2n_sn_t *sss); /* defined in sn_utils.c */
static void mgmt_reload_communities (mgmt_req_t *req, strbuf_t *buf) {
if(req->type!=N2N_MGMT_WRITE) {
mgmt_error(req, buf, "writeonly");
return;
}
if(!req->sss->community_file) {
mgmt_error(req, buf, "nofile");
return;
}
int ok = load_allowed_sn_community(req->sss);
send_json_1uint(req, buf, "row", "ok", ok);
}
static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf) {
size_t msg_len;
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"start_time\":%lu,"
"\"last_fwd\":%ld,"
"\"last_reg_super\":%ld}\n",
req->tag,
req->sss->start_time,
req->sss->stats.last_fwd,
req->sss->stats.last_reg_super);
send_reply(req, buf, msg_len);
}
static void mgmt_packetstats (mgmt_req_t *req, strbuf_t *buf) {
size_t msg_len;
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"forward\","
"\"tx_pkt\":%lu}\n",
req->tag,
req->sss->stats.fwd);
send_reply(req, buf, msg_len);
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"broadcast\","
"\"tx_pkt\":%lu}\n",
req->tag,
req->sss->stats.broadcast);
send_reply(req, buf, msg_len);
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"reg_super\","
"\"rx_pkt\":%lu,"
"\"nak\":%lu}\n",
req->tag,
req->sss->stats.reg_super,
req->sss->stats.reg_super_nak);
/* Note: reg_super_nak is not currently incremented anywhere */
send_reply(req, buf, msg_len);
/* Generic errors when trying to sendto() */
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"type\":\"errors\","
"\"tx_pkt\":%lu}\n",
req->tag,
req->sss->stats.errors);
send_reply(req, buf, msg_len);
}
static void mgmt_communities (mgmt_req_t *req, strbuf_t *buf) {
size_t msg_len;
struct sn_community *community, *tmp;
dec_ip_bit_str_t ip_bit_str = {'\0'};
HASH_ITER(hh, req->sss->communities, community, tmp) {
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"community\":\"%s\","
"\"purgeable\":%i,"
"\"is_federation\":%i,"
"\"ip4addr\":\"%s\"}\n",
req->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));
send_reply(req, buf, msg_len);
}
}
static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf) {
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, req->sss->communities, community, tmp) {
HASH_ITER(hh, community->edges, peer, tmpPeer) {
msg_len = snprintf(buf->str, buf->size,
"{"
"\"_tag\":\"%s\","
"\"_type\":\"row\","
"\"community\":\"%s\","
"\"ip4addr\":\"%s\","
"\"purgeable\":%i,"
"\"macaddr\":\"%s\","
"\"sockaddr\":\"%s\","
"\"proto\":\"%s\","
"\"desc\":\"%s\","
"\"last_seen\":%li}\n",
req->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 != req->sss->sock)) ? "TCP" : "UDP",
peer->dev_desc,
peer->last_seen);
send_reply(req, buf, msg_len);
}
}
}
// Forward define so we can include this in the mgmt_handlers[] table
static void mgmt_help (mgmt_req_t *req, strbuf_t *buf);
static const 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},
};
// TODO: want to keep the mgmt_handlers defintion const static, otherwise
// this whole function could be shared
static void mgmt_help (mgmt_req_t *req, strbuf_t *buf) {
/*
* Even though this command is readonly, we deliberately do not check
* the type - allowing help replies to both read and write requests
*/
int i;
int nr_handlers = sizeof(mgmt_handlers) / sizeof(mgmt_handler_t);
for( i=0; i < nr_handlers; i++ ) {
mgmt_help_row(req, buf, mgmt_handlers[i].cmd, mgmt_handlers[i].help);
}
}
// TODO: DRY
static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) {
strbuf_t *buf;
char cmdlinebuf[80];
/* 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);
/* we reuse the buffer already on the stack for all our strings */
// xx
STRBUF_INIT(buf, udp_buf);
mgmt_req_init2(req, buf, (char *)&cmdlinebuf);
int handler;
lookup_handler(handler, mgmt_handlers, req->argv0);
if(handler == -1) {
mgmt_error(req, buf, "unknowncmd");
return;
}
if((req->type==N2N_MGMT_WRITE) && !(mgmt_handlers[handler].flags & FLAG_WROK)) {
mgmt_error(req, buf, "readonly");
return;
}
/*
* TODO:
* The tag provided by the requester could contain chars
* that make our JSON invalid.
* - do we care?
*/
send_json_1str(req, buf, "begin", "cmd", req->argv0);
mgmt_handlers[handler].func(req, buf);
send_json_1str(req, buf, "end", "cmd", req->argv0);
return;
}
static int sendto_mgmt (n2n_sn_t *sss,
const struct sockaddr_in *sender_sock,
const uint8_t *mgmt_buf,
size_t mgmt_size) {
ssize_t r = sendto(sss->mgmt_sock, (void *)mgmt_buf, mgmt_size, 0 /*flags*/,
(struct sockaddr *)sender_sock, sizeof (struct sockaddr_in));
if(r <= 0) {
++(sss->stats.errors);
traceEvent(TRACE_ERROR, "sendto_mgmt : sendto failed. %s", strerror(errno));
return -1;
}
return 0;
}
int process_mgmt (n2n_sn_t *sss,
const struct sockaddr_in *sender_sock,
char *mgmt_buf,
size_t mgmt_size,
time_t now) {
char resbuf[N2N_SN_PKTBUF_SIZE];
size_t ressize = 0;
mgmt_req_t req;
uint32_t num_edges = 0;
uint32_t num_comm = 0;
uint32_t num = 0;
struct sn_community *community, *tmp;
struct peer_info *peer, *tmpPeer;
macstr_t mac_buf;
n2n_sock_str_t sockbuf;
char time_buf[10]; /* 9 digits + 1 terminating zero */
dec_ip_bit_str_t ip_bit_str = {'\0'};
traceEvent(TRACE_DEBUG, "process_mgmt");
req.eee = NULL;
req.sss = sss;
req.mgmt_sock = sss->mgmt_sock;
req.keep_running = sss->keep_running;
req.mgmt_password_hash = sss->mgmt_password_hash;
memcpy(&req.sender_sock, sender_sock, sizeof(req.sender_sock));
/* avoid parsing any uninitialized junk from the stack */
mgmt_buf[mgmt_size] = 0;
// process input, if any
if((0 == memcmp(mgmt_buf, "help", 4)) || (0 == memcmp(mgmt_buf, "?", 1))) {
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"Help for supernode management console:\n"
"\thelp | This help message\n"
"\treload_communities | Reloads communities and user's public keys\n"
"\t | Display status and statistics\n");
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
return 0; /* no status output afterwards */
}
if(0 == memcmp(mgmt_buf, "reload_communities", 18)) {
if(!sss->community_file) {
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"No community file provided (-c command line option)\n");
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
return 0; /* no status output afterwards */
}
traceEvent(TRACE_NORMAL, "'reload_communities' command");
if(load_allowed_sn_community(sss)) {
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"Error while re-loading community file (not found or no valid content)\n");
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
return 0; /* no status output afterwards */
}
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"OK.\n");
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
return 0; /* no status output afterwards */
}
if((mgmt_buf[0] >= 'a' || mgmt_buf[0] <= 'z') && (mgmt_buf[1] == ' ')) {
/* this is a JSON request */
handleMgmtJson(&req, mgmt_buf, mgmt_size);
return 0;
}
// output current status
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
" ### | TAP | MAC | EDGE | HINT | LAST SEEN\n");
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"========================================================================================================\n");
HASH_ITER(hh, sss->communities, community, tmp) {
if(num_comm)
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"--------------------------------------------------------------------------------------------------------\n");
num_comm++;
num_edges += HASH_COUNT(community->edges);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"%s '%s'\n",
(community->is_federation) ? "FEDERATION" : ((community->purgeable == COMMUNITY_UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"),
(community->is_federation) ? "-/-" : community->community);
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
ressize = 0;
num = 0;
HASH_ITER(hh, community->edges, peer, tmpPeer) {
sprintf(time_buf, "%9u", (unsigned int)(now - peer->last_seen));
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n",
++num,
(peer->dev_addr.net_addr == 0) ? ((peer->purgeable == SN_UNPURGEABLE) ? "-l" : "") : ip_subnet_to_str(ip_bit_str, &peer->dev_addr),
(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" : "",
peer->dev_desc,
(peer->last_seen) ? time_buf : "");
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
ressize = 0;
}
}
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"========================================================================================================\n");
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"uptime %lu | ", (now - sss->start_time));
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"edges %u | ",
num_edges);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"reg_sup %u | ",
(unsigned int) sss->stats.reg_super);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"reg_nak %u | ",
(unsigned int) sss->stats.reg_super_nak);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"errors %u \n",
(unsigned int) sss->stats.errors);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"fwd %u | ",
(unsigned int) sss->stats.fwd);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"broadcast %u | ",
(unsigned int) sss->stats.broadcast);
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"cur_cmnts %u\n", HASH_COUNT(sss->communities));
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"last_fwd %lu sec ago | ",
(long unsigned int) (now - sss->stats.last_fwd));
ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize,
"last reg %lu sec ago\n\n",
(long unsigned int) (now - sss->stats.last_reg_super));
sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize);
return 0;
}