mirror of https://github.com/ntop/n2n.git
Browse Source
* Add management commands to show data in JSON format * Add a script to query the JSON management interface * Suprisingly, the github runner does not have flake8 installed * Add n2nctl debugging output to show the raw data received from the JSON * Ensure well known tag wrap-around semantics * Try to ensure we check every edge case in the protocol handling - only valid packets are allowed * Add a very simple http to management port gateway * Fix the lint issuepull/855/head
Hamish Coleman
3 years ago
committed by
GitHub
5 changed files with 440 additions and 1 deletions
@ -0,0 +1,148 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
# Licensed under GPLv3 |
||||
|
# |
||||
|
# Simple script to query the management interface of a running n2n edge node |
||||
|
|
||||
|
import argparse |
||||
|
import socket |
||||
|
import json |
||||
|
import collections |
||||
|
|
||||
|
next_tag = 0 |
||||
|
|
||||
|
|
||||
|
def send_cmd(port, debug, cmd): |
||||
|
global next_tag |
||||
|
|
||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
||||
|
|
||||
|
tagstr = str(next_tag) |
||||
|
next_tag = (next_tag + 1) % 1000 |
||||
|
|
||||
|
message = "{} {}".format(cmd, tagstr).encode('utf8') |
||||
|
sock.sendto(message, ("127.0.0.1", 5644)) |
||||
|
|
||||
|
# FIXME: |
||||
|
# - there is no timeout for any of the socket handling |
||||
|
|
||||
|
begin, _ = sock.recvfrom(1024) |
||||
|
begin = json.loads(begin.decode('utf8')) |
||||
|
assert(begin['_tag'] == tagstr) |
||||
|
assert(begin['_type'] == 'begin') |
||||
|
assert(begin['_cmd'] == cmd) |
||||
|
|
||||
|
result = list() |
||||
|
|
||||
|
while True: |
||||
|
data, _ = sock.recvfrom(1024) |
||||
|
data = json.loads(data.decode('utf8')) |
||||
|
assert(data['_tag'] == tagstr) |
||||
|
|
||||
|
if data['_type'] == 'unknowncmd': |
||||
|
raise ValueError('Unknown command {}'.format(cmd)) |
||||
|
|
||||
|
if data['_type'] == 'end': |
||||
|
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 debug: |
||||
|
print(data) |
||||
|
|
||||
|
result.append(data) |
||||
|
|
||||
|
|
||||
|
def str_table(rows, columns): |
||||
|
"""Given an array of dicts, do a simple table print""" |
||||
|
result = list() |
||||
|
widths = collections.defaultdict(lambda: 0) |
||||
|
for row in rows: |
||||
|
for col in columns: |
||||
|
if col in row: |
||||
|
widths[col] = max(widths[col], len(str(row[col]))) |
||||
|
|
||||
|
for col in columns: |
||||
|
if widths[col] == 0: |
||||
|
widths[col] = 1 |
||||
|
result += "{:{}.{}} ".format(col, widths[col], widths[col]) |
||||
|
result += "\n" |
||||
|
|
||||
|
for row in rows: |
||||
|
for col in columns: |
||||
|
if col in row: |
||||
|
data = row[col] |
||||
|
else: |
||||
|
data = '' |
||||
|
result += "{:{}} ".format(data, widths[col]) |
||||
|
result += "\n" |
||||
|
|
||||
|
return ''.join(result) |
||||
|
|
||||
|
|
||||
|
def subcmd_show_supernodes(args): |
||||
|
rows = send_cmd(args.port, args.debug, 'j.super') |
||||
|
columns = [ |
||||
|
'version', |
||||
|
'current', |
||||
|
'macaddr', |
||||
|
'sockaddr', |
||||
|
'uptime', |
||||
|
] |
||||
|
|
||||
|
return str_table(rows, columns) |
||||
|
|
||||
|
|
||||
|
def subcmd_show_peers(args): |
||||
|
rows = send_cmd(args.port, args.debug, 'j.peer') |
||||
|
columns = [ |
||||
|
'mode', |
||||
|
'ip4addr', |
||||
|
'macaddr', |
||||
|
'sockaddr', |
||||
|
'desc', |
||||
|
] |
||||
|
|
||||
|
return str_table(rows, columns) |
||||
|
|
||||
|
|
||||
|
subcmds = { |
||||
|
'supernodes': { |
||||
|
'func': subcmd_show_supernodes, |
||||
|
'help': 'Show the list of supernodes', |
||||
|
}, |
||||
|
'peers': { |
||||
|
'func': subcmd_show_peers, |
||||
|
'help': 'Show the list of peers', |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
ap = argparse.ArgumentParser( |
||||
|
description='Query the running local n2n edge') |
||||
|
ap.add_argument('-t', '--port', action='store', default=5644, |
||||
|
help='Management Port (default=5644)') |
||||
|
ap.add_argument('-d', '--debug', action='store_true', |
||||
|
help='Also show raw internal data') |
||||
|
|
||||
|
subcmd = ap.add_subparsers(help='Subcommand', dest='cmd') |
||||
|
subcmd.required = True |
||||
|
|
||||
|
for key, value in subcmds.items(): |
||||
|
value['parser'] = subcmd.add_parser(key, help=value['help']) |
||||
|
value['parser'].set_defaults(func=value['func']) |
||||
|
|
||||
|
args = ap.parse_args() |
||||
|
|
||||
|
result = args.func(args) |
||||
|
print(result) |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
main() |
@ -0,0 +1,118 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
# Licensed under GPLv3 |
||||
|
# |
||||
|
# Simple http server to allow user control of n2n edge nodes |
||||
|
# |
||||
|
# Currently only for demonstration - needs javascript written to render the |
||||
|
# results properly. |
||||
|
# |
||||
|
# Try it out with |
||||
|
# http://localhost:8080/edge/peer |
||||
|
# http://localhost:8080/edge/super |
||||
|
|
||||
|
import argparse |
||||
|
import socket |
||||
|
import json |
||||
|
import socketserver |
||||
|
import http.server |
||||
|
|
||||
|
from http import HTTPStatus |
||||
|
|
||||
|
next_tag = 0 |
||||
|
|
||||
|
|
||||
|
def send_cmd(port, debug, cmd): |
||||
|
"""Send a text command to the edge and process the JSON reply packets""" |
||||
|
global next_tag |
||||
|
|
||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
||||
|
|
||||
|
tagstr = str(next_tag) |
||||
|
next_tag = (next_tag + 1) % 1000 |
||||
|
|
||||
|
message = "{} {}".format(cmd, tagstr).encode('utf8') |
||||
|
sock.sendto(message, ("127.0.0.1", 5644)) |
||||
|
|
||||
|
# FIXME: |
||||
|
# - there is no timeout for any of the socket handling |
||||
|
|
||||
|
begin, _ = sock.recvfrom(1024) |
||||
|
begin = json.loads(begin.decode('utf8')) |
||||
|
assert(begin['_tag'] == tagstr) |
||||
|
assert(begin['_type'] == 'begin') |
||||
|
assert(begin['_cmd'] == cmd) |
||||
|
|
||||
|
result = list() |
||||
|
|
||||
|
while True: |
||||
|
data, _ = sock.recvfrom(1024) |
||||
|
data = json.loads(data.decode('utf8')) |
||||
|
assert(data['_tag'] == tagstr) |
||||
|
|
||||
|
if data['_type'] == 'unknowncmd': |
||||
|
raise ValueError('Unknown command {}'.format(cmd)) |
||||
|
|
||||
|
if data['_type'] == 'end': |
||||
|
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 debug: |
||||
|
print(data) |
||||
|
|
||||
|
result.append(data) |
||||
|
|
||||
|
|
||||
|
class SimpleHandler(http.server.BaseHTTPRequestHandler): |
||||
|
|
||||
|
def log_request(self, code='-', size='-'): |
||||
|
# Dont spam the output |
||||
|
pass |
||||
|
|
||||
|
def do_GET(self): |
||||
|
url_tail = self.path |
||||
|
if url_tail.startswith("/edge/"): |
||||
|
tail = url_tail.split('/') |
||||
|
cmd = 'j.' + tail[2] |
||||
|
|
||||
|
try: |
||||
|
data = send_cmd(5644, False, cmd) |
||||
|
except ValueError: |
||||
|
self.send_response(HTTPStatus.BAD_REQUEST) |
||||
|
self.end_headers() |
||||
|
self.wfile.write(b'Bad Command') |
||||
|
return |
||||
|
|
||||
|
self.send_response(HTTPStatus.OK) |
||||
|
self.send_header('Content-type', 'application/json') |
||||
|
self.end_headers() |
||||
|
self.wfile.write(json.dumps(data).encode('utf8')) |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
ap = argparse.ArgumentParser( |
||||
|
description='Control the running local n2n edge via http') |
||||
|
# TODO - this needs to pass into the handler object |
||||
|
# ap.add_argument('-t', '--mgmtport', action='store', default=5644, |
||||
|
# help='Management Port (default=5644)') |
||||
|
# ap.add_argument('-d', '--debug', action='store_true', |
||||
|
# help='Also show raw internal data') |
||||
|
ap.add_argument('port', action='store', |
||||
|
default=8080, type=int, nargs='?', |
||||
|
help='Serve requests on TCP port (default 8080)') |
||||
|
|
||||
|
args = ap.parse_args() |
||||
|
|
||||
|
with socketserver.TCPServer(("", args.port), SimpleHandler) as httpd: |
||||
|
httpd.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
||||
|
httpd.serve_forever() |
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
main() |
Loading…
Reference in new issue