From 82658805c7858774af1a664ca70b13e45b8a59fd Mon Sep 17 00:00:00 2001 From: "Roger A. Light" Date: Thu, 20 Jan 2022 15:00:21 +0000 Subject: [PATCH] listListeners command. --- apps/mosquitto_ctrl/broker.c | 66 +++++++++++ src/broker_control.c | 99 +++++++++++++++- test/broker/17-control-list-listeners.py | 139 +++++++++++++++++++++++ test/broker/Makefile | 5 +- test/broker/test.py | 2 + 5 files changed, 309 insertions(+), 2 deletions(-) create mode 100755 test/broker/17-control-list-listeners.py diff --git a/apps/mosquitto_ctrl/broker.c b/apps/mosquitto_ctrl/broker.c index 881dbef1..a8f9ed42 100644 --- a/apps/mosquitto_ctrl/broker.c +++ b/apps/mosquitto_ctrl/broker.c @@ -31,6 +31,7 @@ void broker__print_usage(void) printf("=======================\n"); printf("Get plugin information: getPluginInfo\n"); + printf("List listeners : listListeners\n"); } /* ################################################################ @@ -39,6 +40,53 @@ void broker__print_usage(void) * # * ################################################################ */ +static void print_listeners(cJSON *j_response) +{ + cJSON *j_data, *j_listeners, *j_listener, *jtmp; + int i=1; + + j_data = cJSON_GetObjectItem(j_response, "data"); + if(j_data == NULL || !cJSON_IsObject(j_data)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + j_listeners = cJSON_GetObjectItem(j_data, "listeners"); + if(j_listeners == NULL || !cJSON_IsArray(j_listeners)){ + fprintf(stderr, "Error: Invalid response from server.\n"); + return; + } + + cJSON_ArrayForEach(j_listener, j_listeners){ + printf("Listener %d:\n", i); + + jtmp = cJSON_GetObjectItem(j_listener, "port"); + if(jtmp && cJSON_IsNumber(jtmp)){ + printf(" Port: %d\n", jtmp->valueint); + } + + jtmp = cJSON_GetObjectItem(j_listener, "protocol"); + if(jtmp && cJSON_IsString(jtmp)){ + printf(" Protocol: %s\n", jtmp->valuestring); + } + + jtmp = cJSON_GetObjectItem(j_listener, "socket-path"); + if(jtmp && cJSON_IsString(jtmp)){ + printf(" Socket path: %s\n", jtmp->valuestring); + } + + jtmp = cJSON_GetObjectItem(j_listener, "bind-address"); + if(jtmp && cJSON_IsString(jtmp)){ + printf(" Bind address: %s\n", jtmp->valuestring); + } + + jtmp = cJSON_GetObjectItem(j_listener, "tls"); + printf(" TLS: %s\n", jtmp && cJSON_IsBool(jtmp) && cJSON_IsTrue(jtmp)?"true":"false"); + printf("\n"); + } +} + + static void print_plugin_info(cJSON *j_response) { cJSON *j_data, *j_plugins, *j_plugin, *jtmp, *j_eps; @@ -131,6 +179,8 @@ static void broker__payload_callback(struct mosq_ctrl *ctrl, long payloadlen, co }else{ if(!strcasecmp(j_command->valuestring, "getPluginInfo")){ print_plugin_info(j_response); + }else if(!strcasecmp(j_command->valuestring, "listListeners")){ + print_listeners(j_response); }else{ /* fprintf(stderr, "%s: Success\n", j_command->valuestring); */ } @@ -152,6 +202,20 @@ static int broker__get_plugin_info(int argc, char *argv[], cJSON *j_command) return MOSQ_ERR_SUCCESS; } +static int broker__list_listeners(int argc, char *argv[], cJSON *j_command) +{ + UNUSED(argc); + UNUSED(argv); + + if(cJSON_AddStringToObject(j_command, "command", "listListeners") == NULL + ){ + + return MOSQ_ERR_NOMEM; + } + + return MOSQ_ERR_SUCCESS; +} + /* ################################################################ * # * # Main @@ -195,6 +259,8 @@ int broker__main(int argc, char *argv[], struct mosq_ctrl *ctrl) if(!strcasecmp(argv[0], "getPluginInfo")){ rc = broker__get_plugin_info(argc-1, &argv[1], j_command); + }else if(!strcasecmp(argv[0], "listListeners")){ + rc = broker__list_listeners(argc-1, &argv[1], j_command); }else{ fprintf(stderr, "Command '%s' not recognised.\n", argv[0]); diff --git a/src/broker_control.c b/src/broker_control.c index ea0d42fa..193fa04a 100644 --- a/src/broker_control.c +++ b/src/broker_control.c @@ -112,7 +112,7 @@ static int add_plugin_info(cJSON *j_plugins, mosquitto_plugin_id_t *pid) } cJSON_AddItemToArray(j_eps, j_ep); } - + cJSON_AddItemToArray(j_plugins, j_plugin); return MOSQ_ERR_SUCCESS; } @@ -173,6 +173,101 @@ internal_error: } +static int add_listener(cJSON *j_listeners, struct mosquitto__listener *listener) +{ + cJSON *j_listener; + const char *protocol = NULL; + + j_listener = cJSON_CreateObject(); + if(j_listener == NULL){ + return MOSQ_ERR_NOMEM; + } + cJSON_AddItemToArray(j_listeners, j_listener); + + if(listener->protocol == mp_mqtt){ + protocol = "mqtt"; + }else if(listener->protocol == mp_websockets){ + protocol = "mqtt+websockets"; + } + + if(cJSON_AddNumberToObject(j_listener, "port", listener->port) == NULL + || (protocol && cJSON_AddStringToObject(j_listener, "protocol", protocol) == NULL) + || (listener->host && cJSON_AddStringToObject(j_listener, "bind-address", listener->host) == NULL) +#ifdef WITH_UNIX_SOCKETS + || (listener->unix_socket_path && cJSON_AddStringToObject(j_listener, "socket-path", listener->unix_socket_path) == NULL) +#endif + ){ + + return MOSQ_ERR_NOMEM; + } + +#ifdef WITH_TLS + if(cJSON_AddBoolToObject(j_listener, "tls", listener->ssl_ctx != NULL) == NULL + ){ + + return MOSQ_ERR_NOMEM; + } +#endif + + return MOSQ_ERR_SUCCESS; +} + + +static int broker__process_list_listeners(cJSON *j_responses, struct mosquitto *context, cJSON *command, char *correlation_data) +{ + cJSON *tree, *jtmp, *j_data, *j_listeners; + const char *admin_clientid, *admin_username; + int i; + + UNUSED(command); + + tree = cJSON_CreateObject(); + if(tree == NULL){ + broker__command_reply(j_responses, context, "listListeners", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; + } + + admin_clientid = mosquitto_client_id(context); + admin_username = mosquitto_client_username(context); + mosquitto_log_printf(MOSQ_LOG_INFO, "Broker: %s/%s | listListeners", + admin_clientid, admin_username); + + if(cJSON_AddStringToObject(tree, "command", "listListeners") == NULL + || ((j_data = cJSON_AddObjectToObject(tree, "data")) == NULL) + + ){ + goto internal_error; + } + + j_listeners = cJSON_AddArrayToObject(j_data, "listeners"); + if(j_listeners == NULL){ + goto internal_error; + } + + for(i=0; ilistener_count; i++){ + if(add_listener(j_listeners, &db.config->listeners[i])){ + goto internal_error; + } + } + + cJSON_AddItemToArray(j_responses, tree); + + if(correlation_data){ + jtmp = cJSON_AddStringToObject(tree, "correlationData", correlation_data); + if(jtmp == NULL){ + goto internal_error; + } + } + + return MOSQ_ERR_SUCCESS; + +internal_error: + cJSON_Delete(tree); + broker__command_reply(j_responses, context, "listListeners", "Internal error", correlation_data); + return MOSQ_ERR_NOMEM; +} + + static int broker_control_callback(int event, void *event_data, void *userdata) { struct mosquitto_evt_control *ed = event_data; @@ -273,6 +368,8 @@ static int broker__handle_control(cJSON *j_responses, struct mosquitto *context, if(!strcasecmp(command, "getPluginInfo")){ rc = broker__process_get_plugin_info(j_responses, context, aiter, correlation_data); + }else if(!strcasecmp(command, "listListeners")){ + rc = broker__process_list_listeners(j_responses, context, aiter, correlation_data); /* Unknown */ }else{ diff --git a/test/broker/17-control-list-listeners.py b/test/broker/17-control-list-listeners.py new file mode 100755 index 00000000..7f3f6fc3 --- /dev/null +++ b/test/broker/17-control-list-listeners.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python3 + +# Test $CONTROL/broker/v1 listListeners + +from mosq_test_helper import * +import json +import shutil + +def write_config(filename, ports): + with open(filename, 'w') as f: + f.write("enable_control_api true\n") + f.write("allow_anonymous true\n") + f.write("listener %d\n" % (ports[0])) + f.write("protocol mqtt\n") + f.write("listener %d\n" % (ports[1])) + f.write("protocol websockets\n") + f.write("listener %d\n" % (ports[2])) + f.write("protocol mqtt\n") + f.write("certfile ../ssl/server.crt\n") + f.write("keyfile ../ssl/server.key\n") + f.write("listener %d\n" % (ports[3])) + f.write("protocol websockets\n") + f.write("certfile ../ssl/server.crt\n") + f.write("keyfile ../ssl/server.key\n") + f.write("listener 0 17-list-listeners-mqtt.sock\n") + f.write("protocol mqtt\n") + f.write("listener 0 17-list-listeners-websockets.sock\n") + f.write("protocol websockets\n") + +def command_check(sock, command_payload, expected_response): + command_packet = mosq_test.gen_publish(topic="$CONTROL/broker/v1", qos=0, payload=json.dumps(command_payload)) + sock.send(command_packet) + response = json.loads(mosq_test.read_publish(sock)) + if response != expected_response: + print(expected_response) + print(response) + raise ValueError(response) + +def invalid_command_check(sock, command_payload, cmd_name, error_msg): + command_packet = mosq_test.gen_publish(topic="$CONTROL/broker/v1", qos=0, payload=command_payload) + sock.send(command_packet) + response = json.loads(mosq_test.read_publish(sock)) + expected_response = {'responses': [{'command': cmd_name, 'error': error_msg}]} + if response != expected_response: + print(expected_response) + print(response) + raise ValueError(response) + + + +ports = mosq_test.get_port(4) +conf_file = os.path.basename(__file__).replace('.py', '.conf') +write_config(conf_file, ports) + +cmd_success = {"commands":[{"command": "listListeners", "correlationData": "m3CtYVnySLCOwnHzITSeowvgla0InV4G"}]} + +response_success = {'responses': [{'command': 'listListeners', "correlationData": "m3CtYVnySLCOwnHzITSeowvgla0InV4G", 'data':{ + 'listeners':[ + { + 'port': ports[0], + 'protocol': 'mqtt', + 'tls': False + },{ + 'port': ports[1], + 'protocol': 'mqtt+websockets', + 'tls': False + },{ + 'port': ports[2], + 'protocol': 'mqtt', + 'tls': True + },{ + 'port': ports[3], + 'protocol': 'mqtt+websockets', + 'tls': True + },{ + 'port': 0, + 'protocol': 'mqtt', + 'socket-path': '17-list-listeners-mqtt.sock', + 'tls': False + },{ + 'port': 0, + 'protocol': 'mqtt+websockets', + 'socket-path': '17-list-listeners-websockets.sock', + 'tls': False + } + ]}}]} + +rc = 1 +connect_packet = mosq_test.gen_connect("17-list-listeners") +connack_packet = mosq_test.gen_connack(rc=0) + +mid = 2 +subscribe_packet = mosq_test.gen_subscribe(mid, "$CONTROL/broker/#", 0) +suback_packet = mosq_test.gen_suback(mid, 0) + +broker = mosq_test.start_broker(filename=os.path.basename(__file__), use_conf=True, port=ports[0]) + +try: + sock = mosq_test.do_client_connect(connect_packet, connack_packet, port=ports[0]) + mosq_test.do_send_receive(sock, subscribe_packet, suback_packet, "suback") + + invalid_command_check(sock, "not valid json", "Unknown command", "Payload not valid JSON") + invalid_command_check(sock, "{}", "Unknown command", "Invalid/missing commands") + + cmd = {"commands":["command"]} + invalid_command_check(sock, json.dumps(cmd), "Unknown command", "Command not an object") + + cmd = {"commands":[{}]} + invalid_command_check(sock, json.dumps(cmd), "Unknown command", "Missing command") + + cmd = {"commands":[{"command": "unknown command"}]} + invalid_command_check(sock, json.dumps(cmd), "unknown command", "Unknown command") + + cmd = {"commands":[{"command": "listListeners", "correlationData": True}]} + invalid_command_check(sock, json.dumps(cmd), "listListeners", "Invalid correlationData data type.") + + command_check(sock, cmd_success, response_success) + mosq_test.do_ping(sock) + + rc = 0 + + sock.close() +except mosq_test.TestError: + pass +finally: + os.remove(conf_file) + for f in ["17-list-listeners-mqtt.sock", "17-list-listeners-websockets.sock"]: + try: + os.remove(f) + except FileNotFoundError: + pass + broker.terminate() + broker.wait() + (stdo, stde) = broker.communicate() + if rc: + print(stde.decode('utf-8')) + + +exit(rc) diff --git a/test/broker/Makefile b/test/broker/Makefile index da3d1e3c..43a1b3ee 100644 --- a/test/broker/Makefile +++ b/test/broker/Makefile @@ -17,7 +17,7 @@ test-compile : ptest : test-compile msg_sequence_test ./test.py -test : test-compile msg_sequence_test 01 02 03 04 05 06 07 08 09 10 11 12 13 14 16 +test : test-compile msg_sequence_test 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 msg_sequence_test: ./msg_sequence_test.py @@ -285,3 +285,6 @@ endif ./16-cmd-args.py ./16-config-includedir.py ./16-config-parse-errors.py + +17 : + ./17-control-list-listeners.py diff --git a/test/broker/test.py b/test/broker/test.py index b572917d..8dd8cfff 100755 --- a/test/broker/test.py +++ b/test/broker/test.py @@ -245,6 +245,8 @@ tests = [ (1, './16-cmd-args.py'), (1, './16-config-includedir.py'), (1, './16-config-parse-errors.py'), + + (4, './17-control-list-listeners.py'), ] ptest.run_tests(tests)