diff --git a/.github/workflows/quick_windows.yml b/.github/workflows/quick_windows.yml index 17dd0b1f..0042656d 100644 --- a/.github/workflows/quick_windows.yml +++ b/.github/workflows/quick_windows.yml @@ -24,7 +24,7 @@ jobs: export CFLAGS="-fprofile-arcs -ftest-coverage" export LDFLAGS="--coverage" ./scripts/hack_fakeautoconf.sh - make -j4 + make -k -j4 make test.builtin test.units shell: bash diff --git a/Makefile b/Makefile index 20b5a856..34a373eb 100644 --- a/Makefile +++ b/Makefile @@ -103,6 +103,7 @@ OBJS=\ src/initfuncs.o \ src/json.o \ src/logging.o \ + src/mainloop.o \ src/management.o \ src/metrics.o \ src/minilzo.o \ @@ -338,6 +339,11 @@ clean.cov: apps/*.gcno apps/*.gcda \ tools/*.gcno tools/*.gcda +.PHONY: iwyu +iwyu: iwyu.out +iwyu.out: + CFLAGS="-Xiwyu --error_always" $(MAKE) -k CC=include-what-you-use 2> iwyu.out + .PHONY: clean clean: clean.cov rm -rf $(OBJS) $(SUBDIR_LIBS) $(DOCS) $(COVERAGEDIR)/ *.dSYM *~ diff --git a/apps/example_edge_embed.c b/apps/example_edge_embed.c index 166e1c6c..917de6c0 100644 --- a/apps/example_edge_embed.c +++ b/apps/example_edge_embed.c @@ -20,6 +20,7 @@ #include #include // for edge_init_conf_defaults, edge_verify_conf +#include // for mainloop_register_fd, fd_info_proto #include // for n3n_peer_add_by_hostname #include #include // for snprintf, NULL @@ -68,6 +69,11 @@ int main () { return -1; } +#ifndef _WIN32 + // TODO: this internal fn should not be called publicly + mainloop_register_fd(tuntap.fd, fd_info_proto_tuntap); +#endif + eee = edge_init(&conf, &rc); if(eee == NULL) { exit(1); diff --git a/apps/n3n-edge.c b/apps/n3n-edge.c index 06cd7e88..7867fdd3 100644 --- a/apps/n3n-edge.c +++ b/apps/n3n-edge.c @@ -20,7 +20,6 @@ #include // for isspace -#include // for slots_listen_close #include // for errno #include // for required_argument, no_argument #include // for PRIu64 @@ -29,6 +28,7 @@ #include // for macaddr_str, macstr_t #include // for n3n_initfuncs() #include // for traceEvent +#include // for mainloop_register_fd #include // for test_hashing #include // for n3n_rand_seeds, n3n_rand_seeds_s... #include // for n3n_transform_lookup_id @@ -697,7 +697,7 @@ static void term_handler (int sig) { #endif #ifdef _WIN32 -struct n3n_runtime_data *windows_stop_eee; +extern int windows_stop_fd; // Note well, this gets called from a brand new thread, thus is completely // different to how signals work in POSIX @@ -718,7 +718,7 @@ BOOL WINAPI ConsoleCtrlHandler (DWORD sig) { // mainloop to notice that we are no longer wanting to run. // // something something, darkside - slots_listen_close(windows_stop_eee->mgmt_slots); + closesocket(windows_stop_fd); switch(sig) { case CTRL_CLOSE_EVENT: @@ -743,14 +743,8 @@ int main (int argc, char* argv[]) { uint8_t seek_answer = 1; /* expecting answer from supernode */ time_t now, last_action = 0; /* timeout */ macstr_t mac_buf; /* output mac address */ - fd_set socket_mask; /* for supernode answer */ - struct timeval wait_time; /* timeout for sn answer */ peer_info_t *scan, *scan_tmp; /* supernode iteration */ - uint16_t expected = sizeof(uint16_t); - uint16_t position = 0; - uint8_t pktbuf[N2N_SN_PKTBUF_SIZE + sizeof(uint16_t)]; /* buffer + prepended buffer length in case of tcp */ - #ifdef HAVE_LIBCAP cap_t caps; #endif @@ -830,6 +824,7 @@ int main (int argc, char* argv[]) { traceEvent(TRACE_ERROR, "failed in edge_init"); exit(1); } + eee->keep_running = &keep_on_running; switch(eee->conf.tuntap_ip_mode) { case TUNTAP_IP_MODE_SN_ASSIGN: @@ -850,11 +845,18 @@ int main (int argc, char* argv[]) { // for the sake of quickly establishing connection. REVISIT when a more elegant way to re-use main loop code // is found - // find at least one supernode alive to faster establish connection + // find at least one supernode alive to faster establish connection. // exceptions: - if((HASH_COUNT(eee->conf.supernodes) <= 1) || (eee->conf.connect_tcp) || (eee->conf.shared_secret)) { - // skip the initial supernode ping - traceEvent(TRACE_DEBUG, "skip PING to supernode"); + if(eee->conf.connect_tcp) { + traceEvent(TRACE_DEBUG, "skip PING to supernode: TCP mode"); + runlevel = 2; + } + if(eee->conf.shared_secret) { + traceEvent(TRACE_DEBUG, "skip PING to supernode: shared secret"); + runlevel = 2; + } + if(HASH_COUNT(eee->conf.supernodes) <= 1) { + traceEvent(TRACE_DEBUG, "skip PING to supernode: only one supernode"); runlevel = 2; } @@ -957,6 +959,10 @@ int main (int argc, char* argv[]) { eee->conf.mtu, eee->conf.metric) < 0) exit(1); +#ifndef _WIN32 + // TODO: this internal fn should not be called publicly + mainloop_register_fd(eee->device.fd, fd_info_proto_tuntap); +#endif in_addr_t addr = eee->conf.tuntap_v4.net_addr; struct in_addr *tmp = (struct in_addr *)&addr; traceEvent(TRACE_NORMAL, "created local tap device IPv4: %s/%u, MAC: %s", @@ -970,19 +976,10 @@ int main (int argc, char* argv[]) { // we usually wait for some answer, there however are exceptions when going back to a previous runlevel if(seek_answer) { - FD_ZERO(&socket_mask); - FD_SET(eee->sock, &socket_mask); - wait_time.tv_sec = BOOTSTRAP_TIMEOUT; - wait_time.tv_usec = 0; - - if(select(eee->sock + 1, &socket_mask, NULL, NULL, &wait_time) > 0) { - if(FD_ISSET(eee->sock, &socket_mask)) { + mainloop_runonce(eee); - fetch_and_eventually_process_data(eee, eee->sock, - pktbuf, &expected, &position, - now); - } - } + // FIXME: the mainloop could wait for BOOTSTRAP_TIMEOUT, not its + // usual timeout ?!? } seek_answer = 1; @@ -1061,11 +1058,9 @@ int main (int argc, char* argv[]) { signal(SIGINT, term_handler); #endif #ifdef _WIN32 - windows_stop_eee = eee; SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); #endif - eee->keep_running = &keep_on_running; traceEvent(TRACE_NORMAL, "edge started"); rc = run_edge_loop(eee); print_edge_stats(eee); diff --git a/apps/n3n-supernode.c b/apps/n3n-supernode.c index 94409b6c..b39a6295 100644 --- a/apps/n3n-supernode.c +++ b/apps/n3n-supernode.c @@ -351,15 +351,10 @@ static void n3n_sn_config (int argc, char **argv, char *defname, struct n3n_runt /* *************************************************** */ -static bool keep_running = true; +static bool keep_on_running = true; -#if defined(__linux__) || defined(_WIN32) -#ifdef _WIN32 -BOOL WINAPI term_handler (DWORD sig) -#else -static void term_handler (int sig) -#endif -{ +#ifndef _WIN32 +static void term_handler (int sig) { static int called = 0; if(called) { @@ -370,12 +365,44 @@ static void term_handler (int sig) called = 1; } - keep_running = false; + keep_on_running = false; +} +#endif + #ifdef _WIN32 +extern int windows_stop_fd; + +// Note well, this gets called from a brand new thread, thus is completely +// different to how signals work in POSIX +BOOL WINAPI ConsoleCtrlHandler (DWORD sig) { + // Tell the mainloop to exit next time it wakes + keep_on_running = false; + + traceEvent(TRACE_INFO, "starting stopping"); + // The windows environment claims to support signals, but they dont + // interrupt a running select() statement. Also, this console handler + // is run in its own thread, so it is also not interrupting the select() + // This is clearly contrary to how select was designed to be used and it + // makes process termination annoying, so we need a workaround. + // + // Since windows usually has a managment TCP port listening in the + // select fdset, we can close that - this immediately causes the select + // to return with activity on that file descriptor and allows the + // mainloop to notice that we are no longer wanting to run. + // + // something something, darkside + closesocket(windows_stop_fd); + + switch(sig) { + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + // Will terminate us after we return, blocking it to cleanup + Sleep(INFINITE); + } return(TRUE); -#endif } -#endif /* defined(__linux__) || defined(_WIN32) */ +#endif /* *************************************************** */ @@ -546,6 +573,12 @@ int main (int argc, char * argv[]) { } traceEvent(TRACE_NORMAL, "supernode is listening on TCP %u (management)", sss_node.conf.mgmt_port); } +#ifdef _WIN32 + // HACK! + // Remove this once the supernode users mainloop and it also supports + // stopping on windows + windows_stop_fd = sss_node.mgmt_slots->listen[0]; +#endif n3n_config_setup_sessiondir(&sss_node.conf); @@ -606,15 +639,15 @@ int main (int argc, char * argv[]) { traceEvent(TRACE_NORMAL, "supernode started"); -#ifdef __linux__ +#ifndef _WIN32 signal(SIGPIPE, SIG_IGN); signal(SIGTERM, term_handler); signal(SIGINT, term_handler); #endif #ifdef _WIN32 - SetConsoleCtrlHandler(term_handler, TRUE); + SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE); #endif - sss_node.keep_running = &keep_running; + sss_node.keep_running = &keep_on_running; return run_sn_loop(&sss_node); } diff --git a/include/auth.h b/include/auth.h index a360b669..712af07c 100644 --- a/include/auth.h +++ b/include/auth.h @@ -23,7 +23,9 @@ #include // for size_t #include // for uint8_t, uint32_t + #include "n2n.h" // for n2n_private_public_key_t, n2n_community_t, N2N_A... +#include "n2n_typedefs.h" int bin_to_ascii(char *out, uint8_t *in, size_t in_len); diff --git a/include/hexdump.h b/include/hexdump.h index b4edd83e..15ea79a0 100644 --- a/include/hexdump.h +++ b/include/hexdump.h @@ -6,6 +6,8 @@ #ifndef HEXDUMP_H #define HEXDUMP_H +#include + void fhexdump(unsigned int display_addr, void *in, int size, FILE *stream); #endif diff --git a/include/n2n.h b/include/n2n.h index a8cb1488..1e339271 100644 --- a/include/n2n.h +++ b/include/n2n.h @@ -66,7 +66,6 @@ #include // for uint8_t, uint64_t, uint32_t, uint16_t #include // for time_t #include // for close -#define closesocket(a) close(a) #ifdef __linux__ #define N2N_CAN_NAME_IFACE 1 @@ -141,7 +140,6 @@ void update_supernode_reg (struct n3n_runtime_data * eee, time_t nowTime); void readFromIPSocket (struct n3n_runtime_data * eee, int in_sock); void edge_term (struct n3n_runtime_data *eee); void edge_send_packet2net (struct n3n_runtime_data *eee, uint8_t *tap_pkt, size_t len); -void edge_read_from_tap (struct n3n_runtime_data *eee); int run_edge_loop (struct n3n_runtime_data *eee); int quick_edge_init (char *device_name, char *community_name, char *encrypt_key, char *device_mac, diff --git a/include/n2n_define.h b/include/n2n_define.h index 23f2e169..bd6ce6c4 100644 --- a/include/n2n_define.h +++ b/include/n2n_define.h @@ -173,12 +173,5 @@ enum n3n_event_topic { #define N2N_TRANSFORM_ID_USER_START 64 #define N2N_TRANSFORM_ID_MAX 65535 -#ifndef max -#define max(a, b) (((a) < (b)) ? (b) : (a)) -#endif - -#ifndef min -#define min(a, b) (((a) >(b)) ? (b) : (a)) -#endif #endif diff --git a/include/n2n_typedefs.h b/include/n2n_typedefs.h index f29563e9..9c4b5b4d 100644 --- a/include/n2n_typedefs.h +++ b/include/n2n_typedefs.h @@ -132,11 +132,13 @@ typedef char devstr_t[N2N_IFNAMSIZ]; typedef struct tuntap_dev { +#ifndef _WIN32 int fd; + devstr_t dev_name; +#endif in_addr_t ip_addr; n2n_mac_t mac_addr; uint16_t mtu; - devstr_t dev_name; #ifdef _WIN32 HANDLE device_handle; char *device_name; @@ -375,34 +377,6 @@ struct network_traffic_filter { /* *************************************************** */ -/* Callbacks allow external programs to attach functions in response to - * N2N events. */ -typedef struct n2n_edge_callbacks { - /* The supernode registration has been updated */ - void (*sn_registration_updated)(struct n3n_runtime_data *eee, time_t now, const n2n_sock_t *sn); - - /* A packet has been received from a peer. N2N_DROP can be returned to - * drop the packet. The packet payload can be modified. This only allows - * the packet size to be reduced */ - n2n_verdict (*packet_from_peer)(struct n3n_runtime_data *eee, const n2n_sock_t *peer, uint8_t *payload, uint16_t *payload_size); - - /* A packet has been received from the TAP interface. N2N_DROP can be - * returned to drop the packet. The packet payload can be modified. - * This only allows the packet size to be reduced */ - n2n_verdict (*packet_from_tap)(struct n3n_runtime_data *eee, uint8_t *payload, uint16_t *payload_size); - - /* Called whenever the IP address of the TAP interface changes. */ - void (*ip_address_changed)(struct n3n_runtime_data *eee, uint32_t old_ip, uint32_t new_ip); - - /* Called periodically in the main loop. */ - void (*main_loop_period)(struct n3n_runtime_data *eee, time_t now); - - /* Called when a new socket to supernode is created. */ - void (*sock_opened)(struct n3n_runtime_data *eee); -} n2n_edge_callbacks_t; - -/* *************************************************** */ - typedef enum n2n_transform { N2N_TRANSFORM_ID_INVAL = 0, N2N_TRANSFORM_ID_NULL = 1, @@ -554,7 +528,6 @@ struct n3n_runtime_data { n2n_trans_op_t transop; /**< The transop to use when encoding */ n2n_trans_op_t transop_lzo; /**< The transop for LZO compression */ n2n_trans_op_t transop_zstd; /**< The transop for ZSTD compression */ - n2n_edge_callbacks_t cb; /**< API callbacks */ SN_SELECTION_CRITERION_DATA_TYPE sn_selection_criterion_common_data; /* Sockets */ diff --git a/include/n2n_wire.h b/include/n2n_wire.h index 2a10a22a..8ae8b01e 100644 --- a/include/n2n_wire.h +++ b/include/n2n_wire.h @@ -139,7 +139,8 @@ int fill_sockaddr (struct sockaddr * addr, const n2n_sock_t * sock); int fill_n2nsock (n2n_sock_t* sock, - const struct sockaddr* sa); + const struct sockaddr* sa, + int type); int encode_PACKET (uint8_t * base, size_t * idx, diff --git a/include/n3n/edge.h b/include/n3n/edge.h index bfba2431..5e7311a6 100644 --- a/include/n3n/edge.h +++ b/include/n3n/edge.h @@ -28,8 +28,16 @@ void edge_term_conf (n2n_edge_conf_t *conf); void send_register_super (struct n3n_runtime_data *eee); void send_query_peer (struct n3n_runtime_data *eee, const n2n_mac_t dst_mac); int supernode_connect (struct n3n_runtime_data *eee); -int fetch_and_eventually_process_data (struct n3n_runtime_data *eee, SOCKET sock, - uint8_t *pktbuf, uint16_t *expected, uint16_t *position, - time_t now); + +void edge_read_proto3_udp (struct n3n_runtime_data *eee, + SOCKET sock, + uint8_t *pktbuf, + ssize_t pktbuf_len, + time_t now); +void edge_read_proto3_tcp (struct n3n_runtime_data *eee, + SOCKET sock, + uint8_t *pktbuf, + ssize_t pktbuf_len, + time_t now); #endif diff --git a/include/n3n/mainloop.h b/include/n3n/mainloop.h new file mode 100644 index 00000000..00ce77cb --- /dev/null +++ b/include/n3n/mainloop.h @@ -0,0 +1,47 @@ +/** + * Copyright (C) Hamish Coleman + * + * non public structure and function definitions + * + * TODO: + * - fix the layering confusion in the calling code + * (apps/example_edge_embed.c apps/n3n-edge.c) + * and move this header back to the non-public src/ location + */ + +#ifndef _MAINLOOP_H_ +#define _MAINLOOP_H_ + +#include // for strbuf_t +#include // for n3n_runtime_data + +#ifndef _WIN32 +#include // for fd_set +#endif + +enum __attribute__((__packed__)) fd_info_proto { + fd_info_proto_unknown = 0, + fd_info_proto_tuntap, + fd_info_proto_listen_http, + fd_info_proto_v3udp, + fd_info_proto_v3tcp, + fd_info_proto_http, +}; + +// Place debug info from the slots into the strbuf +void mainloop_dump (strbuf_t **); + +// Starts sending a packet, queuing if needed +// returns false when: +// - no queue slot is available +// - the file handle is not registered with the mainloop +// - the file handle is not registered as a v3tcp proto +bool mainloop_send_v3tcp (int, const void *, int); + +int mainloop_runonce (struct n3n_runtime_data *); + +void mainloop_register_fd (int, enum fd_info_proto); +void mainloop_unregister_fd (int); + + +#endif diff --git a/include/n3n/metrics.h b/include/n3n/metrics.h index 52175211..dd6d2427 100644 --- a/include/n3n/metrics.h +++ b/include/n3n/metrics.h @@ -14,6 +14,7 @@ enum __attribute__((__packed__)) n3n_metrics_items_type { n3n_metrics_type_invalid = 0, n3n_metrics_type_uint32, // items_uint32 is valid n3n_metrics_type_llu32, + n3n_metrics_type_cb, }; // The simplest type of metrics: everything is the same storage type, there are @@ -52,6 +53,7 @@ struct n3n_metrics_module { union { const struct n3n_metrics_items_uint32 *items_uint32; const struct n3n_metrics_items_llu32 *items_llu32; + void (*cb)(strbuf_t **, const struct n3n_metrics_module *); }; const enum n3n_metrics_items_type type; }; @@ -59,7 +61,20 @@ struct n3n_metrics_module { // Register a block of metrics void n3n_metrics_register (struct n3n_metrics_module *); +// Helper to assist with rendering during n3n_metrics_type_cb +void n3n_metrics_render_u32tags ( + strbuf_t **reply, + const struct n3n_metrics_module *module, + const char *name, + const int offset, + const int tags, // The number of following tag+val pairs + ... +); + // Render all the metrics into a strbuf void n3n_metrics_render (strbuf_t **reply); +// Set the session name for all metrics +void n3n_metrics_set_session (char *); + #endif diff --git a/libs/connslot/connslot.c b/libs/connslot/connslot.c index 66db4fb9..b0382c79 100644 --- a/libs/connslot/connslot.c +++ b/libs/connslot/connslot.c @@ -7,25 +7,25 @@ #define _GNU_SOURCE #ifndef _WIN32 -#include +#include // for ntohs #endif -#include -#include +#include // for errno, EAGAIN, ENOENT, EWOULDBLOCK +#include // for fcntl, F_SETFL, O_NONBLOCK #ifndef _WIN32 -#include +#include // for htons, htonl, sockaddr_in, sock... #endif -#include -#include -#include +#include // for uint16_t +#include // for remove +#include // for free, abort, malloc, strtoul +#include // for memmem, memcpy, strlen, strncpy #ifndef _WIN32 -#include -#include -#include -#include -#include +#include // for socket, bind, listen, setsockopt +#include // for chmod +#include // for writev +#include // for sockaddr_un #endif -#include -#include +#include // for NULL, time, size_t +#include // for close, chown #ifdef _WIN32 #include @@ -33,6 +33,7 @@ #endif #include "connslot.h" +#include "strbuf.h" // for sb_reprintf, sb_len, strbuf_t #ifdef _WIN32 // Windows is a strange place to live, if you are a POSIX programmer @@ -63,6 +64,7 @@ void *memmem(void *haystack, size_t haystack_len, void * needle, size_t needle_l void conn_zero(conn_t *conn) { conn->fd = -1; conn->state = CONN_EMPTY; + conn->proto = CONN_PROTO_UNK; conn->reply = NULL; conn->reply_sendpos = 0; conn->activity = 0; @@ -87,7 +89,95 @@ int conn_init(conn_t *conn, size_t request_max, size_t reply_header_max) { return 0; } -void conn_read(conn_t *conn) { +void conn_accept(conn_t *conn, int fd, enum conn_proto proto) { + +#ifndef _WIN32 + fcntl(fd, F_SETFL, O_NONBLOCK); +#else + u_long arg = 1; + ioctlsocket(fd, FIONBIO, &arg); +#endif + + // This will truncate the time to a int - usually 32bits + conn->activity = time(NULL); + conn->fd = fd; + conn->proto = proto; +} + +void conn_check_ready(conn_t *conn) { + unsigned int expected_length; + + switch (conn->proto) { + case CONN_PROTO_HTTP: + if (sb_len(conn->request)<4) { + // Not enough bytes to match the end of header check + return; + } + + // retrieve the cached expected length, if any + expected_length = conn->request->rd_pos; + + if (expected_length == 0) { + char *p = memmem(conn->request->str, sb_len(conn->request), "\r\n\r\n", 4); + if (!p) { + // As yet, we dont have an entire header + return; + } + + int body_pos = p - conn->request->str + 4; + + // Determine if we need to read a body + p = memmem( + conn->request->str, + body_pos, + "Content-Length:", + 15 + ); + + if (!p) { + // We have an end of header, and the header has no content length field + // so assume there is no body to read + conn->state = CONN_READY; + return; + } + + p+=15; // Skip the field name + unsigned int content_length = strtoul(p, NULL, 10); + expected_length = body_pos + content_length; + // FIXME: what if Content-Length: is larger than unsigned int? + } + break; + case CONN_PROTO_BE16LEN: + if (sb_len(conn->request)<2) { + // Not enough bytes to have the header + return; + } + + expected_length = ntohs(*(uint16_t *)&conn->request->str) + 2; + break; + + default: + return; + } + + + // By this point we must have an expected_length + + // cache the calculated total length in the conn + conn->request->rd_pos = expected_length; + + if (sb_len(conn->request) < expected_length) { + // Dont have enough length + return; + } + + // Do have enough length + conn->state = CONN_READY; + conn->request->rd_pos = 0; + return; +} + +void conn_read(conn_t *conn, int fd) { conn->state = CONN_READING; // If no space available, try increasing our capacity @@ -98,7 +188,7 @@ void conn_read(conn_t *conn) { } } - ssize_t size = sb_read(conn->fd, conn->request); + ssize_t size = sb_read(fd, conn->request); if (size == 0) { // As we are dealing with non blocking sockets, and have made a non @@ -119,69 +209,15 @@ void conn_read(conn_t *conn) { // This will truncate the time to a int - usually 32bits conn->activity = time(NULL); - - // case protocol==HTTP - - if (sb_len(conn->request)<4) { - // Not enough bytes to match the end of header check - return; - } - - // retrieve the cached expected length, if any - unsigned int expected_length = conn->request->rd_pos; - - if (expected_length == 0) { - char *p = memmem(conn->request->str, sb_len(conn->request), "\r\n\r\n", 4); - if (!p) { - // As yet, we dont have an entire header - return; - } - - int body_pos = p - conn->request->str + 4; - - // Determine if we need to read a body - p = memmem( - conn->request->str, - body_pos, - "Content-Length:", - 15 - ); - - if (!p) { - // We have an end of header, and the header has no content length field - // so assume there is no body to read - conn->state = CONN_READY; - return; - } - - p+=15; // Skip the field name - unsigned int content_length = strtoul(p, NULL, 10); - expected_length = body_pos + content_length; - // FIXME: what if Content-Length: is larger than unsigned int? - } - - // By this point we must have an expected_length - - // cache the calculated total length in the conn - conn->request->rd_pos = expected_length; - - if (sb_len(conn->request) < expected_length) { - // Dont have enough length - return; - } - - // Do have enough length - conn->state = CONN_READY; - conn->request->rd_pos = 0; - return; + conn_check_ready(conn); } -ssize_t conn_write(conn_t *conn) { +ssize_t conn_write(conn_t *conn, int fd) { ssize_t sent; conn->state = CONN_SENDING; - if (conn->fd == -1) { + if (fd == -1) { return 0; } #ifndef _WIN32 @@ -209,20 +245,20 @@ ssize_t conn_write(conn_t *conn) { nr++; } - sent = writev(conn->fd, &vecs[0], nr); + sent = writev(fd, &vecs[0], nr); #else // no iovec // if (conn->reply_sendpos < sb_len(conn->reply_header)) { sent = sb_write( - conn->fd, + fd, conn->reply_header, conn->reply_sendpos, -1 ); } else { sent = sb_write( - conn->fd, + fd, conn->reply, conn->reply_sendpos - sb_len(conn->reply_header), -1 @@ -238,7 +274,7 @@ ssize_t conn_write(conn_t *conn) { conn->state = CONN_EMPTY; conn->reply_sendpos = 0; sb_zero(conn->reply_header); - sb_zero(conn->request); + sb_zero(conn->reply); } // This will truncate the time to a int - usually 32bits @@ -255,12 +291,22 @@ int conn_iswriter(conn_t *conn) { } } -void conn_close(conn_t *conn) { - closesocket(conn->fd); +void conn_close(conn_t *conn, int fd) { + closesocket(fd); conn_zero(conn); // TODO: could shrink the size here, maybe in certain circumstances? } +bool conn_closeidle(conn_t *conn, int fd, int now, int timeout) { + int delta_t = now - conn->activity; + if (delta_t > timeout) { + // TODO: metrics timeouts ++ + conn_close(conn, fd); + return true; + } + return false; +} + void conn_dump(strbuf_t **buf, conn_t *conn) { sb_reprintf( buf, @@ -383,12 +429,7 @@ static int _slots_listen_find_empty(slots_t *slots) { return listen_nr; } -int slots_listen_tcp(slots_t *slots, int port, bool allow_remote) { - int listen_nr = _slots_listen_find_empty(slots); - if (listen_nr <0) { - return -2; - } - +int slots_create_listen_tcp(int port, bool allow_remote) { int server; #ifndef _WIN32 int on = 1; @@ -433,17 +474,26 @@ int slots_listen_tcp(slots_t *slots, int port, bool allow_remote) { return -1; } - slots->listen[listen_nr] = server; - return 0; + return server; } -#ifndef _WIN32 -int slots_listen_unix(slots_t *slots, char *path, int mode, int uid, int gid) { +int slots_listen_tcp(slots_t *slots, int port, bool allow_remote) { int listen_nr = _slots_listen_find_empty(slots); if (listen_nr <0) { return -2; } + int fd = slots_create_listen_tcp(port, allow_remote); + if(fd == -1) { + return fd; + } + + slots->listen[listen_nr] = fd; + return 0; +} + +#ifndef _WIN32 +int slots_create_listen_unix(char *path, int mode, int uid, int gid) { struct sockaddr_un addr; if (strlen(path) > sizeof(addr.sun_path) -1) { @@ -483,14 +533,33 @@ int slots_listen_unix(slots_t *slots, char *path, int mode, int uid, int gid) { result += chown(path, uid, gid); } + if(result != 0) { + return -1; + } + // backlog of 1 - low, but sheds load quickly when we run out of slots if (listen(server, 1) < 0) { return -1; } - slots->listen[listen_nr] = server; - return result; + return server; +} + +int slots_listen_unix(slots_t *slots, char *path, int mode, int uid, int gid) { + int listen_nr = _slots_listen_find_empty(slots); + if (listen_nr <0) { + return -2; + } + + int fd = slots_create_listen_unix(path, mode, uid, gid); + if(fd == -1) { + return fd; + } + + slots->listen[listen_nr] = fd; + return 0; } + #endif /* @@ -545,7 +614,7 @@ int slots_fdset(slots_t *slots, fd_set *readers, fd_set *writers) { return fdmax; } -int slots_accept(slots_t *slots, int listen_nr) { +int slots_accept(slots_t *slots, int fd, enum conn_proto proto) { int i; // TODO: remember previous checked slot and dont start at zero @@ -560,22 +629,13 @@ int slots_accept(slots_t *slots, int listen_nr) { return -2; } - int client = accept(slots->listen[listen_nr], NULL, 0); + int client = accept(fd, NULL, 0); if (client == -1) { return -1; } -#ifndef _WIN32 - fcntl(client, F_SETFL, O_NONBLOCK); -#else - u_long arg = 1; - ioctlsocket(client, FIONBIO, &arg); -#endif - + conn_accept(&slots->conn[i], client, proto); slots->nr_open++; - // This will truncate the time to a int - usually 32bits - slots->conn[i].activity = time(NULL); - slots->conn[i].fd = client; return i; } @@ -588,10 +648,8 @@ int slots_closeidle(slots_t *slots) { if (slots->conn[i].fd == -1) { continue; } - int delta_t = now - slots->conn[i].activity; - if (delta_t > slots->timeout) { - // TODO: metrics timeouts ++ - conn_close(&slots->conn[i]); + int fd = slots->conn[i].fd; + if (conn_closeidle(&slots->conn[i], fd, now, slots->timeout)) { nr_closed++; } } @@ -611,7 +669,9 @@ int slots_fdset_loop(slots_t *slots, fd_set *readers, fd_set *writers) { } if (FD_ISSET(slots->listen[i], readers)) { // A new connection - int slotnr = slots_accept(slots, i); + // TODO: + // - allow each listen socket to have a protocol + int slotnr = slots_accept(slots, slots->listen[i], CONN_PROTO_HTTP); switch (slotnr) { case -1: @@ -636,7 +696,7 @@ int slots_fdset_loop(slots_t *slots, fd_set *readers, fd_set *writers) { nr_open++; if (FD_ISSET(slots->conn[i].fd, readers)) { - conn_read(&slots->conn[i]); + conn_read(&slots->conn[i], slots->conn[i].fd); // possibly sets state to CONN_READY } @@ -654,14 +714,14 @@ int slots_fdset_loop(slots_t *slots, fd_set *readers, fd_set *writers) { /* fallsthrough */ case CONN_CLOSED: slots->nr_open--; - conn_close(&slots->conn[i]); + conn_close(&slots->conn[i], slots->conn[i].fd); continue; default: break; } if (FD_ISSET(slots->conn[i].fd, writers)) { - conn_write(&slots->conn[i]); + conn_write(&slots->conn[i], slots->conn[i].fd); } } diff --git a/libs/connslot/connslot.h b/libs/connslot/connslot.h index d90d2e02..fc243251 100644 --- a/libs/connslot/connslot.h +++ b/libs/connslot/connslot.h @@ -8,28 +8,36 @@ #ifndef CONNSLOT_H #define CONNSLOT_H -#include +#include // for bool +#include // for ssize_t + #ifndef _WIN32 -#include +#include // for fd_set #endif #ifdef _WIN32 #include #endif -#include "strbuf.h" +#include "strbuf.h" // for strbuf_t #ifdef _WIN32 void *memmem(void *haystack, size_t haystack_len, void * needle, size_t needle_len); #endif enum __attribute__((__packed__)) conn_state { - CONN_EMPTY, - CONN_READING, - CONN_READY, - CONN_SENDING, - CONN_CLOSED, - CONN_ERROR, + CONN_EMPTY = 0, + CONN_READING = 1, + CONN_READY = 2, + CONN_SENDING = 3, + CONN_CLOSED = 4, + CONN_ERROR = 5, +}; + +enum __attribute__((__packed__)) conn_proto { + CONN_PROTO_UNK = 0, + CONN_PROTO_HTTP = 1, + CONN_PROTO_BE16LEN = 2, }; typedef struct conn { @@ -40,6 +48,7 @@ typedef struct conn { int fd; unsigned int reply_sendpos; enum conn_state state; + enum conn_proto proto; } conn_t; #define SLOTS_LISTEN 2 @@ -53,18 +62,24 @@ typedef struct slots { void conn_zero(conn_t *); int conn_init(conn_t *, size_t, size_t); -void conn_read(conn_t *); -ssize_t conn_write(conn_t *); +void conn_accept(conn_t *, int, enum conn_proto); +void conn_check_ready(conn_t *); +void conn_read(conn_t *, int); +ssize_t conn_write(conn_t *, int); int conn_iswriter(conn_t *); -void conn_close(conn_t *); +void conn_close(conn_t *, int); +bool conn_closeidle(conn_t *, int, int, int); +void conn_dump(strbuf_t **, conn_t *); void slots_free(slots_t *slots); slots_t *slots_malloc(int nr_slots, size_t, size_t); +int slots_create_listen_tcp(int, bool); int slots_listen_tcp(slots_t *, int, bool); +int slots_create_listen_unix(char *, int, int, int); int slots_listen_unix(slots_t *, char *, int, int, int); void slots_listen_close(slots_t *); int slots_fdset(slots_t *, fd_set *, fd_set *); -int slots_accept(slots_t *, int); +int slots_accept(slots_t *, int, enum conn_proto); int slots_closeidle(slots_t *); int slots_fdset_loop(slots_t *, fd_set *, fd_set *); void slots_dump(strbuf_t **, slots_t *); diff --git a/libs/connslot/httpd-test.c b/libs/connslot/httpd-test.c index 6f2a9d46..a30b5ff0 100644 --- a/libs/connslot/httpd-test.c +++ b/libs/connslot/httpd-test.c @@ -166,7 +166,7 @@ void httpd_test(int port) { // continue; // Try to immediately start sending the reply - conn_write(&slots->conn[i]); + conn_write(&slots->conn[i], slots->conn[i].fd); } } } diff --git a/libs/connslot/strbuf.c b/libs/connslot/strbuf.c index 24064364..11778b5a 100644 --- a/libs/connslot/strbuf.c +++ b/libs/connslot/strbuf.c @@ -6,18 +6,17 @@ * SPDX-License-Identifier: LGPL-2.1-only */ -#include -#include -#include -#include -#include -#include -#include +#include // for va_list, va_end, va_start +#include // for bool, true, false +#include // for size_t, NULL +#include // for printf, vsnprintf +#include // for malloc, realloc +#include // for memcpy #ifdef _WIN32 #include #else -#include +#include // for recv, send #endif #include "strbuf.h" @@ -144,7 +143,7 @@ bool sb_overflowed(strbuf_t *p) { * @param bufsize is the length of the new data to copy * @return the total length of the stored data */ -size_t sb_append(strbuf_t *p, void *buf, ssize_t bufsize) { +size_t sb_append(strbuf_t *p, const void *buf, ssize_t bufsize) { ssize_t avail = sb_avail(p); if (avail <= 0) { // Cannot append to a full buffer @@ -178,7 +177,7 @@ size_t sb_append(strbuf_t *p, void *buf, ssize_t bufsize) { * @param bufsize is the length of the new data to copy * @return the total length of the stored data */ -strbuf_t *sb_reappend(strbuf_t **pp, void *buf, size_t bufsize) { +strbuf_t *sb_reappend(strbuf_t **pp, const void *buf, size_t bufsize) { strbuf_t *p = *pp; size_t needed = p->wr_pos + bufsize; if (needed > p->capacity) { diff --git a/libs/connslot/strbuf.h b/libs/connslot/strbuf.h index 7250f547..d3ed13ba 100644 --- a/libs/connslot/strbuf.h +++ b/libs/connslot/strbuf.h @@ -48,8 +48,8 @@ size_t sb_len(strbuf_t *); ssize_t sb_avail(strbuf_t *); bool sb_full(strbuf_t *); bool sb_overflowed(strbuf_t *); -size_t sb_append(strbuf_t *, void *, ssize_t); -strbuf_t *sb_reappend(strbuf_t **, void *, size_t); +size_t sb_append(strbuf_t *, const void *, ssize_t); +strbuf_t *sb_reappend(strbuf_t **, const void *, size_t); size_t sb_vprintf(strbuf_t *, const char *, va_list); size_t sb_printf(strbuf_t *, const char *, ...) __attribute__ ((format (printf, 2, 3))); diff --git a/packages/lib/systemd/system/n3n-edge.service b/packages/lib/systemd/system/n3n-edge.service index f0cea9fb..3bd1511b 100644 --- a/packages/lib/systemd/system/n3n-edge.service +++ b/packages/lib/systemd/system/n3n-edge.service @@ -1,5 +1,6 @@ [Unit] Description=n3n edge process +Documentation=man:n3n-edge(8) After=network-online.target nfw.target Wants=network-online.target diff --git a/packages/lib/systemd/system/n3n-edge@.service b/packages/lib/systemd/system/n3n-edge@.service index c6601253..ba6b0530 100644 --- a/packages/lib/systemd/system/n3n-edge@.service +++ b/packages/lib/systemd/system/n3n-edge@.service @@ -1,5 +1,6 @@ [Unit] Description=n3n edge process, on %I +Documentation=man:n3n-edge(8) After=network-online.target nfw.target Wants=network-online.target diff --git a/packages/lib/systemd/system/n3n-supernode.service b/packages/lib/systemd/system/n3n-supernode.service index fe304bae..5fb7dcc9 100644 --- a/packages/lib/systemd/system/n3n-supernode.service +++ b/packages/lib/systemd/system/n3n-supernode.service @@ -1,5 +1,6 @@ [Unit] Description=n3n supernode process +Documentation=man:n3n-supernode(8) After=network-online.target Wants=network-online.target diff --git a/scripts/test_integration_edge_tcp.sh b/scripts/test_integration_edge_tcp.sh new file mode 100755 index 00000000..84c3b18e --- /dev/null +++ b/scripts/test_integration_edge_tcp.sh @@ -0,0 +1,66 @@ +#!/bin/bash +# +# Copyright (C) Hamish Coleman +# SPDX-License-Identifier: GPL-3.0-only +# +# Do some quick tests via the Json API against the edge +# + +AUTH=n3n + +# boilerplate so we can support whaky cmake dirs +[ -z "$TOPDIR" ] && TOPDIR=. +[ -z "$BINDIR" ] && BINDIR=. + +docmd() { + echo "### test: $*" + "$@" + local S=$? + echo + return $S +} + +# We dont have perms for writing to the /run dir, TODO: improve this +sudo mkdir -p /run/n3n +sudo chown "$USER" /run/n3n + +# start a supernode +docmd "${BINDIR}"/apps/n3n-supernode start ci_sn \ + -v \ + --daemon \ + -Osupernode.macaddr=02:00:00:55:00:00 + +# Start the edge in the background +docmd sudo "${BINDIR}"/apps/n3n-edge start ci_edge1 \ + --daemon \ + -l localhost:7654 \ + -c test \ + -Oconnection.bind=:7700 \ + -Oconnection.connect_tcp=true \ + -Oconnection.description=ci_edge1 \ + -Odaemon.userid="$USER" \ + -Otuntap.macaddr=02:00:00:77:00:00 \ + 1>&2 + +# TODO: probe the api endpoint, waiting for both the supernode and edge to be +# available? +sleep 0.1 + +docmd "${TOPDIR}"/scripts/n3nctl -s ci_edge1 get_communities + +echo "### test: ${TOPDIR}/scripts/n3nctl -s ci_edge1 get_packetstats" +"${TOPDIR}"/scripts/n3nctl -s ci_edge1 get_packetstats |jq '.[0:5]' +# this is filtering out the type=multicast_drop line as that has a changing +# number of packets counted +echo + +docmd "${TOPDIR}"/scripts/n3nctl -s ci_edge1 get_edges --raw |grep -v "last_seen" +docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn get_edges --raw |grep -v -E "last_seen|time_alloc" + + +docmd "${TOPDIR}"/scripts/n3nctl -s ci_edge1 get_supernodes --raw + +# stop them both +docmd "${TOPDIR}"/scripts/n3nctl -s ci_edge1 -k $AUTH stop +docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn -k $AUTH stop + diff --git a/scripts/test_integration_packets.sh b/scripts/test_integration_packets.sh index 68bd5ef5..729564f8 100755 --- a/scripts/test_integration_packets.sh +++ b/scripts/test_integration_packets.sh @@ -78,4 +78,17 @@ docmd "${BINDIR}"/scripts/test_packets \ docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 get_edges --raw +# TCP +# +docmd "${BINDIR}"/scripts/test_packets \ + --tcp \ + --bind 7000 \ + -s localhost:7001 \ + test_QUERY_PEER_ping + +# TODO: +# - run all the same tests above but that needs a persistant TCP connection +# - remember that since we bound the port to TCP/7000 that becomes busy until +# the TIME-WAIT period ends + docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 -k $AUTH stop diff --git a/scripts/test_integration_supernode.sh b/scripts/test_integration_supernode.sh index 398f76a3..97eba88f 100755 --- a/scripts/test_integration_supernode.sh +++ b/scripts/test_integration_supernode.sh @@ -26,10 +26,12 @@ sudo chown "$USER" /run/n3n docmd "${BINDIR}"/apps/n3n-supernode start ci_sn1 \ --daemon \ -Oconnection.bind=7001 \ + -Osupernode.macaddr=02:00:00:00:70:01 \ -Osupernode.peer=localhost:7002 docmd "${BINDIR}"/apps/n3n-supernode start ci_sn2 \ --daemon \ -Oconnection.bind=7002 \ + -Osupernode.macaddr=02:00:00:00:70:02 \ -Osupernode.peer=localhost:7001 # TODO: probe the api endpoint, waiting for the supernode to be available? @@ -41,8 +43,8 @@ docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn2 get_communities docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 get_packetstats docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn2 get_packetstats -docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 get_edges --raw | grep -vE "last_seen|macaddr|time_alloc" -docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn2 get_edges --raw | grep -vE "last_seen|macaddr|time_alloc" +docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 get_edges --raw | grep -vE "last_seen|time_alloc" +docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn2 get_edges --raw | grep -vE "last_seen|time_alloc" # Test with bad auth docmd "${TOPDIR}"/scripts/n3nctl -s ci_sn1 set_verbose 1 diff --git a/scripts/test_packets b/scripts/test_packets index f04ecf5c..a86a22f7 100755 --- a/scripts/test_packets +++ b/scripts/test_packets @@ -187,6 +187,7 @@ class PacketBase(): def decode_sock(self, buffer, prefix="sock"): name_family = f"{prefix}_family" + name_type = f"{prefix}_type" name_port = f"{prefix}_port" name_addr = f"{prefix}_addr" @@ -202,6 +203,11 @@ class PacketBase(): format = "!HH16s" else: raise ValueError(f"Unknown sock_family {data[0]}") + sock_type = data[0] &0x4000 + if sock_type == 0: + self.data[name_type] = socket.SOCK_DGRAM + else: + self.data[name_type] = socket.SOCK_STREAM calcsize = struct.calcsize(format) buffer = buffer[:calcsize] @@ -694,9 +700,16 @@ def main(): ) ap.add_argument( "--raw", + action="store_true", help="Dont stablise the results - show the real data", default=False ) + ap.add_argument( + "--tcp", + action="store_true", + help="Switch to TCP connection", + default=False, + ) ap.add_argument("scenario", help="What packet to send") args = ap.parse_args() @@ -707,23 +720,43 @@ def main(): community = args.community.encode("utf8") PacketBase.set_default("community", community) - sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + if args.tcp: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1) + else: + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.settimeout(args.timeout) + if args.bind: sock.bind(('', args.bind)) + if args.tcp: + sock.connect((host, port)) + if args.scenario != "listen": send_pkt = PacketGeneric.from_scenario(args.scenario) buf = send_pkt.encode() print("test:") hexdump(0, buf) + if args.tcp: + # add framing + header = struct.pack('>H', len(buf)) + sock.sendto(header, (host, port)) + sock.sendto(buf, (host, port)) if args.timeout == 0: return - data = sock.recv(1600) + if args.tcp: + # fetch framing + header = sock.recv(2, socket.MSG_WAITALL) + size = struct.unpack('>H', header)[0] + data = sock.recv(size, socket.MSG_WAITALL) + else: + data = sock.recv(1600) recv_pkt = PacketGeneric.from_buffer(data) recv_pkt.decode(data) diff --git a/src/aes.c b/src/aes.c index e1ab8363..0a7879a7 100644 --- a/src/aes.c +++ b/src/aes.c @@ -18,7 +18,7 @@ * */ -#include "config.h" +#include "config.h" // for HAVE_LIBCRYPTO #include // for traceEvent #include // for uint32_t, uint8_t diff --git a/src/auth.c b/src/auth.c index 227c25a2..aa59a2ca 100644 --- a/src/auth.c +++ b/src/auth.c @@ -20,12 +20,14 @@ #include "auth.h" + #include // for traceEvent -#include // for calloc, free #include // for strlen, size_t + #include "curve25519.h" // for curve25519 #include "pearson.h" // for pearson_hash_128, pearson_hash_256 #include "speck.h" // for speck_context_t, speck_128_encrypt, speck_init +#include "n2n.h" // mapping six binary bits to printable ascii character diff --git a/src/cc20.c b/src/cc20.c index 0bde7b76..87746102 100644 --- a/src/cc20.c +++ b/src/cc20.c @@ -22,6 +22,7 @@ #include // for traceEvent #include // for calloc, free, size_t #include // for memcpy + #include "cc20.h" #include "config.h" // HAVE_LIBCRYPTO #include "portable_endian.h" // for htole32 diff --git a/src/conffile.c b/src/conffile.c index cb5250e2..5aaeb35e 100644 --- a/src/conffile.c +++ b/src/conffile.c @@ -7,11 +7,12 @@ #include // for isprint and friends #include // for errno +#include #include -#include // for n3n_peer_add_by_hostname #include // for setTraceLevel -#include // for n3n_transform_lookup_ #include +#include // for n3n_peer_add_by_hostname +#include // for n3n_transform_lookup_ #include // for true, false #include // for uint32_t #include // for printf @@ -19,14 +20,22 @@ #include // for strcmp #include // for mkdir #include // for access + #include "peer_info.h" // for struct peer_info +#include "n2n_typedefs.h" +#include "n3n/ethernet.h" +#include "uthash.h" #ifdef _WIN32 #include "win32/defs.h" + #include // for _mkdir #else +#include #include // for getgrnam #include // for sockaddr_in +#include +#include #endif #include // for generate_private_key diff --git a/src/conffile_defs.c b/src/conffile_defs.c index 7263a5f9..e8a49000 100644 --- a/src/conffile_defs.c +++ b/src/conffile_defs.c @@ -9,6 +9,8 @@ #include // for n2n_edge_conf_t #include +#include "n2n_define.h" + static struct n3n_conf_option section_auth[] = { { @@ -129,8 +131,7 @@ static struct n3n_conf_option section_connection[] = { .desc = "Control use of TCP connections to supernode", .help = "Defaulting to false, this is used to enable the use of a " "TCP connection to the supernode. If set, the allow_p2p " - "setting should usually also be set to false. This feature " - "is not currently available on Windows edge nodes.", + "setting should usually also be set to false.", }, { .name = "description", diff --git a/src/edge_utils.c b/src/edge_utils.c index 7879eff1..e92efbb4 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -29,6 +29,7 @@ #include // for n3n_peer_add_by_hostname #include // for is_null_mac #include // for traceEvent +#include // for mainloop_runonce, mainloop_regis... #include #include // for create_network_traffic_filte... #include // for n3n_rand, n3n_rand_sqr @@ -39,34 +40,43 @@ #include // for snprintf, sprintf #include // for free, calloc, getenv #include // for memcpy, memset, NULL, memcmp -#include // for timeval #include // for time_t, ssize_t, u_int #include // for time #include // for gethostname, sleep -#include "auth.h" // for generate_private_key +#include + +#include "edge_utils.h" #include "header_encryption.h" // for packet_header_encrypt, packet_he... -#include "management.h" // for readFromMgmtSocket +#include "management.h" // for mgmt_event_post +#include "minmax.h" // for MIN, MAX #include "n2n.h" // for n3n_runtime_data, n2n_edge_... #include "n2n_wire.h" // for fill_sockaddr, decod... #include "pearson.h" // for pearson_hash_128, pearson_hash_64 #include "peer_info.h" // for peer_info, clear_peer_list, ... -#include "portable_endian.h" // for be16toh, htobe16 #include "resolve.h" // for resolve_create_thread, resolve_c... #include "sn_selection.h" // for sn_selection_criterion_common_da... #include "speck.h" // for speck_128_decrypt, speck_128_enc... #include "uthash.h" // for UT_hash_handle, HASH_COUNT, HASH... +#include "n2n_define.h" +#include "n2n_typedefs.h" #ifdef _WIN32 #include // for _mkdir + #include "win32/edge_utils_win32.h" #else #include // for inet_ntoa, inet_addr, inet_ntop #include // for sockaddr_in, ntohl, IPPROTO_IP #include // for TCP_NODELAY +#include #include // for select, FD_SET, FD_ISSET, FD_ZERO #include // for setsockopt, AF_INET, connect #endif +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif /* ************************************** */ @@ -238,8 +248,11 @@ static int is_ip6_discovery (const void * buf, size_t bufsize) { // reset number of supernode connection attempts: try only once for already more realiable tcp connections void reset_sup_attempts (struct n3n_runtime_data *eee) { - - eee->sup_attempts = (eee->conf.connect_tcp) ? 1 : N2N_EDGE_SUP_ATTEMPTS; + if(eee->conf.connect_tcp) { + eee->sup_attempts = 1; + } else { + eee->sup_attempts = N2N_EDGE_SUP_ATTEMPTS; + } } @@ -329,111 +342,147 @@ void supernode_connect (struct n3n_runtime_data *eee) { n2n_sock_t local_sock; n2n_sock_str_t sockbuf; - if((eee->conf.connect_tcp) && (eee->sock >= 0)) { + if(eee->conf.connect_tcp) { + // It might be already closed, but we can simply ignore errors and + // carry on + mainloop_unregister_fd(eee->sock); closesocket(eee->sock); eee->sock = -1; } - if(eee->sock < 0) { + if(eee->sock >= 0) { + return; + } - eee->sock = open_socket( - eee->conf.bind_address, - sizeof(struct sockaddr_in), // FIXME this forces only IPv4 bindings - eee->conf.connect_tcp - ); + eee->sock = open_socket( + eee->conf.bind_address, + sizeof(struct sockaddr_in), // FIXME this forces only IPv4 bindings + eee->conf.connect_tcp + ); - if(eee->sock < 0) { - traceEvent(TRACE_ERROR, "failed to bind main UDP port"); - return; - } + if(eee->sock < 0) { + traceEvent(TRACE_ERROR, "failed to bind main UDP port"); + return; + } - fill_sockaddr((struct sockaddr*)&sn_sock, sizeof(sn_sock), &eee->curr_sn->sock); + if(eee->conf.connect_tcp) { + mainloop_register_fd(eee->sock, fd_info_proto_v3tcp); + } else { + mainloop_register_fd(eee->sock, fd_info_proto_v3udp); + } - // set tcp socket to O_NONBLOCK so connect does not hang - // requires checking the socket for readiness before sending and receving - if(eee->conf.connect_tcp) { + // REVISIT: TODO: + // - add a management event for "new supernode socket" to make it simpler + // to track subscriptions externally + + // set tcp socket to O_NONBLOCK so connect does not hang + // requires checking the socket for readiness before sending and receving + if(eee->conf.connect_tcp) { #ifdef _WIN32 - u_long value = 1; - ioctlsocket(eee->sock, FIONBIO, &value); + u_long value = 1; + ioctlsocket(eee->sock, FIONBIO, &value); #else - fcntl(eee->sock, F_SETFL, O_NONBLOCK); + fcntl(eee->sock, F_SETFL, O_NONBLOCK); #endif - if((connect(eee->sock, (struct sockaddr*)&(sn_sock), sizeof(struct sockaddr)) < 0) - && (errno != EINPROGRESS)) { - traceEvent(TRACE_INFO, "Error connecting TCP: %i", errno); - eee->sock = -1; - return; - } - } - - if(eee->conf.tos) { - /* - * See https://www.tucny.com/Home/dscp-tos for a quick table of - * the intended functions of each TOS value - * - * Note that the tos value is a byte and the manpage for IP_TOS - * defines it as a byte, but we hand setsockopt() an int value. - * This does work on linux, but - TODO, check this on other OS - */ - sockopt = eee->conf.tos; - if(setsockopt(eee->sock, IPPROTO_IP, IP_TOS, (char *)&sockopt, sizeof(sockopt)) == 0) - traceEvent(TRACE_INFO, "TOS set to 0x%x", eee->conf.tos); - else - traceEvent(TRACE_WARNING, "could not set TOS 0x%x[%d]: %s", eee->conf.tos, errno, strerror(errno)); - } -#ifdef IP_PMTUDISC_DO - if(eee->conf.pmtu_discovery) { - sockopt = IP_PMTUDISC_DO; - } else { - sockopt = IP_PMTUDISC_DONT; - } - traceEvent( - TRACE_INFO, - "Setting pmtu_discovery %s", - (eee->conf.pmtu_discovery) ? "true" : "false" - ); + fill_sockaddr((struct sockaddr*)&sn_sock, sizeof(sn_sock), &eee->curr_sn->sock); - int i = setsockopt( + int result = connect( eee->sock, - IPPROTO_IP, - IP_MTU_DISCOVER, - &sockopt, - sizeof(sockopt) + (struct sockaddr*)&(sn_sock), + sizeof(struct sockaddr) ); - if(i < 0) { +#ifndef _WIN32 + if((result == -1) && (errno != EINPROGRESS)) { + traceEvent(TRACE_INFO, "Error connecting TCP: %i", errno); + closesocket(eee->sock); + mainloop_unregister_fd(eee->sock); + eee->sock = -1; + return; + } +#else + // Oh Windows, this just seems needlessly incompatible + int wsaerr = WSAGetLastError(); + if((result == -1) && (wsaerr != WSAEWOULDBLOCK)) { traceEvent( - TRACE_WARNING, - "Setting pmtu_discovery failed: %s(%d)", - strerror(errno), - errno + TRACE_INFO, + "Error connecting TCP: WSAGetLastError %i", + wsaerr ); + closesocket(eee->sock); + mainloop_unregister_fd(eee->sock); + eee->sock = -1; + return; } -#else - traceEvent(TRACE_INFO, "No platform support for setting pmtu_discovery"); #endif + } - if(detect_local_ip_address(&local_sock, eee) == 0) { - // always overwrite local port even/especially if chosen by OS... - eee->conf.preferred_sock.port = local_sock.port; - // only if auto-detection mode, ... - if(eee->conf.preferred_sock.family != AF_INVALID) { - // ... overwrite IP address, too (whole socket struct here) - memcpy(&eee->conf.preferred_sock, &local_sock, sizeof(n2n_sock_t)); - traceEvent(TRACE_INFO, "determined local socket [%s]", - sock_to_cstr(sockbuf, &local_sock)); - } - } + if(eee->conf.tos) { + /* + * See https://www.tucny.com/Home/dscp-tos for a quick table of + * the intended functions of each TOS value + * + * Note that the tos value is a byte and the manpage for IP_TOS + * defines it as a byte, but we hand setsockopt() an int value. + * This does work on linux, but - TODO, check this on other OS + */ + sockopt = eee->conf.tos; - if(eee->cb.sock_opened) - eee->cb.sock_opened(eee); + if(setsockopt(eee->sock, IPPROTO_IP, IP_TOS, (char *)&sockopt, sizeof(sockopt)) == 0) + traceEvent(TRACE_INFO, "TOS set to 0x%x", eee->conf.tos); + else + traceEvent(TRACE_WARNING, "could not set TOS 0x%x[%d]: %s", eee->conf.tos, errno, strerror(errno)); } - // REVISIT: add mgmt port notification to listener for better mgmt port - // subscription support +#ifdef IP_PMTUDISC_DO + if(eee->conf.pmtu_discovery) { + sockopt = IP_PMTUDISC_DO; + } else { + sockopt = IP_PMTUDISC_DONT; + } + traceEvent( + TRACE_INFO, + "Setting pmtu_discovery %s", + (eee->conf.pmtu_discovery) ? "true" : "false" + ); - return; + int i = setsockopt( + eee->sock, + IPPROTO_IP, + IP_MTU_DISCOVER, + &sockopt, + sizeof(sockopt) + ); + + if(i < 0) { + traceEvent( + TRACE_WARNING, + "Setting pmtu_discovery failed: %s(%d)", + strerror(errno), + errno + ); + } +#else + traceEvent(TRACE_INFO, "No platform support for setting pmtu_discovery"); +#endif + + if(detect_local_ip_address(&local_sock, eee) != 0) { + return; + } + + // always overwrite local port even/especially if chosen by OS... + eee->conf.preferred_sock.port = local_sock.port; + + // only if auto-detection mode, ... + if(eee->conf.preferred_sock.family == AF_INVALID) { + return; + } + + // ... overwrite IP address, too (whole socket struct here) + memcpy(&eee->conf.preferred_sock, &local_sock, sizeof(n2n_sock_t)); + traceEvent(TRACE_INFO, "determined local socket [%s]", + sock_to_cstr(sockbuf, &local_sock)); } @@ -444,6 +493,7 @@ void supernode_disconnect (struct n3n_runtime_data *eee) { } if(eee->sock >= 0) { closesocket(eee->sock); + mainloop_unregister_fd(eee->sock); eee->sock = -1; traceEvent(TRACE_DEBUG, "closed"); } @@ -1029,54 +1079,20 @@ static void check_known_peer_sock_change (struct n3n_runtime_data *eee, /* ************************************** */ -/* - * Confirm that we can send to this edge. - * TODO: for the TCP case, this could cause a stall in the packet - * send path, so this probably should be reworked to use a queue - * (and non blocking IO) - */ -static bool check_sock_ready (struct n3n_runtime_data *eee) { - if(!eee->conf.connect_tcp) { - // Just show udp sockets as ready - // TODO: this is may not be always true - return true; - } - - if(eee->sock == -1) { - // If we have no sock, dont attempt to FD_SET() it - return false; - } - - // if required (tcp), wait until writeable as soket is set to - // O_NONBLOCK, could require some wait time directly after re-opening - fd_set socket_mask; - struct timeval wait_time; - - FD_ZERO(&socket_mask); - FD_SET(eee->sock, &socket_mask); - wait_time.tv_sec = 0; - wait_time.tv_usec = 500000; - return select(eee->sock + 1, NULL, &socket_mask, NULL, &wait_time); -} - /** Send a datagram to a socket file descriptor */ -static ssize_t sendto_fd (struct n3n_runtime_data *eee, const void *buf, - size_t len, struct sockaddr_in *dest, - const n2n_sock_t * n2ndest) { +static void sendto_fd (struct n3n_runtime_data *eee, const void *buf, + size_t len, struct sockaddr_in *dest, + const n2n_sock_t * n2ndest) { ssize_t sent = 0; - if(!check_sock_ready(eee)) { - goto err_out; - } - sent = sendto(eee->sock, buf, len, 0 /*flags*/, (struct sockaddr *)dest, sizeof(struct sockaddr_in)); if(sent != -1) { // sendto success traceEvent(TRACE_DEBUG, "sent=%d", (signed int)sent); - return sent; + return; } // We only get here if sendto failed, so errno must be valid @@ -1095,6 +1111,10 @@ static ssize_t sendto_fd (struct n3n_runtime_data *eee, const void *buf, level = TRACE_DEBUG; } + // TODO: + // - remove n2ndest param, as the only reason it is here is to + // stringify for errors. + // Better would be to stringify the dest sockaddr_t traceEvent(level, "sendto(%s) failed (%d) %s", sock_to_cstr(sockbuf, n2ndest), errno, errstr); @@ -1103,25 +1123,9 @@ static ssize_t sendto_fd (struct n3n_runtime_data *eee, const void *buf, #endif /* - * we get here if the sock is not ready or - * if the sendto had an error - */ -err_out: - if(eee->conf.connect_tcp) { - supernode_disconnect(eee); - eee->sn_wait = 1; - // Not true if eee->sock == -1 - traceEvent(TRACE_DEBUG, "error in sendto_fd"); - } - - /* - * If we got an error and are using UDP, this is still an error - * case. The only caller of sendto_fd() checks the return only - * in the TCP case. - * - * Thus, we can safely return an error code for any error. + * TODO: metrics for errors */ - return -1; + return; } @@ -1130,14 +1134,6 @@ static void sendto_sock (struct n3n_runtime_data *eee, const void * buf, size_t len, const n2n_sock_t * dest) { struct sockaddr_in peer_addr; - ssize_t sent; - int value = 0; - - // TODO: audit callers and confirm if this can ever happen - if(!eee) { - traceEvent(TRACE_WARNING, "bad eee"); - return; - } if(!dest->family) // invalid socket @@ -1147,41 +1143,24 @@ static void sendto_sock (struct n3n_runtime_data *eee, const void * buf, // invalid socket file descriptor, e.g. TCP unconnected has fd of '-1' return; - peer_addr.sin_port = 0; - - // network order socket - fill_sockaddr((struct sockaddr *) &peer_addr, sizeof(peer_addr), dest); + // TODO: + // - also check n2n_sock_t type == SOCK_STREAM as a TCP indicator? // if the connection is tcp, i.e. not the regular sock... if(eee->conf.connect_tcp) { - - setsockopt(eee->sock, IPPROTO_TCP, TCP_NODELAY, (void *)&value, sizeof(value)); - value = 1; -#ifdef LINUX - setsockopt(eee->sock, IPPROTO_TCP, TCP_CORK, &value, sizeof(value)); -#endif - - // prepend packet length... - uint16_t pktsize16 = htobe16(len); - sent = sendto_fd(eee, (uint8_t*)&pktsize16, sizeof(pktsize16), &peer_addr, dest); - - if(sent <= 0) - return; - // ...before sending the actual data + mainloop_send_v3tcp(eee->sock, buf, len); + /* + * TODO: metrics for errors + */ + return; } - sent = sendto_fd(eee, buf, len, &peer_addr, dest); - // if the connection is tcp, i.e. not the regular sock... - if(eee->conf.connect_tcp) { - value = 1; /* value should still be set to 1 */ - setsockopt(eee->sock, IPPROTO_TCP, TCP_NODELAY, (void *)&value, sizeof(value)); -#ifdef LINUX - value = 0; - setsockopt(eee->sock, IPPROTO_TCP, TCP_CORK, &value, sizeof(value)); -#endif - } + peer_addr.sin_port = 0; - return; + // network order socket + fill_sockaddr((struct sockaddr *) &peer_addr, sizeof(peer_addr), dest); + + sendto_fd(eee, buf, len, &peer_addr, dest); } @@ -1397,56 +1376,56 @@ static void send_unregister_super (struct n3n_runtime_data *eee) { } -static int sort_supernodes (struct n3n_runtime_data *eee, time_t now) { +static void sort_supernodes (struct n3n_runtime_data *eee, time_t now) { struct peer_info *scan, *tmp; - if(now - eee->last_sweep > SWEEP_TIME) { - // this routine gets periodically called - - if(!eee->sn_wait) { - // sort supernodes in ascending order of their selection_criterion fields - sn_selection_sort(&(eee->conf.supernodes)); - } + if(now - eee->last_sweep <= SWEEP_TIME) { + return; + } - if(eee->curr_sn != eee->conf.supernodes) { - // we have not been connected to the best/top one - send_unregister_super(eee); - eee->curr_sn = eee->conf.supernodes; - reset_sup_attempts(eee); - supernode_connect(eee); + // this routine gets periodically called - traceEvent( - TRACE_INFO, - "registering with supernode [%s][number of supernodes %d][attempts left %u]", - peer_info_get_hostname(eee->curr_sn), - HASH_COUNT(eee->conf.supernodes), - (unsigned int)eee->sup_attempts - ); + if(!eee->sn_wait) { + // sort supernodes in ascending order of their selection_criterion fields + sn_selection_sort(&(eee->conf.supernodes)); + } - send_register_super(eee); - eee->last_register_req = now; - eee->sn_wait = 1; - } + if(eee->curr_sn != eee->conf.supernodes) { + // we have not been connected to the best/top one + send_unregister_super(eee); + eee->curr_sn = eee->conf.supernodes; + reset_sup_attempts(eee); + supernode_connect(eee); - HASH_ITER(hh, eee->conf.supernodes, scan, tmp) { - if(scan == eee->curr_sn) - sn_selection_criterion_good(&(scan->selection_criterion)); - else - sn_selection_criterion_default(&(scan->selection_criterion)); - } - sn_selection_criterion_common_data_default(eee); + traceEvent( + TRACE_INFO, + "registering with supernode [%s][number of supernodes %d][attempts left %u]", + peer_info_get_hostname(eee->curr_sn), + HASH_COUNT(eee->conf.supernodes), + (unsigned int)eee->sup_attempts + ); - // send PING to all the supernodes - if(!eee->conf.connect_tcp) - send_query_peer(eee, null_mac); - eee->last_sweep = now; + send_register_super(eee); + eee->last_register_req = now; + eee->sn_wait = 1; + } - // no answer yet (so far, unused in regular edge code; mainly used during bootstrap loading) - eee->sn_pong = 0; + HASH_ITER(hh, eee->conf.supernodes, scan, tmp) { + if(scan == eee->curr_sn) + sn_selection_criterion_good(&(scan->selection_criterion)); + else + sn_selection_criterion_default(&(scan->selection_criterion)); } + sn_selection_criterion_common_data_default(eee); + + // send PING to all the supernodes + if(!eee->conf.connect_tcp) + send_query_peer(eee, null_mac); + eee->last_sweep = now; - return 0; /* OK */ + // no answer yet (so far, unused in regular edge code; mainly used during bootstrap loading) + eee->sn_pong = 0; } /** Send a REGISTER packet to another edge. */ @@ -1862,15 +1841,6 @@ static int handle_PACKET (struct n3n_runtime_data * eee, return(0); } - if(eee->cb.packet_from_peer) { - uint16_t tmp_eth_size = eth_size; - if(eee->cb.packet_from_peer(eee, orig_sender, eth_payload, &tmp_eth_size) == N2N_DROP) { - traceEvent(TRACE_DEBUG, "DROP packet of size %u", (unsigned int)eth_size); - return(0); - } - eth_size = tmp_eth_size; - } - /* Write ethernet packet to tap device. */ traceEvent(TRACE_DEBUG, "sending data of size %u to TAP", (unsigned int)eth_size); data_sent_len = tuntap_write(&(eee->device), eth_payload, eth_size); @@ -2038,8 +2008,9 @@ static int send_packet (struct n3n_runtime_data * eee, // if no supernode around, foward the broadcast to all known peers if(eee->sn_wait) { - HASH_ITER(hh, eee->known_peers, peer, tmp_peer) - sendto_sock(eee, pktbuf, pktlen, &peer->sock); + HASH_ITER(hh, eee->known_peers, peer, tmp_peer) { + sendto_sock(eee, pktbuf, pktlen, &peer->sock); + } return 0; } // fall through otherwise @@ -2178,7 +2149,7 @@ void edge_send_packet2net (struct n3n_runtime_data * eee, if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED) // in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size - packet_header_encrypt(pktbuf, headerIdx + (NULL != eee->conf.shared_secret) * min(idx - headerIdx, N2N_SPECK_IVEC_SIZE), idx, + packet_header_encrypt(pktbuf, headerIdx + (NULL != eee->conf.shared_secret) * MIN(idx - headerIdx, N2N_SPECK_IVEC_SIZE), idx, eee->conf.header_encryption_ctx_dynamic, eee->conf.header_iv_ctx_dynamic, time_stamp()); @@ -2210,6 +2181,10 @@ void edge_read_from_tap (struct n3n_runtime_data * eee) { len = tuntap_read( &(eee->device), eth_pkt, N2N_PKT_BUF_SIZE ); if((len <= 0) || (len > N2N_PKT_BUF_SIZE)) { + // TODO: + // - how often does this actually happen + // - why does it happen + // - can we just remove this special case? traceEvent( TRACE_WARNING, "read()=%d [%d/%s]", @@ -2221,6 +2196,9 @@ void edge_read_from_tap (struct n3n_runtime_data * eee) { eee->stats.tx_tuntap_error++; sleep(3); +#ifndef _WIN32 + mainloop_unregister_fd(eee->device.fd); +#endif tuntap_close(&(eee->device)); tuntap_open(&(eee->device), eee->conf.tuntap_dev_name, @@ -2230,6 +2208,9 @@ void edge_read_from_tap (struct n3n_runtime_data * eee) { eee->conf.mtu, eee->conf.metric ); +#ifndef _WIN32 + mainloop_register_fd(eee->device.fd, fd_info_proto_tuntap); +#endif return; } @@ -2260,15 +2241,6 @@ void edge_read_from_tap (struct n3n_runtime_data * eee) { } } - if(eee->cb.packet_from_tap) { - uint16_t tmp_len = len; - if(eee->cb.packet_from_tap(eee, eth_pkt, &tmp_len) == N2N_DROP) { - traceEvent(TRACE_DEBUG, "DROP packet of size %u", (unsigned int)len); - return; - } - len = tmp_len; - } - edge_send_packet2net(eee, eth_pkt, len); } @@ -2277,8 +2249,13 @@ void edge_read_from_tap (struct n3n_runtime_data * eee) { /** handle a datagram from the main UDP socket to the internet. */ -void process_udp (struct n3n_runtime_data *eee, const struct sockaddr *sender_sock, const SOCKET in_sock, - uint8_t *udp_buf, size_t udp_size, time_t now) { +void process_udp (struct n3n_runtime_data *eee, + const struct sockaddr *sender_sock, + const SOCKET in_sock, + uint8_t *udp_buf, + size_t udp_size, + time_t now, + int type) { n2n_common_t cmn; /* common fields in the packet header */ n2n_sock_str_t sockbuf1; @@ -2301,15 +2278,14 @@ void process_udp (struct n3n_runtime_data *eee, const struct sockaddr *sender_so /* REVISIT: when UDP/IPv6 is supported we will need a flag to indicate which * IP transport version the packet arrived on. May need to UDP sockets. */ + // TODO: pass the sender to process_udp, dont calculate it here if(eee->conf.connect_tcp) // TCP expects that we know our comm partner and does not deliver the sender memcpy(&sender, &(eee->curr_sn->sock), sizeof(sender)); else { - // FIXME: do not do random memset on the packet processing path - memset(&sender, 0, sizeof(sender)); // REVISIT: type conversion back and forth, choose a consistent approach throughout whole code, // i.e. stick with more general sockaddr as long as possible and narrow only if required - fill_n2nsock(&sender, sender_sock); + fill_n2nsock(&sender, sender_sock, type); } /* The packet may not have an orig_sender socket spec. So default to last * hop as sender. */ @@ -2337,9 +2313,9 @@ void process_udp (struct n3n_runtime_data *eee, const struct sockaddr *sender_so // check static now (very likely to be REGISTER_SUPER_ACK, REGISTER_SUPER_NAK or invalid) if(eee->conf.shared_secret) { // hash the still encrypted packet to eventually be able to check it later (required for REGISTER_SUPER_ACK with user/pw auth) - pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN)); + pearson_hash_128(hash_buf, udp_buf, MAX(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN)); } - header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), + header_enc = packet_header_decrypt(udp_buf, MAX(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), (char *)eee->conf.community_name, eee->conf.header_encryption_ctx_static, eee->conf.header_iv_ctx_static, &stamp); @@ -2687,9 +2663,6 @@ void process_udp (struct n3n_runtime_data *eee, const struct sockaddr *sender_so // NOTE: the register_interval should be chosen by the edge node based on its NAT configuration. // eee->conf.register_interval = ra.lifetime; - if(eee->cb.sn_registration_updated && !is_null_mac(eee->device.mac_addr)) - eee->cb.sn_registration_updated(eee, now, &sender); - break; } @@ -2878,92 +2851,93 @@ void process_udp (struct n3n_runtime_data *eee, const struct sockaddr *sender_so /* ************************************** */ - -int fetch_and_eventually_process_data (struct n3n_runtime_data *eee, SOCKET sock, - uint8_t *pktbuf, uint16_t *expected, uint16_t *position, - time_t now) { - - ssize_t bread = 0; - +void edge_read_proto3_udp (struct n3n_runtime_data *eee, + SOCKET sock, + uint8_t *pktbuf, + ssize_t pktbuf_len, + time_t now) { struct sockaddr_storage sas; struct sockaddr *sender_sock = (struct sockaddr*)&sas; socklen_t ss_size = sizeof(sas); - if((!eee->conf.connect_tcp) -#ifndef SKIP_MULTICAST_PEERS_DISCOVERY - || (sock == eee->udp_multicast_sock) -#endif - ) { - // udp - bread = recvfrom(sock, (void *)pktbuf, N2N_PKT_BUF_SIZE, 0 /*flags*/, - sender_sock, &ss_size); + ssize_t bread = recvfrom( + sock, + pktbuf, + pktbuf_len, + 0 /*flags*/, + sender_sock, + &ss_size + ); - if((bread < 0) -#ifdef _WIN32 - && (WSAGetLastError() != WSAECONNRESET) -#endif - ) { - /* For UDP bread of zero just means no data (unlike TCP). */ - /* The fd is no good now. Maybe we lost our interface. */ - traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno)); + if(bread < 0) { #ifdef _WIN32 - traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError()); -#endif - return -1; + unsigned int wsaerr = WSAGetLastError(); + if(wsaerr == WSAECONNRESET) { + return; } + traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", wsaerr); +#endif - // TODO: if bread > 64K, something is wrong - // but this case should not happen + /* For UDP bread of zero just means no data (unlike TCP). */ + /* The fd is no good now. Maybe we lost our interface. */ + traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno)); + *eee->keep_running = false; + return; + } + if(bread == 0) { + return; + } - // we have a datagram to process... - if(bread > 0) { - // ...and the datagram has data (not just a header) - process_udp(eee, sender_sock, sock, pktbuf, bread, now); - } + // TODO: + // - detect when pktbuf is too small for the packet and add that to stats + // (could switch to using recvmsg() for that) - } else { - // tcp - bread = recvfrom(sock, - (void *)(pktbuf + *position), *expected - *position, 0 /*flags*/, - sender_sock, &ss_size); - if((bread <= 0) && (errno)) { - traceEvent(TRACE_ERROR, "recvfrom() failed %d errno %d (%s)", bread, errno, strerror(errno)); + // we have a datagram to process... + // ...and the datagram has data (not just a header) + // + process_udp(eee, sender_sock, sock, pktbuf, bread, now, SOCK_DGRAM); + return; +} + +void edge_read_proto3_tcp (struct n3n_runtime_data *eee, + SOCKET sock, + uint8_t *pktbuf, + ssize_t pktbuf_len, + time_t now) { + + // tcp gets handed a pre filled pktbuf + + // zero contents means an error + if(pktbuf_len <= 0) { + traceEvent(TRACE_ERROR, "tcp conn read error %i", pktbuf_len); #ifdef _WIN32 - traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError()); + traceEvent(TRACE_ERROR, "WSAGetLastError(): %u", WSAGetLastError()); #endif - supernode_disconnect(eee); - eee->sn_wait = 1; - goto tcp_done; - } - *position = *position + bread; + supernode_disconnect(eee); + eee->sn_wait = 1; + return; + } - if(*position == *expected) { - if(*position == sizeof(uint16_t)) { - // the prepended length has been read, preparing for the packet - *expected = *expected + be16toh(*(uint16_t*)(pktbuf)); - if(*expected > N2N_PKT_BUF_SIZE) { - supernode_disconnect(eee); - eee->sn_wait = 1; - traceEvent(TRACE_DEBUG, "too many bytes expected"); - goto tcp_done; - } - } else { - // full packet read, handle it - process_udp(eee, sender_sock, sock, - pktbuf + sizeof(uint16_t), *position - sizeof(uint16_t), now); - // reset, await new prepended length - *expected = sizeof(uint16_t); - *position = 0; - } - } + if(pktbuf_len > N2N_PKT_BUF_SIZE + 2) { + supernode_disconnect(eee); + eee->sn_wait = 1; + traceEvent(TRACE_DEBUG, "too many bytes expected"); + return; } -tcp_done: - ; - return 0; + // have a valid packet read, handle it + process_udp( + eee, + NULL, + sock, + pktbuf, + pktbuf_len, + now, + SOCK_STREAM + ); + return; } - void print_edge_stats (const struct n3n_runtime_data *eee) { const struct n2n_edge_stats *s = &eee->stats; @@ -2991,10 +2965,6 @@ int run_edge_loop (struct n3n_runtime_data *eee) { time_t last_purge_host = 0; #endif - uint16_t expected = sizeof(uint16_t); - uint16_t position = 0; - uint8_t pktbuf[N2N_PKT_BUF_SIZE + sizeof(uint16_t)]; /* buffer + prepended buffer length in case of tcp */ - #ifdef _WIN32 struct tunread_arg arg; arg.eee = eee; @@ -3017,133 +2987,13 @@ int run_edge_loop (struct n3n_runtime_data *eee) { */ while(*eee->keep_running) { + mainloop_runonce(eee); - int rc, max_sock = 0; - fd_set readers; - fd_set writers; - time_t now; - - FD_ZERO(&readers); - FD_ZERO(&writers); - - if(eee->sock >= 0) { - FD_SET(eee->sock, &readers); - max_sock = max(max_sock, eee->sock); - } -#ifndef SKIP_MULTICAST_PEERS_DISCOVERY - if((eee->conf.allow_p2p) - && (eee->conf.preferred_sock.family == (uint8_t)AF_INVALID)) { - FD_SET(eee->udp_multicast_sock, &readers); - max_sock = max(max_sock, eee->udp_multicast_sock); - } -#endif - -#ifndef _WIN32 - FD_SET(eee->device.fd, &readers); - max_sock = max(max_sock, eee->device.fd); -#endif - - slots_t *slots = eee->mgmt_slots; - max_sock = max( - max_sock, - slots_fdset( - slots, - &readers, - &writers - ) - ); - - // FIXME: - // unlock the windows tun reader thread before select() and lock it - // again after select(). It currently works by accident, but the - // structures it manipulates are not thread-safe, so try to make it - // work by /design/ - - struct timeval wait_time; - wait_time.tv_sec = (eee->sn_wait) ? (SOCKET_TIMEOUT_INTERVAL_SECS / 10 + 1) : (SOCKET_TIMEOUT_INTERVAL_SECS); - wait_time.tv_usec = 0; - rc = select(max_sock + 1, &readers, &writers, NULL, &wait_time); - now = time(NULL); - - if(rc > 0) { - // any or all of the FDs could have input; check them all - - // external packets - if((eee->sock != -1) && FD_ISSET(eee->sock, &readers)) { - if(0 != fetch_and_eventually_process_data( - eee, - eee->sock, - pktbuf, - &expected, - &position, - now - )) { - *eee->keep_running = false; - } - if(eee->conf.connect_tcp) { - if((expected >= N2N_PKT_BUF_SIZE) || (position >= N2N_PKT_BUF_SIZE)) { - // something went wrong, possibly even before - // e.g. connection failure/closure in the middle of transmission (between len & data) - supernode_disconnect(eee); - eee->sn_wait = 1; - - expected = sizeof(uint16_t); - position = 0; - } - } - } - -#ifndef SKIP_MULTICAST_PEERS_DISCOVERY - if((eee->udp_multicast_sock != -1) && FD_ISSET(eee->udp_multicast_sock, &readers)) { - if(0 != fetch_and_eventually_process_data( - eee, - eee->udp_multicast_sock, - pktbuf, - &expected, - &position, - now - )) { - *eee->keep_running = false; - } - } -#endif - -#ifndef _WIN32 - if((eee->device.fd != -1) && FD_ISSET(eee->device.fd, &readers)) { - // read an ethernet frame from the TAP socket; write on the IP socket - edge_read_from_tap(eee); - } -#endif - - int slots_ready = slots_fdset_loop(slots, &readers, &writers); - - if(slots_ready < 0) { - traceEvent( - TRACE_ERROR, - "slots_fdset_loop returns %i (Is daemon exiting?)", slots_ready - ); - } else if(slots_ready > 0) { - // A linear scan is not ideal, but this is a select() loop - // not one built for performance. - // - update connslot to have callbacks instead of scan - // - switch to a modern poll loop (and reimplement differently - // for each OS supported) - // This should only be a concern if we are doing a large - // number of slot connections - for(int i=0; inr_slots; i++) { - if(slots->conn[i].fd == -1) { - continue; - } - - if(slots->conn[i].state == CONN_READY) { - mgmt_api_handler(eee, &slots->conn[i]); - } - } - } - } + // TODO: + // - migrate all the following regular actions into the + // mainloop_runonce() function - // check for timed out slots - slots_closeidle(slots); + time_t now = time(NULL); // If anything we recieved caused us to stop.. if(!(*eee->keep_running)) @@ -3194,14 +3044,9 @@ int run_edge_loop (struct n3n_runtime_data *eee) { // - multi-homing support if((eee->conf.tuntap_ip_mode == TUNTAP_IP_MODE_DHCP) && ((now - lastIfaceCheck) > IFACE_UPDATE_INTERVAL)) { - uint32_t old_ip = eee->device.ip_addr; - traceEvent(TRACE_INFO, "re-checking dynamic IP address"); tuntap_get_address(&(eee->device)); lastIfaceCheck = now; - - if((old_ip != eee->device.ip_addr) && eee->cb.ip_address_changed) - eee->cb.ip_address_changed(eee, old_ip, eee->device.ip_addr); } sort_supernodes(eee, now); @@ -3212,9 +3057,6 @@ int run_edge_loop (struct n3n_runtime_data *eee) { now ); - if(eee->cb.main_loop_period) - eee->cb.main_loop_period(eee, now); - } /* while */ send_unregister_super(eee); @@ -3241,12 +3083,18 @@ void edge_term (struct n3n_runtime_data * eee) { resolve_cancel_thread(eee->resolve_parameter); - if(eee->sock >= 0) + if(eee->sock >= 0) { closesocket(eee->sock); + mainloop_unregister_fd(eee->sock); + eee->sock = -1; + } #ifndef SKIP_MULTICAST_PEERS_DISCOVERY - if(eee->udp_multicast_sock >= 0) + if(eee->udp_multicast_sock >= 0) { closesocket(eee->udp_multicast_sock); + mainloop_unregister_fd(eee->udp_multicast_sock); + eee->udp_multicast_sock = -1; + } #endif clear_peer_list(&eee->pending_peers); @@ -3289,7 +3137,6 @@ void edge_term (struct n3n_runtime_data * eee) { closeTraceFile(); - slots_free(eee->mgmt_slots); free(eee); #ifdef _WIN32 @@ -3301,18 +3148,25 @@ void edge_term (struct n3n_runtime_data * eee) { /* ************************************** */ -static int edge_init_sockets (struct n3n_runtime_data *eee) { +#ifdef _WIN32 +// HACK! +// Remove this once the mainloop supports stopping on windows +int windows_stop_fd; +#endif - eee->mgmt_slots = slots_malloc(5, 5000, 500); - if(!eee->mgmt_slots) { - abort(); - } +static int edge_init_sockets (struct n3n_runtime_data *eee) { if(eee->conf.mgmt_port) { - if(slots_listen_tcp(eee->mgmt_slots, eee->conf.mgmt_port, false)!=0) { + int fd = slots_create_listen_tcp(eee->conf.mgmt_port, false); + if(fd < 0) { perror("slots_listen_tcp"); exit(1); } + mainloop_register_fd(fd, fd_info_proto_listen_http); +#ifdef _WIN32 + // HACK! + windows_stop_fd = fd; +#endif } n3n_config_setup_sessiondir(&eee->conf); @@ -3321,8 +3175,7 @@ static int edge_init_sockets (struct n3n_runtime_data *eee) { char unixsock[1024]; snprintf(unixsock, sizeof(unixsock), "%s/mgmt", eee->conf.sessiondir); - int e = slots_listen_unix( - eee->mgmt_slots, + int fd = slots_create_listen_unix( unixsock, eee->conf.mgmt_sock_perms, eee->conf.userid, @@ -3331,15 +3184,25 @@ static int edge_init_sockets (struct n3n_runtime_data *eee) { // TODO: // - do we actually want to tie the user/group to the running pid? - if(e!=0) { + if(fd < 0) { perror("slots_listen_tcp"); exit(1); } + mainloop_register_fd(fd, fd_info_proto_listen_http); #endif #ifndef SKIP_MULTICAST_PEERS_DISCOVERY - if(eee->udp_multicast_sock >= 0) + // TODO: + // We used to gate multicast listening on: + // if((eee->conf.allow_p2p) + // && (eee->conf.preferred_sock.family == (uint8_t)AF_INVALID)) + // So, perhaps we should do that here? + + if(eee->udp_multicast_sock >= 0) { closesocket(eee->udp_multicast_sock); + mainloop_unregister_fd(eee->udp_multicast_sock); + eee->udp_multicast_sock = -1; + } /* Populate the multicast group for local edge */ eee->multicast_peer.family = AF_INET; @@ -3372,6 +3235,7 @@ static int edge_init_sockets (struct n3n_runtime_data *eee) { setsockopt(eee->udp_multicast_sock, SOL_SOCKET, SO_REUSEPORT, &enable_reuse, sizeof(enable_reuse)); #endif + mainloop_register_fd(eee->udp_multicast_sock, fd_info_proto_v3udp); #endif return(0); @@ -3391,6 +3255,7 @@ void edge_init_conf_defaults (n2n_edge_conf_t *conf, char *sessionname) { } else { conf->sessionname = "NULL"; } + n3n_metrics_set_session(conf->sessionname); conf->is_edge = true; diff --git a/src/edge_utils.h b/src/edge_utils.h new file mode 100644 index 00000000..a1c4f60a --- /dev/null +++ b/src/edge_utils.h @@ -0,0 +1,11 @@ +/** + * Copyright (C) Hamish Coleman + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef _EDGE_UTILS_H_ +#define _EDGE_UTILS_H_ + +void edge_read_from_tap (struct n3n_runtime_data *eee); + +#endif diff --git a/src/header_encryption.c b/src/header_encryption.c index 2d7ca173..00e2025f 100644 --- a/src/header_encryption.c +++ b/src/header_encryption.c @@ -22,10 +22,8 @@ #include // for traceEvent #include // for n3n_rand #include // for uint32_t, uint8_t, uint64_t, uint16_t -#include // for calloc #include // for memcpy #include "header_encryption.h" // for packet_header_change_dynamic_key, pac... -#include "n2n.h" // for N2N_COMMUNITY_SIZE... #include "n2n_define.h" // for N2N_COMMUNITY_SIZE #include "n2n_typedefs.h" // for N2N_AUTH_CHALLENGE_SIZE #include "pearson.h" // for pearson_hash_128, pearson_hash_64 @@ -33,6 +31,8 @@ #include "speck.h" // for speck_init, speck_context_t, speck_ctr #include "uthash.h" // for HASH_FIND_STR +struct speck_context_t; + #define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out) diff --git a/src/initfuncs.c b/src/initfuncs.c index 1621b0ad..0a787627 100644 --- a/src/initfuncs.c +++ b/src/initfuncs.c @@ -7,6 +7,7 @@ // prototype any internal (non-public) initfuncs (always sorted!) void n3n_initfuncs_conffile_defs (); +void n3n_initfuncs_mainloop (); void n3n_initfuncs_metrics (); void n3n_initfuncs_pearson (); void n3n_initfuncs_peer_info (); @@ -28,6 +29,7 @@ void n3n_initfuncs () { // (sorted list) n3n_initfuncs_conffile_defs(); + n3n_initfuncs_mainloop(); n3n_initfuncs_metrics(); n3n_initfuncs_pearson(); n3n_initfuncs_peer_info(); diff --git a/src/mainloop.c b/src/mainloop.c new file mode 100644 index 00000000..5bc7ab95 --- /dev/null +++ b/src/mainloop.c @@ -0,0 +1,587 @@ +/** + * Copyright (C) Hamish Coleman + * SPDX-License-Identifier: GPL-3.0-only + * + */ + +#include +#include // for slots_fdset +#include // for n3n_runtime_data +#include // for edge_read_proto3_udp +#include // for traceEvent +#include // for fd_info_proto +#include +#include // for traceEvent +#include +#include + +#ifndef _WIN32 +#include // for select, FD_ZERO, +#include // for close +#endif + +#include "edge_utils.h" // for edge_read_from_tap +#include "management.h" // for readFromMgmtSocket +#include "minmax.h" // for min, max +#include "portable_endian.h" // for htobe16 + +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif + +static struct metrics { + uint32_t mainloop; // mainloop_runonce() is called + uint32_t register_fd; // mainloop_register_fd() is called + uint32_t unregister_fd; // mainloop_unregister_fd() is called + uint32_t connlist_alloc; + uint32_t connlist_free; + uint32_t send_queue_fail; // Attempted to send v3tcp but buffer in use +} metrics; + +static struct n3n_metrics_items_llu32 metrics_items = { + .name = "count", + .desc = "Track the events in the lifecycle of mainloop objects", + .name1 = "event", + .items = { + { + .val1 = "mainloop", + .offset = offsetof(struct metrics, mainloop), + }, + { + .val1 = "register_fd", + .offset = offsetof(struct metrics, register_fd), + }, + { + .val1 = "unregister_fd", + .offset = offsetof(struct metrics, unregister_fd), + }, + { + .val1 = "connlist_alloc", + .offset = offsetof(struct metrics, connlist_alloc), + }, + { + .val1 = "connlist_free", + .offset = offsetof(struct metrics, connlist_free), + }, + { + .val1 = "send_queue_fail", + .offset = offsetof(struct metrics, send_queue_fail), + }, + { }, + }, +}; + +static char *proto_str[] = { + [fd_info_proto_unknown] = "?", + [fd_info_proto_tuntap] = "tuntap", + [fd_info_proto_listen_http] = "listen_http", + [fd_info_proto_v3udp] = "v3udp", + [fd_info_proto_v3tcp] = "v3tcp", + [fd_info_proto_http] = "http", +}; + +struct fd_info { + int fd; // The file descriptor for this connection + int stats_reads; // The number of ready to read events + enum fd_info_proto proto; // What protocol to use on a read event + int8_t connnr; // which connlist[] is being used as buffer +}; + +// A static array of known file descriptors will not scale once full TCP +// connection support is added, but will work for now +#define MAX_HANDLES 16 +static struct fd_info fdlist[MAX_HANDLES]; +static int fdlist_next_search; + +#define MAX_CONN 8 +// TODO: need pools of struct conn, for each expected buffer size +static struct conn connlist[MAX_CONN]; +static int connlist_next_search; + +static void metrics_callback (strbuf_t **reply, const struct n3n_metrics_module *module) { + int slot = 0; + char buf[16]; + while(slot < MAX_HANDLES) { + if(fdlist[slot].fd == -1) { + slot++; + continue; + } + + snprintf(buf, sizeof(buf), "%i", fdlist[slot].fd); + + n3n_metrics_render_u32tags( + reply, + module, + "fd_reads", + (char *)&fdlist[slot].stats_reads - (char *)&fdlist, + 2, // number of tag+val pairs + "fd", + buf, + "proto", + proto_str[fdlist[slot].proto] + ); + // TODO: + // - do we need to keep each fd lifecycle clear by tracking and + // outputting the open timestamp? + slot++; + } +} + +static struct n3n_metrics_module metrics_module_dynamic = { + .name = "mainloop", + .data = &fdlist, + .cb = &metrics_callback, + .type = n3n_metrics_type_cb, +}; + +static struct n3n_metrics_module metrics_module_static = { + .name = "mainloop", + .data = &metrics, + .items_llu32 = &metrics_items, + .type = n3n_metrics_type_llu32, +}; + +static void connlist_init () { + int conn = 0; + while(conn < MAX_CONN) { + conn_init(&connlist[conn], 4000, 1000); + conn++; + } + connlist_next_search = 0; +} + +static int connlist_alloc (enum conn_proto proto) { + int conn = connlist_next_search % MAX_CONN; + int count = MAX_CONN; + while(count) { + if(connlist[conn].proto == CONN_PROTO_UNK) { + connlist[conn].proto = proto; + connlist_next_search = conn + 1; + metrics.connlist_alloc++; + return conn; + } + conn = (conn + 1) % MAX_CONN; + count--; + } + return -1; +} + +static void connlist_free (int connnr) { + if(connnr > MAX_CONN) { + // TODO: error! + return; + } + connlist[connnr].fd = -1; + connlist[connnr].proto = CONN_PROTO_UNK; + connlist[connnr].state = CONN_EMPTY; + connlist_next_search = connnr; + metrics.connlist_free++; +} + +// Used only to initialise the array at startup +static void fdlist_zero () { + int slot = 0; + while(slot < MAX_HANDLES) { + fdlist[slot].connnr = -1; + fdlist[slot].fd = -1; + fdlist[slot].proto = fd_info_proto_unknown; + slot++; + } + fdlist_next_search = 0; +} + +static int fdlist_allocslot (int fd, enum fd_info_proto proto) { + int slot = fdlist_next_search % MAX_HANDLES; + int count = MAX_HANDLES; + while(count) { + if(fdlist[slot].fd == -1) { + metrics.register_fd++; + fdlist[slot].fd = fd; + fdlist[slot].proto = proto; + fdlist[slot].stats_reads = 0; + + if(proto == fd_info_proto_v3tcp) { + int connnr = connlist_alloc(CONN_PROTO_BE16LEN); + assert(connnr != -1); + + fdlist[slot].connnr = connnr; + conn_accept(&connlist[connnr], fd, CONN_PROTO_BE16LEN); + } else { + fdlist[slot].connnr = -1; + } + + fdlist_next_search = slot + 1; + return slot; + } + slot = (slot + 1) % MAX_HANDLES; + count--; + } + + // TODO: the moment this starts to fire, we need to revamp the + // implementation of the fdlist table + assert(slot != -1); + return -1; +} + +static void fdlist_freefd (int fd) { + int slot = 0; + while(slot < MAX_HANDLES) { + if(fdlist[slot].fd != fd) { + slot++; + continue; + } + metrics.unregister_fd++; + if(fdlist[slot].connnr != -1) { + connlist_free(fdlist[slot].connnr); + fdlist[slot].connnr = -1; + } + fdlist[slot].fd = -1; + fdlist[slot].proto = fd_info_proto_unknown; + fdlist_next_search = slot; + return; + } + + // TODO: + // - could assert or similar +} + +static int fdlist_fd_set (fd_set *rd, fd_set *wr) { + int max_sock = 0; + int slot = 0; + while(slot < MAX_HANDLES) { + if(fdlist[slot].fd == -1) { + slot++; + continue; + } + + // TODO: + // - if no empty conn, dont FD_SET on proto TCP listen + + FD_SET(fdlist[slot].fd, rd); + max_sock = MAX(max_sock, fdlist[slot].fd); + + if(fdlist[slot].connnr == -1) { + slot++; + continue; + } + + if(conn_iswriter(&connlist[fdlist[slot].connnr])) { + FD_SET(fdlist[slot].fd, wr); + } + + slot++; + } + return max_sock; +} + +static void handle_fd (const time_t now, const struct fd_info info, struct n3n_runtime_data *eee) { + switch(info.proto) { + case fd_info_proto_unknown: + // should not happen! + assert(false); + return; + + case fd_info_proto_tuntap: + // read an ethernet frame from the TAP socket; write on the IP + // socket + // TODO: change API to tell it which fd + edge_read_from_tap(eee); + return; + + case fd_info_proto_listen_http: { + int client = accept(info.fd, NULL, 0); + if(client == -1) { + // TODO: + // - increment error stats + return; + } + + int slotnr = fdlist_allocslot(client, fd_info_proto_http); + int connnr = connlist_alloc(CONN_PROTO_HTTP); + if(slotnr < 0 || connnr < 0) { + // TODO: + // - increment error stats + // - send static text + closesocket(client); + return; + } + fdlist[slotnr].connnr = connnr; + conn_accept(&connlist[connnr], client, CONN_PROTO_HTTP); + + return; + } + + case fd_info_proto_v3udp: { + uint8_t pktbuf[N2N_PKT_BUF_SIZE]; + edge_read_proto3_udp( + eee, + info.fd, + pktbuf, + sizeof(pktbuf), + now + ); + return; + } + + case fd_info_proto_v3tcp: { + struct conn *conn = &connlist[info.connnr]; + conn_read(conn, info.fd); + + switch(conn->state) { + case CONN_EMPTY: + case CONN_READING: + case CONN_SENDING: + // These states dont require us to do anything + // TODO: + // - handle reading/sending simultaneous? + return; + + case CONN_ERROR: + case CONN_CLOSED: + conn_close(conn, info.fd); + sb_zero(conn->request); + // Let the upper layer realise its connection is gone by + // showing it a zero sized request + + // TODO: if the upper layer doesnt react properly by + // unregistering the dead filehandle, we leak slots and + // conns here + + edge_read_proto3_tcp(eee, -1, NULL, -1, now); + return; + + case CONN_READY: { + int size = ntohs(*(uint16_t *)&conn->request->str); + + edge_read_proto3_tcp( + eee, + info.fd, + (uint8_t *)&conn->request->str[2], + size, + now + ); + + if(sb_len(conn->request) == (size + 2)) { + // We read exactly one packet + // TODO: this crosses layers by reaching inside the + // conn object + sb_zero(conn->request); + conn->state = CONN_EMPTY; + return; + } + + // Our buffer contains data beyond the single packet + + // TODO: this crosses layers by reaching inside the + // conn object + int more = sb_len(conn->request) - (size + 2); + traceEvent(TRACE_DEBUG, "packet has %i more bytes", more); + memmove( + conn->request->str, + &conn->request->str[size + 2], + more + ); + conn->request->rd_pos = 0; + conn->request->wr_pos = more; + conn->state = CONN_READING; + + // FIXME: sometimes we will have an entire next packet in + // the buffer, which means we should not wait for the FD + // to be read ready again + return; + } + } + return; + } + + case fd_info_proto_http: { + struct conn *conn = &connlist[info.connnr]; + conn_read(conn, info.fd); + + switch(conn->state) { + case CONN_EMPTY: + case CONN_READING: + case CONN_SENDING: + // These states dont require us to do anything + // TODO: + // - handle reading/sending simultaneous? + return; + + case CONN_READY: + mgmt_api_handler(eee, conn); + return; + + case CONN_ERROR: + case CONN_CLOSED: + conn_close(conn, info.fd); + // TODO: freefd() is doing a fd search, we could optimise + fdlist_freefd(info.fd); + } + return; + } + } +} + +static void fdlist_check_ready (fd_set *rd, fd_set *wr, const time_t now, struct n3n_runtime_data *eee) { + int slot = 0; + // A linear scan is not ideal, but until we support things other than + // select() it will need to suffice + while(slot < MAX_HANDLES) { + int fd = fdlist[slot].fd; + if(fd == -1) { + slot++; + continue; + } + if(FD_ISSET(fd, rd)) { + fdlist[slot].stats_reads++; + handle_fd(now, fdlist[slot], eee); + } + if(FD_ISSET(fd, wr)) { + // We should not be listening on this socket if there is no + // connnr assigned, but paranoia.. + if(fdlist[slot].connnr == -1) { + traceEvent(TRACE_DEBUG, "writer bad connnr"); + slot++; + continue; + } + + // TODO: track the stats on writes? + conn_write(&connlist[fdlist[slot].connnr], fd); + } + + if(fdlist[slot].connnr != -1) { + int timeout = 60; + struct conn *conn = &connlist[fdlist[slot].connnr]; + bool closed = conn_closeidle(conn, fd, now, timeout); + if(closed) { + fdlist_freefd(fd); + } + } + slot++; + } +} + +int mainloop_runonce (struct n3n_runtime_data *eee) { + fd_set rd; + fd_set wr; + + metrics.mainloop++; + + FD_ZERO(&rd); + FD_ZERO(&wr); + int maxfd = fdlist_fd_set(&rd, &wr); + + // FIXME: + // unlock the windows tun reader thread before select() and lock it + // again after select(). It currently works by accident, but the + // structures it manipulates are not thread-safe, so try to make it + // work by /design/ + + struct timeval wait_time; + if(eee->sn_wait) { + wait_time.tv_sec = (SOCKET_TIMEOUT_INTERVAL_SECS / 10 + 1); + } else { + wait_time.tv_sec = (SOCKET_TIMEOUT_INTERVAL_SECS); + } + wait_time.tv_usec = 0; + + int ready = select(maxfd + 1, &rd, &wr, NULL, &wait_time); + + if(ready < 1) { + // Nothing ready or an error + return ready; + } + + // One timestamp to use for this entire loop iteration + time_t now = time(NULL); + + fdlist_check_ready(&rd, &wr, now, eee); + + return ready; +} + +void mainloop_dump (strbuf_t **buf) { + int i; + sb_reprintf(buf, "i : fd(read) pr connnr\n"); + for(i=0; ireply) { + conn->reply = sb_malloc(N2N_PKT_BUF_SIZE + 2, N2N_PKT_BUF_SIZE + 2); + } else { + if(sb_len(conn->reply)) { + // send buffer already in use + // TODO: + // - metrics! + return false; + } + sb_zero(conn->reply); + } + + uint16_t pktsize16 = htobe16(bufsize); + sb_append(conn->reply, &pktsize16, sizeof(pktsize16)); + + // TODO: + // - avoid memcpy by using a global buffer pool and transferring ownership + sb_append(conn->reply, buf, bufsize); + + // TODO: + // - check bufsize for N2N_PKT_BUF_SIZE overflow + + conn_write(conn, fd); + return true; +} + +void mainloop_register_fd (int fd, enum fd_info_proto proto) { + fdlist_allocslot(fd, proto); +} + +void mainloop_unregister_fd (int fd) { + fdlist_freefd(fd); +} + +void n3n_initfuncs_mainloop () { + connlist_init(); + fdlist_zero(); + n3n_metrics_register(&metrics_module_dynamic); + n3n_metrics_register(&metrics_module_static); +} diff --git a/src/management.c b/src/management.c index 69157e68..7b60dcf1 100644 --- a/src/management.c +++ b/src/management.c @@ -11,22 +11,32 @@ #include // for jsonrpc_t, jsonrpc_parse #include // for is_null_mac #include // for traceEvent +#include // for mainloop_unregister_fd #include // for n3n_metrics_render #include // for ip_subnet_to_str, sock_to_cstr #include // for load_allowed_sn_community #include // for sn_selection_criterion_str #include -#include // for snprintf, NULL, size_t +#include +#include #include // for strtoul #include // for strtok, strlen, strncpy +#include +#include + #include "base64.h" // for base64decode +#include "connslot/strbuf.h" #include "management.h" +#include "n2n.h" +#include "n2n_typedefs.h" #include "peer_info.h" // for peer_info +#include "uthash.h" #ifdef _WIN32 #include "win32/defs.h" #else #include // for getnameinfo, NI_NUMERICHOST, NI_NUMERICSERV +#include #include // for sendto, sockaddr #endif @@ -245,7 +255,10 @@ static void event_subscribe (struct n3n_runtime_data *eee, conn_t *conn) { // Take the filehandle away from the connslots. mgmt_event_subscribers[topicid] = conn->fd; - conn_zero(conn); + // TODO: + // - Keep these filehandles in the mainloop + // - change the mainloop proto mark it as "event" + mainloop_unregister_fd(conn->fd); // TODO: shutdown(fd, SHUT_RD) - but that does nothing for unix domain @@ -1077,7 +1090,7 @@ static void render_metrics_page (struct n3n_runtime_data *eee, conn_t *conn) { // Update the reply buffer after last potential realloc conn->reply = conn->request; - generate_http_headers(conn, "text/plain", 501); + generate_http_headers(conn, "text/plain", 200); } #include "management_index.html.h" @@ -1101,13 +1114,22 @@ static void render_script_page (struct n3n_runtime_data *eee, conn_t *conn) { static void render_debug_slots (struct n3n_runtime_data *eee, conn_t *conn) { int status; sb_zero(conn->request); - if(eee->conf.enable_debug_pages) { - slots_dump(&conn->request, eee->mgmt_slots); - status = 200; - } else { + if(!eee->conf.enable_debug_pages) { sb_printf(conn->request, "enable_debug_pages is false\n"); status = 403; + // Update the reply buffer after last potential realloc + conn->reply = conn->request; + generate_http_headers(conn, "text/plain", status); + return; + } + + if(eee->mgmt_slots) { + slots_dump(&conn->request, eee->mgmt_slots); + sb_reprintf(&conn->request, "\n"); } + mainloop_dump(&conn->request); + + status = 200; // Update the reply buffer after last potential realloc conn->reply = conn->request; generate_http_headers(conn, "text/plain", status); @@ -1166,11 +1188,16 @@ void mgmt_api_handler (struct n3n_runtime_data *eee, conn_t *conn) { } } if( i >= nr_handlers ) { + traceEvent( + TRACE_DEBUG, + "unknown endpoint %s", + conn->request->str + ); render_error(conn, "unknown endpoint"); } else { api_endpoints[i].func(eee, conn); } // Try to immediately start sending the reply - conn_write(conn); + conn_write(conn, conn->fd); } diff --git a/src/management.h b/src/management.h index 68f3e430..bc0c814e 100644 --- a/src/management.h +++ b/src/management.h @@ -18,8 +18,11 @@ #include // for size_t #include // for uint64_t #include // for ssize_t + #include "n2n_define.h" // for n2n_event_topic +struct n3n_runtime_data; + #ifdef _WIN32 #include #else diff --git a/src/metrics.c b/src/metrics.c index edf747ec..b2dc2161 100644 --- a/src/metrics.c +++ b/src/metrics.c @@ -7,8 +7,12 @@ #include #include +#include +#include #include +static char *sessionname; + static struct n3n_metrics_module *registered_metrics; void n3n_metrics_register (struct n3n_metrics_module *module) { @@ -47,7 +51,7 @@ static void metrics_render_uint32 (strbuf_t **reply, struct n3n_metrics_module * // - " UNIT name type\n" // - " HELP name type\n" metrics_name(reply, module->name, module->items_uint32[i].name); - sb_reprintf(reply, " "); + sb_reprintf(reply, "{session=\"%s\"} ", sessionname); metric_stringify_uint32( reply, module->items_uint32[i].offset, @@ -57,34 +61,74 @@ static void metrics_render_uint32 (strbuf_t **reply, struct n3n_metrics_module * } } +void n3n_metrics_render_u32tags ( + strbuf_t **reply, + const struct n3n_metrics_module *module, + const char *name, + const int offset, + const int tags, + ...) { + + va_list ap; + + metrics_name(reply, module->name, name); + sb_reprintf(reply, "{session=\"%s\"", sessionname); + + int count = tags; + va_start(ap, tags); + while(count) { + char *tag = va_arg(ap, char *); + char *val = va_arg(ap, char *); + + // Skip empty tags + if(!tag || !val) { + count--; + continue; + } + + if(count) { + sb_reprintf(reply,","); + } + sb_reprintf(reply,"%s=\"%s\"", tag, val); + count--; + } + va_end(ap); + + sb_reprintf(reply,"} "); + + metric_stringify_uint32( + reply, + offset, + module->data + ); + sb_reprintf(reply, "\n"); +} + + static void metrics_render_llu32 (strbuf_t **reply, struct n3n_metrics_module *module) { const struct n3n_metrics_items_llu32 *info = module->items_llu32; if(info->desc) { sb_reprintf(reply, "# HELP "); metrics_name(reply, module->name, info->name); - sb_reprintf(reply, "%s\n", info->desc); + sb_reprintf(reply, " %s\n", info->desc); } for(int i = 0; info->items[i].val1; i++) { // TODO: // - " TYPE name type\n" // - " UNIT name type\n" - metrics_name(reply, module->name, info->name); - sb_reprintf( + n3n_metrics_render_u32tags( reply, - "{%s=\"%s\",%s=\"%s\"} ", + module, + info->name, + info->items[i].offset, + 2, // number of tag+val pairs info->name1, info->items[i].val1, info->name2, info->items[i].val2 ); - metric_stringify_uint32( - reply, - info->items[i].offset, - module->data - ); - sb_reprintf(reply, "\n"); } } @@ -105,6 +149,9 @@ void n3n_metrics_render (strbuf_t **reply) { case n3n_metrics_type_llu32: metrics_render_llu32(reply, module); break; + case n3n_metrics_type_cb: + module->cb(reply, module); + break; } } } @@ -160,7 +207,10 @@ static struct n3n_metrics_module strbuf_metrics_module = { }; /**********************************************************/ - void n3n_initfuncs_metrics () { n3n_metrics_register(&strbuf_metrics_module); } + +void n3n_metrics_set_session (char *name) { + sessionname = name; +} diff --git a/src/minmax.h b/src/minmax.h new file mode 100644 index 00000000..313b30dc --- /dev/null +++ b/src/minmax.h @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2024 Hamish Coleman + * SPDX-License-Identifier: GPL-3.0-only + * + */ + +// TODO: +// - on linux there are headers with these predefined +// - on windows, there are different predefines +// - use them! +#ifndef MAX +#define MAX(a, b) (((a) < (b)) ? (b) : (a)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) >(b)) ? (b) : (a)) +#endif diff --git a/src/n2n.c b/src/n2n.c index b57a2ef6..21016437 100644 --- a/src/n2n.c +++ b/src/n2n.c @@ -24,19 +24,23 @@ #include // for traceEvent #include // for n3n_rand #include // for ip_subnet_to_str, sock_to_cstr -#include +#include +#include #include // for free, atoi, calloc, strtol #include // for memcmp, memcpy, memset, strlen, strerror #include // for gettimeofday, timeval -#include // for time, localtime, strftime + #include "n2n.h" -#include "uthash.h" // for UT_hash_handle, HASH_DEL, HASH_ITER, HAS... +#include "n2n_define.h" +#include "n2n_typedefs.h" #ifdef _WIN32 #include "win32/defs.h" + #include #else #include // for inet_ntop +#include #include // for AF_INET, PF_INET, bind, setsockopt, shut... #endif @@ -271,24 +275,34 @@ extern char * sock_to_cstr (n2n_sock_str_t out, } memset(out, 0, N2N_SOCKBUF_SIZE); + bool is_tcp = (sock->type == SOCK_STREAM); + if(AF_INET6 == sock->family) { char tmp[INET6_ADDRSTRLEN+1]; tmp[0] = '\0'; inet_ntop(AF_INET6, sock->addr.v6, tmp, sizeof(n2n_sock_str_t)); - snprintf(out, N2N_SOCKBUF_SIZE, "[%s]:%hu", tmp[0] ? tmp : "", sock->port); - return out; - } else { - const uint8_t * a = sock->addr.v4; - - snprintf(out, N2N_SOCKBUF_SIZE, "%hu.%hu.%hu.%hu:%hu", - (unsigned short)(a[0] & 0xff), - (unsigned short)(a[1] & 0xff), - (unsigned short)(a[2] & 0xff), - (unsigned short)(a[3] & 0xff), - (unsigned short)sock->port); + snprintf( + out, + N2N_SOCKBUF_SIZE, + "%s[%s]:%hu", + is_tcp ? "TCP/" : "", + tmp[0] ? tmp : "", + sock->port + ); return out; } + + const uint8_t * a = sock->addr.v4; + + snprintf(out, N2N_SOCKBUF_SIZE, "%s%hu.%hu.%hu.%hu:%hu", + is_tcp ? "TCP/" : "", + (unsigned short)(a[0] & 0xff), + (unsigned short)(a[1] & 0xff), + (unsigned short)(a[2] & 0xff), + (unsigned short)(a[3] & 0xff), + (unsigned short)sock->port); + return out; } // TODO: move to a strings helper source file diff --git a/src/n2n_port_mapping.c b/src/n2n_port_mapping.c index 814b651a..a63fba58 100644 --- a/src/n2n_port_mapping.c +++ b/src/n2n_port_mapping.c @@ -54,17 +54,11 @@ */ -#include // for N2N_NETMASK_STR_SIZE -#include // for traceEvent #include // for uint16_t -#include // for memcpy #ifdef _WIN32 #include "win32/defs.h" #else -#include // for inet_ntoa -#include // for in_addr, htonl, in_addr_t -#include // for sendto, recvfrom, sockaddr_storage #endif #include "n2n_port_mapping.h" // for n2n_del_port_mapping, n2n_set_port_map... diff --git a/src/network_traffic_filter.c b/src/network_traffic_filter.c index 99a2e4bc..f727648a 100644 --- a/src/network_traffic_filter.c +++ b/src/network_traffic_filter.c @@ -25,8 +25,9 @@ #include // for sprintf #include // for free, malloc, atoi #include // for memcpy, strcpy, NULL, memset -#include "n2n.h" // for filter_rule_t, filter_rule_pair_... + #include "uthash.h" // for UT_hash_handle, HASH_ITER, HASH_DEL +#include "n2n_typedefs.h" #ifdef _WIN32 #include "win32/defs.h" diff --git a/src/peer_info.c b/src/peer_info.c index d5ca9e7c..29a656b0 100644 --- a/src/peer_info.c +++ b/src/peer_info.c @@ -11,10 +11,23 @@ #include // for traceEvent #include // for sn_selection_criterion_default #include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + #include "management.h" // for mgmt_event_post #include "peer_info.h" #include "resolve.h" // for supernode2sock +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif + static struct metrics { uint32_t init; // peer_info_init() is called uint32_t alloc; // peer_info_malloc() is called diff --git a/src/peer_info.h b/src/peer_info.h index 7d5290b1..a1f54085 100644 --- a/src/peer_info.h +++ b/src/peer_info.h @@ -9,6 +9,12 @@ #define _PEER_INFO_H_ #include // for n2n_mac_t, n2n_ip_subnet_t, n2n_desc_t, n2n_sock_t +#include +#include + +#include "n2n_typedefs.h" +#include "n3n/ethernet.h" +#include "uthash.h" #define HASH_ADD_PEER(head,add) \ HASH_ADD(hh,head,mac_addr,sizeof(n2n_mac_t),add) diff --git a/src/random_numbers.c b/src/random_numbers.c index f8223d7a..820c2abd 100644 --- a/src/random_numbers.c +++ b/src/random_numbers.c @@ -25,6 +25,7 @@ #include // for NULL, size_t #include // for clock, time #include // for syscall +#include // syscall and inquiring random number from hardware generators might fail, so // we will retry diff --git a/src/resolve.c b/src/resolve.c index 1603f0dc..d857ee73 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -6,14 +6,20 @@ * The resolver thread code and functions */ -#include // for sock_equal #include #include #include // for n3n_resolve_parameter_t -#include // for sock_to_cstr -#include // for sleep -#include "resolve.h" +#include +#include +#include +#include + #include "config.h" // for HAVE_LIBPTHREAD +#include "resolve.h" +#include "n2n_define.h" +#include "n2n_typedefs.h" + +struct peer_info; #ifdef HAVE_LIBPTHREAD #include @@ -24,6 +30,7 @@ #include #else #include // for addrinfo, freeaddrinfo, gai_strerror +#include #include // for AF_INET, PF_INET #include // for gettimeofday, timersub #endif diff --git a/src/resolve.h b/src/resolve.h index 974c9890..6c4f1475 100644 --- a/src/resolve.h +++ b/src/resolve.h @@ -9,12 +9,17 @@ #ifndef _RESOLVE_H_ #define _RESOLVE_H_ -#include // for n2n_resolve_parameter_t #include // for n2n_sock_t +#include // for n2n_resolve_parameter_t +#include +#include #include // for UT_hash_handle + #include "config.h" // for HAVE_LIBPTHREAD #include "peer_info.h" // for struct peer_info +struct peer_info; + #ifdef HAVE_LIBPTHREAD struct n3n_resolve_ip_sock { char *org_ip; /* pointer to original ip/named address string (used read only) */ diff --git a/src/sn_selection.c b/src/sn_selection.c index e4c27a8e..77c3da81 100644 --- a/src/sn_selection.c +++ b/src/sn_selection.c @@ -24,6 +24,9 @@ #include // for snprintf, NULL #include // for memcpy, memset #include "n2n.h" // for n3n_runtime_data, SN_SELECTION_CRIT... +#include "n2n_define.h" +#include "n2n_typedefs.h" +#include "n3n/ethernet.h" #include "peer_info.h" // for peer_info_t #include "portable_endian.h" // for be32toh, be64toh, htobe64 #include "sn_selection.h" // for selection_criterion_str_t, sn_selection_cr... @@ -133,6 +136,12 @@ int sn_selection_criterion_common_data_default (struct n3n_runtime_data *eee) { switch(eee->conf.sn_selection_strategy) { case SN_SELECTION_STRATEGY_LOAD: { + // something something Windows, something something Complete + if(!eee->pending_peers) { + eee->sn_selection_criterion_common_data = 0; + return 0; + } + SN_SELECTION_CRITERION_DATA_TYPE tmp = 0; tmp = HASH_COUNT(eee->pending_peers); diff --git a/src/sn_utils.c b/src/sn_utils.c index 99915df3..3af51978 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -32,14 +32,17 @@ #include // for free, calloc, getenv #include // for memcpy, NULL, memset, size_t, strerror #include // for MAX -#include // for timeval -#include // for ssize_t #include // for time_t, time +#include + #include "auth.h" // for ascii_to_bin, calculate_dynamic_key #include "header_encryption.h" // for packet_header_encrypt, packet_header_... #include "management.h" // for process_mgmt +#include "minmax.h" // for MIN, MAX #include "n2n.h" // for sn_community, n3n_runtime_data +#include "n2n_define.h" #include "n2n_regex.h" // for re_matchp, re_compile +#include "n2n_typedefs.h" #include "n2n_wire.h" // for encode_buf, encode_PEER_INFO, encode_... #include "pearson.h" // for pearson_hash_128, pearson_hash_32 #include "peer_info.h" // for purge_peer_list, clear_peer_list @@ -50,16 +53,23 @@ #include "uthash.h" // for UT_hash_handle, HASH_ITER, HASH_DEL #ifdef _WIN32 -#include // for _rmdir #include "win32/defs.h" + +#include // for _rmdir #else #include // for inet_addr, inet_ntoa #include // for ntohl, in_addr_t, sockaddr_in, INADDR... #include // for TCP_NODELAY +#include #include // for FD_ISSET, FD_SET, select, FD_SETSIZE #include // for recvfrom, shutdown, sockaddr_storage #endif +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif + #define HASH_FIND_COMMUNITY(head, name, out) HASH_FIND_STR(head, name, out) @@ -94,14 +104,6 @@ static int sort_communities (struct n3n_runtime_data *sss, time_t* p_last_sort, time_t now); -static int process_udp (struct n3n_runtime_data *sss, - const struct sockaddr *sender_sock, socklen_t sock_size, - const SOCKET socket_fd, - uint8_t *udp_buf, - size_t udp_size, - time_t now); - - /* ************************************** */ @@ -1638,7 +1640,8 @@ static int process_udp (struct n3n_runtime_data * sss, const SOCKET socket_fd, uint8_t * udp_buf, size_t udp_size, - time_t now) { + time_t now, + int type) { n2n_common_t cmn; /* common fields in the packet header */ size_t rem; @@ -1659,8 +1662,7 @@ static int process_udp (struct n3n_runtime_data * sss, int skip_add; time_t any_time = 0; - memset(&sender, 0, sizeof(n2n_sock_t)); - fill_n2nsock(&sender, sender_sock); + fill_n2nsock(&sender, sender_sock, SOCK_DGRAM); orig_sender = &sender; traceEvent(TRACE_DEBUG, "processing incoming UDP packet [len: %lu][sender: %s]", @@ -1720,8 +1722,8 @@ static int process_udp (struct n3n_runtime_data * sss, header_enc = 2; } if(!header_enc) { - pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN)); - header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community, + pearson_hash_128(hash_buf, udp_buf, MAX(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN)); + header_enc = packet_header_decrypt(udp_buf, MAX(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community, comm->header_encryption_ctx_static, comm->header_iv_ctx_static, &stamp); } @@ -1864,7 +1866,7 @@ static int process_udp (struct n3n_runtime_data * sss, if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { // in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size - packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * min(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx, + packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * MIN(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx, comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic, time_stamp()); } @@ -1879,7 +1881,7 @@ static int process_udp (struct n3n_runtime_data * sss, if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { // in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size - packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * min(encx - idx, N2N_SPECK_IVEC_SIZE), encx, + packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * MIN(encx - idx, N2N_SPECK_IVEC_SIZE), encx, comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic, time_stamp()); } @@ -2615,6 +2617,10 @@ static int process_udp (struct n3n_runtime_data * sss, pi.preferred_sock = scan->preferred_sock; } + // FIXME: + // If we get the request on TCP, the reply should indicate + // our prefered sock is TCP ?? + encode_PEER_INFO(encbuf, &encx, &cmn2, &pi); if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) { @@ -2764,7 +2770,7 @@ int run_sn_loop (struct n3n_runtime_data *sss) { #endif slots_t *slots = sss->mgmt_slots; - max_sock = max( + max_sock = MAX( max_sock, slots_fdset( slots, @@ -2840,7 +2846,8 @@ int run_sn_loop (struct n3n_runtime_data *sss) { sss->sock, pktbuf, bread, - now + now, + SOCK_DGRAM ); } } @@ -2904,7 +2911,8 @@ int run_sn_loop (struct n3n_runtime_data *sss) { conn->socket_fd, conn->buffer + sizeof(uint16_t), conn->position - sizeof(uint16_t), - now + now, + SOCK_STREAM ); // reset, await new prepended length diff --git a/src/test_hashing.c b/src/test_hashing.c index a996b3e2..6d375564 100644 --- a/src/test_hashing.c +++ b/src/test_hashing.c @@ -5,10 +5,9 @@ */ #include // for PRIx64, PRIx16, PRIx32 -#include // for uint8_t, uint16_t, uint32_t, uint64_t #include // for printf, fprintf, stderr, stdout #include // for memcmp -#include "n2n.h" + #include "hexdump.h" // for fhexdump #include "pearson.h" // for pearson_hash_128, pearson_hash_16, pearson_has... diff --git a/src/transform_aes.c b/src/transform_aes.c index f1ac92c9..81622d98 100644 --- a/src/transform_aes.c +++ b/src/transform_aes.c @@ -26,8 +26,11 @@ #include // for calloc, free #include // for memcpy, size_t, memset, memcmp, strlen #include // for u_char, ssize_t, time_t + #include "aes.h" // for AES_BLOCK_SIZE, aes_cbc_decrypt, aes_cbc... #include "n2n.h" // for n2n_trans_op_t +#include "n2n_define.h" +#include "n2n_typedefs.h" #include "pearson.h" // for pearson_hash_256 diff --git a/src/transform_cc20.c b/src/transform_cc20.c index fc57dd44..bb10da0a 100644 --- a/src/transform_cc20.c +++ b/src/transform_cc20.c @@ -26,8 +26,11 @@ #include // for size_t, calloc, free #include // for memset, strlen #include // for u_char, ssize_t, time_t + #include "cc20.h" // for CC20_IV_SIZE, cc20_crypt, cc20_deinit #include "n2n.h" // for n2n_trans_op_t +#include "n2n_define.h" +#include "n2n_typedefs.h" #include "pearson.h" // for pearson_hash_256 diff --git a/src/transform_lzo.c b/src/transform_lzo.c index 75089b85..ee201ef3 100644 --- a/src/transform_lzo.c +++ b/src/transform_lzo.c @@ -24,9 +24,12 @@ #include // for uint8_t #include // for size_t, calloc, free, NULL #include // for memset -#include // for time_t +#include + #include "minilzo.h" // for lzo1x_1_compress, lzo1x_decompress, LZO1X_1_M... #include "n2n.h" // for n2n_trans_op_t, N2N_... +#include "n2n_define.h" +#include "n2n_typedefs.h" /* heap allocation for compression as per lzo example doc */ diff --git a/src/transform_none.c b/src/transform_none.c index 0aa3ad06..4e1ba114 100644 --- a/src/transform_none.c +++ b/src/transform_none.c @@ -5,6 +5,8 @@ */ #include // for n3n_transform_register +#include + #include "n2n_define.h" // for N2N_COMPRESSION_ID_NONE // A dummy transform struct for the no-op compression diff --git a/src/transform_null.c b/src/transform_null.c index 991f32dc..19094fa0 100644 --- a/src/transform_null.c +++ b/src/transform_null.c @@ -23,8 +23,9 @@ #include // for n3n_transform_register #include // for uint8_t #include // for memcpy, size_t, memset -#include // for time_t + #include "n2n.h" // for n2n_trans_op_t, N2N_... +#include "n2n_typedefs.h" static int transop_deinit_null (n2n_trans_op_t *arg ) { diff --git a/src/transform_speck.c b/src/transform_speck.c index 9e2cdf69..7b981748 100644 --- a/src/transform_speck.c +++ b/src/transform_speck.c @@ -26,7 +26,10 @@ #include // for size_t, calloc, free #include // for memset, strlen #include // for u_char, ssize_t, time_t + #include "n2n.h" // for n2n_trans_op_t +#include "n2n_define.h" +#include "n2n_typedefs.h" #include "pearson.h" // for pearson_hash_256 #include "speck.h" // for N2N_SPECK_IVEC_SIZE, speck_ctr, speck_de... diff --git a/src/transform_tf.c b/src/transform_tf.c index 83a2f9bc..d78ad7c2 100644 --- a/src/transform_tf.c +++ b/src/transform_tf.c @@ -26,7 +26,10 @@ #include // for calloc, free #include // for memcpy, size_t, memset, memcmp, strlen #include // for u_char, ssize_t, time_t + #include "n2n.h" // for n2n_trans_op_t +#include "n2n_define.h" +#include "n2n_typedefs.h" #include "pearson.h" // for pearson_hash_256 #include "tf.h" // for TF_BLOCK_SIZE, tf_cbc_decrypt, tf_cbc_en... diff --git a/src/tuntap_linux.c b/src/tuntap_linux.c index fa990aeb..5a872151 100644 --- a/src/tuntap_linux.c +++ b/src/tuntap_linux.c @@ -38,8 +38,12 @@ #include // for ioctl, SIOCGIFADDR, SIOCGIFFLAGS #include // for MIN #include // for socket, msghdr, AF_INET, sockaddr +#include // for iovec #include // for close, getpid, read, write, ssi... + #include "n2n.h" // for tuntap_dev, ... +#include "n2n_typedefs.h" +#include "n3n/ethernet.h" static int setup_ifname (int fd, const char *ifname, diff --git a/src/win32/edge_utils_win32.c b/src/win32/edge_utils_win32.c index 9e7227e2..f7090b55 100644 --- a/src/win32/edge_utils_win32.c +++ b/src/win32/edge_utils_win32.c @@ -22,6 +22,7 @@ #include #include // for traceEvent +#include "../edge_utils.h" // for edge_read_from_tap #include "edge_utils_win32.h" /* ************************************** */ diff --git a/src/wire.c b/src/wire.c index 0663d63d..b7160e27 100644 --- a/src/wire.c +++ b/src/wire.c @@ -29,16 +29,18 @@ #include // for uint8_t, uint16_t, uint32_t, uint64_t #include // for size_t, memset, memcpy -#include "portable_endian.h" // for be64toh, htobe64 -#include "n2n.h" // for n2n_sock_t, n2n_common_t, n2n_auth_t, n2n_RE... + +#include "n2n_define.h" +#include "n2n_typedefs.h" #include "n2n_wire.h" // for decode_PACKET, decode_PEER_INFO, decode_QUER... +#include "n3n/ethernet.h" +#include "portable_endian.h" // for be64toh, htobe64 #ifdef _WIN32 #include "win32/defs.h" #else #include // for sockaddr_in, sockaddr_in6, in6_addr, in_addr #include // for AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM -#include // for sa_family_t #endif @@ -688,9 +690,14 @@ int fill_sockaddr (struct sockaddr * addr, // fills struct sockaddr's data into n2n_sock -int fill_n2nsock (n2n_sock_t* sock, const struct sockaddr* sa) { +int fill_n2nsock (n2n_sock_t* sock, const struct sockaddr* sa, int type) { + // Ensure the return struct is fully initialised + // TODO: could be optimised + memset(sock, 0, sizeof(n2n_sock_t)); sock->family = sa->sa_family; + // TODO: re enable this when it doesnt break things + // sock->type = type; // SOCK_DGRAM or SOCK_STREAM switch(sock->family) { case AF_INET: { diff --git a/tests/test_integration_edge_tcp.sh.expected b/tests/test_integration_edge_tcp.sh.expected new file mode 100644 index 00000000..f7641bcd --- /dev/null +++ b/tests/test_integration_edge_tcp.sh.expected @@ -0,0 +1,80 @@ +### test: ./apps/n3n-supernode start ci_sn -v --daemon -Osupernode.macaddr=02:00:00:55:00:00 + +### test: ./scripts/n3nctl -s ci_edge1 get_communities +[ + { + "community": "test" + } +] + +### test: ./scripts/n3nctl -s ci_edge1 get_packetstats +[ + { + "rx_pkt": 0, + "tx_pkt": 2, + "type": "transop" + }, + { + "rx_pkt": 0, + "tx_pkt": 0, + "type": "p2p" + }, + { + "rx_pkt": 0, + "tx_pkt": 2, + "type": "super" + }, + { + "rx_pkt": 0, + "tx_pkt": 2, + "type": "super_broadcast" + }, + { + "tx_pkt": 0, + "type": "tuntap_error" + } +] + +### test: ./scripts/n3nctl -s ci_edge1 get_edges --raw +[] + +### test: ./scripts/n3nctl -s ci_sn get_edges --raw +[ + { + "community": "test", + "desc": "ci_edge1", + "ip4addr": "10.200.175.139/24", + "last_p2p": 0, + "last_sent_query": 0, + "local": 0, + "macaddr": "02:00:00:77:00:00", + "mode": "sn", + "prefered_sockaddr": "0.0.0.0:0", + "purgeable": 1, + "sockaddr": "127.0.0.1:7700", + "timeout": 0, + "uptime": 0, + "version": "" + } +] + +### test: ./scripts/n3nctl -s ci_edge1 get_supernodes --raw +[ + { + "current": 1, + "last_seen": 0, + "macaddr": "02:00:00:55:00:00", + "purgeable": 0, + "selection": "", + "sockaddr": "127.0.0.1:7654", + "uptime": 0, + "version": "" + } +] + +### test: ./scripts/n3nctl -s ci_edge1 -k n3n stop +0 + +### test: ./scripts/n3nctl -s ci_sn -k n3n stop +0 + diff --git a/tests/test_integration_packets.sh.expected b/tests/test_integration_packets.sh.expected index 25f29681..af257ca7 100644 --- a/tests/test_integration_packets.sh.expected +++ b/tests/test_integration_packets.sh.expected @@ -22,6 +22,7 @@ recv: 'srcMac': b'\x02\x00\x00\x00\x00\x01', 'mac': b'\x00\x00\x00\x00\x00\x00', 'sock_family': , + 'sock_type': , 'sock_port': 7000, 'sock_addr': b'\x7f\x00\x00\x01', 'load': 4294967295, @@ -52,6 +53,7 @@ recv: 'masklen': 24, 'lifetime': 15, 'sock_family': , + 'sock_type': , 'sock_port': 7000, 'sock_addr': b'\x7f\x00\x00\x01', 'auth_scheme': 0, @@ -101,6 +103,7 @@ recv: 'srcMac': b'\x00\x00\x00\x00\x00\x01', 'mac': b'\x00\x00\x00\x00\x00\x03', 'sock_family': , + 'sock_type': , 'sock_port': 7000, 'sock_addr': b'\x7f\x00\x00\x01', 'load': 4294967295, @@ -130,6 +133,7 @@ recv: 'srcMac': b'\x00\x00\x00\x00\x00\x01', 'edgeMac': b'\x00\x00\x00\x00\x00\x03', 'sock_family': , + 'sock_type': , 'sock_port': 7000, 'sock_addr': b'\x7f\x00\x00\x01', 'ipv4': b'\x00\x00\x00\x00', @@ -163,6 +167,7 @@ recv: 'srcMac': b'\x00\x00\x00\x00\x00\x01', 'edgeMac': b'\x00\x00\x00\x00\x00\x03', 'sock_family': , + 'sock_type': , 'sock_port': 7000, 'sock_addr': b'\x7f\x00\x00\x01', 'compression': 1, @@ -181,6 +186,35 @@ test: ### test: ./scripts/n3nctl -s ci_sn1 get_edges --raw [] +### test: ./scripts/test_packets --tcp --bind 7000 -s localhost:7001 test_QUERY_PEER_ping +test: +000: 03 02 00 0b 74 65 73 74 00 00 00 00 00 00 00 00 | test | +010: 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 | | +020: 00 00 00 00 00 00 | | +recv: +000: 03 02 00 2a 74 65 73 74 00 00 00 00 00 00 00 00 | *test | +010: 00 00 00 00 00 00 00 00 00 00 02 00 00 00 00 01 | | +020: 00 00 00 00 00 00 00 00 1b 58 7f 00 00 01 ff ff | X | +030: ff ff ff ff ff ff 5f 73 74 61 62 69 6c 69 73 65 | _stabilise| +040: 64 5f 00 00 00 00 00 00 00 00 |d_ | +{'_name': 'PEER_INFO', + 'proto_version': 3, + 'ttl': 2, + 'type': 10, + 'flags': 32, + 'community': b'test\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00', + 'aflags': 0, + 'srcMac': b'\x02\x00\x00\x00\x00\x01', + 'mac': b'\x00\x00\x00\x00\x00\x00', + 'sock_family': , + 'sock_type': , + 'sock_port': 7000, + 'sock_addr': b'\x7f\x00\x00\x01', + 'load': 4294967295, + 'uptime': 4294967295, + 'version': b'_stabilised_'} + ### test: ./scripts/n3nctl -s ci_sn1 -k n3n stop 0 diff --git a/tests/test_integration_supernode.sh.expected b/tests/test_integration_supernode.sh.expected index 8b442e7a..31011a09 100644 --- a/tests/test_integration_supernode.sh.expected +++ b/tests/test_integration_supernode.sh.expected @@ -1,6 +1,6 @@ -### test: ./apps/n3n-supernode start ci_sn1 --daemon -Oconnection.bind=7001 -Osupernode.peer=localhost:7002 +### test: ./apps/n3n-supernode start ci_sn1 --daemon -Oconnection.bind=7001 -Osupernode.macaddr=02:00:00:00:70:01 -Osupernode.peer=localhost:7002 -### test: ./apps/n3n-supernode start ci_sn2 --daemon -Oconnection.bind=7002 -Osupernode.peer=localhost:7001 +### test: ./apps/n3n-supernode start ci_sn2 --daemon -Oconnection.bind=7002 -Osupernode.macaddr=02:00:00:00:70:02 -Osupernode.peer=localhost:7001 ### test: ./scripts/n3nctl -s ci_sn1 get_communities [ @@ -131,6 +131,7 @@ "last_p2p": 0, "last_sent_query": 0, "local": 0, + "macaddr": "02:00:00:00:70:02", "mode": "sn", "prefered_sockaddr": "0.0.0.0:0", "purgeable": 0, @@ -150,6 +151,7 @@ "last_p2p": 0, "last_sent_query": 0, "local": 0, + "macaddr": "02:00:00:00:70:01", "mode": "sn", "prefered_sockaddr": "0.0.0.0:0", "purgeable": 0, diff --git a/tests/tests_integration.list b/tests/tests_integration.list index 4bf3bc81..29ff94c1 100644 --- a/tests/tests_integration.list +++ b/tests/tests_integration.list @@ -4,3 +4,4 @@ test_integration_packets.sh test_integration_supernode.sh test_integration_edge.sh +test_integration_edge_tcp.sh diff --git a/tools/crypto_helper.c b/tools/crypto_helper.c index 05b3e877..f1a47e32 100644 --- a/tools/crypto_helper.c +++ b/tools/crypto_helper.c @@ -16,6 +16,17 @@ #include #include // for read, write +// TODO: +// - on linux there are headers with these predefined +// - on windows, there are different predefines +// - use them! +#ifndef MAX +#define MAX(a, b) (((a) < (b)) ? (b) : (a)) +#endif + +#ifndef MIN +#define MIN(a, b) (((a) >(b)) ? (b) : (a)) +#endif #define GETOPTS "Vhv" @@ -69,11 +80,11 @@ static void cmd_header_decrypt (int argc, char **argv, void *conf) { pearson_hash_128( hash_buf, buf, - max(0, (int)size - (int)N2N_REG_SUP_HASH_CHECK_LEN) + MAX(0, (int)size - (int)N2N_REG_SUP_HASH_CHECK_LEN) ); ok = packet_header_decrypt( buf, - max(0, (int)size - (int)N2N_REG_SUP_HASH_CHECK_LEN), + MAX(0, (int)size - (int)N2N_REG_SUP_HASH_CHECK_LEN), community, ctx_static, ctx_iv_static, diff --git a/tools/n3n-portfwd.c b/tools/n3n-portfwd.c index d90bca52..55f1bcdf 100644 --- a/tools/n3n-portfwd.c +++ b/tools/n3n-portfwd.c @@ -45,6 +45,11 @@ #include // for connect, recv, send, socket, AF_INET #endif +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif + #define WITH_PORT 1 #define CORRECT_TAG 2 diff --git a/tools/n3n-route.c b/tools/n3n-route.c index 8b1ad9b3..009ccabd 100644 --- a/tools/n3n-route.c +++ b/tools/n3n-route.c @@ -56,6 +56,11 @@ #include // for send, socket, AF_INET, recv, connect #endif +#ifndef _WIN32 +// Another wonderful gift from the world of POSIX compliance is not worth much +#define closesocket(a) close(a) +#endif + // FIXME, this tool needs porting to JsonRPC #define N2N_EDGE_MGMT_PORT 5644