diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b72c3fd..bd04bd9 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -66,3 +66,17 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more # detail run: ctest -C $BUILD_TYPE + + - if: ${{ failure() }} + name: Upload test data + uses: actions/upload-artifact@v2 + with: + name: tests-out + path: tests + + - if: ${{ failure() }} + name: Upload cmake test output + uses: actions/upload-artifact@v2 + with: + name: tests-out + path: build/Testing diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 68e5906..3d2b8a5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -91,18 +91,18 @@ jobs: ./configure shell: bash - - name: Run embedded tests - run: make test + - name: Run embedded unit tests + run: make test.units shell: bash - - if: ${{ always() }} + - if: ${{ failure() }} name: Move test outputs to an arch specific location shell: bash run: | mkdir -p tests/${{ matrix.os }} mv tests/*.out tests/${{ matrix.os }} - - if: ${{ always() }} + - if: ${{ failure() }} name: Upload tests output uses: actions/upload-artifact@v2 with: @@ -162,18 +162,18 @@ jobs: ./configure shell: bash - - name: Run embedded tests - run: make test + - name: Run embedded unit tests + run: make test.units shell: bash - - if: ${{ always() }} + - if: ${{ failure() }} name: Move test outputs to an arch specific location shell: bash run: | mkdir -p tests/${{ matrix.os }} mv tests/*.out tests/${{ matrix.os }} - - if: ${{ always() }} + - if: ${{ failure() }} name: Upload tests output uses: actions/upload-artifact@v2 with: @@ -237,18 +237,18 @@ jobs: ./scripts/hack_fakeautoconf.sh shell: bash - - name: Run embedded tests - run: make test + - name: Run embedded unit tests + run: make test.units shell: bash - - if: ${{ always() }} + - if: ${{ failure() }} name: Move test outputs to an arch specific location shell: bash run: | mkdir -p tests/${{ matrix.os }} mv tests/*.out tests/${{ matrix.os }} - - if: ${{ always() }} + - if: ${{ failure() }} name: Upload tests output uses: actions/upload-artifact@v2 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 34b4ab4..501f9ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -309,10 +309,21 @@ install(FILES ${PROJECT_BINARY_DIR}/doc/supernode.1.gz DESTINATION /usr/share/man/man1) install(FILES ${PROJECT_BINARY_DIR}/doc/n2n.7.gz DESTINATION /usr/share/man/man7) +endif(DEFINED UNIX) + +if (CMAKE_SYSTEM_NAME STREQUAL Linux) # TODO: # - Add the right dependancy so that the tests binaries get built first enable_testing() -add_test(tests ${PROJECT_SOURCE_DIR}/scripts/test_harness.sh ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR}/tests) - -endif(DEFINED UNIX) +add_test(NAME unit + COMMAND ${CMAKE_COMMAND} -E env + TOPDIR=${PROJECT_SOURCE_DIR} BINDIR=${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/scripts/test_harness.sh ${PROJECT_SOURCE_DIR}/tests/tests_units.list +) +add_test(NAME integration + COMMAND ${CMAKE_COMMAND} -E env + TOPDIR=${PROJECT_SOURCE_DIR} BINDIR=${PROJECT_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/scripts/test_harness.sh ${PROJECT_SOURCE_DIR}/tests/tests_integration.list +) +endif() diff --git a/Makefile.in b/Makefile.in index 5f4bd95..1f54f58 100644 --- a/Makefile.in +++ b/Makefile.in @@ -84,16 +84,30 @@ N2N_DEPS=$(wildcard include/*.h) $(wildcard src/*.c) Makefile # As source files pass the linter, they can be added here (If all the source # is passing the linter tests, this can be refactored) LINT_CCODE=\ + include/curve25519.h \ include/edge_utils_win32.h \ + include/header_encryption.h \ + include/hexdump.h \ include/n2n_define.h \ + include/n2n_wire.h \ + include/network_traffic_filter.h \ include/pearson.h \ + include/random_numbers.h \ + include/sn_selection.h \ include/speck.h \ + include/tf.h \ + src/edge_management.c \ src/edge_utils_win32.c \ src/example_edge_embed_quick_edge_init.c \ src/header_encryption.c \ + src/sn_management.c \ src/sn_selection.c \ src/transform_cc20.c \ + src/transform_null.c \ + src/tuntap_freebsd.c \ src/tuntap_linux.c \ + src/tuntap_netbsd.c \ + src/tuntap_osx.c \ src/wire.c \ tools/tests-auth.c \ tools/tests-compress.c \ @@ -146,9 +160,8 @@ SUBDIRS+=tools COVERAGEDIR?=coverage .PHONY: $(SUBDIRS) -.PHONY: steps build push all clean distclean install test cover gcov build-dep -.PHONY: lint lint.python lint.ccode lint.shell lint.yaml +.PHONY: all all: version $(APPS) $(DOCS) $(SUBDIRS) # This allows breaking the build if the version.sh script discovers @@ -188,9 +201,16 @@ $(N2N_LIB): $(N2N_OBJS) win32/n2n_win32.a: win32 -test: tools - scripts/test_harness.sh +.PHONY: test test.units test.integration +test: test.units test.integration +test.units: tools + scripts/test_harness.sh tests/tests_units.list + +test.integration: $(APPS) + scripts/test_harness.sh tests/tests_integration.list + +.PHONY: lint lint.python lint.ccode lint.shell lint.yaml lint: lint.python lint.ccode lint.shell lint.yaml lint.python: @@ -209,6 +229,7 @@ lint.yaml: # CFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS="--coverage" # and run the desired tests. Ensure that package gcovr is installed # and then run "make cover" +.PHONY: cover cover: mkdir -p $(COVERAGEDIR) gcovr -s --html --html-details --output=$(COVERAGEDIR)/index.html @@ -217,11 +238,13 @@ cover: # Unfortunately, these end up in the wrong directory due to the # makefile layout # The steps to use this are similar to the "make cover" above +.PHONY: gcov gcov: gcov $(N2N_OBJS) $(MAKE) -C tools gcov # This is a convinent target to use during development or from a CI/CD system +.PHONY: build-dep build-dep: ifeq ($(CONFIG_TARGET),generic) sudo apt install $(BUILD_DEP) @@ -231,11 +254,13 @@ else echo Not attempting to install dependancies for system $(CONFIG_TARGET) endif +.PHONY: clean clean: rm -rf $(N2N_OBJS) $(N2N_LIB) $(APPS) $(DOCS) $(COVERAGEDIR)/ *.dSYM *~ rm -f tests/*.out src/*.gcno src/*.gcda for dir in $(SUBDIRS); do $(MAKE) -C $$dir clean; done +.PHONY: distclean distclean: rm -f tests/*.out src/*.gcno src/*.gcda src/*.indent src/*.unc-backup* rm -rf autom4te.cache/ @@ -246,6 +271,7 @@ distclean: rm -f packages/rpm/config.log packages/rpm/config.status rm -f $(addprefix src/,$(APPS)) +.PHONY: install install: edge supernode edge.8.gz supernode.1.gz n2n.7.gz echo "MANDIR=$(MANDIR)" $(MKDIR) $(SBINDIR) $(MAN1DIR) $(MAN7DIR) $(MAN8DIR) @@ -261,6 +287,7 @@ DOCKER_IMAGE_NAME=ntop/supernode DOCKER_IMAGE_VERSION=$N2N_VERSION_SHORT N2N_COMMIT_HASH=$(shell scripts/version.sh hash) +.PHONY: default steps build push default: steps steps: diff --git a/doc/ManagementAPI.md b/doc/ManagementAPI.md index 617e345..3f179f4 100644 --- a/doc/ManagementAPI.md +++ b/doc/ManagementAPI.md @@ -1,6 +1,6 @@ # Management API -This document is focused on the machine readable API interfaces. +This document is focused on the machine readable API interfaces. Both the edge and the supernode provide a management interface UDP port. These interfaces have some documentation on their non machine readable @@ -48,7 +48,7 @@ but this is intended for debugging. The request is a single UDP packet containing one line of text with at least three space separated fields. Any text after the third field is available for -the API method to use for additional parameters +the API method to use for additional parameters Fields: - Message Type @@ -58,15 +58,23 @@ Fields: The maximum length of the entire line of text is 80 octets. +All request packets should generate a reply. However, this reply may simply +be an error. + ### Message Type -This is a single octet that is either "r" for a read (or query) method -call or "w" for a write (or change) method call. +This is a single octet specifying the type: + +- "r" for a read-only method (or one that does not need change permissions) +- "w" for a write method (or one that makes changes) +- "s" for a subscribe method to request this socket receive some events To simplify the interface, the reply from both read and write calls to the same method is expected to contain the same data. In the case of a write call, the reply will contain the new state after making the requested change. +The subscribe and events message flow works with a different set of messages. + ### Options The options field is a colon separated set of options for this request. Only @@ -83,14 +91,16 @@ SubFields: Each request provides a tag value. Any non error reply associated with this request will include this tag value, allowing all related messages to be -collected within the client. +collected within the client. The tag will be truncated if needed by the +daemon, but there will be at least 8 octets of space available. Where possible, the error replies will also include this tag, however some errors occur before the tag is parsed. The tag is not interpreted by the daemon, it is simply echoed back in all -the replies. It is expected to be a short string that the client chooses -to be unique amongst all recent or still outstanding requests. +the replies. It is expected to be a short string that the client knows +will be unique amongst all recent, still outstanding or subscription requests +on a given socket. One possible client implementation is a number between 0 and 999, incremented for each request and wrapping around to zero when it is greater than 999. @@ -127,11 +137,14 @@ e.g: Each UDP packet in the reply is a complete and valid JSON dictionary containing a fragment of information related to the entire reply. +Reply packets are generated both in response to requests and whenever +an event is published to a subscribed channel. + ### Common metadata There are two keys in each dictionary containing metadata. First is the `_tag`, containing the Message Tag from the original request. -Second is the `_type` whic identifies the expected contents of this +Second is the `_type` which identifies the expected contents of this packet. ### `_type: error` @@ -176,6 +189,47 @@ packets will be required. e.g: `{"_tag":"108","_type":"row","mode":"p2p","ip4addr":"10.135.98.84","macaddr":"86:56:21:E4:AA:39","sockaddr":"192.168.7.191:41701","desc":"client4","lastseen":1584682200}` +### `_type: subscribed` + +Signals that the subscription request has been successfully completed. +Any future events on the requested channel will be asynchronously sent +as `event` packets using the same tag as the subscribe request. + +### `_type: unsubscribed` + +Only one management client can be subscribed to any given event topic, so if +another subscribe request arrives, the older client will be sent this message +to let them know that they have been replaced. + +(In the future, this may also be sent as a reply to a explicit unsubscribe +request) + +### `_type: replacing` + +If a new subscription request will replace an existing one, this message is +sent to the new client to inform them that they have replaced an older +connection. + +### `_type: event` + +Asynchronous events will arrive with this message type, using the same tag as +the original subscribe request. Just like with the `row` packets, the non +metadata contents are entirely defined by the topic and the specific n2n +version. + +## Subscribe API + +A client can subscribe to events using a request with the type of "s". +Once a subscribe has been successfully completed, any events published +on that channel will be forwarded to the client. + +Only one management client can be subscribed to any given event topic, +with newer subscriptions replacing older ones. + +The special channel "debug" will receive copies of all events published. +Note that this is for debugging of events and the packets may not have +the same tag as the debug subscription. + ## Authentication Some API requests will make global changes to the running daemon and may diff --git a/doc/Scripts.md b/doc/Scripts.md index 297ea24..63e5833 100644 --- a/doc/Scripts.md +++ b/doc/Scripts.md @@ -51,10 +51,6 @@ This shell script is a wrapper for the `uncrustify` C code style checker which checks or applies a set of rules to the code. It is used during the automated lint checks. -### `test_harness.sh` - -This shell script is used to run automated tests during development. - ### `n2n-gateway.sh` A sample script to route all the host traffic towards a remote gateway, @@ -96,3 +92,26 @@ Manually test fetching and config: /etc/munin/plugins/n2n_supernode_pkts /etc/munin/plugins/n2n_supernode_pkts config ``` + +## Testing scripts + +### `test_harness.sh` + +This shell script is used to run automated tests during development. It is +run with a testlist filename - pointing at a file containing the list of +tests to run. + +Each test needs a file containing the expected output `${TESTNAME}.expected` +which is expected to exist in the same directory as the testlist (this dir is +referred to as `${listdir}` below). + +Each test is a program, searched for in several locations, including the +`${listdir}/../scripts` dir. + +Each test is run with its output being sent to `*.out` files in the `listdir` +and compared with the expected output. + +### `scripts/test_integration_supernode.sh` + +This starts a supernode and runs an integration test on the Json API using +the `n2n-ctl` command. diff --git a/include/n2n.h b/include/n2n.h index 6ecfb4c..3874609 100644 --- a/include/n2n.h +++ b/include/n2n.h @@ -285,6 +285,7 @@ int assign_one_ip_subnet (n2n_sn_t *sss, struct sn_community *comm); const char* compression_str (uint8_t cmpr); const char* transop_str (enum n2n_transform tr); -void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock); -void handleMgmtJson_sn (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock); +void readFromMgmtSocket (n2n_edge_t *eee); + +void mgmt_event_post (enum n2n_event_topic topic, int data0, void *data1); #endif /* _N2N_H_ */ diff --git a/include/n2n_define.h b/include/n2n_define.h index cdee5da..4c4da21 100644 --- a/include/n2n_define.h +++ b/include/n2n_define.h @@ -121,11 +121,17 @@ enum sn_purge {SN_PURGEABLE = 0, SN_UNPURGEABLE = 1}; #define N2N_EDGE_MGMT_PORT 5644 #define N2N_SN_MGMT_PORT 5645 -enum n2n_mgmt_type { - N2N_MGMT_READ = 0, - N2N_MGMT_WRITE = 1, +enum n2n_event_topic { + N2N_EVENT_DEBUG = 0, + N2N_EVENT_TEST = 1, + N2N_EVENT_PEER = 2, }; +#define N2N_EVENT_PEER_PURGE 1 +#define N2N_EVENT_PEER_CLEAR 2 +#define N2N_EVENT_PEER_DEL_P2P 3 +#define N2N_EVENT_PEER_ADD_P2P 4 + #define N2N_MGMT_PASSWORD "n2n" /* default password for management port access (so far, json only) */ diff --git a/scripts/indent.sh b/scripts/indent.sh index 50d38ba..90d977a 100755 --- a/scripts/indent.sh +++ b/scripts/indent.sh @@ -13,6 +13,7 @@ help() { [ -z "$1" ] && help [ "$1" = "-h" ] && help +[ "$1" = "--help" ] && help INPLACE=0 if [ "$1" = "-i" ]; then diff --git a/scripts/n2n-ctl b/scripts/n2n-ctl index 4e63eda..84b48cb 100755 --- a/scripts/n2n-ctl +++ b/scripts/n2n-ctl @@ -41,23 +41,37 @@ class JsonUDP(): def _rx(self, tagstr): """Wait for rx packets""" - # TODO: there are no timeouts with any of the recv calls - data, _ = self.sock.recvfrom(1024) - data = json.loads(data.decode('utf8')) + seen_begin = False + while not seen_begin: + # TODO: there are no timeouts with any of the recv calls + data, _ = self.sock.recvfrom(1024) + data = json.loads(data.decode('utf8')) - # TODO: We assume the first packet we get will be tagged for us - # and be either an "error" or a "begin" - assert(data['_tag'] == tagstr) + # TODO: We assume the first packet we get will be tagged for us + assert(data['_tag'] == tagstr) - if data['_type'] == 'error': - raise ValueError('Error: {}'.format(data['error'])) + if data['_type'] == 'error': + raise ValueError('Error: {}'.format(data['error'])) - assert(data['_type'] == 'begin') + if data['_type'] == 'replacing': + # a signal that we have evicted an earlier subscribe + continue - # Ideally, we would confirm that this is our "begin", but that - # would need the cmd passed into this method, and that would - # probably require parsing the cmdline passed to us :-( - # assert(data['cmd'] == cmd) + if data['_type'] == 'subscribe': + return True + + if data['_type'] == 'begin': + seen_begin = True + + # Ideally, we would confirm that this is our "begin", but that + # would need the cmd passed into this method, and that would + # probably require parsing the cmdline passed to us :-( + # assert(data['cmd'] == cmd) + + continue + + raise ValueError('Unknown data type {} from ' + 'edge'.format(data['_type'])) result = list() error = None @@ -105,6 +119,21 @@ class JsonUDP(): def write(self, cmdline): return self._call('w', cmdline) + def sub(self, cmdline): + return self._call('s', cmdline) + + def readevent(self): + self.sock.settimeout(3600) + + data, _ = self.sock.recvfrom(1024) + data = json.loads(data.decode('utf8')) + # assert(data['_tag'] == tagstr) + assert(data['_type'] == 'event') + + del data['_tag'] + del data['_type'] + return data + def str_table(rows, columns, orderby): """Given an array of dicts, do a simple table print""" @@ -203,8 +232,17 @@ def subcmd_default(rpc, args): cmdline = ' '.join([args.cmd] + args.args) if args.write: rows = rpc.write(cmdline) - else: + elif args.read: rows = rpc.read(cmdline) + elif args.sub: + if not rpc.sub(cmdline): + raise ValueError('Could not subscribe') + while True: + event = rpc.readevent() + # FIXME: violates layering.. + print(json.dumps(event, sort_keys=True, indent=4)) + else: + raise ValueError('Unknown request type') return json.dumps(rows, sort_keys=True, indent=4) @@ -228,6 +266,8 @@ def main(): group.add_argument('--write', action='store_true', help='Make a write request (only to non pretty' 'printed cmds)') + group.add_argument('--sub', action='store_true', + help='Make a subscribe request') ap.add_argument('cmd', action='store', help='Command to run (try "help" for list)') @@ -236,6 +276,9 @@ def main(): args = ap.parse_args() + if not args.read and not args.write and not args.sub: + args.read = True + if args.raw or (args.cmd not in subcmds): func = subcmd_default else: diff --git a/scripts/n2n-httpd b/scripts/n2n-httpd index 9211915..a875047 100755 --- a/scripts/n2n-httpd +++ b/scripts/n2n-httpd @@ -23,102 +23,31 @@ import base64 from http import HTTPStatus +import os +import sys +import importlib.machinery +import importlib.util -class JsonUDP(): - """encapsulate communication with the edge""" - def __init__(self, port): - self.address = "127.0.0.1" - self.port = port - self.tag = 0 - self.key = None - self.debug = False - self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self.sock.settimeout(1) +def import_filename(modulename, filename): + # look in the same dir as this script + pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), + filename) + loader = importlib.machinery.SourceFileLoader(modulename, pathname) + spec = importlib.util.spec_from_loader(modulename, loader) + module = importlib.util.module_from_spec(spec) - def _next_tag(self): - tagstr = str(self.tag) - self.tag = (self.tag + 1) % 1000 - return tagstr + try: + loader.exec_module(module) + except FileNotFoundError: + print("Script {} not found".format(pathname), file=sys.stderr) + sys.exit(1) + return module - def _cmdstr(self, msgtype, cmdline): - """Create the full command string to send""" - tagstr = self._next_tag() - options = [tagstr] - if self.key is not None: - options += ['1'] # Flags set for auth key field - options += [self.key] - optionsstr = ':'.join(options) - - return tagstr, ' '.join((msgtype, optionsstr, cmdline)) - - def _rx(self, tagstr): - """Wait for rx packets""" - - # TODO: there are no timeouts with any of the recv calls - data, _ = self.sock.recvfrom(1024) - data = json.loads(data.decode('utf8')) - - # TODO: We assume the first packet we get will be tagged for us - # and be either an "error" or a "begin" - assert(data['_tag'] == tagstr) - - if data['_type'] == 'error': - raise ValueError('Error: {}'.format(data['error'])) - - assert(data['_type'] == 'begin') - - # Ideally, we would confirm that this is our "begin", but that - # would need the cmd passed into this method, and that would - # probably require parsing the cmdline passed to us :-( - # assert(data['cmd'] == cmd) - - result = list() - error = None - - while True: - data, _ = self.sock.recvfrom(1024) - data = json.loads(data.decode('utf8')) - - if data['_tag'] != tagstr: - # this packet is not for us, ignore it - continue - - if data['_type'] == 'error': - # we still expect an end packet, so save the error - error = ValueError('Error: {}'.format(data['error'])) - continue - - if data['_type'] == 'end': - if error: - raise error - return result - - if data['_type'] != 'row': - raise ValueError('Unknown data type {} from ' - 'edge'.format(data['_type'])) - - # remove our boring metadata - del data['_tag'] - del data['_type'] - - if self.debug: - print(data) - - result.append(data) - - def _call(self, msgtype, cmdline): - """Perform a rpc call""" - tagstr, msgstr = self._cmdstr(msgtype, cmdline) - self.sock.sendto(msgstr.encode('utf8'), (self.address, self.port)) - return self._rx(tagstr) - - def read(self, cmdline): - return self._call('r', cmdline) - - def write(self, cmdline): - return self._call('w', cmdline) +# We share the implementation of the RPC class with the n2n-ctl script. We +# cannot just import the module as 'n2n-ctl' has a dash in its name :-( +JsonUDP = import_filename('n2nctl', 'n2n-ctl').JsonUDP pages = { diff --git a/scripts/test_harness.sh b/scripts/test_harness.sh index 5193297..2904305 100755 --- a/scripts/test_harness.sh +++ b/scripts/test_harness.sh @@ -1,40 +1,47 @@ #!/bin/sh # -# This expects to find the tests in the tools dir and the expected results -# in the tests dir. +# Run with the name of a test list file. +# +# This expects to find the tests in the tools dir or scripts dir and the +# expected results in the tests dir. -TESTS=" - tests-auth - tests-compress - tests-elliptic - tests-hashing - tests-transform - tests-wire -" +# boilerplate so we can support whaky cmake dirs +[ -z "$TOPDIR" ] && TOPDIR="." +[ -z "$BINDIR" ] && BINDIR="." +export TOPDIR +export BINDIR -TOOLSDIR=tools -TESTDATA=tests +if [ -z "$1" ]; then + echo need test list filename + exit 1 +fi +TESTLIST="$1" +LISTDIR=$(dirname "$TESTLIST") -# Allow both dirs be overidden -[ -n "$1" ] && TOOLSDIR="$1" -[ -n "$2" ] && TESTDATA="$2" - -# Confirm we have all the tools and data -for i in $TESTS; do - if [ ! -e "$TOOLSDIR/$i" ]; then - echo "Could not find test $TOOLSDIR/$i" - exit 1 - fi - if [ ! -e "$TESTDATA/$i.expected" ]; then - echo "Could not find testdata $TESTDATA/$i.expected" - exit 1 - fi -done +TESTS=$(sed -e "s/#.*//" "$TESTLIST") # Actually run the tests -set -e for i in $TESTS; do - echo "$TOOLSDIR/$i >$TESTDATA/$i.out" - "$TOOLSDIR/$i" >"$TESTDATA/$i.out" - cmp "$TESTDATA/$i.expected" "$TESTDATA/$i.out" + # Look in several places for the test program + if [ -e "$BINDIR/$i" ]; then + TEST="$BINDIR/$i" + elif [ -e "$BINDIR/tools/$i" ]; then + TEST="$BINDIR/tools/$i" + elif [ -e "$LISTDIR/../scripts/$i" ]; then + TEST="$LISTDIR/../scripts/$i" + else + echo "Could not find test $i" + exit 1 + fi + + if [ ! -e "$LISTDIR/$i.expected" ]; then + echo "Could not find testdata $LISTDIR/$i.expected" + exit 1 + fi + + echo "$TEST >$LISTDIR/$i.out" + set -e + "$TEST" >"$LISTDIR/$i.out" + cmp "$LISTDIR/$i.expected" "$LISTDIR/$i.out" + set +e done diff --git a/scripts/test_integration_edge.sh b/scripts/test_integration_edge.sh new file mode 100755 index 0000000..93a5424 --- /dev/null +++ b/scripts/test_integration_edge.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Do some quick tests via the Json API against the edge +# + +AUTH=n2n + +# boilerplate so we can support whaky cmake dirs +[ -z "$TOPDIR" ] && TOPDIR=. +[ -z "$BINDIR" ] && BINDIR=. + +docmd() { + echo "###" + "$@" + echo +} + +# start a supernode +docmd ${BINDIR}/supernode -v + +# Start the edge in the background +docmd sudo ${BINDIR}/edge -l localhost:7654 -c test >/dev/null +# TODO: +# - send edge messages to stderr? + +docmd ${TOPDIR}/scripts/n2n-ctl communities +docmd ${TOPDIR}/scripts/n2n-ctl packetstats +docmd ${TOPDIR}/scripts/n2n-ctl edges --raw + +# TODO: +# docmd ${TOPDIR}/scripts/n2n-ctl supernodes --raw +# - need fixed mac address +# - need to mask out: +# - version string +# - last_seen timestamp +# - uptime + +docmd ${TOPDIR}/scripts/n2n-ctl verbose +docmd ${TOPDIR}/scripts/n2n-ctl --write verbose 1 2>/dev/null +echo $? +docmd ${TOPDIR}/scripts/n2n-ctl -k $AUTH --write verbose 1 + +# looks strange, but we are querying the state of the "stop" verb +docmd ${TOPDIR}/scripts/n2n-ctl stop + +# stop them both +docmd ${TOPDIR}/scripts/n2n-ctl -k $AUTH --write stop +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 -k $AUTH --write stop + diff --git a/scripts/test_integration_supernode.sh b/scripts/test_integration_supernode.sh new file mode 100755 index 0000000..57f44bc --- /dev/null +++ b/scripts/test_integration_supernode.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# +# Do some quick tests via the Json API against the supernode +# + +AUTH=n2n + +# boilerplate so we can support whaky cmake dirs +[ -z "$TOPDIR" ] && TOPDIR=. +[ -z "$BINDIR" ] && BINDIR=. + +docmd() { + echo "###" + "$@" + echo +} + +# start it running in the background +docmd ${BINDIR}/supernode -v + +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 communities +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 packetstats +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 edges --raw + +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 verbose +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 -k $AUTH --write verbose 1 + +# looks strange, but we are querying the state of the "stop" verb +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 stop + +# stop it +docmd ${TOPDIR}/scripts/n2n-ctl -t 5645 -k $AUTH --write stop + diff --git a/src/edge_management.c b/src/edge_management.c index 5438ae3..86898e5 100644 --- a/src/edge_management.c +++ b/src/edge_management.c @@ -19,95 +19,184 @@ #include "n2n.h" #include "edge_utils_win32.h" +typedef struct strbuf { + size_t size; + char str[]; +} strbuf_t; + +#define STRBUF_INIT(buf,p) do { \ + buf = (void *)p; \ + buf->size = sizeof(*p) - sizeof(size_t); \ +} while(0) + +enum n2n_mgmt_type { + N2N_MGMT_UNKNOWN = 0, + N2N_MGMT_READ = 1, + N2N_MGMT_WRITE = 2, + N2N_MGMT_SUB = 3, +}; + +/* + * Everything needed to reply to a request + */ +typedef struct mgmt_req { + n2n_edge_t *eee; + enum n2n_mgmt_type type; + char tag[10]; + struct sockaddr_in sender_sock; +} mgmt_req_t; + +/* + * Read/Write handlers are defined in this structure + */ #define FLAG_WROK 1 -typedef struct n2n_mgmt_handler { +typedef struct mgmt_handler { int flags; char *cmd; char *help; - void (*func)(n2n_edge_t *eee, char *udp_buf, struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv); -} n2n_mgmt_handler_t; + void (*func)(mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); +} mgmt_handler_t; -static void mgmt_error (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, char *tag, char *msg) { - size_t msg_len; - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"error\"," - "\"error\":\"%s\"}\n", - tag, - msg); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); +/* + * Event topic names are defined in this structure + */ +typedef struct mgmt_events { + enum n2n_event_topic topic; + char *cmd; + char *help; +} mgmt_events_t; + +// Lookup the index of matching argv0 in a cmd list +// store index in "Result", or -1 for not found +#define lookup_handler(Result, list, argv0) do { \ + int nr_max = sizeof(list) / sizeof(list[0]); \ + for( Result=0; Result < nr_max; Result++ ) { \ + if(0 == strcmp(list[Result].cmd, argv0)) { \ + break; \ + } \ + } \ + if( Result >= nr_max ) { \ + Result = -1; \ + } \ +} while(0) + +ssize_t send_reply (mgmt_req_t *req, strbuf_t *buf, size_t msg_len) { + // TODO: better error handling (counters?) + return sendto(req->eee->udp_mgmt_sock, buf->str, msg_len, 0, + (struct sockaddr *) &req->sender_sock, sizeof(struct sockaddr_in)); } -static void mgmt_stop (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { - size_t msg_len; +size_t gen_json_1str (strbuf_t *buf, char *tag, char *_type, char *key, char *val) { + return snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"%s\"," + "\"%s\":\"%s\"}\n", + tag, + _type, + key, + val); +} - if(type==N2N_MGMT_WRITE) { - *eee->keep_running = 0; +size_t gen_json_1uint (strbuf_t *buf, char *tag, char *_type, char *key, unsigned int val) { + return snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"%s\"," + "\"%s\":%u}\n", + tag, + _type, + key, + val); +} + +static void send_json_1str (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, char *val) { + size_t msg_len = gen_json_1str(buf, req->tag, _type, key, val); + send_reply(req, buf, msg_len); +} + +static void send_json_1uint (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, unsigned int val) { + size_t msg_len = gen_json_1uint(buf, req->tag, _type, key, val); + send_reply(req, buf, msg_len); +} + +size_t event_debug (strbuf_t *buf, char *tag, int data0, void *data1) { + traceEvent(TRACE_DEBUG, "Unexpected call to event_debug"); + return 0; +} + +size_t event_test (strbuf_t *buf, char *tag, int data0, void *data1) { + size_t msg_len = gen_json_1str(buf, tag, "event", "test", (char *)data1); + return msg_len; +} + +size_t event_peer (strbuf_t *buf, char *tag, int data0, void *data1) { + int action = data0; + struct peer_info *peer = (struct peer_info *)data1; + + macstr_t mac_buf; + n2n_sock_str_t sockbuf; + + /* + * Just the peer_info bits that are needed for lookup (maccaddr) or + * firewall and routing (sockaddr) + * If needed, other details can be fetched via the edges method call. + */ + return snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"event\"," + "\"action\":%i," + "\"macaddr\":\"%s\"," + "\"sockaddr\":\"%s\"}\n", + tag, + action, + (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), + sock_to_cstr(sockbuf, &(peer->sock))); +} + +static void mgmt_error (mgmt_req_t *req, strbuf_t *buf, char *msg) { + send_json_1str(req, buf, "error", "error", msg); +} + +static void mgmt_stop (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { + + if(req->type==N2N_MGMT_WRITE) { + *req->eee->keep_running = 0; } - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"row\"," - "\"keep_running\":%u}\n", - tag, - *eee->keep_running); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_json_1uint(req, buf, "row", "keep_running", *req->eee->keep_running); } -static void mgmt_verbose (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { - size_t msg_len; +static void mgmt_verbose (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - if(type==N2N_MGMT_WRITE) { + if(req->type==N2N_MGMT_WRITE) { if(argv) { setTraceLevel(strtoul(argv, NULL, 0)); } } - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"row\"," - "\"traceLevel\":%u}\n", - tag, - getTraceLevel()); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_json_1uint(req, buf, "row", "traceLevel", getTraceLevel()); } -static void mgmt_communities (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { - size_t msg_len; +static void mgmt_communities (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - if(eee->conf.header_encryption != HEADER_ENCRYPTION_NONE) { - mgmt_error(eee, udp_buf, sender_sock, tag, "noaccess"); + if(req->eee->conf.header_encryption != HEADER_ENCRYPTION_NONE) { + mgmt_error(req, buf, "noaccess"); return; } - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"row\"," - "\"community\":\"%s\"}", - tag, - eee->conf.community_name); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_json_1str(req, buf, "row", "community", (char *)req->eee->conf.community_name); } -static void mgmt_supernodes (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +static void mgmt_supernodes (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { size_t msg_len; struct peer_info *peer, *tmpPeer; macstr_t mac_buf; n2n_sock_str_t sockbuf; selection_criterion_str_t sel_buf; - HASH_ITER(hh, eee->conf.supernodes, peer, tmpPeer) { + HASH_ITER(hh, req->eee->conf.supernodes, peer, tmpPeer) { /* * TODO: @@ -116,7 +205,7 @@ static void mgmt_supernodes (n2n_edge_t *eee, char *udp_buf, const struct sockad * - do we care? */ - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," @@ -128,28 +217,27 @@ static void mgmt_supernodes (n2n_edge_t *eee, char *udp_buf, const struct sockad "\"selection\":\"%s\"," "\"last_seen\":%li," "\"uptime\":%li}\n", - tag, + req->tag, peer->version, peer->purgeable, - (peer == eee->curr_sn) ? (eee->sn_wait ? 2 : 1 ) : 0, + (peer == req->eee->curr_sn) ? (req->eee->sn_wait ? 2 : 1 ) : 0, is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), sock_to_cstr(sockbuf, &(peer->sock)), - sn_selection_criterion_str(eee, sel_buf, peer), + sn_selection_criterion_str(req->eee, sel_buf, peer), peer->last_seen, peer->uptime); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); } } -static void mgmt_edges_row (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, char *tag, struct peer_info *peer, char *mode) { +static void mgmt_edges_row (mgmt_req_t *req, strbuf_t *buf, struct peer_info *peer, char *mode) { size_t msg_len; macstr_t mac_buf; n2n_sock_str_t sockbuf; dec_ip_bit_str_t ip_bit_str = {'\0'}; - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," @@ -163,7 +251,7 @@ static void mgmt_edges_row (n2n_edge_t *eee, char *udp_buf, const struct sockadd "\"last_p2p\":%li,\n" "\"last_sent_query\":%li,\n" "\"last_seen\":%li}\n", - tag, + req->tag, mode, (peer->dev_addr.net_addr == 0) ? "" : ip_subnet_to_str(ip_bit_str, &peer->dev_addr), peer->purgeable, @@ -175,111 +263,113 @@ static void mgmt_edges_row (n2n_edge_t *eee, char *udp_buf, const struct sockadd peer->last_sent_query, peer->last_seen); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0 /*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); } -static void mgmt_edges (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { struct peer_info *peer, *tmpPeer; // dump nodes with forwarding through supernodes - HASH_ITER(hh, eee->pending_peers, peer, tmpPeer) { - mgmt_edges_row(eee, udp_buf, sender_sock, tag, peer, "pSp"); + HASH_ITER(hh, req->eee->pending_peers, peer, tmpPeer) { + mgmt_edges_row(req, buf, peer, "pSp"); } // dump peer-to-peer nodes - HASH_ITER(hh, eee->known_peers, peer, tmpPeer) { - mgmt_edges_row(eee, udp_buf, sender_sock, tag, peer, "p2p"); + HASH_ITER(hh, req->eee->known_peers, peer, tmpPeer) { + mgmt_edges_row(req, buf, peer, "p2p"); } } -static void mgmt_timestamps (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { size_t msg_len; - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"start_time\":%lu," "\"last_super\":%ld," "\"last_p2p\":%ld}\n", - tag, - eee->start_time, - eee->last_sup, - eee->last_p2p); + req->tag, + req->eee->start_time, + req->eee->last_sup, + req->eee->last_p2p); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); } -static void mgmt_packetstats (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +static void mgmt_packetstats (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { size_t msg_len; - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"type\":\"transop\"," "\"tx_pkt\":%lu," "\"rx_pkt\":%lu}\n", - tag, - eee->transop.tx_cnt, - eee->transop.rx_cnt); + req->tag, + req->eee->transop.tx_cnt, + req->eee->transop.rx_cnt); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"type\":\"p2p\"," "\"tx_pkt\":%u," "\"rx_pkt\":%u}\n", - tag, - eee->stats.tx_p2p, - eee->stats.rx_p2p); + req->tag, + req->eee->stats.tx_p2p, + req->eee->stats.rx_p2p); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"type\":\"super\"," "\"tx_pkt\":%u," "\"rx_pkt\":%u}\n", - tag, - eee->stats.tx_sup, - eee->stats.rx_sup); + req->tag, + req->eee->stats.tx_sup, + req->eee->stats.rx_sup); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"type\":\"super_broadcast\"," "\"tx_pkt\":%u," "\"rx_pkt\":%u}\n", - tag, - eee->stats.tx_sup_broadcast, - eee->stats.rx_sup_broadcast); + req->tag, + req->eee->stats.tx_sup_broadcast, + req->eee->stats.rx_sup_broadcast); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); } -static void mgmt_unimplemented (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +static void mgmt_post_test (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - mgmt_error(eee, udp_buf, sender_sock, tag, "unimplemented"); + send_json_1str(req, buf, "row", "sending", "test"); + mgmt_event_post(N2N_EVENT_TEST, -1, argv); } -static void mgmt_help (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv); +static void mgmt_unimplemented (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { -n2n_mgmt_handler_t mgmt_handlers[] = { + mgmt_error(req, buf, "unimplemented"); +} + +// Forward define so we can include this in the mgmt_handlers[] table +static void mgmt_help (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); +static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); + +static const mgmt_handler_t mgmt_handlers[] = { { .cmd = "reload_communities", .flags = FLAG_WROK, .help = "Reserved for supernode", .func = mgmt_unimplemented}, { .cmd = "stop", .flags = FLAG_WROK, .help = "Gracefully exit edge", .func = mgmt_stop}, @@ -289,32 +379,129 @@ n2n_mgmt_handler_t mgmt_handlers[] = { { .cmd = "supernodes", .help = "List current supernodes", .func = mgmt_supernodes}, { .cmd = "timestamps", .help = "Event timestamps", .func = mgmt_timestamps}, { .cmd = "packetstats", .help = "traffic counters", .func = mgmt_packetstats}, + { .cmd = "post.test", .help = "send a test event", .func = mgmt_post_test}, { .cmd = "help", .flags = FLAG_WROK, .help = "Show JSON commands", .func = mgmt_help}, - { .cmd = NULL }, + { .cmd = "help.events", .help = "Show available Subscribe topics", .func = mgmt_help_events}, }; -static void mgmt_help (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { +/* Current subscriber for each event topic */ +static mgmt_req_t mgmt_event_subscribers[] = { + [N2N_EVENT_DEBUG] = { .eee = NULL, .type = N2N_MGMT_UNKNOWN, .tag = "\0" }, + [N2N_EVENT_TEST] = { .eee = NULL, .type = N2N_MGMT_UNKNOWN, .tag = "\0" }, + [N2N_EVENT_PEER] = { .eee = NULL, .type = N2N_MGMT_UNKNOWN, .tag = "\0" }, +}; + +/* Map topic number to function */ +static const size_t (*mgmt_events[])(strbuf_t *buf, char *tag, int data0, void *data1) = { + [N2N_EVENT_DEBUG] = event_debug, + [N2N_EVENT_TEST] = event_test, + [N2N_EVENT_PEER] = event_peer, +}; + +/* Allow help and subscriptions to use topic name */ +static const mgmt_events_t mgmt_event_names[] = { + { .cmd = "debug", .topic = N2N_EVENT_DEBUG, .help = "All events - for event debugging"}, + { .cmd = "test", .topic = N2N_EVENT_TEST, .help = "Used only by post.test"}, + { .cmd = "peer", .topic = N2N_EVENT_PEER, .help = "Changes to peer list"}, +}; + +void mgmt_event_post (enum n2n_event_topic topic, int data0, void *data1) { + mgmt_req_t *debug = &mgmt_event_subscribers[N2N_EVENT_DEBUG]; + mgmt_req_t *sub = &mgmt_event_subscribers[topic]; + + traceEvent(TRACE_DEBUG, "post topic=%i data0=%i", topic, data0); + + if( sub->type != N2N_MGMT_SUB && debug->type != N2N_MGMT_SUB) { + // If neither of this topic or the debug topic have a subscriber + // then we dont need to do any work + return; + } + + char buf_space[100]; + strbuf_t *buf; + STRBUF_INIT(buf, buf_space); + + char *tag; + if(sub->type == N2N_MGMT_SUB) { + tag = sub->tag; + } else { + tag = debug->tag; + } + + size_t msg_len = mgmt_events[topic](buf, tag, data0, data1); + + if(sub->type == N2N_MGMT_SUB) { + send_reply(sub, buf, msg_len); + } + if(debug->type == N2N_MGMT_SUB) { + send_reply(debug, buf, msg_len); + } +} + +static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { + size_t msg_len; + + int i; + int nr_handlers = sizeof(mgmt_event_names) / sizeof(mgmt_events_t); + for( i=0; i < nr_handlers; i++ ) { + int topic = mgmt_event_names[i].topic; + mgmt_req_t *sub = &mgmt_event_subscribers[topic]; + char host[40]; + char serv[6]; + + if((sub->type != N2N_MGMT_SUB) || + getnameinfo((struct sockaddr *)&sub->sender_sock, sizeof(sub->sender_sock), + host, sizeof(host), + serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + host[0] = '?'; + host[1] = 0; + serv[0] = '?'; + serv[1] = 0; + } + + // TODO: handle a topic with no subscribers more cleanly + + msg_len = snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"row\"," + "\"topic\":\"%s\"," + "\"tag\":\"%s\"," + "\"sockaddr\":\"%s:%s\"," + "\"help\":\"%s\"}\n", + req->tag, + mgmt_event_names[i].cmd, + sub->tag, + host, serv, + mgmt_event_names[i].help); + + send_reply(req, buf, msg_len); + } +} + +static void mgmt_help (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { size_t msg_len; - n2n_mgmt_handler_t *handler; /* * Even though this command is readonly, we deliberately do not check * the type - allowing help replies to both read and write requests */ - for( handler=mgmt_handlers; handler->cmd; handler++ ) { - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, + int i; + int nr_handlers = sizeof(mgmt_handlers) / sizeof(mgmt_handler_t); + for( i=0; i < nr_handlers; i++ ) { + msg_len = snprintf(buf->str, buf->size, "{" "\"_tag\":\"%s\"," "\"_type\":\"row\"," "\"cmd\":\"%s\"," "\"help\":\"%s\"}\n", - tag, - handler->cmd, - handler->help); + req->tag, + mgmt_handlers[i].cmd, + mgmt_handlers[i].help); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_reply(req, buf, msg_len); } } @@ -325,37 +512,39 @@ static void mgmt_help (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in * Reads are not dangerous, so they are simply allowed * Writes are possibly dangerous, so they need a fake password */ -static int mgmt_auth (n2n_edge_t *eee, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *auth, char *argv0, char *argv) { +static int mgmt_auth (mgmt_req_t *req, char *auth, char *argv0, char *argv) { if(auth) { /* If we have an auth key, it must match */ - if(eee->conf.mgmt_password_hash == pearson_hash_64((uint8_t*)auth, strlen(auth))) { + if(req->eee->conf.mgmt_password_hash == pearson_hash_64((uint8_t*)auth, strlen(auth))) { return 1; } return 0; } /* if we dont have an auth key, we can still read */ - if(type == N2N_MGMT_READ) { + if(req->type == N2N_MGMT_READ) { return 1; } return 0; } -void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in sender_sock) { +static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) { + strbuf_t *buf; char cmdlinebuf[80]; - enum n2n_mgmt_type type; char *typechar; char *options; char *argv0; char *argv; - char *tag; char *flagstr; int flags; char *auth; - n2n_mgmt_handler_t *handler; - size_t msg_len; + + /* Initialise the tag field until we extract it from the cmdline */ + req->tag[0] = '-'; + req->tag[1] = '1'; + req->tag[2] = '\0'; /* save a copy of the commandline before we reuse the udp_buf */ strncpy(cmdlinebuf, udp_buf, sizeof(cmdlinebuf)-1); @@ -363,32 +552,36 @@ void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in se traceEvent(TRACE_DEBUG, "mgmt json %s", cmdlinebuf); + /* we reuse the buffer already on the stack for all our strings */ + STRBUF_INIT(buf, udp_buf); + typechar = strtok(cmdlinebuf, " \r\n"); if(!typechar) { /* should not happen */ - mgmt_error(eee, udp_buf, sender_sock, "-1", "notype"); + mgmt_error(req, buf, "notype"); return; } if(*typechar == 'r') { - type=N2N_MGMT_READ; + req->type=N2N_MGMT_READ; } else if(*typechar == 'w') { - type=N2N_MGMT_WRITE; + req->type=N2N_MGMT_WRITE; + } else if(*typechar == 's') { + req->type=N2N_MGMT_SUB; } else { - /* dunno how we got here */ - mgmt_error(eee, udp_buf, sender_sock, "-1", "badtype"); + mgmt_error(req, buf, "badtype"); return; } /* Extract the tag to use in all reply packets */ options = strtok(NULL, " \r\n"); if(!options) { - mgmt_error(eee, udp_buf, sender_sock, "-1", "nooptions"); + mgmt_error(req, buf, "nooptions"); return; } argv0 = strtok(NULL, " \r\n"); if(!argv0) { - mgmt_error(eee, udp_buf, sender_sock, "-1", "nocmd"); + mgmt_error(req, buf, "nocmd"); return; } @@ -401,7 +594,10 @@ void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in se /* * There might be an auth token mixed in with the tag */ - tag = strtok(options, ":"); + char *tagp = strtok(options, ":"); + strncpy(req->tag, tagp, sizeof(req->tag)-1); + req->tag[sizeof(req->tag)-1] = '\0'; + flagstr = strtok(NULL, ":"); if(flagstr) { flags = strtoul(flagstr, NULL, 16); @@ -416,23 +612,41 @@ void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in se auth = NULL; } - if(!mgmt_auth(eee, sender_sock, type, auth, argv0, argv)) { - mgmt_error(eee, udp_buf, sender_sock, tag, "badauth"); + if(!mgmt_auth(req, auth, argv0, argv)) { + mgmt_error(req, buf, "badauth"); return; } - for( handler=mgmt_handlers; handler->cmd; handler++ ) { - if(0 == strcmp(handler->cmd, argv0)) { - break; + if(req->type == N2N_MGMT_SUB) { + int handler; + lookup_handler(handler, mgmt_event_names, argv0); + if(handler == -1) { + mgmt_error(req, buf, "unknowntopic"); + return; } - } - if(!handler->cmd) { - mgmt_error(eee, udp_buf, sender_sock, tag, "unknowncmd"); + + int topic = mgmt_event_names[handler].topic; + if(mgmt_event_subscribers[topic].type == N2N_MGMT_SUB) { + send_json_1str(&mgmt_event_subscribers[topic], buf, + "unsubscribed", "topic", argv0); + send_json_1str(req, buf, "replacing", "topic", argv0); + } + + memcpy(&mgmt_event_subscribers[topic], req, sizeof(*req)); + + send_json_1str(req, buf, "subscribe", "topic", argv0); return; } - if((type==N2N_MGMT_WRITE) && !(handler->flags & FLAG_WROK)) { - mgmt_error(eee, udp_buf, sender_sock, tag, "readonly"); + int handler; + lookup_handler(handler, mgmt_handlers, argv0); + if(handler == -1) { + mgmt_error(req, buf, "unknowncmd"); + return; + } + + if((req->type==N2N_MGMT_WRITE) && !(mgmt_handlers[handler].flags & FLAG_WROK)) { + mgmt_error(req, buf, "readonly"); return; } @@ -442,16 +656,243 @@ void handleMgmtJson (n2n_edge_t *eee, char *udp_buf, const struct sockaddr_in se * that make our JSON invalid. * - do we care? */ - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{\"_tag\":\"%s\",\"_type\":\"begin\",\"cmd\":\"%s\"}\n", tag, argv0); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_json_1str(req, buf, "begin", "cmd", argv0); - handler->func(eee, udp_buf, sender_sock, type, tag, argv0, argv); + mgmt_handlers[handler].func(req, buf, argv0, argv); - msg_len = snprintf(udp_buf, N2N_PKT_BUF_SIZE, - "{\"_tag\":\"%s\",\"_type\":\"end\"}\n", tag); - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); + send_json_1str(req, buf, "end", "cmd", argv0); return; } + +/** Read a datagram from the management UDP socket and take appropriate + * action. */ +void readFromMgmtSocket (n2n_edge_t *eee) { + + char udp_buf[N2N_PKT_BUF_SIZE]; /* Compete UDP packet */ + ssize_t recvlen; + /* ssize_t sendlen; */ + mgmt_req_t req; + socklen_t i; + size_t msg_len; + time_t now; + struct peer_info *peer, *tmpPeer; + macstr_t mac_buf; + char time_buf[10]; /* 9 digits + 1 terminating zero */ + char uptime_buf[11]; /* 10 digits + 1 terminating zero */ + /* dec_ip_bit_str_t ip_bit_str = {'\0'}; */ + /* dec_ip_str_t ip_str = {'\0'}; */ + in_addr_t net; + n2n_sock_str_t sockbuf; + uint32_t num_pending_peers = 0; + uint32_t num_known_peers = 0; + uint32_t num = 0; + selection_criterion_str_t sel_buf; + + req.eee = eee; + + now = time(NULL); + i = sizeof(req.sender_sock); + recvlen = recvfrom(eee->udp_mgmt_sock, udp_buf, N2N_PKT_BUF_SIZE, 0 /*flags*/, + (struct sockaddr *) &req.sender_sock, (socklen_t *) &i); + + if(recvlen < 0) { + traceEvent(TRACE_WARNING, "mgmt recvfrom failed: %d - %s", errno, strerror(errno)); + return; /* failed to receive data from UDP */ + } + + /* avoid parsing any uninitialized junk from the stack */ + udp_buf[recvlen] = 0; + + if((0 == memcmp(udp_buf, "help", 4)) || (0 == memcmp(udp_buf, "?", 1))) { + strbuf_t *buf; + STRBUF_INIT(buf, &udp_buf); + msg_len = snprintf(buf->str, buf->size, + "Help for edge management console:\n" + "\tstop | Gracefully exit edge\n" + "\thelp | This help message\n" + "\t+verb | Increase verbosity of logging\n" + "\t-verb | Decrease verbosity of logging\n" + "\tr ... | start query with JSON reply\n" + "\tw ... | start update with JSON reply\n" + "\ts ... | subscribe to event channel JSON reply\n" + "\t | Display statistics\n\n"); + + send_reply(&req, buf, msg_len); + + return; + } + + if(0 == memcmp(udp_buf, "stop", 4)) { + traceEvent(TRACE_NORMAL, "stop command received"); + *eee->keep_running = 0; + return; + } + + if(0 == memcmp(udp_buf, "+verb", 5)) { + setTraceLevel(getTraceLevel() + 1); + + traceEvent(TRACE_NORMAL, "+verb traceLevel=%u", (unsigned int) getTraceLevel()); + + strbuf_t *buf; + STRBUF_INIT(buf, &udp_buf); + msg_len = snprintf(buf->str, buf->size, + "> +OK traceLevel=%u\n", (unsigned int) getTraceLevel()); + + send_reply(&req, buf, msg_len); + + return; + } + + if(0 == memcmp(udp_buf, "-verb", 5)) { + strbuf_t *buf; + STRBUF_INIT(buf, &udp_buf); + + if(getTraceLevel() > 0) { + setTraceLevel(getTraceLevel() - 1); + msg_len = snprintf(buf->str, buf->size, + "> -OK traceLevel=%u\n", getTraceLevel()); + } else { + msg_len = snprintf(buf->str, buf->size, + "> -NOK traceLevel=%u\n", getTraceLevel()); + } + + traceEvent(TRACE_NORMAL, "-verb traceLevel=%u", (unsigned int) getTraceLevel()); + + send_reply(&req, buf, msg_len); + return; + } + + if((udp_buf[0] >= 'a' && udp_buf[0] <= 'z') && (udp_buf[1] == ' ')) { + /* this is a JSON request */ + handleMgmtJson(&req, udp_buf, recvlen); + return; + } + + traceEvent(TRACE_DEBUG, "mgmt status requested"); + + msg_len = 0; + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "COMMUNITY '%s'\n\n", + (eee->conf.header_encryption == HEADER_ENCRYPTION_NONE) ? (char*)eee->conf.community_name : "-- header encrypted --"); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + " ### | TAP | MAC | EDGE | HINT | LAST SEEN | UPTIME\n"); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "=============================================================================================================\n"); + + // dump nodes with forwarding through supernodes + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "SUPERNODE FORWARD\n"); + num = 0; + HASH_ITER(hh, eee->pending_peers, peer, tmpPeer) { + ++num_pending_peers; + net = htonl(peer->dev_addr.net_addr); + snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "%4u | %-15s | %-17s | %-21s | %-15s | %9s |\n", + ++num, + (peer->dev_addr.net_addr == 0) ? "" : inet_ntoa(*(struct in_addr *) &net), + (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), + sock_to_cstr(sockbuf, &(peer->sock)), + peer->dev_desc, + (peer->last_seen) ? time_buf : ""); + + sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, + (struct sockaddr *) &req.sender_sock, sizeof(struct sockaddr_in)); + msg_len = 0; + } + + // dump peer-to-peer nodes + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "-------------------------------------------------------------------------------------------------------------\n"); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "PEER TO PEER\n"); + num = 0; + HASH_ITER(hh, eee->known_peers, peer, tmpPeer) { + ++num_known_peers; + net = htonl(peer->dev_addr.net_addr); + snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "%4u | %-15s | %-17s | %-21s | %-15s | %9s |\n", + ++num, + (peer->dev_addr.net_addr == 0) ? "" : inet_ntoa(*(struct in_addr *) &net), + (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), + sock_to_cstr(sockbuf, &(peer->sock)), + peer->dev_desc, + (peer->last_seen) ? time_buf : ""); + + sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, + (struct sockaddr *) &req.sender_sock, sizeof(struct sockaddr_in)); + msg_len = 0; + } + + // dump supernodes + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "-------------------------------------------------------------------------------------------------------------\n"); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "SUPERNODES\n"); + HASH_ITER(hh, eee->conf.supernodes, peer, tmpPeer) { + net = htonl(peer->dev_addr.net_addr); + snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); + snprintf(uptime_buf, sizeof(uptime_buf), "%10u", (unsigned int)(peer->uptime)); + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "%-19s %1s%1s | %-17s | %-21s | %-15s | %9s | %10s\n", + peer->version, + (peer->purgeable == SN_UNPURGEABLE) ? "l" : "", + (peer == eee->curr_sn) ? (eee->sn_wait ? "." : "*" ) : "", + is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), + sock_to_cstr(sockbuf, &(peer->sock)), + sn_selection_criterion_str(eee, sel_buf, peer), + (peer->last_seen) ? time_buf : "", + (peer->uptime) ? uptime_buf : ""); + + sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, + (struct sockaddr *) &req.sender_sock, sizeof(struct sockaddr_in)); + msg_len = 0; + } + + // further stats + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "=============================================================================================================\n"); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "uptime %lu | ", + time(NULL) - eee->start_time); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "pend_peers %u | ", + num_pending_peers); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "known_peers %u | ", + num_known_peers); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "transop %u,%u\n", + (unsigned int) eee->transop.tx_cnt, + (unsigned int) eee->transop.rx_cnt); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "super %u,%u | ", + (unsigned int) eee->stats.tx_sup, + (unsigned int) eee->stats.rx_sup); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "p2p %u,%u\n", + (unsigned int) eee->stats.tx_p2p, + (unsigned int) eee->stats.rx_p2p); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "last_super %ld sec ago | ", + (now - eee->last_sup)); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "last_p2p %ld sec ago\n", + (now - eee->last_p2p)); + + msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), + "\nType \"help\" to see more commands.\n\n"); + + sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, + (struct sockaddr *) &req.sender_sock, sizeof(struct sockaddr_in)); +} diff --git a/src/edge_utils.c b/src/edge_utils.c index b10cf56..fdc47ff 100644 --- a/src/edge_utils.c +++ b/src/edge_utils.c @@ -811,6 +811,7 @@ static void peer_set_p2p_confirmed (n2n_edge_t * eee, scan_tmp = find_peer_by_sock(peer, eee->known_peers); if(scan_tmp != NULL) { HASH_DEL(eee->known_peers, scan_tmp); + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_DEL_P2P,scan); free(scan); scan = scan_tmp; memcpy(scan->mac_addr, mac, sizeof(n2n_mac_t)); @@ -828,6 +829,7 @@ static void peer_set_p2p_confirmed (n2n_edge_t * eee, HASH_ADD_PEER(eee->known_peers, scan); scan->last_p2p = now; + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_ADD_P2P,scan); traceEvent(TRACE_DEBUG, "p2p connection established: %s [%s]", macaddr_str(mac_buf, mac), @@ -994,6 +996,7 @@ static void check_known_peer_sock_change (n2n_edge_t *eee, sock_to_cstr(sockbuf2, peer)); /* The peer has changed public socket. It can no longer be assumed to be reachable. */ HASH_DEL(eee->known_peers, scan); + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_DEL_P2P,scan); free(scan); register_with_new_peer(eee, from_supernode, via_multicast, mac, dev_addr, dev_desc, peer); @@ -1821,238 +1824,6 @@ static char *get_ip_from_arp (dec_ip_str_t buf, const n2n_mac_t req_mac) { #endif #endif -/** Read a datagram from the management UDP socket and take appropriate - * action. */ -static void readFromMgmtSocket (n2n_edge_t *eee) { - - char udp_buf[N2N_PKT_BUF_SIZE]; /* Compete UDP packet */ - ssize_t recvlen; - /* ssize_t sendlen; */ - struct sockaddr_in sender_sock; - socklen_t i; - size_t msg_len; - time_t now; - struct peer_info *peer, *tmpPeer; - macstr_t mac_buf; - char time_buf[10]; /* 9 digits + 1 terminating zero */ - char uptime_buf[11]; /* 10 digits + 1 terminating zero */ - /* dec_ip_bit_str_t ip_bit_str = {'\0'}; */ - /* dec_ip_str_t ip_str = {'\0'}; */ - in_addr_t net; - n2n_sock_str_t sockbuf; - uint32_t num_pending_peers = 0; - uint32_t num_known_peers = 0; - uint32_t num = 0; - selection_criterion_str_t sel_buf; - - - now = time(NULL); - i = sizeof(sender_sock); - recvlen = recvfrom(eee->udp_mgmt_sock, udp_buf, N2N_PKT_BUF_SIZE, 0/*flags*/, - (struct sockaddr *) &sender_sock, (socklen_t *) &i); - - if(recvlen < 0) { - traceEvent(TRACE_WARNING, "mgmt recvfrom failed: %d - %s", errno, strerror(errno)); - return; /* failed to receive data from UDP */ - } - - /* avoid parsing any uninitialized junk from the stack */ - udp_buf[recvlen] = 0; - - if((0 == memcmp(udp_buf, "help", 4)) || (0 == memcmp(udp_buf, "?", 1))) { - msg_len = 0; - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "Help for edge management console:\n" - "\tstop | Gracefully exit edge\n" - "\thelp | This help message\n" - "\t+verb | Increase verbosity of logging\n" - "\t-verb | Decrease verbosity of logging\n" - "\tr ... | start query with JSON reply\n" - "\tw ... | start update with JSON reply\n" - "\t | Display statistics\n\n"); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - - return; - } - - if(0 == memcmp(udp_buf, "stop", 4)) { - traceEvent(TRACE_NORMAL, "stop command received"); - *eee->keep_running = 0; - return; - } - - if(0 == memcmp(udp_buf, "+verb", 5)) { - msg_len = 0; - setTraceLevel(getTraceLevel() + 1); - - traceEvent(TRACE_NORMAL, "+verb traceLevel=%u", (unsigned int) getTraceLevel()); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "> +OK traceLevel=%u\n", (unsigned int) getTraceLevel()); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - - return; - } - - if(0 == memcmp(udp_buf, "-verb", 5)) { - msg_len = 0; - - if(getTraceLevel() > 0) { - setTraceLevel(getTraceLevel() - 1); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "> -OK traceLevel=%u\n", getTraceLevel()); - } else { - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "> -NOK traceLevel=%u\n", getTraceLevel()); - } - - traceEvent(TRACE_NORMAL, "-verb traceLevel=%u", (unsigned int) getTraceLevel()); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - return; - } - - if((udp_buf[0] == 'r' || udp_buf[0] == 'w') && (udp_buf[1] == ' ')) { - /* this is a JSON request */ - handleMgmtJson(eee, udp_buf, sender_sock); - return; - } - - traceEvent(TRACE_DEBUG, "mgmt status requested"); - - msg_len = 0; - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "COMMUNITY '%s'\n\n", - (eee->conf.header_encryption == HEADER_ENCRYPTION_NONE) ? (char*)eee->conf.community_name : "-- header encrypted --"); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - " ### | TAP | MAC | EDGE | HINT | LAST SEEN | UPTIME\n"); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "=============================================================================================================\n"); - - // dump nodes with forwarding through supernodes - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "SUPERNODE FORWARD\n"); - num = 0; - HASH_ITER(hh, eee->pending_peers, peer, tmpPeer) { - ++num_pending_peers; - net = htonl(peer->dev_addr.net_addr); - snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "%4u | %-15s | %-17s | %-21s | %-15s | %9s |\n", - ++num, - (peer->dev_addr.net_addr == 0) ? "" : inet_ntoa(*(struct in_addr *) &net), - (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), - sock_to_cstr(sockbuf, &(peer->sock)), - peer->dev_desc, - (peer->last_seen) ? time_buf : ""); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - msg_len = 0; - } - - // dump peer-to-peer nodes - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "-------------------------------------------------------------------------------------------------------------\n"); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "PEER TO PEER\n"); - num = 0; - HASH_ITER(hh, eee->known_peers, peer, tmpPeer) { - ++num_known_peers; - net = htonl(peer->dev_addr.net_addr); - snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "%4u | %-15s | %-17s | %-21s | %-15s | %9s |\n", - ++num, - (peer->dev_addr.net_addr == 0) ? "" : inet_ntoa(*(struct in_addr *) &net), - (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), - sock_to_cstr(sockbuf, &(peer->sock)), - peer->dev_desc, - (peer->last_seen) ? time_buf : ""); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - msg_len = 0; - } - - // dump supernodes - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "-------------------------------------------------------------------------------------------------------------\n"); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "SUPERNODES\n"); - HASH_ITER(hh, eee->conf.supernodes, peer, tmpPeer) { - net = htonl(peer->dev_addr.net_addr); - snprintf(time_buf, sizeof(time_buf), "%9u", (unsigned int)(now - peer->last_seen)); - snprintf(uptime_buf, sizeof(uptime_buf), "%10u", (unsigned int)(peer->uptime)); - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "%-19s %1s%1s | %-17s | %-21s | %-15s | %9s | %10s\n", - peer->version, - (peer->purgeable == SN_UNPURGEABLE) ? "l" : "", - (peer == eee->curr_sn) ? (eee->sn_wait ? "." : "*" ) : "", - is_null_mac(peer->mac_addr) ? "" : macaddr_str(mac_buf, peer->mac_addr), - sock_to_cstr(sockbuf, &(peer->sock)), - sn_selection_criterion_str(eee, sel_buf, peer), - (peer->last_seen) ? time_buf : "", - (peer->uptime) ? uptime_buf : ""); - - sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); - msg_len = 0; - } - - // further stats - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "=============================================================================================================\n"); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "uptime %lu | ", - time(NULL) - eee->start_time); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "pend_peers %u | ", - num_pending_peers); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "known_peers %u | ", - num_known_peers); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "transop %u,%u\n", - (unsigned int) eee->transop.tx_cnt, - (unsigned int) eee->transop.rx_cnt); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "super %u,%u | ", - (unsigned int) eee->stats.tx_sup, - (unsigned int) eee->stats.rx_sup); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "p2p %u,%u\n", - (unsigned int) eee->stats.tx_p2p, - (unsigned int) eee->stats.rx_p2p); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "last_super %ld sec ago | ", - (now - eee->last_sup)); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "last_p2p %ld sec ago\n", - (now - eee->last_p2p)); - - msg_len += snprintf((char *) (udp_buf + msg_len), (N2N_PKT_BUF_SIZE - msg_len), - "\nType \"help\" to see more commands.\n\n"); - - /* sendlen = */ sendto(eee->udp_mgmt_sock, udp_buf, msg_len, 0/*flags*/, - (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); -} - - /* ************************************** */ static int check_query_peer_info (n2n_edge_t *eee, time_t now, n2n_mac_t mac) { @@ -2112,6 +1883,7 @@ static int find_peer_destination (n2n_edge_t * eee, * since the peer address may have changed. */ traceEvent(TRACE_DEBUG, "refreshing idle known peer"); HASH_DEL(eee->known_peers, scan); + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_DEL_P2P,scan); free(scan); /* NOTE: registration will be performed upon the receival of the next response packet */ } else { diff --git a/src/n2n.c b/src/n2n.c index a879439..de62e10 100644 --- a/src/n2n.c +++ b/src/n2n.c @@ -636,6 +636,8 @@ size_t purge_peer_list (struct peer_info **peer_list, } } HASH_DEL(*peer_list, scan); + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_PURGE,scan); + /* FIXME: generates events for more than just p2p */ retval++; free(scan); } @@ -652,6 +654,8 @@ size_t clear_peer_list (struct peer_info ** peer_list) { HASH_ITER(hh, *peer_list, scan, tmp) { HASH_DEL(*peer_list, scan); + mgmt_event_post(N2N_EVENT_PEER,N2N_EVENT_PEER_CLEAR,scan); + /* FIXME: generates events for more than just p2p */ retval++; free(scan); } diff --git a/src/sn_management.c b/src/sn_management.c index 0f34a39..63a2b31 100644 --- a/src/sn_management.c +++ b/src/sn_management.c @@ -26,13 +26,18 @@ int load_allowed_sn_community (n2n_sn_t *sss); /* defined in sn_utils.c */ +enum n2n_mgmt_type { + N2N_MGMT_READ = 0, + N2N_MGMT_WRITE = 1, +}; + #define FLAG_WROK 1 -typedef struct n2n_mgmt_handler { +typedef struct mgmt_handler { int flags; char *cmd; char *help; void (*func)(n2n_sn_t *sss, char *udp_buf, struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv); -} n2n_mgmt_handler_t; +} mgmt_handler_t; static void mgmt_error (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, char *tag, char *msg) { size_t msg_len; @@ -263,7 +268,7 @@ static void mgmt_unimplemented (n2n_sn_t *sss, char *udp_buf, const struct socka static void mgmt_help (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv); -n2n_mgmt_handler_t mgmt_handlers[] = { +mgmt_handler_t mgmt_handlers[] = { { .cmd = "supernodes", .help = "Reserved for edge", .func = mgmt_unimplemented}, { .cmd = "stop", .flags = FLAG_WROK, .help = "Gracefully exit edge", .func = mgmt_stop}, @@ -279,7 +284,7 @@ n2n_mgmt_handler_t mgmt_handlers[] = { static void mgmt_help (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock, enum n2n_mgmt_type type, char *tag, char *argv0, char *argv) { size_t msg_len; - n2n_mgmt_handler_t *handler; + mgmt_handler_t *handler; /* * Even though this command is readonly, we deliberately do not check @@ -326,7 +331,7 @@ static int mgmt_auth (n2n_sn_t *sss, const struct sockaddr_in sender_sock, enum return 0; } -void handleMgmtJson_sn (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock) { +static void handleMgmtJson (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in sender_sock) { char cmdlinebuf[80]; enum n2n_mgmt_type type; @@ -338,7 +343,7 @@ void handleMgmtJson_sn (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in s char *flagstr; int flags; char *auth; - n2n_mgmt_handler_t *handler; + mgmt_handler_t *handler; size_t msg_len; /* save a copy of the commandline before we reuse the udp_buf */ @@ -439,3 +444,164 @@ void handleMgmtJson_sn (n2n_sn_t *sss, char *udp_buf, const struct sockaddr_in s (struct sockaddr *) &sender_sock, sizeof(struct sockaddr_in)); return; } + +static int sendto_mgmt (n2n_sn_t *sss, + const struct sockaddr_in *sender_sock, + const uint8_t *mgmt_buf, + size_t mgmt_size) { + + ssize_t r = sendto(sss->mgmt_sock, (void *)mgmt_buf, mgmt_size, 0 /*flags*/, + (struct sockaddr *)sender_sock, sizeof (struct sockaddr_in)); + + if(r <= 0) { + ++(sss->stats.errors); + traceEvent(TRACE_ERROR, "sendto_mgmt : sendto failed. %s", strerror(errno)); + return -1; + } + + return 0; +} + +int process_mgmt (n2n_sn_t *sss, + const struct sockaddr_in *sender_sock, + char *mgmt_buf, + size_t mgmt_size, + time_t now) { + + char resbuf[N2N_SN_PKTBUF_SIZE]; + size_t ressize = 0; + uint32_t num_edges = 0; + uint32_t num_comm = 0; + uint32_t num = 0; + struct sn_community *community, *tmp; + struct peer_info *peer, *tmpPeer; + macstr_t mac_buf; + n2n_sock_str_t sockbuf; + char time_buf[10]; /* 9 digits + 1 terminating zero */ + dec_ip_bit_str_t ip_bit_str = {'\0'}; + + traceEvent(TRACE_DEBUG, "process_mgmt"); + + /* avoid parsing any uninitialized junk from the stack */ + mgmt_buf[mgmt_size] = 0; + + // process input, if any + if((0 == memcmp(mgmt_buf, "help", 4)) || (0 == memcmp(mgmt_buf, "?", 1))) { + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "Help for supernode management console:\n" + "\thelp | This help message\n" + "\treload_communities | Reloads communities and user's public keys\n" + "\t | Display status and statistics\n"); + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + return 0; /* no status output afterwards */ + } + + if(0 == memcmp(mgmt_buf, "reload_communities", 18)) { + if(!sss->community_file) { + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "No community file provided (-c command line option)\n"); + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + return 0; /* no status output afterwards */ + } + traceEvent(TRACE_NORMAL, "'reload_communities' command"); + + if(load_allowed_sn_community(sss)) { + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "Error while re-loading community file (not found or no valid content)\n"); + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + return 0; /* no status output afterwards */ + } + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "OK.\n"); + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + return 0; /* no status output afterwards */ + } + + if((mgmt_buf[0] == 'r' || mgmt_buf[0] == 'w') && (mgmt_buf[1] == ' ')) { + /* this is a JSON request */ + handleMgmtJson(sss, mgmt_buf, *sender_sock); + return 0; + } + + // output current status + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + " ### | TAP | MAC | EDGE | HINT | LAST SEEN\n"); + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "========================================================================================================\n"); + HASH_ITER(hh, sss->communities, community, tmp) { + if(num_comm) + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "--------------------------------------------------------------------------------------------------------\n"); + num_comm++; + num_edges += HASH_COUNT(community->edges); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "%s '%s'\n", + (community->is_federation) ? "FEDERATION" : ((community->purgeable == COMMUNITY_UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"), + (community->is_federation) ? "-/-" : community->community); + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + ressize = 0; + + num = 0; + HASH_ITER(hh, community->edges, peer, tmpPeer) { + sprintf(time_buf, "%9u", (unsigned int)(now - peer->last_seen)); + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n", + ++num, + (peer->dev_addr.net_addr == 0) ? ((peer->purgeable == SN_UNPURGEABLE) ? "-l" : "") : ip_subnet_to_str(ip_bit_str, &peer->dev_addr), + (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), + sock_to_cstr(sockbuf, &(peer->sock)), + ((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "", + peer->dev_desc, + (peer->last_seen) ? time_buf : ""); + + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + ressize = 0; + } + } + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "========================================================================================================\n"); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "uptime %lu | ", (now - sss->start_time)); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "edges %u | ", + num_edges); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "reg_sup %u | ", + (unsigned int) sss->stats.reg_super); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "reg_nak %u | ", + (unsigned int) sss->stats.reg_super_nak); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "errors %u \n", + (unsigned int) sss->stats.errors); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "fwd %u | ", + (unsigned int) sss->stats.fwd); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "broadcast %u | ", + (unsigned int) sss->stats.broadcast); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "cur_cmnts %u\n", HASH_COUNT(sss->communities)); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "last_fwd %lu sec ago | ", + (long unsigned int) (now - sss->stats.last_fwd)); + + ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, + "last reg %lu sec ago\n\n", + (long unsigned int) (now - sss->stats.last_reg_super)); + + sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); + + return 0; +} diff --git a/src/sn_utils.c b/src/sn_utils.c index ea5398d..6b8474e 100644 --- a/src/sn_utils.c +++ b/src/sn_utils.c @@ -30,11 +30,6 @@ static ssize_t sendto_peer (n2n_sn_t *sss, const uint8_t *pktbuf, size_t pktsize); -static int sendto_mgmt (n2n_sn_t *sss, - const struct sockaddr_in *sender_sock, - const uint8_t *mgmt_buf, - size_t mgmt_size); - static uint16_t reg_lifetime (n2n_sn_t *sss); static int update_edge (n2n_sn_t *sss, @@ -61,7 +56,7 @@ static int sort_communities (n2n_sn_t *sss, time_t* p_last_sort, time_t now); -static int process_mgmt (n2n_sn_t *sss, +int process_mgmt (n2n_sn_t *sss, const struct sockaddr_in *sender_sock, char *mgmt_buf, size_t mgmt_size, @@ -1507,170 +1502,6 @@ static int sort_communities (n2n_sn_t *sss, } -static int process_mgmt (n2n_sn_t *sss, - const struct sockaddr_in *sender_sock, - char *mgmt_buf, - size_t mgmt_size, - time_t now) { - - char resbuf[N2N_SN_PKTBUF_SIZE]; - size_t ressize = 0; - uint32_t num_edges = 0; - uint32_t num_comm = 0; - uint32_t num = 0; - struct sn_community *community, *tmp; - struct peer_info *peer, *tmpPeer; - macstr_t mac_buf; - n2n_sock_str_t sockbuf; - char time_buf[10]; /* 9 digits + 1 terminating zero */ - dec_ip_bit_str_t ip_bit_str = {'\0'}; - - traceEvent(TRACE_DEBUG, "process_mgmt"); - - /* avoid parsing any uninitialized junk from the stack */ - mgmt_buf[mgmt_size] = 0; - - // process input, if any - if((0 == memcmp(mgmt_buf, "help", 4)) || (0 == memcmp(mgmt_buf, "?", 1))) { - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "Help for supernode management console:\n" - "\thelp | This help message\n" - "\treload_communities | Reloads communities and user's public keys\n" - "\t | Display status and statistics\n"); - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - return 0; /* no status output afterwards */ - } - - if(0 == memcmp(mgmt_buf, "reload_communities", 18)) { - if(!sss->community_file) { - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "No community file provided (-c command line option)\n"); - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - return 0; /* no status output afterwards */ - } - traceEvent(TRACE_NORMAL, "'reload_communities' command"); - - if(load_allowed_sn_community(sss)) { - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "Error while re-loading community file (not found or no valid content)\n"); - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - return 0; /* no status output afterwards */ - } - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "OK.\n"); - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - return 0; /* no status output afterwards */ - } - - if((mgmt_buf[0] == 'r' || mgmt_buf[0] == 'w') && (mgmt_buf[1] == ' ')) { - /* this is a JSON request */ - handleMgmtJson_sn(sss, mgmt_buf, *sender_sock); - return 0; - } - - // output current status - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - " ### | TAP | MAC | EDGE | HINT | LAST SEEN\n"); - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "========================================================================================================\n"); - HASH_ITER(hh, sss->communities, community, tmp) { - if(num_comm) - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "--------------------------------------------------------------------------------------------------------\n"); - num_comm++; - num_edges += HASH_COUNT(community->edges); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "%s '%s'\n", - (community->is_federation) ? "FEDERATION" : - ((community->purgeable == COMMUNITY_UNPURGEABLE) ? "FIXED NAME COMMUNITY" : "COMMUNITY"), - (community->is_federation) ? "-/-" : community->community); - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - ressize = 0; - - num = 0; - HASH_ITER(hh, community->edges, peer, tmpPeer) { - sprintf (time_buf, "%9u", (unsigned int)(now - peer->last_seen)); - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "%4u | %-19s | %-17s | %-21s %-3s | %-15s | %9s\n", - ++num, - (peer->dev_addr.net_addr == 0) ? ((peer->purgeable == SN_UNPURGEABLE) ? "-l" : "") : - ip_subnet_to_str(ip_bit_str, &peer->dev_addr), - (is_null_mac(peer->mac_addr)) ? "" : macaddr_str(mac_buf, peer->mac_addr), - sock_to_cstr(sockbuf, &(peer->sock)), - ((peer->socket_fd >= 0) && (peer->socket_fd != sss->sock)) ? "TCP" : "", - peer->dev_desc, - (peer->last_seen) ? time_buf : ""); - - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - ressize = 0; - } - } - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "========================================================================================================\n"); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "uptime %lu | ", (now - sss->start_time)); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "edges %u | ", - num_edges); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "reg_sup %u | ", - (unsigned int) sss->stats.reg_super); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "reg_nak %u | ", - (unsigned int) sss->stats.reg_super_nak); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "errors %u \n", - (unsigned int) sss->stats.errors); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "fwd %u | ", - (unsigned int) sss->stats.fwd); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "broadcast %u | ", - (unsigned int) sss->stats.broadcast); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "cur_cmnts %u\n", HASH_COUNT(sss->communities)); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "last_fwd %lu sec ago | ", - (long unsigned int) (now - sss->stats.last_fwd)); - - ressize += snprintf(resbuf + ressize, N2N_SN_PKTBUF_SIZE - ressize, - "last reg %lu sec ago\n\n", - (long unsigned int) (now - sss->stats.last_reg_super)); - - sendto_mgmt(sss, sender_sock, (const uint8_t *) resbuf, ressize); - - return 0; -} - - -static int sendto_mgmt (n2n_sn_t *sss, - const struct sockaddr_in *sender_sock, - const uint8_t *mgmt_buf, - size_t mgmt_size) { - - ssize_t r = sendto(sss->mgmt_sock, (void *)mgmt_buf, mgmt_size, 0 /*flags*/, - (struct sockaddr *)sender_sock, sizeof (struct sockaddr_in)); - - if(r <= 0) { - ++(sss->stats.errors); - traceEvent (TRACE_ERROR, "sendto_mgmt : sendto failed. %s", strerror (errno)); - return -1; - } - - return 0; -} - /** Examine a datagram and determine what to do with it. * */ diff --git a/src/supernode.c b/src/supernode.c index c821614..8e78a77 100644 --- a/src/supernode.c +++ b/src/supernode.c @@ -639,10 +639,24 @@ int main (int argc, char * const argv[]) { scan->socket_fd = sss_node.sock; #ifndef WIN32 + /* + * If no uid/gid is specified on the commandline, use the uid/gid of the + * first found out of user "n2n" or "nobody" + */ if(((pw = getpwnam ("n2n")) != NULL) || ((pw = getpwnam ("nobody")) != NULL)) { + /* + * If the uid/gid is not set from the CLI, set it from getpwnam + * otherwise reset it to zero + * (TODO: this looks wrong) + */ sss_node.userid = sss_node.userid == 0 ? pw->pw_uid : 0; sss_node.groupid = sss_node.groupid == 0 ? pw->pw_gid : 0; } + + /* + * If we have a non-zero requested uid/gid, attempt to switch to use + * those + */ if((sss_node.userid != 0) || (sss_node.groupid != 0)) { traceEvent(TRACE_NORMAL, "dropping privileges to uid=%d, gid=%d", (signed int)sss_node.userid, (signed int)sss_node.groupid); @@ -651,7 +665,6 @@ int main (int argc, char * const argv[]) { if((setgid(sss_node.groupid) != 0) || (setuid(sss_node.userid) != 0)) { traceEvent(TRACE_ERROR, "unable to drop privileges [%u/%s]", errno, strerror(errno)); - exit(1); } } diff --git a/tests/test_integration_edge.sh.expected b/tests/test_integration_edge.sh.expected new file mode 100644 index 0000000..f8a009f --- /dev/null +++ b/tests/test_integration_edge.sh.expected @@ -0,0 +1,74 @@ +### + +### +[ + { + "community": "test" + } +] + +### +[ + { + "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" + } +] + +### +[] + +### +[ + { + "traceLevel": 2 + } +] + +### + +0 +### +[ + { + "traceLevel": 1 + } +] + +### +[ + { + "keep_running": 1 + } +] + +### +[ + { + "keep_running": 0 + } +] + +### +[ + { + "keep_running": 0 + } +] + diff --git a/tests/test_integration_supernode.sh.expected b/tests/test_integration_supernode.sh.expected new file mode 100644 index 0000000..b1d2a78 --- /dev/null +++ b/tests/test_integration_supernode.sh.expected @@ -0,0 +1,64 @@ +### + +### +[ + { + "community": "-/-", + "ip4addr": "", + "is_federation": 1, + "purgeable": 0 + } +] + +### +[ + { + "tx_pkt": 0, + "type": "forward" + }, + { + "tx_pkt": 0, + "type": "broadcast" + }, + { + "nak": 0, + "rx_pkt": 0, + "type": "reg_super" + }, + { + "tx_pkt": 0, + "type": "errors" + } +] + +### +[] + +### +[ + { + "traceLevel": 3 + } +] + +### +[ + { + "traceLevel": 1 + } +] + +### +[ + { + "keep_running": 1 + } +] + +### +[ + { + "keep_running": 0 + } +] + diff --git a/tests/tests_integration.list b/tests/tests_integration.list new file mode 100644 index 0000000..ff7ffcc --- /dev/null +++ b/tests/tests_integration.list @@ -0,0 +1,5 @@ +# +# The integration tests + +test_integration_supernode.sh +test_integration_edge.sh diff --git a/tests/tests_units.list b/tests/tests_units.list new file mode 100644 index 0000000..6491c1b --- /dev/null +++ b/tests/tests_units.list @@ -0,0 +1,9 @@ +# +# The unit tests + +tests-auth +tests-compress +tests-elliptic +tests-hashing +tests-transform +tests-wire