From 31e8cbaf507145ce549023cd8e0acc90aad19395 Mon Sep 17 00:00:00 2001 From: Frederik Kriewitz Date: Wed, 13 Mar 2024 17:32:56 +0000 Subject: [PATCH] add /status page --- Makefile | 13 +++ README | 18 +++- mptsd.c | 7 +- web_pages.c | 299 +++++++++++++++++++++++++++++++++++++++++++++++++++ web_pages.h | 1 + web_server.c | 2 + 6 files changed, 337 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 67ed2b8..d6e1a04 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,15 @@ else CFLAGS += -DGIT_VER=\"$(VERSION)\" endif +ifndef WITH_JSON +$(info enabeling JSON support) +$(info if you don't want JSON support run) +$(info make WITH_JSON=0) +WITH_JSON = 1 +endif + +CFLAGS += -DWITH_JSON=$(WITH_JSON) + RM = /bin/rm -f Q = @ @@ -23,6 +32,10 @@ else LIBS = -lpthread -lm -lrt endif +ifeq "$(WITH_JSON)" "1" +LIBS += -ljson-c +endif + FUNCS_DIR = libfuncs FUNCS_LIB = $(FUNCS_DIR)/libfuncs.a diff --git a/README b/README index 069b157..2948b1c 100644 --- a/README +++ b/README @@ -7,8 +7,9 @@ is used in production in couple of small DVB-C networks. Installation ============ -mptsd do not depend on any external libraries. There are two source code +mptsd has no hard dependencies on any external libraries. There are two source code dependancies that come with mptsd - libfuncs and libtsfuncs. +The JSON-C library is used for some optional functionality but can be disabled. Make sure your kernel has CONFIG_HIGH_RES_TIMERS enabled. Otherwise sleep timeout probably won't be able to calibrate itself and mptsd will not work. @@ -43,6 +44,14 @@ mptsd was tested and found to be working ok with Dektec DTE-3114 & HiDes UT-100C To enable RTP output instead of plain UDP for network streams, specify the SSRC identifier via the -s flag (must be != 0). +Integrated Webserver +========= +When mptsd is started with the -p option, it will start a webserver at the +specified port. + +If JSON support is enabled, sending a GET request to /status.json will return a +JSON file containing various details about the configuration and statistics. + Development =========== The development is tracked using git. The repository is hosted at github @@ -54,11 +63,18 @@ to get it, run the following command: OR git submodule update --recursive --remote // if you like to checkout HEAD submodules. + Compiling ========= +mptsd can use the JSON-C library. +On debian based systems it can be installed using +`sudo apt install libjson-c-dev` + After cloning the git repository as described in Development section just run `make`. +If you don't want JSON support run `make WITH_JSON=0` + Releases ======== Official releases can be downloaded from tsdecrypt home page which is: diff --git a/mptsd.c b/mptsd.c index 7b7586d..67cf035 100644 --- a/mptsd.c +++ b/mptsd.c @@ -34,10 +34,13 @@ #define PROGRAM_NAME "ux-mptsd" +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + #ifdef BUILD_ID -const char *program_id = PROGRAM_NAME " " GIT_VER " build " BUILD_ID; +const char *program_id = PROGRAM_NAME " " GIT_VER " build " BUILD_ID " WITH_JSON=" STR(WITH_JSON); #else -const char *program_id = PROGRAM_NAME " " GIT_VER; +const char *program_id = PROGRAM_NAME " " GIT_VER " WITH_JSON=" WITH_JSON; #endif char *server_sig = PROGRAM_NAME; diff --git a/web_pages.c b/web_pages.c index e0736c0..3ab1c46 100644 --- a/web_pages.c +++ b/web_pages.c @@ -15,6 +15,10 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. */ +#if WITH_JSON == 1 +#include +#endif + #include #include #include @@ -31,6 +35,267 @@ extern CONFIG *config; +#if WITH_JSON == 1 +json_object *json_object_new_string_safe(char *str) { + if (str) { + return json_object_new_string(str); + } else { + return json_object_new_null(); + } +} + +json_object *json_add_traffic_stats_entry(TRAFFIC_STATS_ENTRY *entry) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + + jobtemp = json_object_new_double(entry->kpbs); + json_object_object_add(job, "kpbs", jobtemp); + + jobtemp = json_object_new_double(entry->padding); + json_object_object_add(job, "padding", jobtemp); + + jobtemp = json_object_new_uint64(entry->traffic); + json_object_object_add(job, "traffic", jobtemp); + + return job; +} + +json_object *json_add_traffic_stats(TRAFFIC_STATS *stats) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + + jobtemp = json_add_traffic_stats_entry(&stats->last); + json_object_object_add(job, "last", jobtemp); + + jobtemp = json_add_traffic_stats_entry(&stats->max); + json_object_object_add(job, "max", jobtemp); + + jobtemp = json_add_traffic_stats_entry(&stats->min); + json_object_object_add(job, "min", jobtemp); + + return job; +} + +json_object *json_add_rtp_stats(RTP_STATS *rtp_stats) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + + jobtemp = json_object_new_uint64(rtp_stats->ssrc); + json_object_object_add(job, "ssrc", jobtemp); + + jobtemp = json_object_new_int(rtp_stats->last_sequence_number); + json_object_object_add(job, "last_sequence_number", jobtemp); + + jobtemp = json_object_new_uint64(rtp_stats->packets_lost); + json_object_object_add(job, "packets_lost", jobtemp); + + jobtemp = json_object_new_uint64(rtp_stats->packets_received); + json_object_object_add(job, "packets_received", jobtemp); + + return job; +} + +json_object *json_add_channel(CHANNEL *c) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + json_object *jobtemp2; + + jobtemp = json_object_new_int(c->index); + json_object_object_add(job, "index", jobtemp); + + jobtemp = json_object_new_string_safe(c->id); + json_object_object_add(job, "id", jobtemp); + + jobtemp = json_object_new_string_safe(c->name); + json_object_object_add(job, "name", jobtemp); + + jobtemp = json_object_new_int(c->service_id); + json_object_object_add(job, "service_id", jobtemp); + + jobtemp = json_object_new_int(c->base_pid); + json_object_object_add(job, "base_pid", jobtemp); + + jobtemp = json_object_new_int(c->pmt_pid); + json_object_object_add(job, "pmt_pid", jobtemp); + + jobtemp = json_object_new_int(c->radio); + json_object_object_add(job, "radio", jobtemp); + + jobtemp = json_object_new_int(c->lcn); + json_object_object_add(job, "lcn", jobtemp); + + jobtemp = json_object_new_int(c->lcn_visible); + json_object_object_add(job, "lcn_visible", jobtemp); + + jobtemp = json_object_new_string_safe(c->source); + json_object_object_add(job, "source", jobtemp); + + jobtemp = json_object_new_int(c->curr_src); + json_object_object_add(job, "curr_src", jobtemp); + + jobtemp2 = json_object_new_array(); + for (uint8_t i = 0; i < c->num_src; i++) { + jobtemp = json_object_new_string_safe(c->sources[i]); + json_object_array_add(jobtemp2, jobtemp); + } + json_object_object_add(job, "sources", jobtemp2); + + return job; +} + +json_object *json_add_channels(LIST *channels) { + json_object *jarray; + json_object *jobtemp; + LNODE *lc, *lctmp; + jarray = json_object_new_array(); + list_for_each(channels, lc, lctmp) { + CHANNEL *c = lc->data; + jobtemp = json_add_channel(c); + json_object_array_add(jarray, jobtemp); + } + return jarray; +} + +json_object *json_add_input(INPUT *r) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + + jobtemp = json_object_new_string_safe(r->name); + json_object_object_add(job, "name", jobtemp); + + jobtemp = json_object_new_uint64(r->traffic); + json_object_object_add(job, "traffic", jobtemp); + + jobtemp = json_add_traffic_stats(&r->traffic_stats); + json_object_object_add(job, "traffic_stats", jobtemp); + + jobtemp = json_object_new_int(r->outputed_packets); + json_object_object_add(job, "outputed_packets", jobtemp); + + jobtemp = json_add_rtp_stats(&r->rtp_stats); + json_object_object_add(job, "rtp_stats", jobtemp); + + jobtemp = json_add_channel(r->channel); + json_object_object_add(job, "channel", jobtemp); + + return job; +} + +json_object *json_add_inputs(LIST *inputs) { + json_object *jarray; + json_object *jobtemp; + LNODE *lc, *lctmp; + jarray = json_object_new_array(); + list_for_each(inputs, lc, lctmp) { + INPUT *r = lc->data; + jobtemp = json_add_input(r); + json_object_array_add(jarray, jobtemp); + } + return jarray; +} + +json_object *json_add_output(OUTPUT *o) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + char in_addr_str[INET_ADDRSTRLEN]; + + inet_ntop(AF_INET, &o->out_host, in_addr_str, INET_ADDRSTRLEN); + jobtemp = json_object_new_string_safe(in_addr_str); + json_object_object_add(job, "out_host", jobtemp); + + jobtemp = json_object_new_int(o->out_port); + json_object_object_add(job, "out_port", jobtemp); + + jobtemp = json_object_new_uint64(o->traffic); + json_object_object_add(job, "traffic", jobtemp); + + jobtemp = json_object_new_double(o->output_bitrate); + json_object_object_add(job, "output_bitrate", jobtemp); + + jobtemp = json_add_traffic_stats(&o->traffic_stats); + json_object_object_add(job, "traffic_stats", jobtemp); + + jobtemp = json_object_new_uint64(o->rtp_ssrc); + json_object_object_add(job, "rtp_ssrc", jobtemp); + + jobtemp = json_object_new_int(o->rtp_sequence_number); + json_object_object_add(job, "rtp_sequence_number", jobtemp); + + return job; +} + +json_object *json_add_global(CONFIG *c) { + json_object *job = json_object_new_object(); + json_object *jobtemp; + char in_addr_str[INET_ADDRSTRLEN]; + + jobtemp = json_object_new_string_safe(c->ident); + json_object_object_add(job, "ident", jobtemp); + + jobtemp = json_object_new_int(c->network_id); + json_object_object_add(job, "network_id", jobtemp); + + jobtemp = json_object_new_string_safe(c->network_name); + json_object_object_add(job, "network_name", jobtemp); + + jobtemp = json_object_new_double(c->output_bitrate); + json_object_object_add(job, "output_bitrate", jobtemp); + + inet_ntop(AF_INET, &c->output_intf, in_addr_str, INET_ADDRSTRLEN); + jobtemp = json_object_new_string_safe(in_addr_str); + json_object_object_add(job, "output_intf", jobtemp); + + jobtemp = json_object_new_int64(c->output_packets_per_sec); + json_object_object_add(job, "output_packets_per_sec", jobtemp); + + jobtemp = json_object_new_int64(c->output_tmout); + json_object_object_add(job, "output_tmout", jobtemp); + + jobtemp = json_object_new_int(c->pcr_mode); + json_object_object_add(job, "pcr_mode", jobtemp); + + jobtemp = json_object_new_int(c->use_lcn); + json_object_object_add(job, "use_lcn", jobtemp); + + jobtemp = json_object_new_string_safe(c->provider_name); + json_object_object_add(job, "provider_name", jobtemp); + + jobtemp = json_object_new_int(c->transport_stream_id); + json_object_object_add(job, "transport_stream_id", jobtemp); + + // timeouts + json_object *job2 = json_object_new_object(); + + jobtemp = json_object_new_int(c->timeouts.pat); + json_object_object_add(job2, "pat", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.pmt); + json_object_object_add(job2, "pmt", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.nit); + json_object_object_add(job2, "nit", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.sdt); + json_object_object_add(job2, "sdt", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.eit); + json_object_object_add(job2, "eit", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.tdt); + json_object_object_add(job2, "tdt", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.tot); + json_object_object_add(job2, "tot", jobtemp); + + jobtemp = json_object_new_int(c->timeouts.stats); + json_object_object_add(job2, "stats", jobtemp); + + json_object_object_add(job, "timeouts", job2); + + return job; +} +#endif + void cmd_index(int clientsock) { send_200_ok(clientsock); send_header_textplain(clientsock); @@ -42,3 +307,37 @@ void cmd_reconnect(int clientsock) { send_header_textplain(clientsock); fdputsf(clientsock, "\nReconnecting %d inputs.\n", config->inputs->items); } + +#if WITH_JSON == 1 +void cmd_status_json(int clientsock) { + send_200_ok(clientsock); + send_header_applicationjson(clientsock); + + json_object *job = json_object_new_object(); + json_object *jobtemp; + + jobtemp = json_add_global(config); + json_object_object_add(job, "global", jobtemp); + + jobtemp = json_add_channels(config->channels); + json_object_object_add(job, "channels", jobtemp); + + jobtemp = json_add_inputs(config->inputs); + json_object_object_add(job, "inputs", jobtemp); + + jobtemp = json_add_output(config->output); + json_object_object_add(job, "output", jobtemp); + + size_t length; + const char *jbuf = json_object_to_json_string_length( + job, JSON_C_TO_STRING_PRETTY | JSON_C_TO_STRING_SPACED, &length); + send_header_content_length(clientsock, length); + fdputs(clientsock, "\n"); + fdwrite(clientsock, jbuf, length); + json_object_put(job); // delete job (free memory) +} +#else // WTIH_JSON != 1 +void cmd_status_json(int clientsock) { + send_400_bad_request(clientsock, "not supported (WTIH_JSON=0)"); +} +#endif diff --git a/web_pages.h b/web_pages.h index ab92e70..40c7dc3 100644 --- a/web_pages.h +++ b/web_pages.h @@ -20,5 +20,6 @@ void cmd_index(int clientsock); void cmd_reconnect(int clientsock); +void cmd_status_json(int clientsock); #endif diff --git a/web_server.c b/web_server.c index ee41f68..0e484d2 100644 --- a/web_server.c +++ b/web_server.c @@ -128,6 +128,8 @@ void *process_web_request(void *in_req) { if (strlen(path) == 0) { cmd_index(clientsock); + } else if (strstr(path,"status.json")==path) { + cmd_status_json(clientsock); } else if (strstr(path,"reconnect")==path) { cmd_reconnect(clientsock); } else {