mirror of https://github.com/ntop/n2n.git
Browse Source
* 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 generatorpull/865/head
Hamish Coleman
3 years ago
committed by
GitHub
18 changed files with 1354 additions and 446 deletions
@ -0,0 +1,449 @@ |
|||||
|
/**
|
||||
|
* (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/>
|
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
#include "n2n.h" |
||||
|
#include "edge_utils_win32.h" |
||||
|
|
||||
|
#define FLAG_WROK 1 |
||||
|
typedef struct n2n_mgmt_handler { |
||||
|
int flags; |
||||
|
char *cmd; |
||||
|
char *help; |
||||
|
void (*func)(n2n_edge_t *eee, 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_edge_t *eee, 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(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_stop (n2n_edge_t *eee, 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) { |
||||
|
*eee->keep_running = 0; |
||||
|
} |
||||
|
|
||||
|
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, |
||||
|
"{" |
||||
|
"\"_tag\":\"%s\"," |
||||
|
"\"_type\":\"row\"," |
||||
|
"\"keep_running\":%u}\n", |
||||
|
tag, |
||||
|
*eee->keep_running); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_verbose (n2n_edge_t *eee, 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(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_communities (n2n_edge_t *eee, 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(eee->conf.header_encryption != HEADER_ENCRYPTION_NONE) { |
||||
|
mgmt_error(eee, udp_buf, sender_sock, tag, "noaccess"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, |
||||
|
"{" |
||||
|
"\"_tag\":\"%s\"," |
||||
|
"\"_type\":\"row\"," |
||||
|
"\"community\":\"%s\"}", |
||||
|
tag, |
||||
|
eee->conf.community_name); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_supernodes (n2n_edge_t *eee, 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 peer_info *peer, *tmpPeer; |
||||
|
macstr_t mac_buf; |
||||
|
n2n_sock_str_t sockbuf; |
||||
|
selection_criterion_str_t sel_buf; |
||||
|
|
||||
|
HASH_ITER(hh, eee->conf.supernodes, peer, tmpPeer) { |
||||
|
|
||||
|
/*
|
||||
|
* TODO: |
||||
|
* The version string provided by the remote supernode could contain |
||||
|
* chars that make our JSON invalid. |
||||
|
* - do we care? |
||||
|
*/ |
||||
|
|
||||
|
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, |
||||
|
"{" |
||||
|
"\"_tag\":\"%s\"," |
||||
|
"\"_type\":\"row\"," |
||||
|
"\"version\":\"%s\"," |
||||
|
"\"purgeable\":%i," |
||||
|
"\"current\":%i," |
||||
|
"\"macaddr\":\"%s\"," |
||||
|
"\"sockaddr\":\"%s\"," |
||||
|
"\"selection\":\"%s\"," |
||||
|
"\"lastseen\":%li," |
||||
|
"\"uptime\":%li}\n", |
||||
|
tag, |
||||
|
peer->version, |
||||
|
peer->purgeable, |
||||
|
(peer == eee->curr_sn) ? (eee->sn_wait ? 2 : 1 ) : 0, |
||||
|
is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), |
||||
|
sock_to_cstr(sockbuf, &(peer->sock)), |
||||
|
sn_selection_criterion_str(sel_buf, peer), |
||||
|
peer->last_seen, |
||||
|
peer->uptime); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void mgmt_edges_row (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, char *tag, struct peer_info *peer, char *mode) { |
||||
|
size_t msg_len; |
||||
|
macstr_t mac_buf; |
||||
|
n2n_sock_str_t sockbuf; |
||||
|
dec_ip_bit_str_t ip_bit_str = {'\0'}; |
||||
|
|
||||
|
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, |
||||
|
"{" |
||||
|
"\"_tag\":\"%s\"," |
||||
|
"\"_type\":\"row\"," |
||||
|
"\"mode\":\"%s\"," |
||||
|
"\"ip4addr\":\"%s\"," |
||||
|
"\"purgeable\":%i," |
||||
|
"\"macaddr\":\"%s\"," |
||||
|
"\"sockaddr\":\"%s\"," |
||||
|
"\"desc\":\"%s\"," |
||||
|
"\"last_seen\":%li}\n", |
||||
|
tag, |
||||
|
mode, |
||||
|
(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->dev_desc, |
||||
|
peer->last_seen); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0 /*flags*/, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_edges (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { |
||||
|
struct peer_info *peer, *tmpPeer; |
||||
|
|
||||
|
// dump nodes with forwarding through supernodes
|
||||
|
HASH_ITER(hh, eee->pending_peers, peer, tmpPeer) { |
||||
|
mgmt_edges_row(eee, udp_buf, sender_sock, tag, peer, "pSp"); |
||||
|
} |
||||
|
|
||||
|
// dump peer-to-peer nodes
|
||||
|
HASH_ITER(hh, eee->known_peers, peer, tmpPeer) { |
||||
|
mgmt_edges_row(eee, udp_buf, sender_sock, tag, peer, "p2p"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static void mgmt_timestamps (n2n_edge_t *eee, 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_super\":%ld," |
||||
|
"\"last_p2p\":%ld}\n", |
||||
|
tag, |
||||
|
eee->start_time, |
||||
|
eee->last_sup, |
||||
|
eee->last_p2p); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_packetstats (n2n_edge_t *eee, 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\":\"transop\"," |
||||
|
"\"tx_pkt\":%lu," |
||||
|
"\"rx_pkt\":%lu}\n", |
||||
|
tag, |
||||
|
eee->transop.tx_cnt, |
||||
|
eee->transop.rx_cnt); |
||||
|
|
||||
|
sendto(eee->udp_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\":\"p2p\"," |
||||
|
"\"tx_pkt\":%u," |
||||
|
"\"rx_pkt\":%u}\n", |
||||
|
tag, |
||||
|
eee->stats.tx_p2p, |
||||
|
eee->stats.rx_p2p); |
||||
|
|
||||
|
sendto(eee->udp_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\":\"super\"," |
||||
|
"\"tx_pkt\":%u," |
||||
|
"\"rx_pkt\":%u}\n", |
||||
|
tag, |
||||
|
eee->stats.tx_sup, |
||||
|
eee->stats.rx_sup); |
||||
|
|
||||
|
sendto(eee->udp_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\":\"super_broadcast\"," |
||||
|
"\"tx_pkt\":%u," |
||||
|
"\"rx_pkt\":%u}\n", |
||||
|
tag, |
||||
|
eee->stats.tx_sup_broadcast, |
||||
|
eee->stats.rx_sup_broadcast); |
||||
|
|
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_unimplemented (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { |
||||
|
|
||||
|
mgmt_error(eee, udp_buf, sender_sock, tag, "unimplemented"); |
||||
|
} |
||||
|
|
||||
|
static void mgmt_help (n2n_edge_t *eee, 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 = "reload_communities", .flags = FLAG_WROK, .help = "Reserved for supernode", .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 = "communities", .help = "Show current community", .func = mgmt_communities}, |
||||
|
{ .cmd = "edges", .help = "List current edges/peers", .func = mgmt_edges}, |
||||
|
{ .cmd = "supernodes", .help = "List current supernodes", .func = mgmt_supernodes}, |
||||
|
{ .cmd = "timestamps", .help = "Event timestamps", .func = mgmt_timestamps}, |
||||
|
{ .cmd = "packetstats", .help = "traffic counters", .func = mgmt_packetstats}, |
||||
|
{ .cmd = "help", .flags = FLAG_WROK, .help = "Show JSON commands", .func = mgmt_help}, |
||||
|
{ .cmd = NULL }, |
||||
|
}; |
||||
|
|
||||
|
static void mgmt_help (n2n_edge_t *eee, 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(eee->udp_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 (n2n_edge_t *eee, 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(eee, 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(eee, 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(eee, udp_buf, sender_sock, "-1", "nooptions"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
argv0 = strtok(NULL, " \r\n"); |
||||
|
if(!argv0) { |
||||
|
mgmt_error(eee, 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(eee, 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(eee, udp_buf, sender_sock, tag, "unknowncmd"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if((type==N2N_MGMT_WRITE) && !(handler->flags & FLAG_WROK)) { |
||||
|
mgmt_error(eee, 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(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
|
||||
|
handler->func(eee, udp_buf, sender_sock, type, tag, argv0, argv); |
||||
|
|
||||
|
msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, |
||||
|
"{\"_tag\":\"%s\",\"_type\":\"end\"}\n", tag); |
||||
|
sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, |
||||
|
(struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); |
||||
|
return; |
||||
|
} |
@ -0,0 +1,439 @@ |
|||||
|
/**
|
||||
|
* (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; |
||||
|
} |
Loading…
Reference in new issue