From 9b4bdad0f23f47d28d3cabe1771d8bbc7590f86f Mon Sep 17 00:00:00 2001 From: Daniel-Constantin Mierla Date: Fri, 9 Jan 2015 17:24:49 +0100 Subject: [PATCH] janssonrpc-c: new jsonrpc client module using jansson library - alternative to jsonrpc-c module, refactored to use jansson and libevent libraries - rescued from branch mgw/json --- modules/janssonrpc-c/Makefile | 26 + modules/janssonrpc-c/README | 355 ++++++++ modules/janssonrpc-c/doc/Makefile | 4 + modules/janssonrpc-c/doc/janssonrpc-c.xml | 36 + .../janssonrpc-c/doc/janssonrpc-c_admin.xml | 408 +++++++++ modules/janssonrpc-c/janssonrpc.h | 137 +++ modules/janssonrpc-c/janssonrpc_connect.c | 317 +++++++ modules/janssonrpc-c/janssonrpc_connect.h | 49 + modules/janssonrpc-c/janssonrpc_funcs.c | 265 ++++++ modules/janssonrpc-c/janssonrpc_funcs.h | 62 ++ modules/janssonrpc-c/janssonrpc_global.c | 29 + modules/janssonrpc-c/janssonrpc_io.c | 854 ++++++++++++++++++ modules/janssonrpc-c/janssonrpc_io.h | 90 ++ modules/janssonrpc-c/janssonrpc_mod.c | 362 ++++++++ modules/janssonrpc-c/janssonrpc_request.c | 340 +++++++ modules/janssonrpc-c/janssonrpc_request.h | 65 ++ modules/janssonrpc-c/janssonrpc_server.c | 611 +++++++++++++ modules/janssonrpc-c/janssonrpc_server.h | 130 +++ modules/janssonrpc-c/janssonrpc_srv.c | 315 +++++++ modules/janssonrpc-c/janssonrpc_srv.h | 53 ++ modules/janssonrpc-c/netstring.c | 323 +++++++ modules/janssonrpc-c/netstring.h | 62 ++ modules/janssonrpc-c/test/mem-test.sh | 4 + modules/janssonrpc-c/test/run-tests.sh | 4 + modules/janssonrpc-c/test/test.cfg | 94 ++ modules/janssonrpc-c/unit_tests/Makefile | 11 + modules/janssonrpc-c/unit_tests/netstring.c | 351 +++++++ .../unit_tests/seatest/license.txt | 19 + .../janssonrpc-c/unit_tests/seatest/seatest.c | 248 +++++ .../janssonrpc-c/unit_tests/seatest/seatest.h | 74 ++ modules/janssonrpc-c/unit_tests/test.h | 48 + 31 files changed, 5746 insertions(+) create mode 100644 modules/janssonrpc-c/Makefile create mode 100644 modules/janssonrpc-c/README create mode 100644 modules/janssonrpc-c/doc/Makefile create mode 100644 modules/janssonrpc-c/doc/janssonrpc-c.xml create mode 100644 modules/janssonrpc-c/doc/janssonrpc-c_admin.xml create mode 100644 modules/janssonrpc-c/janssonrpc.h create mode 100644 modules/janssonrpc-c/janssonrpc_connect.c create mode 100644 modules/janssonrpc-c/janssonrpc_connect.h create mode 100644 modules/janssonrpc-c/janssonrpc_funcs.c create mode 100644 modules/janssonrpc-c/janssonrpc_funcs.h create mode 100644 modules/janssonrpc-c/janssonrpc_global.c create mode 100644 modules/janssonrpc-c/janssonrpc_io.c create mode 100644 modules/janssonrpc-c/janssonrpc_io.h create mode 100644 modules/janssonrpc-c/janssonrpc_mod.c create mode 100644 modules/janssonrpc-c/janssonrpc_request.c create mode 100644 modules/janssonrpc-c/janssonrpc_request.h create mode 100644 modules/janssonrpc-c/janssonrpc_server.c create mode 100644 modules/janssonrpc-c/janssonrpc_server.h create mode 100644 modules/janssonrpc-c/janssonrpc_srv.c create mode 100644 modules/janssonrpc-c/janssonrpc_srv.h create mode 100644 modules/janssonrpc-c/netstring.c create mode 100644 modules/janssonrpc-c/netstring.h create mode 100755 modules/janssonrpc-c/test/mem-test.sh create mode 100755 modules/janssonrpc-c/test/run-tests.sh create mode 100644 modules/janssonrpc-c/test/test.cfg create mode 100644 modules/janssonrpc-c/unit_tests/Makefile create mode 100644 modules/janssonrpc-c/unit_tests/netstring.c create mode 100644 modules/janssonrpc-c/unit_tests/seatest/license.txt create mode 100644 modules/janssonrpc-c/unit_tests/seatest/seatest.c create mode 100644 modules/janssonrpc-c/unit_tests/seatest/seatest.h create mode 100644 modules/janssonrpc-c/unit_tests/test.h diff --git a/modules/janssonrpc-c/Makefile b/modules/janssonrpc-c/Makefile new file mode 100644 index 00000000000..21e4d4c08c9 --- /dev/null +++ b/modules/janssonrpc-c/Makefile @@ -0,0 +1,26 @@ +# +# janssonrpc-c module makefile +# +# +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=janssonrpc-c.so +LIBS=-lm + +BUILDER = $(shell which pkg-config) +ifeq ($(BUILDER),) + DEFS+=-I$(LOCALBASE)/include + LIBS+=-L$(SYSBASE)/include/lib -L$(LOCALBASE)/lib -levent -ljansson +else + DEFS+= $(shell pkg-config --cflags jansson) + LIBS+= $(shell pkg-config --libs jansson) + DEFS+= $(shell pkg-config --cflags libevent) + LIBS+= $(shell pkg-config --libs libevent) +endif + +DEFS+=-DOPENSER_MOD_INTERFACE + +SERLIBPATH=../../lib +include ../../Makefile.modules diff --git a/modules/janssonrpc-c/README b/modules/janssonrpc-c/README new file mode 100644 index 00000000000..d585e055670 --- /dev/null +++ b/modules/janssonrpc-c/README @@ -0,0 +1,355 @@ +janssonrpc-c Module + +Joe Hillenbrand + + + +Edited by + +Matthew Williams + + + + Copyright © 2013 Flowroute LLC (flowroute.com) + __________________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. min_srv_ttl (integer) + 3.2. result_pv (string) + 3.3. server (string) + 3.4. retry_codes (string) + + 4. Functions + + 4.1. janssonrpc_notification(conn, method, parameters) + 4.2. janssonrpc_request(conn, method, params[, options]]) + 4.3. Error Handling + + List of Examples + + 1.1. Set min_srv_ttl parameter + 1.2. Set result_pv parameter + 1.3. Set server parameter + 1.4. Set retry_codes parameter + 1.5. janssonrpc_notification usage + 1.6. janssonrpc_request usage + 1.7. route example with internal_error handling + +Chapter 1. Admin Guide + + Table of Contents + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. min_srv_ttl (integer) + 3.2. result_pv (string) + 3.3. server (string) + 3.4. retry_codes (string) + + 4. Functions + + 4.1. janssonrpc_notification(conn, method, parameters) + 4.2. janssonrpc_request(conn, method, params[, options]]) + 4.3. Error Handling + +1. Overview + + This module provides access to JSON-RPC 2.0 services (operating over + TCP/Netstrings) in accordance with + http://www.jsonrpc.org/specification. It uses JANSSON library for JSON + document management. + + It uses t_suspend() and t_continue() from the TM module. + + Note that after invoking an asyncronous operation, the processing will + continue later, in another application process. Therefore, do not rely + on variables stored in private memory, use shared memory if you want to + get values after the processing is resumend (e.g., $shv(...) or htable + $sht(...)). + +2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + +2.1. Kamailio Modules + + The following modules must be loaded before this module: + * jansson - jansson json handling. + * tm - transaction management. + +2.2. External Libraries or Applications + + The following libraries or applications must be installed before + running Kamailio with this module loaded: + * jansson (http://www.digip.org/jansson/), tested with: 2.2+ + * libevent 2.0.5+ (http://libevent.org/), tested with: 2.0.16 + +3. Parameters + + 3.1. min_srv_ttl (integer) + 3.2. result_pv (string) + 3.3. server (string) + 3.4. retry_codes (string) + +3.1. min_srv_ttl (integer) + + The minimum acceptable TTL in seconds for SRV DNS entries. This means + that TTLs from the DNS will be ignored if they are lower than this + value. It cannot be set lower than 1 second. + + Default is 5 seconds. + + Example 1.1. Set min_srv_ttl parameter +... +modparam("janssonrpc-c", "min_srv_ttl", 30) +... + + This will set any SRV TTL lower than 30 seconds to 30 seconds. + +3.2. result_pv (string) + + The PV spec where to store the result of a call to + janssonrpc_request(). It can be any writtable PV. + + Default value is "$var(jsrpc_result)". + + Example 1.2. Set result_pv parameter +... +modparam("janssonrpc-c", "result_pv", "$var(result)") +... + +3.3. server (string) + + The server providing the remote jsonrpc service. Format can be + "conn=example;addr=localhost;port=9999;priority=10;weight=20" or + "conn=bar;srv=_sip._tcp.example.net". + + * conn - name for a collection of servers (required). + * srv - DNS SRV domain name (optional). + * addr - host address (required, except when using srv). + * port - host port (required, except when using srv). + * priority - server are grouped by priority. Servers with higher + priority (lower number) are used first. Default is 0. (optional + when using addr, invalid otherwise). + * weight - functions the same as a DNS SRV weight. Requests are + distributed between servers of the same priority proportional to + their weight. Default is 1. (optional when using addr, invalid + otherwise). + + Example 1.3. Set server parameter +... +modparam("janssonrpc-c", "server", "conn=tests;srv=_test1._tcp.example.net"); +modparam("janssonrpc-c", "server", "conn=tests;srv=_test2._tcp.example.net"); +modparam("janssonrpc-c", "server", "conn=local;addr=localhost;port=8080;priority +=10;weight=10"); +modparam("janssonrpc-c", "server", "conn=user_db;addr=rpc.prod.exmaple.net;port= +5060;priority=10;weight=10"); +... + +3.4. retry_codes (string) + + A comma delimited list of error codes or error code ranges to + automatically schedule a request retry if received. + + This will only be used if there is no route specified for the request. + + An error code can be any interger, but is typically a negative number. + + An error code range is delimited by ".." . For example, + "-32099..-32000". + + Spaces are ignored. + + Example 1.4. Set retry_codes parameter +... +modparam("janssonrpc-s", "retry_codes", "-32603, -32000..-32099"); +... + +4. Functions + + 4.1. janssonrpc_notification(conn, method, parameters) + 4.2. janssonrpc_request(conn, method, params[, options]]) + 4.3. Error Handling + +4.1. janssonrpc_notification(conn, method, parameters) + + * conn - name for a collection of servers (required) + * method - jsonrpc method (required) + * params - jsonrpc request params (required) Use $null or empty + string to not send any parameters in the jsonrpc notification. + + Unlike janssonrpc_request (below), notifications do not receive a + response. Script processing continues in the usual fashion as soon as + the notification has been sent. + + If no servers can be reached, a message is sent to the logs. + + The 'method' and 'params' can be a static string or dynamic string + value with config variables. + + Example 1.5. janssonrpc_notification usage +... +janssonrpc_notification("user_db", "update_user", '{"id": 1234, "name": "Daniel" +}'); +... + +4.2. janssonrpc_request(conn, method, params[, options]]) + + The conn, method, params, and options can be a static string or a + dynamic string value with config variables. + + * conn - name for a collection of servers (required) + * method - jsonrpc method (required) + * params - jsonrpc request params (required) Use $null or empty + string to not send any parameters in the jsonrpc request. + * options + Options for the janssonrpc_request function. Format can be + "route=RESPONSE;retry=2;timeout=100". All these parameters are + optional. + + retry - number of times you retry a failed request. -1 means + retry forever. Default is 0. Request will be retried if they + either timeout or fail to send. Retries untilize exponential + back off between successive retries, up to 60 seconds. The + equation for time between retries is: + time = n^2 * timeout (for time < 60 seconds) + where n is the number of times a request has been tried. + + timeout - request timeout in milliseconds. Default is 500. + + route - resume script execution at this route. + + When a response is received, processing continues for the SIP request + in the route specified. + + If no route is specified, then any errors are logged and successes are + ignored. The function will also not interupt script execution. + + Since the SIP request handling is resumed in another process, the + config file execution is lost. Only shared variables ($shv, $avp, etc) + should be used for any value that will be needed when the script is + resumed. + + The result is stored in the pseudo-variable specified in the module + parameter 'result_pv'. This pseudo-variable is set after the response + is received. + + Example 1.6. janssonrpc_request usage +... +janssonrpc_request("user_db", "get_user", '{"id": 1234}', "route=RESPONSE;retry= +1"); + ... + +route[RESPONSE] { + xlog("Result received: $var(result)"); + ... +} +... + +4.3. Error Handling + + When a route is specified as part of the janssonrpc_request() function, + a JSON object is stored in the result pseudo-variable (see + 'Parameters'). + + The JSON object can be accessed using the jansson_get() function from + the jansson module and is of the form: +... +{ + "result" : {...}, + "error": {...}, + "internal_error": { "code": ..., "message": ..., "data": ... } +} +... + + 'result' or 'error' come from the server and should follow the JSONRPC + specification. Keep in mind a server's 'error' might not follow the + JSONRPC specification and not include a 'code' and/or 'message', so be + sure to check that they are there before trying to use them. + + When 'internal_error' is present, that means there was a problem with + sending or receiving the request. 'internal_error' contains a 'code' + which is an integer representing the type of error, a 'message' which + is the error in string form, and possibly 'data' which is usually the + failed request, which is optional and can be useful for debugging. + + Here are the possible values for internal error codes: + * -1: "Failed to build request" + * -5: "Failed to send request" + * -10: "JSON parse error" + * -11: "Failed to convert response to a pseudo-variable" + * -20: "Bad response from server" + * -50: "Request retry failed" + * -75: "Request dropped for server disconnection" + * -100: "Message timeout" + * -1000: "There is a bug". Please report these errors to the module + maintainers. + + Example 1.7. route example with internal_error handling +... +route { + janssonrpc_request("user_db", "get_user", '{"id": 1234}', "route=RESPONS +E;retry=1"); +} + +route[RESPONSE] { + if(jansson_get($var(jsrpc_result), "internal_error", "$var(internal)")) +{ + route(INTERNAL); + } else if(jansson_get($var(jsrpc_result), "error", "$var(error)")) { + route(ERROR); + } else if(jansson_get($var(jsrpc_result), "result", "$var(result)")) { + route(RESULT); + } + t_reply("200", "OK"); +} + +route[RESULT] { + xlog("result is $var(result)\n"); + xlog("success\n"); +} + +route[ERROR] { + xlog("There was an error\n"); + if(jansson_get($var(error), "code", "$var(c)")) { + xlog("code is $var(c)\n"); + } + + if(jansson_get($var(error), "message", "$var(r)")) { + xlog("error is $var(r)\n"); + } + + if(jansson_get($var(error), "data", "$var(d)")) { + xlog("data is $var(d)\n"); + } +} + +route[INTERNAL] { + xlog("There was an internal error\n"); + + jansson_get($var(internal), "code", "$var(c)"); + xlog("code is $var(c)\n"); + + jansson_get($var(internal), "message", "$var(r)"); + xlog("error is $var(r)\n"); + + if(jansson_get($var(internal), "data", "$var(d)")) { + xlog("request is $var(d)\n"); + } +} +... diff --git a/modules/janssonrpc-c/doc/Makefile b/modules/janssonrpc-c/doc/Makefile new file mode 100644 index 00000000000..d7e56948db9 --- /dev/null +++ b/modules/janssonrpc-c/doc/Makefile @@ -0,0 +1,4 @@ +docs = janssonrpc-c.xml + +docbook_dir = ../../../docbook +include $(docbook_dir)/Makefile.module diff --git a/modules/janssonrpc-c/doc/janssonrpc-c.xml b/modules/janssonrpc-c/doc/janssonrpc-c.xml new file mode 100644 index 00000000000..13db60603de --- /dev/null +++ b/modules/janssonrpc-c/doc/janssonrpc-c.xml @@ -0,0 +1,36 @@ + + + +%docentities; + +]> + + + + janssonrpc-c Module + sip-router.org + + + Joe + Hillenbrand + joe@flowroute.com + + + Matthew + Williams + matthew@flowroute.com + + + + 2013 + Flowroute LLC (flowroute.com) + + + + + + + diff --git a/modules/janssonrpc-c/doc/janssonrpc-c_admin.xml b/modules/janssonrpc-c/doc/janssonrpc-c_admin.xml new file mode 100644 index 00000000000..27f0f45e3d9 --- /dev/null +++ b/modules/janssonrpc-c/doc/janssonrpc-c_admin.xml @@ -0,0 +1,408 @@ + + + +%docentities; + +]> + + + + + &adminguide; + +
+ Overview + + This module provides access to JSON-RPC 2.0 services (operating over TCP/Netstrings) + in accordance with http://www.jsonrpc.org/specification. It uses JANSSON library for + JSON document management. + + + It uses t_suspend() and t_continue() from the TM module. + + + Note that after invoking an asyncronous operation, the processing + will continue later, in another application process. Therefore, do not + rely on variables stored in private memory, use shared memory if you + want to get values after the processing is resumend (e.g., $shv(...) + or htable $sht(...)). + +
+ +
+ Dependencies +
+ &kamailio; Modules + + The following modules must be loaded before this module: + + + jansson - jansson json handling. + + + tm - transaction management. + + + +
+
+ External Libraries or Applications + + The following libraries or applications must be installed before running + &kamailio; with this module loaded: + + + jansson (http://www.digip.org/jansson/), tested with: 2.2+ + + + + libevent 2.0.5+ (http://libevent.org/), tested with: 2.0.16 + + + + +
+
+
+ Parameters +
+ <varname>min_srv_ttl</varname> (integer) + + The minimum acceptable TTL in seconds for SRV DNS entries. This means that TTLs from the DNS will be ignored if they are lower than this value. It cannot be set lower than 1 second. + + + + Default is 5 seconds. + + + + Set <varname>min_srv_ttl</varname> parameter + +... +modparam("janssonrpc-c", "min_srv_ttl", 30) +... + + + This will set any SRV TTL lower than 30 seconds to 30 seconds. + + +
+
+ <varname>result_pv</varname> (string) + + The PV spec where to store the result of a call to janssonrpc_request(). It can be any writtable PV. + + + + Default value is $var(jsrpc_result). + + + + Set <varname>result_pv</varname> parameter + +... +modparam("janssonrpc-c", "result_pv", "$var(result)") +... + + +
+
+ <varname>server</varname> (string) + + The server providing the remote jsonrpc service. Format can be "conn=example;addr=localhost;port=9999;priority=10;weight=20" or "conn=bar;srv=_sip._tcp.example.net". + + + + + conn - name for a collection of servers (required). + + + srv - DNS SRV domain name (optional). + + + addr - host address (required, except when using srv). + + + port - host port (required, except when using srv). + + + priority - server are grouped by priority. Servers with higher priority (lower number) are used first. Default is 0. (optional when using addr, invalid otherwise). + + + weight - functions the same as a DNS SRV weight. Requests are distributed between servers of the same priority proportional to their weight. Default is 1. (optional when using addr, invalid otherwise). + + + + + Set <varname>server</varname> parameter + +... +modparam("janssonrpc-c", "server", "conn=tests;srv=_test1._tcp.example.net"); +modparam("janssonrpc-c", "server", "conn=tests;srv=_test2._tcp.example.net"); +modparam("janssonrpc-c", "server", "conn=local;addr=localhost;port=8080;priority=10;weight=10"); +modparam("janssonrpc-c", "server", "conn=user_db;addr=rpc.prod.exmaple.net;port=5060;priority=10;weight=10"); +... + + +
+
+ <varname>retry_codes</varname> (string) + + A comma delimited list of error codes or error code ranges to automatically schedule a request retry if received. + + + This will only be used if there is no route specified for the request. + + + An error code can be any interger, but is typically a negative number. + + + An error code range is delimited by ".." . For example, "-32099..-32000". + + + Spaces are ignored. + + + Set <varname>retry_codes</varname> parameter + +... +modparam("janssonrpc-s", "retry_codes", "-32603, -32000..-32099"); +... + + +
+
+
+ Functions +
+ + <function moreinfo="none">janssonrpc_notification(conn, method, parameters)</function> + + + + + conn - name for a collection of servers (required) + + + method - jsonrpc method (required) + + + params - jsonrpc request params (required) + Use $null or empty string to not send any parameters in the jsonrpc notification. + + + + + Unlike janssonrpc_request (below), notifications do not receive a response. + Script processing continues in the usual fashion as soon as the notification + has been sent. + + + If no servers can be reached, a message is sent to the logs. + + + The 'method' and 'params' can be a static string or dynamic string value with + config variables. + + + <function>janssonrpc_notification</function> usage + +... +janssonrpc_notification("user_db", "update_user", '{"id": 1234, "name": "Daniel"}'); +... + + +
+
+ + <function moreinfo="none">janssonrpc_request(conn, method, params[, options]])</function> + + + The conn, method, params, and options can be a static string or + a dynamic string value with config variables. + + + + + conn - name for a collection of servers (required) + + + method - jsonrpc method (required) + + + params - jsonrpc request params (required) + Use $null or empty string to not send any parameters in the jsonrpc request. + + + options + + Options for the janssonrpc_request function. Format can be "route=RESPONSE;retry=2;timeout=100". + All these parameters are optional. + + + + + retry - number of times you retry a failed request. + -1 means retry forever. Default is 0. + Request will be retried if they either timeout or fail to send. + Retries untilize exponential back off between successive retries, + up to 60 seconds. The equation for time between retries is: + + time = n^2 * timeout (for time < 60 seconds) + + where n is the number of times a request has been tried. + + + timeout - request timeout in milliseconds. Default is 500. + + + route - resume script execution at this route. + + + + + + + + When a response is received, processing continues for the SIP request in the route specified. + + + If no route is specified, then any errors are logged and successes are ignored. + The function will also not interupt script execution. + + + Since the SIP request handling is resumed in another process, + the config file execution is lost. Only shared variables ($shv, $avp, etc) + should be used for any value that will be needed when the script is resumed. + + + The result is stored in the pseudo-variable specified in the module parameter 'result_pv'. + This pseudo-variable is set after the response is received. + + + <function>janssonrpc_request</function> usage + +... +janssonrpc_request("user_db", "get_user", '{"id": 1234}', "route=RESPONSE;retry=1"); + ... + +route[RESPONSE] { + xlog("Result received: $var(result)"); + ... +} +... + + +
+
+ + Error Handling + + + When a route is specified as part of the janssonrpc_request() function, + a JSON object is stored in the result pseudo-variable (see 'Parameters'). + + + The JSON object can be accessed using the jansson_get() function + from the jansson module and is of the form: + +... +{ + "result" : {...}, + "error": {...}, + "internal_error": { "code": ..., "message": ..., "data": ... } +} +... + + + + 'result' or 'error' come from the server and should follow the + JSONRPC specification. Keep in mind a server's 'error' might + not follow the JSONRPC specification and not include a 'code' + and/or 'message', so be sure to check that they are there + before trying to use them. + + + When 'internal_error' is present, that means there was a + problem with sending or receiving the request. 'internal_error' + contains a 'code' which is an integer representing the type of + error, a 'message' which is the error in string form, and + possibly 'data' which is usually the failed request, which is + optional and can be useful for debugging. + + + Here are the possible values for internal error codes: + + -1: "Failed to build request" + -5: "Failed to send request" + -10: "JSON parse error" + -11: "Failed to convert response to a pseudo-variable" + -20: "Bad response from server" + -50: "Request retry failed" + -75: "Request dropped for server disconnection" + -100: "Message timeout" + -1000: "There is a bug". Please report these errors to the module maintainers. + + + + route example with internal_error handling + +... +route { + janssonrpc_request("user_db", "get_user", '{"id": 1234}', "route=RESPONSE;retry=1"); +} + +route[RESPONSE] { + if(jansson_get($var(jsrpc_result), "internal_error", "$var(internal)")) { + route(INTERNAL); + } else if(jansson_get($var(jsrpc_result), "error", "$var(error)")) { + route(ERROR); + } else if(jansson_get($var(jsrpc_result), "result", "$var(result)")) { + route(RESULT); + } + t_reply("200", "OK"); +} + +route[RESULT] { + xlog("result is $var(result)\n"); + xlog("success\n"); +} + +route[ERROR] { + xlog("There was an error\n"); + if(jansson_get($var(error), "code", "$var(c)")) { + xlog("code is $var(c)\n"); + } + + if(jansson_get($var(error), "message", "$var(r)")) { + xlog("error is $var(r)\n"); + } + + if(jansson_get($var(error), "data", "$var(d)")) { + xlog("data is $var(d)\n"); + } +} + +route[INTERNAL] { + xlog("There was an internal error\n"); + + jansson_get($var(internal), "code", "$var(c)"); + xlog("code is $var(c)\n"); + + jansson_get($var(internal), "message", "$var(r)"); + xlog("error is $var(r)\n"); + + if(jansson_get($var(internal), "data", "$var(d)")) { + xlog("request is $var(d)\n"); + } +} +... + + +
+
+
+ diff --git a/modules/janssonrpc-c/janssonrpc.h b/modules/janssonrpc-c/janssonrpc.h new file mode 100644 index 00000000000..cfdf5e39a00 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc.h @@ -0,0 +1,137 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_H_ +#define _JANSSONRPC_H_ + +#ifdef TEST + +#include "unit_tests/test.h" + +#else + +#include "../../sr_module.h" +#include "../jansson/jansson_utils.h" +jansson_to_val_f jsontoval; +pv_spec_t jsonrpc_result_pv; + +#endif + +#define JSONRPC_VERSION "2.0" + +#define JSONRPC_INTERNAL_SERVER_ERROR -32603 +#define JSONRPC_ERROR_NO_MEMORY -1; + +/* DEFAULTS */ +/* time (in ms) after which the error route is called */ +#define JSONRPC_DEFAULT_TIMEOUT 500 +#define JSONRPC_RESULT_STR "$var(jsrpc_result)" +#define JSONRPC_DEFAULT_RETRY 0 + +/* helpful macros */ +#define CHECK_MALLOC_VOID(p) if(!(p)) {ERR("Out of memory!\n"); return;} +#define CHECK_MALLOC(p) if(!(p)) {ERR("Out of memory!\n"); return JSONRPC_ERROR_NO_MEMORY;} +#define CHECK_MALLOC_NULL(p) if(!(p)) {ERR("Out of memory!\n"); return NULL;} +#define CHECK_MALLOC_GOTO(p,loc) if(!(p)) {ERR("Out of memory!\n"); goto loc;} +#define CHECK_AND_FREE(p) if((p)!=NULL) shm_free(p) +#define CHECK_AND_FREE_EV(p) \ + if((p) && event_initialized((p))) {\ + event_del(p); \ + event_free(p); \ + p = NULL; \ + } + +#define STR(ss) (ss).len, (ss).s +/* The lack of parens is intentional; this is intended to be used in a list + * of multiple arguments. + * + * Usage: printf("my str %.*s", STR(mystr)) + * + * Expands to: printf("my str %.*s", (mystr).len, (mystr).s) + * */ + + +#define PIT_MATCHES(param) \ + (pit->name.len == sizeof((param))-1 && \ + strncmp(pit->name.s, (param), sizeof((param))-1)==0) + +#include +#include + +typedef void (*libev_cb_f)(int sock, short flags, void *arg); + +typedef struct retry_range { + int start; + int end; + struct retry_range* next; +} retry_range_t; + +/* globals */ +int cmd_pipe; +extern const str null_str; +str result_pv_str; +retry_range_t* global_retry_ranges; + +static inline str pkg_strdup(str src) +{ + str res; + + if (!src.s) { + res.s = NULL; + res.len = 0; + } else if (!(res.s = (char *) pkg_malloc(src.len + 1))) { + res.len = 0; + } else { + strncpy(res.s, src.s, src.len); + res.s[src.len] = 0; + res.len = src.len; + } + return res; +} + +static inline str shm_strdup(str src) +{ + str res; + + if (!src.s) { + res.s = NULL; + res.len = 0; + } else if (!(res.s = (char *) shm_malloc(src.len + 1))) { + res.len = 0; + } else { + strncpy(res.s, src.s, src.len); + res.s[src.len] = 0; + res.len = src.len; + } + return res; +} + +static inline struct timeval ms_to_tv(unsigned int time) +{ + struct timeval tv = {0,0}; + tv.tv_sec = time/1000; + tv.tv_usec = ((time % 1000) * 1000); + return tv; +} + + +#endif /* _JSONRPC_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_connect.c b/modules/janssonrpc-c/janssonrpc_connect.c new file mode 100644 index 00000000000..bfe66c931bc --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_connect.c @@ -0,0 +1,317 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../route.h" +#include "../../route_struct.h" +#include "../../resolve.h" +#include "../../parser/parse_param.h" +#include "../../mem/mem.h" +#include "../../lvalue.h" + +#include "netstring.h" +#include "janssonrpc.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" +#include "janssonrpc_srv.h" +#include "janssonrpc_server.h" +#include "janssonrpc_connect.h" + +void wait_server_backoff(unsigned int timeout /* seconds */, + jsonrpc_server_t* server, bool delay); + +void bev_connect(jsonrpc_server_t* server); + +void bev_disconnect(struct bufferevent* bev) +{ + // close bufferevent + if(bev != NULL) { + short enabled = bufferevent_get_enabled(bev); + if(EV_READ & enabled) + bufferevent_disable(bev, EV_READ); + if(EV_WRITE & enabled) + bufferevent_disable(bev, EV_WRITE); + bufferevent_free(bev); + bev = NULL; + } +} + + +/* This will immediately close a server socket and clean out any pending + * requests that are waiting on that socket. + * */ +void force_disconnect(jsonrpc_server_t* server) +{ + if(!server) { + ERR("Trying to disconnect a NULL server.\n"); + return; + } + + // clear the netstring buffer when disconnecting + free_netstring(server->buffer); + server->buffer = NULL; + + server->status = JSONRPC_SERVER_DISCONNECTED; + + // close bufferevent + bev_disconnect(server->bev); + INFO("Disconnected from server %.*s:%d for conn %.*s.\n", + STR(server->addr), server->port, STR(server->conn)); + + + /* clean out requests */ + jsonrpc_request_t* req = NULL; + int key = 0; + for (key=0; key < JSONRPC_DEFAULT_HTABLE_SIZE; key++) { + for (req = request_table[key]; req != NULL; req = req->next) { + if(req->server != NULL && req->server == server) { + fail_request(JRPC_ERR_SERVER_DISCONNECT, req, + "Failing request for server shutdown"); + } + } + } +} + +typedef struct server_backoff_args { + struct event* ev; + jsonrpc_server_t* server; + unsigned int timeout; +} server_backoff_args_t; + +void server_backoff_cb(int fd, short event, void *arg) +{ + if(!arg) + return; + + server_backoff_args_t* a = (server_backoff_args_t*)arg; + if(!a) + return; + + unsigned int timeout = a->timeout; + + /* exponential backoff */ + if(timeout < 1) { + timeout = 1; + } else { + timeout = timeout * 2; + if(timeout > 60) { + timeout = 60; + } + } + + close(fd); + CHECK_AND_FREE_EV(a->ev); + pkg_free(arg); + + wait_server_backoff(timeout, a->server, false); +} + +void wait_server_backoff(unsigned int timeout /* seconds */, + jsonrpc_server_t* server, bool delay) +{ + if(!server) { + ERR("Trying to close/reconnect a NULL server\n"); + return; + } + + if(delay == false) { + if (requests_using_server(server) <= 0) { + if(server->status == JSONRPC_SERVER_RECONNECTING) { + bev_connect(server); + } else if(server->status == JSONRPC_SERVER_CLOSING) { + close_server(server); + } + return; + } + } + + const struct timeval tv = {timeout, 0}; + + server_backoff_args_t* args = pkg_malloc(sizeof(server_backoff_args_t)); + CHECK_MALLOC_VOID(args); + memset(args, 0, sizeof(server_backoff_args_t)); + + args->ev = evtimer_new(global_ev_base, server_backoff_cb, (void*)args); + CHECK_MALLOC_GOTO(args->ev, error); + + args->server = server; + args->timeout = timeout; + + if(evtimer_add(args->ev, &tv)<0) { + ERR("event_add failed while setting request timer (%s).", strerror(errno)); + goto error; + } + + return; + +error: + ERR("schedule_server failed.\n"); + + if(args) { + if(args->ev) { + evtimer_del(args->ev); + } + pkg_free(args); + } + + if (server->status == JSONRPC_SERVER_CLOSING) { + ERR("Closing server now...\n"); + close_server(server); + } else if (server->status == JSONRPC_SERVER_RECONNECTING) { + ERR("Reconnecting server now...\n"); + force_reconnect(server); + } +} + +void wait_close(jsonrpc_server_t* server) +{ + if(!server) { + ERR("Trying to close null server.\n"); + return; + } + + server->status = JSONRPC_SERVER_CLOSING; + wait_server_backoff(1, server, false); +} + +void wait_reconnect(jsonrpc_server_t* server) +{ + if(!server) { + ERR("Trying to reconnect null server.\n"); + return; + } + + server->status = JSONRPC_SERVER_RECONNECTING; + wait_server_backoff(1, server, false); +} + +void connect_servers(jsonrpc_server_group_t** group) +{ + INIT_SERVER_LOOP + FOREACH_SERVER_IN(group) + server = wgroup->server; + if(server->status != JSONRPC_SERVER_FAILURE + && server->status != JSONRPC_SERVER_RECONNECTING) + { + bev_connect(server); + } + ENDFOR +} + +void force_reconnect(jsonrpc_server_t* server) +{ + INFO("Reconnecting to server %.*s:%d for conn %.*s.\n", + STR(server->addr), server->port, STR(server->conn)); + force_disconnect(server); + bev_connect(server); +} + +/* helper for bev_connect_cb() and bev_connect() */ +void connect_failed(jsonrpc_server_t* server) +{ + bev_disconnect(server->bev); + + server->status = JSONRPC_SERVER_RECONNECTING; + wait_server_backoff(JSONRPC_RECONNECT_INTERVAL, server, true); +} + +void bev_connect_cb(struct bufferevent* bev, short events, void* arg) +{ + jsonrpc_server_t* server = (jsonrpc_server_t*)arg; + if(!arg) { + ERR("Trying to connect null server\n"); + return; + } + + if (events & (BEV_EVENT_ERROR|BEV_EVENT_EOF)) { + WARN("Connection error for %.*s:%d\n", STR(server->addr), server->port); + if (events & BEV_EVENT_ERROR) { + int err = bufferevent_socket_get_dns_error(bev); + if(err) { + ERR("DNS error for %.*s: %s\n", + STR(server->addr), evutil_gai_strerror(err)); + } + } + goto failed; + } else if(events & BEV_EVENT_CONNECTED) { + + if (server->status == JSONRPC_SERVER_CONNECTED) { + return; + } + + server->status = JSONRPC_SERVER_CONNECTED; + INFO("Connected to host %.*s:%d\n", + STR(server->addr), server->port); + } + + return; + +failed: + connect_failed(server); +} + +void bev_connect(jsonrpc_server_t* server) +{ + if(!server) { + ERR("Trying to connect null server\n"); + return; + } + + INFO("Connecting to server %.*s:%d for conn %.*s.\n", + STR(server->addr), server->port, STR(server->conn)); + + server->bev = bufferevent_socket_new( + global_ev_base, + -1, + BEV_OPT_CLOSE_ON_FREE); + if(!(server->bev)) { + ERR("Could not create bufferevent for %.*s:%d\n", STR(server->addr), server->port); + connect_failed(server); + return; + } + + bufferevent_setcb( + server->bev, + bev_read_cb, + NULL, + bev_connect_cb, + server); + bufferevent_enable(server->bev, EV_READ|EV_WRITE); + if(bufferevent_socket_connect_hostname( + server->bev, + global_evdns_base, + AF_UNSPEC, + server->addr.s, + server->port)<0) { + WARN("Failed to connect to %.*s:%d\n", STR(server->addr), server->port); + connect_failed(server); + } +} + diff --git a/modules/janssonrpc-c/janssonrpc_connect.h b/modules/janssonrpc-c/janssonrpc_connect.h new file mode 100644 index 00000000000..a9ce8e4de4b --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_connect.h @@ -0,0 +1,49 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_CONNECT_H_ +#define _JANSSONRPC_CONNECT_H_ + +enum +{ JSONRPC_SERVER_DISCONNECTED = 0 +, JSONRPC_SERVER_CONNECTED +, JSONRPC_SERVER_CONNECTING +, JSONRPC_SERVER_FAILURE +, JSONRPC_SERVER_CLOSING +, JSONRPC_SERVER_RECONNECTING +}; + +/* interval (in seconds) at which failed servers are retried */ +#define JSONRPC_RECONNECT_INTERVAL 3 + +void force_disconnect(jsonrpc_server_t* server); +/* Do not call force_disconnect() from outside the IO process. + * Server's have a bufferevent that is part of local memory and free'd + * at disconnect */ + +void wait_close(jsonrpc_server_t* server); +void wait_reconnect(jsonrpc_server_t* server); +void connect_servers(jsonrpc_server_group_t** group); +void force_reconnect(jsonrpc_server_t* server); +void bev_connect(jsonrpc_server_t* server); + +#endif /* _JSONRPC_CONNECT_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_funcs.c b/modules/janssonrpc-c/janssonrpc_funcs.c new file mode 100644 index 00000000000..65b9b77aefb --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_funcs.c @@ -0,0 +1,265 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "../../mod_fix.h" +#include "../../pvar.h" +#include "../../lvalue.h" +#include "../tm/tm_load.h" + +#include "janssonrpc.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" +#include "janssonrpc_funcs.h" + + +struct tm_binds tmb; + +int jsonrpc_request(struct sip_msg* _m, + char* _conn, + char* _method, + char* _params, + char* _options) +{ + str conn; + str method; + str params; + str options; + str route; + param_hooks_t phooks; + param_t* pit=NULL; + param_t* freeme=NULL; + int retry; + int timeout; + int retval = -1; + + /* defaults */ + options = null_str; + route = null_str; + timeout = JSONRPC_DEFAULT_TIMEOUT; + retry = JSONRPC_DEFAULT_RETRY; + + if (get_str_fparam(&conn, _m, (fparam_t*)_conn) != 0) { + ERR("cannot get connection value\n"); + return -1; + } + + if (get_str_fparam(&method, _m, (fparam_t*)_method) != 0) { + ERR("cannot get method value\n"); + return -1; + } + + if (get_str_fparam(¶ms, _m, (fparam_t*)_params) != 0) { + ERR("cannot get params value\n"); + return -1; + } + + if(_options == NULL) { + + } else if (get_str_fparam(&options, _m, (fparam_t*)_options) != 0) { + ERR("cannot get options value\n"); + return -1; + + } else { + if(options.len == 0) { + goto skip_parse; + }else if (options.len > 0 && options.s[options.len-1] == ';') { + options.len--; + } + + if (parse_params(&options, CLASS_ANY, &phooks, &pit)<0) { + ERR("failed parsing params value\n"); + return -1; + } + + freeme = pit; + + for (; pit;pit=pit->next) + { + if PIT_MATCHES("route") { + route = pkg_strdup(pit->body); + CHECK_MALLOC_GOTO(route.s, end); + + } else if PIT_MATCHES("timeout") { + timeout = atoi(pit->body.s); + + } else if PIT_MATCHES("retry") { + retry = atoi(pit->body.s); + + } else { + ERR("Unrecognized option: %.*s\n", STR(pit->name)); + goto end; + } + } + } +skip_parse: + + /* check options */ + if(timeout < 1) { + ERR("invalid timeout option (%d). Must be > 0.\n", timeout); + goto end; + } + + if(retry < -1) { + ERR("invalid retry option (%d). Must be > -2.\n", retry); + goto end; + } + + retval = 0; + + retval = mod_jsonrpc_request( + _m, /* sip_msg */ + conn, /* connection group */ + method, /* RPC method */ + params, /* JSON param */ + route, /* result route */ + false, /* notify only */ + retry, /* retry attempts */ + (unsigned int)timeout /* request timeout */ + ); + +end: + if(freeme) free_params(freeme); + if(route.s) pkg_free(route.s); + return retval; +} + +int jsonrpc_notification(struct sip_msg* _m, + char* _conn, + char* _method, + char* _params) +{ + str conn; + str method; + str params; + + if (get_str_fparam(&conn, _m, (fparam_t*)_conn) != 0) { + ERR("cannot get connection value\n"); + return -1; + } + + if (get_str_fparam(&method, _m, (fparam_t*)_method) != 0) { + ERR("cannot get method value\n"); + return -1; + } + + if (get_str_fparam(¶ms, _m, (fparam_t*)_params) != 0) { + ERR("cannot get params value\n"); + return -1; + } + + return mod_jsonrpc_request( + _m, /* sip_msg */ + conn, /* connection group */ + method, /* RPC method */ + params, /* JSON param */ + null_str, /* result route */ + true, /* notify only */ + 0, /* retry attempts */ + 0 /* request timeout */ + ); +} + +int mod_jsonrpc_request( + struct sip_msg* msg, + str conn, + str method, + str params, + str route, + bool notify_only, + int retry, + unsigned int timeout + ) +{ + unsigned int hash_index; + unsigned int label; + + if(retry < -1) { + ERR("retry can't be less than -1\n"); + return -1; + } + + + + jsonrpc_req_cmd_t* req_cmd = create_req_cmd(); + CHECK_MALLOC(req_cmd); + + req_cmd->conn = shm_strdup(conn); + CHECK_MALLOC_GOTO(req_cmd->conn.s, error); + + req_cmd->method = shm_strdup(method); + CHECK_MALLOC_GOTO(req_cmd->conn.s, error); + + if(params.s) { + req_cmd->params = shm_strdup(params); + CHECK_MALLOC_GOTO(req_cmd->params.s, error); + } + + if(route.s) { + req_cmd->route = shm_strdup(route); + CHECK_MALLOC_GOTO(req_cmd->route.s, error); + } + + req_cmd->msg = msg; + req_cmd->retry = retry; + req_cmd->notify_only = notify_only; + req_cmd->timeout = timeout; + + if(notify_only || route.len <= 0) { + req_cmd->route = null_str; + if(send_pipe_cmd(CMD_SEND, req_cmd)<0) goto error; + return 1; /* continue script execution */ + } + + tm_cell_t *t = 0; + t = tmb.t_gett(); + if (t==NULL || t==T_UNDEFINED) + { + if(tmb.t_newtran(msg)<0) + { + ERR("cannot create the transaction\n"); + goto error; + } + t = tmb.t_gett(); + if (t==NULL || t==T_UNDEFINED) + { + ERR("cannot look up the transaction\n"); + goto error; + } + } + + if (tmb.t_suspend(msg, &hash_index, &label) < 0) { + ERR("t_suspend() failed\n"); + goto error; + } + req_cmd->t_hash = hash_index; + req_cmd->t_label = label; + + if(send_pipe_cmd(CMD_SEND, req_cmd)<0) goto error; + + return 0; + +error: + free_req_cmd(req_cmd); + ERR("failed to send request to io process\n"); + return -1; +} + diff --git a/modules/janssonrpc-c/janssonrpc_funcs.h b/modules/janssonrpc-c/janssonrpc_funcs.h new file mode 100644 index 00000000000..755ba4e44f2 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_funcs.h @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_FUNCS_H_ +#define _JANSSONRPC_FUNCS_H_ + +#include +#include "../../parser/msg_parser.h" + +int jsonrpc_request(struct sip_msg* _m, + char* _conn, + char* _method, + char* _params, + char* _options); + +int jsonrpc_notification(struct sip_msg* msg, + char* conn, + char* method, + char* params); + +int mod_jsonrpc_request( + struct sip_msg* msg, + str conn, + str method, + str params, + str route, + bool notify_only, + int retry, + unsigned int timeout + ); + +typedef int (*mod_jsonrpc_request_f)( + struct sip_msg* msg, + str conn, + str method, + str params, + str route, + bool notify_only, + unsigned int retry, + unsigned int timeout + ); + +#endif /* _JSONRPC_FUNCS_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_global.c b/modules/janssonrpc-c/janssonrpc_global.c new file mode 100644 index 00000000000..83192ca3a76 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_global.c @@ -0,0 +1,29 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef TEST +#include "../../sr_module.h" +#else +#include "unit_tests/test.h" +#endif + +const str null_str = {0,0}; diff --git a/modules/janssonrpc-c/janssonrpc_io.c b/modules/janssonrpc-c/janssonrpc_io.c new file mode 100644 index 00000000000..0a4ebd68d73 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_io.c @@ -0,0 +1,854 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../route.h" +#include "../../mem/mem.h" +#include "../../action.h" +#include "../../route_struct.h" +#include "../../lvalue.h" +#include "../../rand/fastrand.h" +#include "../tm/tm_load.h" +#include "../jansson/jansson_utils.h" + +#include "janssonrpc.h" +#include "janssonrpc_request.h" +#include "janssonrpc_server.h" +#include "janssonrpc_io.h" +#include "janssonrpc_connect.h" +#include "netstring.h" + +struct tm_binds tmb; + +void cmd_pipe_cb(int fd, short event, void *arg); +void io_shutdown(int sig); + +int jsonrpc_io_child_process(int cmd_pipe) +{ + global_ev_base = event_base_new(); + global_evdns_base = evdns_base_new(global_ev_base, 1); + + set_non_blocking(cmd_pipe); + struct event* pipe_ev = event_new(global_ev_base, cmd_pipe, EV_READ | EV_PERSIST, cmd_pipe_cb, NULL); + if(!pipe_ev) { + ERR("Failed to create pipe event\n"); + return -1; + } + + if(event_add(pipe_ev, NULL)<0) { + ERR("Failed to start pipe event\n"); + return -1; + } + + connect_servers(global_server_group); + +#if 0 + /* attach shutdown signal handler */ + /* The shutdown handler are intended to clean up the remaining memory + * in the IO process. However, catching the signals causes unpreditable + * behavior in the Kamailio shutdown process, so this should be disabled + * except when doing memory debugging. */ + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = io_shutdown; + if(sigaction(SIGTERM, &sa, NULL) == -1) { + ERR("Failed to attach IO shutdown handler to SIGTERM\n"); + } else if(sigaction(SIGINT, NULL, &sa) == -1) { + ERR("Failed to attach IO shutdown handler to SIGINT\n"); + } +#endif + + if(event_base_dispatch(global_ev_base)<0) { + ERR("IO couldn't start event loop\n"); + return -1; + } + return 0; +} + +void io_shutdown(int sig) +{ + INFO("Shutting down JSONRPC IO process...\n"); + lock_get(jsonrpc_server_group_lock); /* blocking */ + + INIT_SERVER_LOOP + FOREACH_SERVER_IN(global_server_group) + close_server(server); + ENDFOR + + evdns_base_free(global_evdns_base, 0); + event_base_loopexit(global_ev_base, NULL); + event_base_free(global_ev_base); + + lock_release(jsonrpc_server_group_lock); +} + +int send_to_script(pv_value_t* val, jsonrpc_req_cmd_t* req_cmd) +{ + if(!(req_cmd)) return -1; + + if(req_cmd->route.len <= 0) return -1; + + jsonrpc_result_pv.setf(req_cmd->msg, &jsonrpc_result_pv.pvp, (int)EQ_T, val); + + int n = route_lookup(&main_rt, req_cmd->route.s); + if(n<0) { + ERR("no such route: %s\n", req_cmd->route.s); + return -1; + } + + struct action* route = main_rt.rlist[n]; + + if(tmb.t_continue(req_cmd->t_hash, req_cmd->t_label, route)<0) { + ERR("Failed to resume transaction\n"); + return -1; + } + return 0; +} + +json_t* internal_error(int code, json_t* data) +{ + json_t* ret = json_object(); + json_t* inner = json_object(); + char* message; + + switch(code){ + case JRPC_ERR_REQ_BUILD: + message = "Failed to build request"; + break; + case JRPC_ERR_SEND: + message = "Failed to send"; + break; + case JRPC_ERR_BAD_RESP: + message = "Bad response result"; + json_object_set(ret, "data", data); + break; + case JRPC_ERR_RETRY: + message = "Retry failed"; + break; + case JRPC_ERR_SERVER_DISCONNECT: + message = "Server disconnected"; + break; + case JRPC_ERR_TIMEOUT: + message = "Message timeout"; + break; + case JRPC_ERR_PARSING: + message = "JSON parse error"; + break; + case JRPC_ERR_BUG: + message = "There is a bug"; + break; + default: + ERR("Unrecognized error code: %d\n", code); + message = "Unknown error"; + break; + } + + json_t* message_js = json_string(message); + json_object_set(inner, "message", message_js); + if(message_js) json_decref(message_js); + + json_t* code_js = json_integer(code); + json_object_set(inner, "code", code_js); + if(code_js) json_decref(code_js); + + if(data) { + json_object_set(inner, "data", data); + } + + json_object_set(ret, "internal_error", inner); + if(inner) json_decref(inner); + return ret; +} + +void fail_request(int code, jsonrpc_request_t* req, char* err_str) +{ + char* req_s; + char* freeme = NULL; + pv_value_t val; + json_t* error; + + if(!req) { +null_req: + WARN("%s: (null)\n", err_str); + goto end; + } + + if(!(req->cmd) || (req->cmd->route.len <= 0)) { +no_route: + req_s = json_dumps(req->payload, JSON_COMPACT); + if(req_s) { + WARN("%s: \n%s\n", err_str, req_s); + free(req_s); + goto end; + } + goto null_req; + } + + error = internal_error(code, req->payload); + jsontoval(&val, &freeme, error); + if(error) json_decref(error); + if(send_to_script(&val, req->cmd)<0) { + goto no_route; + } + +end: + if(freeme) free(freeme); + free_req_cmd(req->cmd); + free_request(req); +} + +void timeout_cb(int fd, short event, void *arg) +{ + jsonrpc_request_t* req = (jsonrpc_request_t*)arg; + if(!req) + return; + + if(!(req->server)) { + ERR("No server defined for request\n"); + return; + } + + if(schedule_retry(req)<0) { + fail_request(JRPC_ERR_TIMEOUT, req, "Request timeout"); + } +} + + +int server_tried(jsonrpc_server_t* server, server_list_t* tried) +{ + if(!server) + return 0; + + int t = 0; + for(;tried!=NULL;tried=tried->next) + { + if(tried->server && + server == tried->server) + { + t = 1; + } + } + return t; +} + +/* loadbalance_by_weight() uses an algorithm to randomly pick a server out of + * a list based on its relative weight. + * + * It is loosely inspired by this: + * http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/ + * + * The insert_server_group() function provides the ability to get the combined + * weight of all the servers off the head of the list, making it possible to + * compute in O(n) in the worst case and O(1) in the best. + * + * A random number out of the total weight is chosen. Each node is inspected and + * its weight added to a recurring sum. Once the sum is larger than the random + * number the last server that was seen is chosen. + * + * A weight of 0 will almost never be chosen, unless if maybe all the other + * servers are offline. + * + * The exception is when all the servers in a group have a weight of 0. In + * this case, the load should be distributed evenly across each of them. This + * requires finding the size of the list beforehand. + * */ +void loadbalance_by_weight(jsonrpc_server_t** s, + jsonrpc_server_group_t* grp, server_list_t* tried) +{ + *s = NULL; + + if(grp == NULL) { + ERR("Trying to pick from an empty group\n"); + return; + } + + if(grp->type != WEIGHT_GROUP) { + ERR("Trying to pick from a non weight group\n"); + return; + } + + jsonrpc_server_group_t* head = grp; + jsonrpc_server_group_t* cur = grp; + + unsigned int pick = 0; + if(head->weight == 0) { + unsigned int size = 0; + size = server_group_size(cur); + if(size == 0) return; + + pick = fastrand_max(size-1); + + int i; + for(i=0; + (i <= pick || *s == NULL) + && cur != NULL; + i++, cur=cur->next) + { + if(cur->server->status == JSONRPC_SERVER_CONNECTED) { + if(!server_tried(cur->server, tried) + && (cur->server->hwm <= 0 + || cur->server->req_count < cur->server->hwm)) + { + *s = cur->server; + } + } + } + } else { + pick = fastrand_max(head->weight - 1); + + unsigned int sum = 0; + while(1) { + if(cur == NULL) break; + if(cur->server->status == JSONRPC_SERVER_CONNECTED) { + if(!server_tried(cur->server, tried) + && (cur->server->hwm <= 0 + || cur->server->req_count < cur->server->hwm)) + { + *s = cur->server; + } + } + sum += cur->server->weight; + if(sum > pick && *s != NULL) break; + cur = cur->next; + } + } +} + +int jsonrpc_send(str conn, jsonrpc_request_t* req, bool notify_only) +{ + char* json = (char*)json_dumps(req->payload, JSON_COMPACT); + + char* ns; + size_t bytes; + bytes = netstring_encode_new(&ns, json, (size_t)strlen(json)); + + bool sent = false; + jsonrpc_server_group_t* c_grp = NULL; + if(global_server_group != NULL) + c_grp = *global_server_group; + jsonrpc_server_group_t* p_grp = NULL; + jsonrpc_server_group_t* w_grp = NULL; + jsonrpc_server_t* s = NULL; + server_list_t* tried_servers = NULL; + DEBUG("SENDING DATA\n"); + for(; c_grp != NULL; c_grp = c_grp->next) { + + if(strncmp(conn.s, c_grp->conn.s, c_grp->conn.len) != 0) continue; + + for(p_grp = c_grp->sub_group; p_grp != NULL; p_grp = p_grp->next) + { + w_grp = p_grp->sub_group; + while(!sent) { + loadbalance_by_weight(&s, w_grp, tried_servers); + if (s == NULL || s->status != JSONRPC_SERVER_CONNECTED) { + break; + } + + if(bufferevent_write(s->bev, ns, bytes) == 0) { + sent = true; + if(!notify_only) { + s->req_count++; + if (s->hwm > 0 && s->req_count >= s->hwm) { + WARN("%.*s:%d in connection group %.*s has exceeded its high water mark (%d)\n", + STR(s->addr), s->port, + STR(s->conn), s->hwm); + } + } + req->server = s; + break; + } else { + addto_server_list(s, &tried_servers); + } + } + + if (sent) { + break; + } + + WARN("Failed to send to priority group, %d\n", p_grp->priority); + if(p_grp->next != NULL) { + INFO("Proceeding to next priority group, %d\n", + p_grp->next->priority); + } + } + + if (sent) { + break; + } + + } + + if(!sent) { + WARN("Failed to send to connection group, \"%.*s\"\n", + STR(conn)); + if(schedule_retry(req)<0) { + fail_request(JRPC_ERR_RETRY, req, "Failed to schedule retry"); + } + } + + free_server_list(tried_servers); + if(ns) pkg_free(ns); + if(json) free(json); + + if (sent && notify_only == false) { + + const struct timeval tv = ms_to_tv(req->timeout); + + req->timeout_ev = evtimer_new(global_ev_base, timeout_cb, (void*)req); + if(event_add(req->timeout_ev, &tv)<0) { + ERR("event_add failed while setting request timer (%s).", + strerror(errno)); + return -1; + } + } + + return sent; +} + + +void cmd_pipe_cb(int fd, short event, void *arg) +{ + struct jsonrpc_pipe_cmd *cmd; + + if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) { + ERR("FATAL ERROR: failed to read from command pipe: %s\n", + strerror(errno)); + return; + } + + + switch(cmd->type) { + case CMD_CLOSE: + if(cmd->server) { + wait_close(cmd->server); + } + goto end; + break; + case CMD_RECONNECT: + if(cmd->server) { + wait_reconnect(cmd->server); + } + goto end; + break; + case CMD_CONNECT: + if(cmd->server) { + bev_connect(cmd->server); + } + goto end; + break; + case CMD_UPDATE_SERVER_GROUP: + if(cmd->new_grp) { + jsonrpc_server_group_t* old_grp = *global_server_group; + *global_server_group = cmd->new_grp; + free_server_group(&old_grp); + } + lock_release(jsonrpc_server_group_lock); + goto end; + break; + + case CMD_SEND: + break; + + default: + ERR("Unrecognized pipe command: %d\n", cmd->type); + goto end; + break; + } + + /* command is SEND */ + + jsonrpc_req_cmd_t* req_cmd = cmd->req_cmd; + if(req_cmd == NULL) { + ERR("req_cmd is NULL. Invalid send command\n"); + goto end; + } + + jsonrpc_request_t* req = NULL; + req = create_request(req_cmd); + if (!req || !req->payload) { + json_t* error = internal_error(JRPC_ERR_REQ_BUILD, NULL); + pv_value_t val; + char* freeme = NULL; + jsontoval(&val, &freeme, error); + if(req_cmd->route.len <=0 && send_to_script(&val, req_cmd)<0) { + ERR("Failed to build request (method: %.*s, params: %.*s)\n", + STR(req_cmd->method), STR(req_cmd->params)); + } + if(freeme) free(freeme); + if(error) json_decref(error); + free_req_cmd(req_cmd); + goto end; + } + + int sent = jsonrpc_send(req_cmd->conn, req, req_cmd->notify_only); + + char* type; + if (sent<0) { + if (req_cmd->notify_only == false) { + type = "Request"; + } else { + type = "Notification"; + } + WARN("%s could not be sent to connection group: %.*s\n", + type, STR(req_cmd->conn)); + fail_request(JRPC_ERR_SEND, req, "Failed to send request"); + } + +end: + free_pipe_cmd(cmd); +} + +int handle_response(json_t* response) +{ + int retval = 0; + jsonrpc_request_t* req = NULL; + json_t* return_obj = NULL; + json_t* internal = NULL; + char* freeme = NULL; + + + /* check if json object */ + if(!json_is_object(response)){ + WARN("jsonrpc response is not an object\n"); + return -1; + } + + /* check version */ + json_t* version = json_object_get(response, "jsonrpc"); + if(!version) { + WARN("jsonrpc response does not have a version.\n"); + retval = -1; + goto end; + } + + const char* version_s = json_string_value(version); + if(!version_s){ + WARN("jsonrpc response version is not a string.\n"); + retval = -1; + goto end; + } + + if (strlen(version_s) != (sizeof(JSONRPC_VERSION)-1) + || strncmp(version_s, JSONRPC_VERSION, sizeof(JSONRPC_VERSION)-1) != 0) { + WARN("jsonrpc response version is not %s. version: %s\n", + JSONRPC_VERSION, version_s); + retval = -1; + goto end; + } + + /* check for an id */ + json_t* _id = json_object_get(response, "id"); + if(!_id) { + WARN("jsonrpc response does not have an id.\n"); + retval = -1; + goto end; + } + + int id = json_integer_value(_id); + if (!(req = pop_request(id))) { + /* don't fail the server for an unrecognized id */ + retval = 0; + goto end; + } + + return_obj = json_object(); + + json_t* error = json_object_get(response, "error"); + json_t* result = json_object_get(response, "result"); + + if(error) { + json_object_set(return_obj, "error", error); + } + + if(result) { + json_object_set(return_obj, "result", result); + } + + if ((!result && !error) || (result && error)) { + WARN("bad response\n"); + internal = internal_error(JRPC_ERR_BAD_RESP, req->payload); + json_object_update(return_obj, internal); + if(internal) json_decref(internal); + } + + pv_value_t val; + + if(jsontoval(&val, &freeme, return_obj)<0) { + fail_request( + JRPC_ERR_TO_VAL, + req, + "Failed to convert response json to pv\n"); + retval = -1; + goto end; + } + + char* error_s = NULL; + + if(send_to_script(&val, req->cmd)>=0) { + goto free_and_end; + } + + if(error) { + // get code from error + json_t* _code = json_object_get(error, "code"); + if(_code) { + int code = json_integer_value(_code); + + // check if code is in global_retry_ranges + retry_range_t* tmpr; + for(tmpr = global_retry_ranges; + tmpr != NULL; + tmpr = tmpr->next) { + if((tmpr->start < tmpr->end + && tmpr->start <= code && code <= tmpr->end) + || (tmpr->end < tmpr->start + && tmpr->end <= code && code <= tmpr->start) + || (tmpr->start == tmpr->end && tmpr->start == code)) { + if(schedule_retry(req)==0) { + goto end; + } + break; + } + } + + } + error_s = json_dumps(error, JSON_COMPACT); + if(error_s) { + WARN("Request recieved an error: \n%s\n", error_s); + free(error_s); + } else { + fail_request( + JRPC_ERR_BAD_RESP, + req, + "Could not convert 'error' response to string"); + retval = -1; + goto end; + } + } + + +free_and_end: + free_req_cmd(req->cmd); + free_request(req); + +end: + if(freeme) free(freeme); + if(return_obj) json_decref(return_obj); + return retval; +} + +void handle_netstring(jsonrpc_server_t* server) +{ + unsigned int old_count = server->req_count; + server->req_count--; + if (server->hwm > 0 + && old_count >= server->hwm + && server->req_count < server->hwm) { + INFO("%.*s:%d in connection group %.*s is back to normal\n", + STR(server->addr), server->port, STR(server->conn)); + } + + json_error_t error; + + json_t* res = json_loads(server->buffer->string, 0, &error); + + if (res) { + if(handle_response(res)<0){ + ERR("Cannot handle jsonrpc response: %s\n", server->buffer->string); + } + json_decref(res); + } else { + ERR("Failed to parse json: %s\n", server->buffer->string); + ERR("PARSE ERROR: %s at %d,%d\n", + error.text, error.line, error.column); + } +} + +void bev_read_cb(struct bufferevent* bev, void* arg) +{ + jsonrpc_server_t* server = (jsonrpc_server_t*)arg; + int retval = 0; + while (retval == 0) { + int retval = netstring_read_evbuffer(bev, &server->buffer); + + if (retval == NETSTRING_INCOMPLETE) { + return; + } else if (retval < 0) { + char* msg = ""; + switch(retval) { + case NETSTRING_ERROR_TOO_LONG: + msg = "too long"; + break; + case NETSTRING_ERROR_NO_COLON: + msg = "no colon after length field"; + break; + case NETSTRING_ERROR_TOO_SHORT: + msg = "too short"; + break; + case NETSTRING_ERROR_NO_COMMA: + msg = "missing comma"; + break; + case NETSTRING_ERROR_LEADING_ZERO: + msg = "length field has a leading zero"; + break; + case NETSTRING_ERROR_NO_LENGTH: + msg = "missing length field"; + break; + case NETSTRING_INCOMPLETE: + msg = "incomplete"; + break; + default: + ERR("bad netstring: unknown error (%d)\n", retval); + goto reconnect; + } + ERR("bad netstring: %s\n", msg); +reconnect: + force_reconnect(server); + return; + } + + handle_netstring(server); + free_netstring(server->buffer); + server->buffer = NULL; + } +} + +int set_non_blocking(int fd) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return flags; + flags |= O_NONBLOCK; + if (fcntl(fd, F_SETFL, flags) < 0) + return -1; + + return 0; +} + +jsonrpc_pipe_cmd_t* create_pipe_cmd() +{ + jsonrpc_pipe_cmd_t* cmd = NULL; + cmd = (jsonrpc_pipe_cmd_t*)shm_malloc(sizeof(jsonrpc_pipe_cmd_t)); + if(!cmd) { + ERR("Failed to malloc pipe cmd.\n"); + return NULL; + } + memset(cmd, 0, sizeof(jsonrpc_pipe_cmd_t)); + + return cmd; +} + +void free_pipe_cmd(jsonrpc_pipe_cmd_t* cmd) +{ + if(!cmd) return; + + shm_free(cmd); +} + +jsonrpc_req_cmd_t* create_req_cmd() +{ + jsonrpc_req_cmd_t* req_cmd = NULL; + req_cmd = (jsonrpc_req_cmd_t*)shm_malloc(sizeof(jsonrpc_req_cmd_t)); + CHECK_MALLOC_NULL(req_cmd); + memset(req_cmd, 0, sizeof(jsonrpc_req_cmd_t)); + + req_cmd->conn = null_str; + req_cmd->method = null_str; + req_cmd->params = null_str; + req_cmd->route = null_str; + return req_cmd; +} + +void free_req_cmd(jsonrpc_req_cmd_t* req_cmd) +{ + if(req_cmd) { + CHECK_AND_FREE(req_cmd->conn.s); + CHECK_AND_FREE(req_cmd->method.s); + CHECK_AND_FREE(req_cmd->params.s); + CHECK_AND_FREE(req_cmd->route.s); + shm_free(req_cmd); + } +} + +int send_pipe_cmd(cmd_type type, void* data) +{ + char* name = ""; + jsonrpc_pipe_cmd_t* cmd = NULL; + cmd = create_pipe_cmd(); + CHECK_MALLOC(cmd); + + cmd->type = type; + + switch(type) { + case CMD_CONNECT: + cmd->server = (jsonrpc_server_t*)data; + name = "connect"; + break; + case CMD_RECONNECT: + cmd->server = (jsonrpc_server_t*)data; + name = "reconnect"; + break; + case CMD_CLOSE: + cmd->server = (jsonrpc_server_t*)data; + name = "close"; + break; + case CMD_UPDATE_SERVER_GROUP: + cmd->new_grp = (jsonrpc_server_group_t*)data; + name = "update"; + break; + case CMD_SEND: + cmd->req_cmd = (jsonrpc_req_cmd_t*)data; + name = "send"; + break; + default: + ERR("Unknown command type %d", type); + goto error; + } + + DEBUG("sending %s command\n", name); + + if (write(cmd_pipe, &cmd, sizeof(cmd)) != sizeof(cmd)) { + ERR("Failed to send '%s' cmd to io pipe: %s\n", name, strerror(errno)); + goto error; + } + + return 0; +error: + free_pipe_cmd(cmd); + return -1; +} diff --git a/modules/janssonrpc-c/janssonrpc_io.h b/modules/janssonrpc-c/janssonrpc_io.h new file mode 100644 index 00000000000..5abb75e95d1 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_io.h @@ -0,0 +1,90 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_IO_H_ +#define _JANSSONRPC_IO_H_ + +#include +#include +#include +#include "../../route_struct.h" +#include "../../pvar.h" +#include "janssonrpc_server.h" +#include "janssonrpc_request.h" +#include "janssonrpc.h" + +/* event bases */ +struct event_base* global_ev_base; +struct evdns_base* global_evdns_base; + +typedef enum +{ CMD_CONNECT = 1000 +, CMD_RECONNECT +, CMD_CLOSE +, CMD_UPDATE_SERVER_GROUP +, CMD_SEND +} cmd_type; + +typedef struct jsonrpc_req_cmd { + str method, params, route, conn; + unsigned int t_hash, t_label, timeout; + bool notify_only; + int retry; + struct sip_msg *msg; +} jsonrpc_req_cmd_t; + +typedef struct jsonrpc_pipe_cmd jsonrpc_pipe_cmd_t; +struct jsonrpc_pipe_cmd +{ + cmd_type type; + union { + jsonrpc_server_t* server; + jsonrpc_req_cmd_t* req_cmd; + jsonrpc_server_group_t* new_grp; + }; +}; + +int jsonrpc_io_child_process(int data_pipe); +int send_pipe_cmd(cmd_type type, void* data); +int handle_response(json_t *response); +jsonrpc_pipe_cmd_t* create_pipe_cmd(); +void free_pipe_cmd(jsonrpc_pipe_cmd_t* cmd); +jsonrpc_req_cmd_t* create_req_cmd(); +void free_req_cmd(jsonrpc_req_cmd_t* cmd); +int set_non_blocking(int fd); +void bev_read_cb(struct bufferevent* bev, void* arg); + +/* Remember to update the docs if you add or change these */ +typedef enum +{ JRPC_ERR_BUG = -1000 +, JRPC_ERR_TIMEOUT = -100 +, JRPC_ERR_SERVER_DISCONNECT = -75 +, JRPC_ERR_RETRY = -50 +, JRPC_ERR_BAD_RESP = -20 +, JRPC_ERR_TO_VAL = -11 +, JRPC_ERR_PARSING = -10 +, JRPC_ERR_SEND = -5 +, JRPC_ERR_REQ_BUILD = -1 +} jsonrpc_error; + + +#endif /* _JSONRPC_IO_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_mod.c b/modules/janssonrpc-c/janssonrpc_mod.c new file mode 100644 index 00000000000..7db22ccc50f --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_mod.c @@ -0,0 +1,362 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include + +#include "../../pvar.h" +#include "../../mod_fix.h" +#include "../../trim.h" +#include "../../sr_module.h" +#include "../../timer_proc.h" +#include "../tm/tm_load.h" +#include "../jansson/jansson_utils.h" + +#include "janssonrpc_funcs.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" +#include "janssonrpc_connect.h" +#include "janssonrpc_server.h" +#include "janssonrpc_srv.h" +#include "janssonrpc.h" + + +MODULE_VERSION + + +static int mod_init(void); +static int child_init(int); +void mod_destroy(void); +int parse_server_param(modparam_t type, void* val); +int parse_retry_codes_param(modparam_t type, void* val); +int parse_min_ttl_param(modparam_t type, void* val); +static int fixup_req(void** param, int param_no); +static int fixup_req_free(void** param, int param_no); +static int fixup_notify(void** param, int param_no); +static int fixup_notify_free(void** param, int param_no); +int fixup_pvar_shm(void** param, int param_no); + +int pipe_fds[2] = {-1,-1}; + +struct tm_binds tmb; + +/* + * Exported Functions + */ +int jsonrpc_request_no_options(struct sip_msg* msg, + char* conn, + char* method, + char* params) { + return jsonrpc_request(msg, conn, method, params, NULL); +} + +static cmd_export_t cmds[]={ + {"janssonrpc_request", (cmd_function)jsonrpc_request, + 4, fixup_req, fixup_req_free, ANY_ROUTE}, + {"jsansonrpc_request", (cmd_function)jsonrpc_request_no_options, + 3, fixup_req, fixup_req_free, ANY_ROUTE}, + {"janssonrpc_notification", (cmd_function)jsonrpc_notification, + 3, fixup_notify, fixup_notify_free, ANY_ROUTE}, + {"mod_janssonrpc_request", (cmd_function)mod_jsonrpc_request, + 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0} +}; + +/* + * Script Parameters + */ +static param_export_t mod_params[]={ + {"server", STR_PARAM|USE_FUNC_PARAM, (void*)parse_server_param}, + {"retry_codes", STR_PARAM|USE_FUNC_PARAM, (void*)parse_retry_codes_param}, + {"min_srv_ttl", INT_PARAM|USE_FUNC_PARAM, (void*)parse_min_ttl_param}, + {"result_pv", STR_PARAM, &result_pv_str.s}, + { 0,0,0 } +}; + +/* + * Exports + */ +struct module_exports exports = { + "janssonrpc-c", /* module name */ + DEFAULT_DLFLAGS, /* dlopen flags */ + cmds, /* Exported functions */ + mod_params, /* Exported parameters */ + 0, /* exported statistics */ + 0, /* exported MI functions */ + 0, /* exported pseudo-variables */ + 0, /* extra processes */ + mod_init, /* module initialization function */ + 0, /* response function*/ + mod_destroy, /* destroy function */ + child_init /* per-child init function */ +}; + + +static int mod_init(void) +{ + /* load the tm functions */ + if(load_tm_api(&tmb)<0) return -1; + + /* load json_to_val from json module */ + jsontoval = (jansson_to_val_f)find_export("jansson_to_val", 0, 0); + if(jsontoval == 0) { + ERR("ERROR:jsonrpc:mod_init: cannot import json_to_val\n"); + return -1; + } + + /* setup result pvar */ + if (result_pv_str.s == NULL) + result_pv_str.s = JSONRPC_RESULT_STR; + result_pv_str.len = strlen(result_pv_str.s); + + if(pv_parse_spec(&result_pv_str, &jsonrpc_result_pv)<0) { + ERR("cannot parse result_pv: %.*s\n", STR(result_pv_str)); + return -1; + } + + if(!(pv_is_w(&jsonrpc_result_pv))) { + ERR("%.*s is read only\n", STR(result_pv_str)); + return -1; + } + + register_procs(1); + register_basic_timers(1); + + if (pipe(pipe_fds) < 0) { + ERR("pipe() failed\n"); + return -1; + } + + if(jsonrpc_min_srv_ttl < ABSOLUTE_MIN_SRV_TTL) { + jsonrpc_min_srv_ttl = JSONRPC_DEFAULT_MIN_SRV_TTL; /* 5s */ + } + + return 0; +} + +static int child_init(int rank) +{ + int pid; + + if (rank>PROC_MAIN) + cmd_pipe = pipe_fds[1]; + + if (rank!=PROC_MAIN) + return 0; + + jsonrpc_server_group_lock = lock_alloc(); + if(jsonrpc_server_group_lock == NULL) { + ERR("cannot allocate the server_group_lock\n"); + return -1; + } + + if(lock_init(jsonrpc_server_group_lock) == 0) { + ERR("failed to initialized the server_group_lock\n"); + lock_dealloc(jsonrpc_server_group_lock); + return -1; + } + + srv_cb_params_t* params = (srv_cb_params_t*)shm_malloc(sizeof(srv_cb_params_t)); + CHECK_MALLOC(params); + params->cmd_pipe = pipe_fds[1]; + params->srv_ttl = jsonrpc_min_srv_ttl; + + /* start timer to check SRV ttl every second */ + if(fork_basic_timer(PROC_TIMER, "jsonrpc SRV timer", 1 /*socks flag*/, + refresh_srv_cb, (void*)params, ABSOLUTE_MIN_SRV_TTL)<0) { + ERR("Failed to start SRV timer\n"); + return -1; + } + + pid=fork_process(PROC_RPC, "jsonrpc io handler", 1); + + if (pid<0) + return -1; /* error */ + if(pid==0){ + /* child */ + close(pipe_fds[1]); + return jsonrpc_io_child_process(pipe_fds[0]); + } + + return 0; +} + +void mod_destroy(void) +{ + lock_get(jsonrpc_server_group_lock); /* blocking */ + if(jsonrpc_server_group_lock) lock_dealloc(jsonrpc_server_group_lock); + + free_server_group(global_server_group); + CHECK_AND_FREE(global_server_group); +} + +int parse_server_param(modparam_t type, void* val) +{ + if(global_server_group == NULL) { + global_server_group = shm_malloc(sizeof(void*)); + *global_server_group = NULL; + } + return jsonrpc_parse_server((char*)val, global_server_group); +} + +/* helper function for parse_retry_codes_param */ +int s2i(char* str, int* result) +{ + char* endptr; + errno = 0; + + long val = strtol(str, &endptr, 10); + + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + || (errno != 0 && val == 0)) { + ERR("%s is not a number: %s\n", str, strerror(errno)); + return -1; + } + + if (endptr == str) { + ERR("failed to convert %s to integer\n", str); + return -1; + } + + *result = (int)val; + return 0; +} + +int parse_retry_codes_param(modparam_t type, void* val) +{ + if (val==NULL) { + ERR("retry_codes cannot be NULL!\n"); + return -1; + } + + if (PARAM_TYPE_MASK(type) != STR_PARAM) { + ERR("retry_codes must be a string\n"); + return -1; + } + + global_retry_ranges = NULL; + + char* save_comma; + char* save_elipse; + char* token; + char* start_s; + int start; + char* end_s; + int end; + char* codes_s = (char*)val; + + char* tmp; + retry_range_t** tmp_range; + tmp_range = &global_retry_ranges; + for (tmp = codes_s; ; tmp = NULL) { + token = strtok_r(tmp, ",", &save_comma); + if (token == NULL) + break; + + start_s = strtok_r(token, ".", &save_elipse); + if (start_s == NULL) { + continue; + } + + if(s2i(start_s, &start)<0) return -1; + + *tmp_range = shm_malloc(sizeof(retry_range_t)); + CHECK_MALLOC(*tmp_range); + memset(*tmp_range, 0, sizeof(retry_range_t)); + + (*tmp_range)->start = start; + + end_s = strtok_r(NULL, ".", &save_elipse); + if (end_s == NULL) { + end_s = start_s; + } + + if(s2i(end_s, &end)<0) return -1; + (*tmp_range)->end = end; + + tmp_range = &((*tmp_range)->next); + } + + return 0; +} + +int parse_min_ttl_param(modparam_t type, void* val) +{ + if (val==0) { + ERR("min_srv_ttl cannot be NULL!\n"); + return -1; + } + + if (PARAM_TYPE_MASK(type) != INT_PARAM) { + ERR("min_srv_ttl must be of type %d, not %d!\n", INT_PARAM, type); + return -1; + } + + jsonrpc_min_srv_ttl = (int)(long)val; + if(jsonrpc_min_srv_ttl < ABSOLUTE_MIN_SRV_TTL) { + ERR("Cannot set min_srv_ttl lower than %d", ABSOLUTE_MIN_SRV_TTL); + return -1; + } + + INFO("min_srv_ttl set to %d\n", jsonrpc_min_srv_ttl); + + return 0; +} + +/* Fixup Functions */ + +static int fixup_req(void** param, int param_no) +{ + if (param_no <= 4) { + return fixup_spve_null(param, 1); + } + ERR("function takes at most 4 parameters.\n"); + return -1; +} + +static int fixup_req_free(void** param, int param_no) +{ + if (param_no <= 4) { + return fixup_free_spve_null(param, 1); + } + ERR("function takes at most 4 parameters.\n"); + return -1; +} + +static int fixup_notify(void** param, int param_no) +{ + if (param_no <= 3) { + return fixup_spve_null(param, 1); + } + ERR("function takes at most 3 parameters.\n"); + return -1; +} + +static int fixup_notify_free(void** param, int param_no) +{ + if (param_no <= 3) { + return fixup_free_spve_null(param, 1); + } + ERR("function takes at most 3 parameters.\n"); + return -1; +} diff --git a/modules/janssonrpc-c/janssonrpc_request.c b/modules/janssonrpc-c/janssonrpc_request.c new file mode 100644 index 00000000000..39c7f313919 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_request.c @@ -0,0 +1,340 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../mem/mem.h" + +#include "janssonrpc.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" + +int next_id = 1; + +int store_request(jsonrpc_request_t* req); + +/* for debugging only */ +void print_request(jsonrpc_request_t* req) +{ + if(!req) { + INFO("request is (null)\n"); + return; + } + + INFO("------request------\n"); + INFO("| id: %d\n", req->id); + + if(req->type == RPC_NOTIFICATION) { + INFO("| type: notification\n"); + } else if(req->type == RPC_REQUEST) { + INFO("| type: request\n"); + } else { + INFO("| type: unknown (%d)\n", (int)req->type); + } + + if(!(req->server)) { + INFO("| server: (null)\n"); + } else { + print_server(req->server); + } + + if(!(req->cmd)) { + INFO("| cmd: (null)\n"); + } else { + INFO("| cmd->route: %.*s\n", STR(req->cmd->route)); + } + + INFO("| payload: %s\n", json_dumps(req->payload, 0)); + INFO("| retry: %d\n", req->retry); + INFO("| ntries: %d\n", req->ntries); + INFO("| timeout: %d\n", req->timeout); + INFO("\t-------------------\n"); +} + +void free_request(jsonrpc_request_t* req) +{ + if(!req) + return; + + pop_request(req->id); + + CHECK_AND_FREE_EV(req->retry_ev); + CHECK_AND_FREE_EV(req->timeout_ev); + + if(req->payload) json_decref(req->payload); + pkg_free(req); +} + +jsonrpc_request_t* create_request(jsonrpc_req_cmd_t* cmd) +{ + if (cmd == NULL) { + ERR("cmd is (null). Cannot build request.\n"); + return NULL; + } + + if (cmd->params.s == NULL) { + ERR("params is (null). Cannot build request.\n"); + return NULL; + } + + jsonrpc_request_t* req = (jsonrpc_request_t*)pkg_malloc(sizeof(jsonrpc_request_t)); + if (!req) { + ERR("Out of memory!"); + return NULL; + } + memset(req, 0, sizeof(jsonrpc_request_t)); + + if (cmd->notify_only) { + req->type = RPC_NOTIFICATION; + } else { + req->type = RPC_REQUEST; + } + + /* settings for both notifications and requests */ + req->ntries = 0; + req->next = NULL; + + req->payload = json_object(); + if(!(req->payload)) { + ERR("Failed to create request payload\n"); + goto fail; + } + + if(req->type == RPC_REQUEST) { + if (next_id>JSONRPC_MAX_ID) { + next_id = 1; + } else { + next_id++; + } + req->id = next_id; + req->timeout = cmd->timeout; + + json_t* id_js = json_integer(next_id); + if(id_js) { + json_object_set(req->payload, "id", id_js); + json_decref(id_js); + } else { + ERR("Failed to create request id\n"); + goto fail; + } + + req->retry = cmd->retry; + req->timeout = cmd->timeout; + if (!store_request(req)) { + ERR("store_request failed\n"); + goto fail; + } + } else if (req->type == RPC_NOTIFICATION) { + req->id = 0; + req->retry = 0; + } else { + ERR("Unknown RPC type: %d\n", (int)req->type); + goto fail; + } + + json_t* version_js = json_string(JSONRPC_VERSION); + if(version_js) { + json_object_set(req->payload, "jsonrpc", version_js); + json_decref(version_js); + } else { + ERR("Failed to create request version\n"); + goto fail; + } + + json_t* method_js = json_string(cmd->method.s); + if(method_js) { + json_object_set(req->payload, "method", method_js); + json_decref(method_js); + } else { + ERR("Failed to create request method\n"); + goto fail; + } + + json_t* params = NULL; + json_error_t error; + if(cmd->params.len > 0) { + params = json_loads(cmd->params.s, 0, &error); + if(!params) { + ERR("Failed to parse json: %.*s\n", STR(cmd->params)); + ERR("PARSE ERROR: %s at %d,%d\n", + error.text, error.line, error.column); + goto fail; + } + } + + json_object_set(req->payload, "params", params); + if(!(req->payload)) { + ERR("Failed to add request payload params\n"); + goto fail; + } + + if(params) json_decref(params); + + req->cmd = cmd; + return req; +fail: + ERR("Failed to create request\n"); + free_request(req); + return NULL; +} + +void retry_cb(int fd, short event, void* arg) +{ + if(!arg) + return; + + jsonrpc_request_t* req = (jsonrpc_request_t*)arg; + + if(!(req->cmd)) { + ERR("request has no cmd\n"); + goto error; + } + + DEBUG("retrying request: id=%d\n", req->id); + + if(jsonrpc_send(req->cmd->conn, req, 0)<0) { + goto error; + } + + CHECK_AND_FREE_EV(req->retry_ev); + return; + +error: + fail_request(JRPC_ERR_SEND, req, "Retry failed to send request"); +} + +int schedule_retry(jsonrpc_request_t* req) +{ + if(!req) { + ERR("Trying to schedule retry for a null request.\n"); + return -1; + } + + if(req->retry == 0) { + return -1; + } + + req->ntries++; + if(req->retry > 0 && req->ntries > req->retry) { + WARN("Number of retries exceeded. Failing request.\n"); + return -1; + } + + /* next retry in milliseconds */ + unsigned int time = req->ntries * req->ntries * req->timeout; + if(time > RETRY_MAX_TIME) { + time = RETRY_MAX_TIME; + } + + jsonrpc_request_t* new_req = create_request(req->cmd); + + new_req->ntries = req->ntries; + + free_request(req); + + const struct timeval tv = ms_to_tv(time); + + new_req->retry_ev = evtimer_new(global_ev_base, retry_cb, (void*)new_req); + if(evtimer_add(new_req->retry_ev, &tv)<0) { + ERR("event_add failed while setting request retry timer (%s).", + strerror(errno)); + goto error; + } + + return 0; +error: + ERR("schedule_retry failed.\n"); + return -1; +} + +int id_hash(int id) { + return (id % JSONRPC_DEFAULT_HTABLE_SIZE); +} + +jsonrpc_request_t* pop_request(int id) +{ + int key = id_hash(id); + jsonrpc_request_t* req = request_table[key]; + jsonrpc_request_t* prev_req = NULL; + + while (req && req->id != id) { + prev_req = req; + if (!(req = req->next)) { + break; + }; + } + + if (req && req->id == id) { + if (prev_req != NULL) { + prev_req->next = req->next; + } else { + request_table[key] = NULL; + } + return req; + } + return 0; +} + +int store_request(jsonrpc_request_t* req) +{ + int key = id_hash(req->id); + jsonrpc_request_t* existing; + + if ((existing = request_table[key])) { /* collision */ + jsonrpc_request_t* i; + for(i=existing; i; i=i->next) { + if (i == NULL) { + i = req; + LM_ERR("!!!!!!!"); + return 1; + } + if (i->next == NULL) { + i->next = req; + return 1; + } + } + } else { + request_table[key] = req; + } + return 1; +} + +unsigned int requests_using_server(jsonrpc_server_t* server) +{ + unsigned int count = 0; + jsonrpc_request_t* req = NULL; + int key = 0; + for (key=0; key < JSONRPC_DEFAULT_HTABLE_SIZE; key++) { + for (req = request_table[key]; req != NULL; req = req->next) { + if(req->server + && req->server == server) { + count++; + } + } + } + return count; +} + diff --git a/modules/janssonrpc-c/janssonrpc_request.h b/modules/janssonrpc-c/janssonrpc_request.h new file mode 100644 index 00000000000..e38c08e6aae --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_request.h @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_REQUEST_H_ +#define _JANSSONRPC_REQUEST_H_ + +#include "janssonrpc_io.h" +#include "janssonrpc_server.h" + +#define JSONRPC_DEFAULT_HTABLE_SIZE 500 +#define JSONRPC_MAX_ID 1000000 +#define RETRY_MAX_TIME 60000 /* milliseconds */ + +typedef enum { + RPC_REQUEST, + RPC_NOTIFICATION +} rpc_type; + +typedef struct jsonrpc_request jsonrpc_request_t; +struct jsonrpc_request { + rpc_type type; + int id; + jsonrpc_request_t *next; /* pkg */ + jsonrpc_server_t* server; /* shm */ + jsonrpc_req_cmd_t* cmd; /* shm */ + json_t* payload; + struct event* timeout_ev; /* pkg */ + struct event* retry_ev; /* pkg */ + int retry; + unsigned int ntries; + unsigned int timeout; +}; + +jsonrpc_request_t* request_table[JSONRPC_DEFAULT_HTABLE_SIZE]; + +jsonrpc_request_t* create_request(jsonrpc_req_cmd_t* cmd); +void print_request(jsonrpc_request_t* req); +jsonrpc_request_t* pop_request(int id); +unsigned int requests_using_server(jsonrpc_server_t* server); +void free_request(jsonrpc_request_t* req); +int schedule_retry(jsonrpc_request_t* req); + +int jsonrpc_send(str conn, jsonrpc_request_t* req, bool notify_only); +void fail_request(int code, jsonrpc_request_t* req, char* error_str); + +#endif /* _JSONRPC_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_server.c b/modules/janssonrpc-c/janssonrpc_server.c new file mode 100644 index 00000000000..09286bc630c --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_server.c @@ -0,0 +1,611 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../route.h" +#include "../../route_struct.h" +#include "../../resolve.h" +#include "../../parser/parse_param.h" +#include "../../mem/mem.h" +#include "../../lvalue.h" + +#include "netstring.h" +#include "janssonrpc.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" +#include "janssonrpc_srv.h" +#include "janssonrpc_server.h" +#include "janssonrpc_connect.h" + +/* used for debugging only */ +void print_server(jsonrpc_server_t* server) +{ + INFO("\t----- server ------\n"); + INFO("\t|pointer: %p\n", server); + INFO("\t|conn: %.*s\n", STR(server->conn)); + INFO("\t|addr: %.*s\n", STR(server->addr)); + switch (server->status) { + case JSONRPC_SERVER_CONNECTED: + INFO("\t|status: connected\n"); + break; + case JSONRPC_SERVER_DISCONNECTED: + INFO("\t|status: disconnected\n"); + break; + case JSONRPC_SERVER_FAILURE: + INFO("\t|status: failure\n"); + break; + case JSONRPC_SERVER_CLOSING: + INFO("\t|status: closing\n"); + break; + case JSONRPC_SERVER_RECONNECTING: + INFO("\t|status: reconnecting\n"); + break; + default: + INFO("\t|status: invalid (%d)\n", server->status); + break; + } + INFO("\t|srv: %.*s\n", STR(server->srv)); + INFO("\t|ttl: %d\n", server->ttl); + INFO("\t|port: %d\n", server->port); + INFO("\t|priority: %d\n", server->priority); + INFO("\t|weight: %d\n", server->weight); + INFO("\t|hwm: %d\n", server->hwm); + INFO("\t|req_count: %d\n", server->req_count); + if(server->added) { + INFO("\t|added: true\n"); + } else { + INFO("\t|added: false\n"); + } + INFO("\t-------------------\n"); +} + +/* used for debugging only */ +void print_group(jsonrpc_server_group_t** group) +{ + jsonrpc_server_group_t* grp = NULL; + + INFO("group addr is %p\n", group); + + if(group == NULL) + return; + + for (grp=*group; grp != NULL; grp=grp->next) { + switch(grp->type) { + case CONN_GROUP: + INFO("Connection group: %.*s\n", STR(grp->conn)); + print_group(&(grp->sub_group)); + break; + case PRIORITY_GROUP: + INFO("Priority group: %d\n", grp->priority); + print_group(&(grp->sub_group)); + break; + case WEIGHT_GROUP: + INFO("Weight group: %d\n", grp->weight); + print_server(grp->server); + break; + } + } +} + +int jsonrpc_parse_server(char* server_s, jsonrpc_server_group_t **group_ptr) +{ + if(group_ptr == NULL) { + ERR("Trying to add server to null group ptr\n"); + return -1; + } + + str s; + param_hooks_t phooks; + param_t* pit=NULL; + param_t* freeme=NULL; + str conn; + str addr; + addr.s = NULL; + str srv; + srv.s = NULL; + + unsigned int priority = JSONRPC_DEFAULT_PRIORITY; + unsigned int weight = JSONRPC_DEFAULT_WEIGHT; + unsigned int hwm = JSONRPC_DEFAULT_HWM; + unsigned int port = 0; + + s.s = server_s; + s.len = strlen(server_s); + if (s.s[s.len-1] == ';') + s.len--; + + if (parse_params(&s, CLASS_ANY, &phooks, &pit)<0) { + ERR("Failed parsing params value\n"); + return -1; + } + + freeme = pit; + + for (; pit;pit=pit->next) + { + if PIT_MATCHES("conn") { + conn = shm_strdup(pit->body); + CHECK_MALLOC(conn.s); + + } else if PIT_MATCHES("srv") { + srv = shm_strdup(pit->body); + CHECK_MALLOC(srv.s); + + } else if PIT_MATCHES("addr") { + addr = shm_strdup(pit->body); + CHECK_MALLOC(addr.s); + + } else if PIT_MATCHES("port") { + port = atoi(pit->body.s); + + } else if PIT_MATCHES("priority") { + priority = atoi(pit->body.s); + + } else if PIT_MATCHES("weight") { + weight = atoi(pit->body.s); + + } else if PIT_MATCHES("hwm") { + hwm = atoi(pit->body.s); + + } else if PIT_MATCHES("proto") { + if(strncmp(pit->body.s, "tcp", sizeof("tcp")-1) != 0) { + ERR("Unsupported proto=%.*s. Only tcp is supported.\n", + STR(pit->body)); + goto error; + } + } else { + ERR("Unrecognized parameter: %.*s\n", STR(pit->name)); + goto error; + } + + DEBUG("%.*s = %.*s\n", STR(pit->name), STR(pit->body)); + } + + if(conn.s == NULL) { + ERR("No conn defined! conn parameter is required.\n"); + goto error; + } + + if (srv.s != NULL) { + if (addr.s != NULL + || port != 0 + || weight != JSONRPC_DEFAULT_WEIGHT + || priority != JSONRPC_DEFAULT_PRIORITY) { + ERR("addr, port, weight, and priority are not supported when using srv\n"); + goto error; + } + + if (jsonrpc_server_from_srv(conn, srv, hwm, group_ptr)<0) goto error; + + } else { + + if (addr.s == NULL || port == 0) { + ERR("no address/port defined\n"); + goto error; + } + + jsonrpc_server_t* server = create_server(); + CHECK_MALLOC(server); + + server->conn = conn; + server->addr = addr; + server->port = port; + server->priority = priority; + server->weight = weight; + server->hwm = hwm; + + if(jsonrpc_add_server(server, group_ptr)<0) goto error; + } + + //print_group(group_ptr); /* debug */ + + CHECK_AND_FREE(srv.s); + if (freeme) free_params(freeme); + return 0; + +error: + CHECK_AND_FREE(srv.s); + if (freeme) free_params(freeme); + return -1; +} + +int jsonrpc_server_from_srv(str conn, str srv, + unsigned int hwm, jsonrpc_server_group_t** group_ptr) +{ + struct rdata *l, *head; + struct srv_rdata *srv_record; + str name; + unsigned int ttl = jsonrpc_min_srv_ttl; + + jsonrpc_server_t* server = NULL; + + resolv_init(); + + head = get_record(srv.s, T_SRV, RES_AR); + if (head == NULL) { + ERR("No SRV record returned for %.*s\n", STR(srv)); + goto error; + } + for (l=head; l; l=l->next) { + if (l->type != T_SRV) + continue; + srv_record = (struct srv_rdata*)l->rdata; + if (srv_record == NULL) { + ERR("BUG: null rdata\n"); + goto error; + } + + if (l->ttl < jsonrpc_min_srv_ttl) { + ttl = jsonrpc_min_srv_ttl; + } else { + ttl = l->ttl; + } + + name.s = srv_record->name; + name.len = srv_record->name_len; + + DBG("server %s\n", srv_record->name); + + server = create_server(); + CHECK_MALLOC(server); + + server->conn = shm_strdup(conn); + CHECK_MALLOC_GOTO(server->conn.s, error); + + server->addr = shm_strdup(name); + CHECK_MALLOC_GOTO(server->addr.s, error); + + server->srv = shm_strdup(srv); + CHECK_MALLOC_GOTO(server->srv.s, error); + + server->port = srv_record->port; + server->priority = srv_record->priority; + server->weight = srv_record->weight; + server->ttl = ttl; + server->hwm = hwm; + + if(jsonrpc_add_server(server, group_ptr)<0) goto error; + } + + jsonrpc_srv_t* new_srv = create_srv(srv, conn, ttl); + addto_srv_list(new_srv, &global_srv_list); + + free_rdata_list(head); + + return 0; +error: + CHECK_AND_FREE(server); + if (head) free_rdata_list(head); + + return -1; +} + +int create_server_group(server_group_t type, jsonrpc_server_group_t** grp) +{ + if(grp == NULL) { + ERR("Trying to dereference null group pointer\n"); + return -1; + } + + jsonrpc_server_group_t* new_grp = + shm_malloc(sizeof(jsonrpc_server_group_t)); + CHECK_MALLOC(new_grp); + + switch(type) { + case CONN_GROUP: + DEBUG("Creating new connection group\n"); + new_grp->conn.s = NULL; + new_grp->conn.len = 0; + break; + case PRIORITY_GROUP: + DEBUG("Creating new priority group\n"); + new_grp->priority = JSONRPC_DEFAULT_PRIORITY; + break; + case WEIGHT_GROUP: + DEBUG("Creating new weight group\n"); + new_grp->server = NULL; + new_grp->weight = JSONRPC_DEFAULT_WEIGHT; + break; + } + + new_grp->next = NULL; + new_grp->sub_group = NULL; + new_grp->type = type; + *grp = new_grp; + return 0; +} + +void free_server_group(jsonrpc_server_group_t** grp) +{ + if(grp == NULL) + return; + + jsonrpc_server_group_t* next = NULL; + jsonrpc_server_group_t* cgroup = NULL; + jsonrpc_server_group_t* pgroup = NULL; + jsonrpc_server_group_t* wgroup = NULL; + + cgroup=*grp; + while(cgroup!=NULL) { + pgroup=cgroup->sub_group; + while(pgroup!=NULL) { + wgroup=pgroup->sub_group; + while(wgroup!=NULL) { + next = wgroup->next; + CHECK_AND_FREE(wgroup); + wgroup = next; + } + next = pgroup->next; + CHECK_AND_FREE(pgroup); + pgroup = next; + } + next = cgroup->next; + CHECK_AND_FREE(cgroup->conn.s); + CHECK_AND_FREE(cgroup); + cgroup = next; + } +} + +int insert_server_group(jsonrpc_server_group_t* new_grp, + jsonrpc_server_group_t** parent) +{ + if(parent == NULL) { + ERR("Trying to insert into NULL group\n"); + return -1; + } + + jsonrpc_server_group_t* head = *parent; + + if (head == NULL) { + *parent = new_grp; + } else { + if (new_grp->type != head->type) { + ERR("Inserting group (%d) into the wrong type of list (%d)\n", + new_grp->type, head->type); + return -1; + } + + jsonrpc_server_group_t* current = head; + jsonrpc_server_group_t** prev = parent; + + while (1) { + if(new_grp->type == PRIORITY_GROUP + && new_grp->priority < current->priority) { + /* Priority groups are organized in ascending order.*/ + new_grp->next = current; + *prev = new_grp; + break; + } else if (new_grp->type == WEIGHT_GROUP ) { + /* Weight groups are special in how they are organized in order + * to facilitate load balancing and weighted random selection. + * + * The weight in the head of a weight group list represents + * the total weight of the list. Subsequent nodes represent the + * remaining total. + * + * In order to achieve this, the weight to be inserted is added + * to each node that is passed before insertion. + * + * Weight groups are organized in descending order. + * + * The actual weight of a node can be found in its server. + * */ + if(new_grp->server == NULL) { + ERR("Trying to insert an empty weight group.\n"); + return -1; + } + if(new_grp->server->weight != new_grp->weight) { + ERR("Weight of the new node (%d) doesn't match its server (%d). This is a bug. Please report this to the maintainer.\n", + new_grp->server->weight, new_grp->weight); + return -1; + } + if(new_grp->weight > current->server->weight) { + new_grp->weight += current->weight; + new_grp->next = current; + *prev = new_grp; + break; + } else { + current->weight += new_grp->weight; + } + } + + if(current->next == NULL) { + current->next = new_grp; + break; + } + prev = &((*prev)->next); // This is madness. Madness? THIS IS POINTERS! + current = current->next; + } + } + return 0; +} + +unsigned int server_group_size(jsonrpc_server_group_t* grp) +{ + unsigned int size = 0; + for(;grp != NULL; grp=grp->next) { + size++; + } + return size; +} + +jsonrpc_server_t* create_server() +{ + jsonrpc_server_t* server = shm_malloc(sizeof(jsonrpc_server_t)); + CHECK_MALLOC_NULL(server); + memset(server, 0, sizeof(jsonrpc_server_t)); + + server->priority = JSONRPC_DEFAULT_PRIORITY; + server->weight = JSONRPC_DEFAULT_WEIGHT; + server->status = JSONRPC_SERVER_DISCONNECTED; + + return server; +} + +void free_server(jsonrpc_server_t* server) +{ + if(!server) + return; + + CHECK_AND_FREE(server->conn.s); + CHECK_AND_FREE(server->addr.s); + CHECK_AND_FREE(server->srv.s); + + if ((server->buffer)!=NULL) free_netstring(server->buffer); + memset(server, 0, sizeof(jsonrpc_server_t)); + shm_free(server); + server = NULL; +} + +int server_eq(jsonrpc_server_t* a, jsonrpc_server_t* b) +{ + if(!a || !b) + return 0; + + if(!STR_EQ(a->conn, b->conn)) return 0; + if(!STR_EQ(a->srv, b->srv)) return 0; + if(!STR_EQ(a->addr, b->addr)) return 0; + if(a->port != b->port) return 0; + if(a->priority != b->priority) return 0; + if(a->weight != b->weight) return 0; + + return 1; +} + +int jsonrpc_add_server(jsonrpc_server_t* server, jsonrpc_server_group_t** group_ptr) +{ + jsonrpc_server_group_t* conn_grp = NULL; + jsonrpc_server_group_t* priority_grp = NULL; + jsonrpc_server_group_t* weight_grp = NULL; + + if(group_ptr == NULL) { + ERR("Trying to add server to null group\n"); + return -1; + } + + if(create_server_group(WEIGHT_GROUP, &weight_grp) < 0) goto error; + + weight_grp->weight = server->weight; + weight_grp->server = server; + + /* find conn group */ + for (conn_grp=*group_ptr; conn_grp != NULL; conn_grp=conn_grp->next) { + if (strncmp(conn_grp->conn.s, server->conn.s, server->conn.len) == 0) + break; + } + + if (conn_grp == NULL) { + if(create_server_group(CONN_GROUP, &conn_grp) < 0) goto error; + if(create_server_group(PRIORITY_GROUP, &priority_grp) < 0) goto error; + + priority_grp->priority = server->priority; + priority_grp->sub_group = weight_grp; + + conn_grp->conn = shm_strdup(server->conn); + CHECK_MALLOC_GOTO(conn_grp->conn.s, error); + + conn_grp->sub_group = priority_grp; + if(insert_server_group(conn_grp, group_ptr) < 0) goto error; + goto success; + } + + /* find priority group */ + for (priority_grp=conn_grp->sub_group; + priority_grp != NULL; + priority_grp=priority_grp->next) { + if (priority_grp->priority == server->priority) break; + } + + if (priority_grp == NULL) { + if(create_server_group(PRIORITY_GROUP, &priority_grp) < 0) goto error; + + priority_grp->priority = server->priority; + priority_grp->sub_group = weight_grp; + + if(insert_server_group(priority_grp, &(conn_grp->sub_group)) < 0) goto error; + goto success; + } + + if(insert_server_group(weight_grp, &(priority_grp->sub_group)) < 0) goto error; + +success: + return 0; +error: + ERR("Failed to add server: %s, %s, %d\n", + server->conn.s, server->addr.s, server->port); + CHECK_AND_FREE(conn_grp); + CHECK_AND_FREE(priority_grp); + CHECK_AND_FREE(weight_grp); + CHECK_AND_FREE(server); + return -1; +} + +void addto_server_list(jsonrpc_server_t* server, server_list_t** list) +{ + server_list_t* new_node = (server_list_t*)pkg_malloc(sizeof(server_list_t)); + CHECK_MALLOC_VOID(new_node); + + new_node->server = server; + new_node->next = NULL; + + if (*list == NULL) { + *list = new_node; + return; + } + + server_list_t* node = *list; + for(; node->next!=NULL; node=node->next); + + node->next = new_node; +} + +void free_server_list(server_list_t* list) +{ + if (!list) + return; + + server_list_t* node = NULL; + for(node=list; node!=NULL; node=node->next) + { + pkg_free(node); + } +} + +void close_server(jsonrpc_server_t* server) +{ + if(!server) + return; + + INFO("Closing server %.*s:%d for conn %.*s.\n", + STR(server->addr), server->port, STR(server->conn)); + force_disconnect(server); + + free_server(server); +} + diff --git a/modules/janssonrpc-c/janssonrpc_server.h b/modules/janssonrpc-c/janssonrpc_server.h new file mode 100644 index 00000000000..0c716309a73 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_server.h @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_SERVER_H_ +#define _JANSSONRPC_SERVER_H_ + +#include +#include +#include +#include +#include "../../locking.h" +#include "netstring.h" + +/* interval (in seconds) at which failed servers are retried */ +#define JSONRPC_RECONNECT_INTERVAL 3 + +/* default values */ +#define JSONRPC_DEFAULT_PRIORITY 0 +#define JSONRPC_DEFAULT_WEIGHT 1 +#define JSONRPC_DEFAULT_HWM 0 /* unlimited */ + +typedef struct jsonrpc_server { + str conn, addr, srv; /* shared mem */ + int port; + unsigned int status, ttl, hwm; + unsigned int req_count; + unsigned int priority, weight; + bool added; + struct bufferevent* bev; /* local mem */ + netstring_t* buffer; +} jsonrpc_server_t; + +typedef enum { + CONN_GROUP, + PRIORITY_GROUP, + WEIGHT_GROUP +} server_group_t; + +/* servers are organized in the following order: + * 1) conn + * 2) priority + * 3) weight + ***/ +typedef struct jsonrpc_server_group { + server_group_t type; + struct jsonrpc_server_group* sub_group; // NULL when type is WEIGHT_GROUP + union { + str conn; // when type is CONN_GROUP + unsigned int priority; // when type is PRIORITY_GROUP + unsigned int weight; //when type is WEIGHT_GROUP + }; + jsonrpc_server_t* server; // only when type is WEIGHT_GROUP + struct jsonrpc_server_group* next; +} jsonrpc_server_group_t; + +gen_lock_t* jsonrpc_server_group_lock; + +typedef struct server_list { + jsonrpc_server_t* server; + struct server_list* next; +} server_list_t; + +/* where all the servers are stored */ +jsonrpc_server_group_t** global_server_group; + +int jsonrpc_parse_server(char *_server, jsonrpc_server_group_t** group_ptr); +int jsonrpc_server_from_srv(str conn, str srv, + unsigned int hwm, jsonrpc_server_group_t** group_ptr); + +void close_server(jsonrpc_server_t* server); +/* Do not call close_server() from outside the IO process. + * Server's have a bufferevent that is part of local memory and free'd + * at disconnect */ + +jsonrpc_server_t* create_server(); +void free_server(jsonrpc_server_t* server); +int create_server_group(server_group_t type, jsonrpc_server_group_t** new_grp); +int jsonrpc_add_server(jsonrpc_server_t* server, jsonrpc_server_group_t** group); +unsigned int server_group_size(jsonrpc_server_group_t* group); +void free_server_group(jsonrpc_server_group_t** grp); +int server_eq(jsonrpc_server_t* a, jsonrpc_server_t* b); +void addto_server_list(jsonrpc_server_t* server, server_list_t** list); +void free_server_list(server_list_t* list); + +#define INIT_SERVER_LOOP \ + jsonrpc_server_group_t* cgroup = NULL; \ + jsonrpc_server_group_t* pgroup = NULL; \ + jsonrpc_server_group_t* wgroup = NULL; \ + jsonrpc_server_t* server = NULL; + +#define FOREACH_SERVER_IN(ii) \ + if(ii == NULL) { \ + cgroup = NULL; \ + } else { \ + cgroup = *(ii); \ + } \ + pgroup = NULL; \ + wgroup = NULL; \ + server = NULL; \ + for(; cgroup!=NULL; cgroup=cgroup->next) { \ + for(pgroup=cgroup->sub_group; pgroup!=NULL; pgroup=pgroup->next) { \ + for(wgroup=pgroup->sub_group; wgroup!=NULL; wgroup=wgroup->next) { \ + server = wgroup->server; + +#define ENDFOR }}} + +/* debugging only */ +void print_server(jsonrpc_server_t* server); +void print_group(jsonrpc_server_group_t** group); + +#endif /* _JSONRPC_SERVER_H_ */ diff --git a/modules/janssonrpc-c/janssonrpc_srv.c b/modules/janssonrpc-c/janssonrpc_srv.c new file mode 100644 index 00000000000..82d8298f1f6 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_srv.c @@ -0,0 +1,315 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "../../sr_module.h" +#include "../../route.h" +#include "../../route_struct.h" +#include "../../resolve.h" +#include "../../parser/parse_param.h" +#include "../../mem/mem.h" +#include "../../lvalue.h" +#include "../../str.h" + +#include "janssonrpc.h" +#include "janssonrpc_srv.h" +#include "janssonrpc_request.h" +#include "janssonrpc_io.h" +#include "janssonrpc_server.h" + +int refresh_srv(jsonrpc_srv_t* srv_obj) +{ + DEBUG("Refreshing SRV for %.*s\n", STR(srv_obj->srv)); + int retval = 0; + + if(!srv_obj) { + ERR("Trying to refresh NULL SRV\n"); + return -1; + } + + unsigned int ttl = ABSOLUTE_MIN_SRV_TTL; + str srv = srv_obj->srv; + jsonrpc_server_group_t* conn_group = srv_obj->cgroup; + + if(!conn_group) { + ERR("SRV (%.*s) has no connections\n", STR(srv)); + return -1; + } + + struct rdata *l, *head; + struct srv_rdata *srv_record; + str name; + + jsonrpc_server_group_t* new_grp = NULL; + + // dns lookup + head = get_record(srv.s, T_SRV, RES_AR); + if (head == NULL) { + ERR("No SRV record returned for %.*s\n", STR(srv)); + return -1; + } + + // get all the servers from the srv record + server_list_t* new_servers = NULL; + jsonrpc_server_t* new_server = NULL; + server_list_t* rm_servers = NULL; + jsonrpc_server_t* rm_server = NULL; + int iter = 0; + for (l=head, iter=0; l; l=l->next, iter++) { + if (l->type != T_SRV) + continue; + srv_record = (struct srv_rdata*)l->rdata; + if (srv_record == NULL) { + ERR("BUG: null rdata\n"); + return -1; + } + + if (l->ttl < jsonrpc_min_srv_ttl) { + ttl = jsonrpc_min_srv_ttl; + } else { + ttl = l->ttl; + } + + srv_obj->ttl = ttl; + + name.s = srv_record->name; + name.len = srv_record->name_len; + + DBG("server %s\n", srv_record->name); + + jsonrpc_server_group_t* cgroup = NULL; + for(cgroup=conn_group; cgroup!=NULL; cgroup=cgroup->next) { + new_server = create_server(); + CHECK_MALLOC(new_server); + + new_server->conn = shm_strdup(cgroup->conn); + CHECK_MALLOC(new_server->conn.s); + + new_server->addr = shm_strdup(name); + CHECK_MALLOC(new_server->addr.s); + + new_server->srv = shm_strdup(srv); + CHECK_MALLOC(new_server->srv.s); + + new_server->port = srv_record->port; + new_server->priority = srv_record->priority; + new_server->weight = srv_record->weight; + new_server->ttl = ttl; + new_server->added = false; + + addto_server_list(new_server, &new_servers); + } + } + + if(iter <= 0) goto end; + + /* aquire global_server_group lock */ + /* this lock is only released when the old global_server_group + * is freed in the IO process */ + lock_get(jsonrpc_server_group_lock); /* blocking */ + //print_group(global_server_group); /* debug */ + + + INIT_SERVER_LOOP + + // copy existing servers + server_list_t* node; + FOREACH_SERVER_IN(global_server_group) + server->added = false; + if(STR_EQ(server->srv, srv)) { + for(node=new_servers; node!=NULL; node=node->next) { + new_server = node->server; + if(server_eq(new_server, server)) { + new_server->added = true; + server->added = true; + server->ttl = srv_obj->ttl; + jsonrpc_add_server(server, &new_grp); + } + } + } else { + server->added = true; + jsonrpc_add_server(server, &new_grp); + } + ENDFOR + + FOREACH_SERVER_IN(global_server_group) + if(server->added == false) { + addto_server_list(server, &rm_servers); + } + ENDFOR + + // add and connect new servers + for(node=new_servers; node!=NULL; node=node->next) { + new_server = node->server; + if(new_server->added == false) { + + jsonrpc_add_server(new_server, &new_grp); + + if(send_pipe_cmd(CMD_CONNECT, new_server) <0) { + print_server(new_server); + } + + } else { + free_server(new_server); + } + } + + // close old servers + for(node=rm_servers; node!=NULL; node=node->next) { + + rm_server = node->server; + + if(send_pipe_cmd(CMD_CLOSE, rm_server) <0) { + print_server(rm_server); + } + } + + if(send_pipe_cmd(CMD_UPDATE_SERVER_GROUP, new_grp)<0) { + free_server_group(&new_grp); + lock_release(jsonrpc_server_group_lock); + } + +end: + // free server lists + free_server_list(new_servers); + free_server_list(rm_servers); + + return retval; +} + +void free_srv(jsonrpc_srv_t* srv) +{ + if(!srv) + return; + + CHECK_AND_FREE(srv->srv.s); + + free_server_group(&(srv->cgroup)); +} + +jsonrpc_srv_t* create_srv(str srv, str conn, unsigned int ttl) +{ + jsonrpc_srv_t* new_srv = shm_malloc(sizeof(jsonrpc_srv_t)); + if(!new_srv) goto error; + new_srv->srv = shm_strdup(srv); + + if (ttl < jsonrpc_min_srv_ttl) { + new_srv->ttl = jsonrpc_min_srv_ttl; + } else { + new_srv->ttl = ttl; + } + + if(create_server_group(CONN_GROUP, &(new_srv->cgroup))<0) goto error; + new_srv->cgroup->conn = shm_strdup(conn); + if(!(new_srv->cgroup->conn.s)) return NULL; + + return new_srv; +error: + ERR("create_srv failed\n"); + free_srv(new_srv); + return NULL; +} + +void refresh_srv_cb(unsigned int ticks, void* params) +{ + if(!params) { + ERR("params is (null)\n"); + return; + } + + if(!global_srv_list) { + return; + } + + srv_cb_params_t* p = (srv_cb_params_t*)params; + + cmd_pipe = p->cmd_pipe; + jsonrpc_min_srv_ttl = p->srv_ttl; + + if(cmd_pipe == 0) { + ERR("cmd_pipe is not set\n"); + return; + } + + jsonrpc_srv_t* srv; + for(srv=global_srv_list; srv!=NULL; srv=srv->next) { + if(ticks % srv->ttl == 0) { + refresh_srv(srv); + } + } + +} + +void addto_srv_list(jsonrpc_srv_t* srv, jsonrpc_srv_t** list) +{ + if (*list == NULL) { + *list = srv; + return; + } + + jsonrpc_srv_t* node = *list; + jsonrpc_srv_t* prev = *list; + jsonrpc_server_group_t* cgroup; + jsonrpc_server_group_t* cprev; + for(node=*list; node!=NULL; prev=node, node=node->next) { + if(STR_EQ(srv->srv, node->srv)) { + for(cgroup=node->cgroup, cprev=node->cgroup; + cgroup!=NULL; + cprev=cgroup, cgroup=cgroup->next) { + if(STR_EQ(cgroup->conn, srv->cgroup->conn)) { + INFO("Trying to add identical srv\n"); + goto clean; + } + } + if(create_server_group(CONN_GROUP, &(cprev->next))<0) goto clean; + cprev->next->conn = shm_strdup(srv->cgroup->conn); + CHECK_MALLOC_GOTO(cprev->next->conn.s, clean); + node->ttl = srv->ttl; + goto clean; + } + } + + prev->next = srv; + return; +clean: + free_srv(srv); +} + +void print_srv(jsonrpc_srv_t* list) +{ + INFO("------SRV list------\n"); + jsonrpc_srv_t* node = NULL; + for(node=list; node!=NULL; node=node->next) { + INFO("-----------------\n"); + INFO("| srv: %.*s\n", STR(node->srv)); + INFO("| ttl: %d\n", node->ttl); + print_group(&(node->cgroup)); + INFO("-----------------\n"); + } +} + diff --git a/modules/janssonrpc-c/janssonrpc_srv.h b/modules/janssonrpc-c/janssonrpc_srv.h new file mode 100644 index 00000000000..c9021837e74 --- /dev/null +++ b/modules/janssonrpc-c/janssonrpc_srv.h @@ -0,0 +1,53 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef _JANSSONRPC_SRV_H_ +#define _JANSSONRPC_SRV_H_ + +#include "janssonrpc_server.h" + +typedef struct jsonrpc_srv jsonrpc_srv_t; +struct jsonrpc_srv { + str srv; + unsigned int ttl; + jsonrpc_server_group_t* cgroup; + jsonrpc_srv_t* next; +}; + +typedef struct srv_cb_params { + int cmd_pipe; + unsigned int srv_ttl; +} srv_cb_params_t; + +jsonrpc_srv_t* global_srv_list; + +unsigned int jsonrpc_min_srv_ttl; + +jsonrpc_srv_t* create_srv(str srv, str conn, unsigned int ttl); +void addto_srv_list(jsonrpc_srv_t* srv, jsonrpc_srv_t** list); +void refresh_srv_cb(unsigned int ticks, void* params); +void print_srv(jsonrpc_srv_t* list); + +#define JSONRPC_DEFAULT_MIN_SRV_TTL 5 +#define ABSOLUTE_MIN_SRV_TTL 1 + +#endif /* _JSONRPC_SRV_H_ */ diff --git a/modules/janssonrpc-c/netstring.c b/modules/janssonrpc-c/netstring.c new file mode 100644 index 00000000000..a559224fab9 --- /dev/null +++ b/modules/janssonrpc-c/netstring.c @@ -0,0 +1,323 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include "netstring.h" +#include "janssonrpc.h" + +#ifdef TEST +#include "unit_tests/test.h" +#else +#include "../../mem/mem.h" +#endif + + +void free_netstring(netstring_t* netstring) { + if(!netstring) return; + if(netstring->buffer) pkg_free(netstring->buffer); + pkg_free(netstring); +} + +//TODO: refactor out common code in the following two functions + +int netstring_read_evbuffer(struct bufferevent *bev, netstring_t **netstring) +{ + int bytes, offset; + size_t read_len; + char *temp_buffer; + temp_buffer = NULL; + offset = 0; + struct evbuffer *ib = bufferevent_get_input(bev); + + if (*netstring == NULL) { + /* No buffer yet. Peek at first 10 bytes, to get length and colon. */ + unsigned char *lenstr; + int i, len; + struct evbuffer_ptr *search_end = pkg_malloc(sizeof(struct evbuffer_ptr)); + CHECK_MALLOC(search_end); + + i = evbuffer_get_length(ib); + len = ((NETSTRING_PEEKLEN <= i) ? (NETSTRING_PEEKLEN) : (i-1)); + evbuffer_ptr_set(ib, search_end, len, EVBUFFER_PTR_SET); + struct evbuffer_ptr loc = evbuffer_search_range(ib, ":", 1, NULL, search_end); + pkg_free(search_end); + if (loc.pos < 0) { + // no colon found + if (i > NETSTRING_PEEKLEN) + return NETSTRING_ERROR_TOO_LONG; + // TODO: peek at what's available and return suitable errors + return NETSTRING_INCOMPLETE; + } + + + lenstr = pkg_malloc(loc.pos+1); + CHECK_MALLOC(lenstr); + bytes = evbuffer_remove(ib, lenstr, loc.pos+1); + + /* First character must be a digit */ + if (!isdigit(lenstr[0])) + return NETSTRING_ERROR_NO_LENGTH; + + /* No leading zeros allowed! */ + if (lenstr[0] == '0' && isdigit(lenstr[1])) + return NETSTRING_ERROR_LEADING_ZERO; + if (lenstr[loc.pos] != ':') { + return NETSTRING_ERROR_NO_COLON; + } + len = i = 0; + + /* Read the number of bytes */ + for (i = 0; i < loc.pos; i++) { + /* Accumulate each digit, assuming ASCII. */ + len = len*10 + (lenstr[i] - '0'); + } + pkg_free(lenstr); + /* alloc the memory needed for the whole netstring */ + read_len = len+1; + temp_buffer = pkg_malloc(read_len); + CHECK_MALLOC(temp_buffer); + + /* initialize the netstring struct */ + *netstring = pkg_malloc(sizeof(netstring_t)); + CHECK_MALLOC(netstring); + (*netstring)->read = 0; + (*netstring)->length = len; + (*netstring)->buffer = temp_buffer; + (*netstring)->string = NULL; + } else { + /* Continue reading into an existing buffer. */ + offset = (*netstring)->read; + read_len = (*netstring)->length-offset+1; + temp_buffer = (*netstring)->buffer + offset; + } + + /* Read from the evbuffer */ + bytes = evbuffer_remove(ib, temp_buffer, read_len); + int total = (*netstring)->read += bytes; + + /* See if we have the whole netstring yet */ + if (read_len > bytes) { + return NETSTRING_INCOMPLETE; + } + + /* Test for the trailing comma */ + if (((*netstring)->buffer)[total-1] != ',') { + return NETSTRING_ERROR_NO_COMMA; + } + + /* Replace the comma with \0 */ + (*netstring)->buffer[total-1] = '\0'; + + /* Set the string pointer to the "body" of the netstring */ + (*netstring)->string = (*netstring)->buffer; + return 0; +} + +int netstring_read_fd(int fd, netstring_t **netstring) +{ + int bytes, offset; + size_t read_len; + char *temp_buffer; + temp_buffer = NULL; + offset = 0; + + if (*netstring == NULL) { + /* No buffer yet. Peek at first 10 bytes, to get length and colon. */ + char peek[10]={0}; + bytes = recv(fd,peek,10,MSG_PEEK); + + if (bytes < 3) return NETSTRING_INCOMPLETE; + + /* No leading zeros allowed! */ + if (peek[0] == '0' && isdigit(peek[1])) + return NETSTRING_ERROR_LEADING_ZERO; + + /* The netstring must start with a number */ + if (!isdigit(peek[0])) return NETSTRING_ERROR_NO_LENGTH; + + int i, len; + len = i = 0; + + /* Read the number of bytes */ + for (i = 0; i < bytes && isdigit(peek[i]); i++) { + /* Error if more than 9 digits */ + if (i >= 9) return NETSTRING_ERROR_TOO_LONG; + /* Accumulate each digit, assuming ASCII. */ + len = len*10 + (peek[i] - '0'); + } + + /* Read the colon */ + if (peek[i++] != ':') return NETSTRING_ERROR_NO_COLON; + + /* alloc the memory needed for the whole netstring */ + read_len = len+i+1; + temp_buffer = pkg_malloc(read_len); + CHECK_MALLOC(temp_buffer); + + /* initialize the netstring struct */ + *netstring = pkg_malloc(sizeof(netstring_t)); + CHECK_MALLOC(netstring); + (*netstring)->start = i; + (*netstring)->read = 0; + (*netstring)->length = len; + (*netstring)->buffer = temp_buffer; + (*netstring)->string = NULL; + } else { + /* Continue reading into an existing buffer. */ + offset = (*netstring)->read; + read_len = (*netstring)->start+(*netstring)->length-offset+1; + temp_buffer = (*netstring)->buffer + offset; + } + + /* Read from the socket */ + bytes = recv(fd, temp_buffer, read_len, 0); + int total = (*netstring)->read += bytes; + + /* See if we have the whole netstring yet */ + if (read_len > bytes) { + return NETSTRING_INCOMPLETE; + } + + /* Test for the trailing comma */ + if (((*netstring)->buffer)[total-1] != ',') { + return NETSTRING_ERROR_NO_COMMA; + } + + /* Replace the comma with \0 */ + (*netstring)->buffer[total-1] = '\0'; + + /* Set the string pointer to the "body" of the netstring */ + (*netstring)->string = (*netstring)->buffer + (*netstring)->start; + return 0; +} + + +/* Reads a netstring from a `buffer` of length `buffer_length`. Writes + to `netstring_start` a pointer to the beginning of the string in + the buffer, and to `netstring_length` the length of the + string. Does not allocate any memory. If it reads successfully, + then it returns 0. If there is an error, then the return value will + be negative. The error values are: + + NETSTRING_ERROR_TOO_LONG More than 999999999 bytes in a field + NETSTRING_ERROR_NO_COLON No colon was found after the number + NETSTRING_ERROR_TOO_SHORT Number of bytes greater than buffer length + NETSTRING_ERROR_NO_COMMA No comma was found at the end + NETSTRING_ERROR_LEADING_ZERO Leading zeros are not allowed + NETSTRING_ERROR_NO_LENGTH Length not given at start of netstring + + If you're sending messages with more than 999999999 bytes -- about + 1 GB -- then you probably should not be doing so in the form of a + single netstring. This restriction is in place partially to protect + from malicious or erroneous input, and partly to be compatible with + D. J. Bernstein's reference implementation. + + Example: + if (netstring_read("3:foo,", 6, &str, &len) < 0) explode_and_die(); +*/ + +int netstring_read(char *buffer, size_t buffer_length, + char **netstring_start, size_t *netstring_length) +{ + int i; + size_t len = 0; + + /* Write default values for outputs */ + *netstring_start = NULL; *netstring_length = 0; + + /* Make sure buffer is big enough. Minimum size is 3. */ + if (buffer_length < 3) return NETSTRING_ERROR_TOO_SHORT; + + /* No leading zeros allowed! */ + if (buffer[0] == '0' && isdigit(buffer[1])) + return NETSTRING_ERROR_LEADING_ZERO; + + /* The netstring must start with a number */ + if (!isdigit(buffer[0])) return NETSTRING_ERROR_NO_LENGTH; + + /* Read the number of bytes */ + for (i = 0; i < buffer_length && isdigit(buffer[i]); i++) { + /* Error if more than 9 digits */ + if (i >= 9) return NETSTRING_ERROR_TOO_LONG; + /* Accumulate each digit, assuming ASCII. */ + len = len*10 + (buffer[i] - '0'); + } + + /* Check buffer length once and for all. Specifically, we make sure + that the buffer is longer than the number we've read, the length + of the string itself, and the colon and comma. */ + if (i + len + 1 >= buffer_length) return NETSTRING_ERROR_TOO_SHORT; + + /* Read the colon */ + if (buffer[i++] != ':') return NETSTRING_ERROR_NO_COLON; + + /* Test for the trailing comma, and set the return values */ + if (buffer[i + len] != ',') return NETSTRING_ERROR_NO_COMMA; + *netstring_start = &buffer[i]; *netstring_length = len; + + return 0; +} + +/* Return the length, in ASCII characters, of a netstring containing + `data_length` bytes. */ +size_t netstring_buffer_size(size_t data_length) +{ + if (data_length == 0) return 3; + return (size_t)ceil(log10((double)data_length + 1)) + data_length + 2; +} + +/* Allocate and create a netstring containing the first `len` bytes of + `data`. This must be manually freed by the client. If `len` is 0 + then no data will be read from `data`, and it may be NULL. */ +size_t netstring_encode_new(char **netstring, char *data, size_t len) +{ + char *ns; + size_t num_len = 1; + + *netstring = NULL; + + if (len == 0) { + ns = pkg_malloc(3); + if (!ns) { + return JSONRPC_ERROR_NO_MEMORY; + } + ns[0] = '0'; + ns[1] = ':'; + ns[2] = ','; + } else { + num_len = (size_t)ceil(log10((double)len + 1)); + ns = pkg_malloc(num_len + len + 2); + if (!ns) { + return JSONRPC_ERROR_NO_MEMORY; + } + sprintf(ns, "%lu:", (unsigned long)len); + memcpy(ns + num_len + 1, data, len); + ns[num_len + len + 1] = ','; + } + + *netstring = ns; + return num_len + len + 2; +} diff --git a/modules/janssonrpc-c/netstring.h b/modules/janssonrpc-c/netstring.h new file mode 100644 index 00000000000..edba4ccb755 --- /dev/null +++ b/modules/janssonrpc-c/netstring.h @@ -0,0 +1,62 @@ +/** + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __NETSTRING_STREAM_H +#define __NETSTRING_STREAM_H + +#include +#include + +typedef struct { + char* buffer; + char* string; + unsigned int start, read, length; +} netstring_t; + +void free_netstring(netstring_t* netstring); + +int netstring_read_evbuffer(struct bufferevent *bev, netstring_t **netstring); + +int netstring_read_fd(int fd, netstring_t **netstring); + +int netstring_read(char *buffer, size_t buffer_length, + char **netstring_start, size_t *netstring_length); + +size_t netstring_buffer_size(size_t data_length); + +size_t netstring_encode_new(char **netstring, char *data, size_t len); + +/* Errors that can occur during netstring parsing */ +typedef enum { + NETSTRING_ERROR_TOO_LONG = -1000, + NETSTRING_ERROR_NO_COLON, + NETSTRING_ERROR_TOO_SHORT, + NETSTRING_ERROR_NO_COMMA, + NETSTRING_ERROR_LEADING_ZERO, + NETSTRING_ERROR_NO_LENGTH, + NETSTRING_ERROR_BAD_FD, + NETSTRING_INCOMPLETE +} netstring_errors; + +#define NETSTRING_PEEKLEN 10 + +#endif diff --git a/modules/janssonrpc-c/test/mem-test.sh b/modules/janssonrpc-c/test/mem-test.sh new file mode 100755 index 00000000000..5405101403f --- /dev/null +++ b/modules/janssonrpc-c/test/mem-test.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd ../../../ +valgrind --leak-check=full ./kamailio -w . -E -f modules/jsonrpc-c/test/test.cfg -m 256 -M 256 diff --git a/modules/janssonrpc-c/test/run-tests.sh b/modules/janssonrpc-c/test/run-tests.sh new file mode 100755 index 00000000000..302b63b0de7 --- /dev/null +++ b/modules/janssonrpc-c/test/run-tests.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +cd ../../../ +./kamailio -w . -E -f modules/jsonrpc-c/test/test.cfg -m 256 -M 256 diff --git a/modules/janssonrpc-c/test/test.cfg b/modules/janssonrpc-c/test/test.cfg new file mode 100644 index 00000000000..6dd16cc6d49 --- /dev/null +++ b/modules/janssonrpc-c/test/test.cfg @@ -0,0 +1,94 @@ +# +# $Id$ +# +# jsonrpc module testing script + +debug = 2 +memlog=1 # debug level is higher or equal memlog +mem_summary=8 +log_stderror=yes +fork=no +children=1 +listen = 127.0.0.1:5060 + +check_via=no # (cmd. line: -v) +dns=yes # (cmd. line: -r) +rev_dns=no # (cmd. line: -R) +sip_warning=yes + +# ------------------ module loading ---------------------------------- + +loadpath "modules:modules_k" + +loadmodule "pv" +loadmodule "tm" +loadmodule "json" +loadmodule "xlog" +loadmodule "jsonrpc-c" + +#modparam("jsonrpc", "min_srv_ttl", 1); +modparam("jsonrpc", "result_pv", "$var(foobar)"); +modparam("jsonrpc", "server", "conn=pingpong;addr=foobar;port=7080;priority=10;weight=0;hwm=1"); +#modparam("jsonrpc", "server", "conn=pingpong;addr=localhost;port=7081;priority=10;weight=0;hwm=10"); +#modparam("jsonrpc", "server", "conn=pingpong;addr=localhost;port=7082;priority=10;weight=0;hwm=10"); +#modparam("jsonrpc", "server", "conn=pingpong;addr=localhost;port=7083;priority=10;weight=0;hwm=10"); +#modparam("jsonrpc", "server", "conn=pingpong;addr=localhost;port=7084;priority=10;weight=0;hwm=1"); +#modparam("jsonrpc", "server", "conn=fail;addr=localhost;port=9999;priority=10;weight=0"); +#modparam("jsonrpc", "server", "conn=test0;srv=_test0._tcp.sandbox.internal"); +#modparam("jsonrpc", "server", "conn=test0;srv=_test1._tcp.sandbox.internal"); +#modparam("jsonrpc", "server", "conn=tests;srv=_test2._tcp.sandbox.internal"); + + +# ------------------- unit tests ------------------------------------ + +route { + + jsonrpc_request("test0", "echo", '[{"foo":"bar"}]', "route=RESPONSE;retry=10;timeout=30"); + + xlog("done\n"); +} + +route[RESPONSE] { + if(json_get($var(jsrpc_result), "internal_error", "$var(internal)")) { + route(INTERNAL); + } else if(json_get($var(jsrpc_result), "error", "$var(error)")) { + route(ERROR); + } else if(json_get($var(jsrpc_result), "result", "$var(result)")) { + route(RESULT); + } + t_reply("200", "OK"); +} + +route[RESULT] { + xlog("result is $var(result)\n"); + xlog("success\n"); +} + +route[ERROR] { + xlog("There was an error\n"); + if(json_get($var(error), "code", "$var(c)")) { + xlog("code is $var(c)\n"); + } + + if(json_get($var(error), "message", "$var(r)")) { + xlog("error is $var(r)\n"); + } + + if(json_get($var(error), "data", "$var(d)")) { + xlog("data is $var(d)\n"); + } +} + +route[INTERNAL] { + xlog("There was an internal error\n"); + + json_get($var(internal), "code", "$var(c)"); + xlog("code is $var(c)\n"); + + json_get($var(internal), "message", "$var(r)"); + xlog("error is $var(r)\n"); + + if(json_get($var(internal), "data", "$var(d)")) { + xlog("request is $var(d)\n"); + } +} diff --git a/modules/janssonrpc-c/unit_tests/Makefile b/modules/janssonrpc-c/unit_tests/Makefile new file mode 100644 index 00000000000..5e6a93759b3 --- /dev/null +++ b/modules/janssonrpc-c/unit_tests/Makefile @@ -0,0 +1,11 @@ +CC = gcc +FILES = *.c ../netstring.c ../jsonrpc_global.c seatest/seatest.c +OUT_EXE = test + +build: $(FILES) + $(CC) -std=gnu99 -pedantic -D TEST -o $(OUT_EXE) $(FILES) -lm -levent + +clean: + rm -f *.o core + +rebuild: clean build diff --git a/modules/janssonrpc-c/unit_tests/netstring.c b/modules/janssonrpc-c/unit_tests/netstring.c new file mode 100644 index 00000000000..19a45893831 --- /dev/null +++ b/modules/janssonrpc-c/unit_tests/netstring.c @@ -0,0 +1,351 @@ +/** + * $Id$ + * + * Copyright (C) 2013 Flowroute LLC (flowroute.com) + * + * This file is part of Kamailio, a free SIP server. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version + * + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include //temp +#include +#include +#include //temp +#include +#include "test.h" +#include "../netstring.h" + + +int fd[2]; +const int writer = 0; +const int reader = 1; +struct event_base *evbase; +struct bufferevent *bev; +netstring_t* ns_buffer; +char* ns; +char* next; + + +/* *********************************************************************** + * tests for netstring_read_evbuffer * + * ***********************************************************************/ + +// +// Test normal operation of netstring_read_fd, with data received in three chunks. +// +void ev_init(void (*cb)(struct bufferevent*, void*)) +{ + ns_buffer = NULL; + evbase = event_base_new(); + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + evutil_make_socket_nonblocking(fd[reader]); + bev = bufferevent_socket_new(evbase, fd[reader], BEV_OPT_CLOSE_ON_FREE); + bufferevent_setcb(bev, cb, NULL, NULL, NULL); + bufferevent_enable(bev, EV_READ); +} + +void read_evb_cb3(struct bufferevent *bev, void *ptr) +{ + int r = netstring_read_evbuffer(bev, &ns_buffer); + assert_int_equal(0, r); + if (r == 0) + assert_string_equal("foobar-bizbaz", ns_buffer->string); + + int res = event_base_loopbreak(evbase); + return; +} + +void read_evb_cb2(struct bufferevent *bev, void *ptr) +{ + assert_int_equal(NETSTRING_INCOMPLETE, netstring_read_evbuffer(bev, &ns_buffer)); + send(fd[writer], next, 10, 0); + bufferevent_setcb(bev, read_evb_cb3, NULL, NULL, NULL); + return; +} + +void read_evb_cb1(struct bufferevent *bev, void *ptr) +{ + assert_int_equal(NETSTRING_INCOMPLETE, netstring_read_evbuffer(bev, &ns_buffer)); + send(fd[writer], next, 5, 0); + next = next+5; + bufferevent_setcb(bev, read_evb_cb2, NULL, NULL, NULL); + return; +} + +void test_read_evbuffer() +{ + ns = "13:foobar-bizbaz,"; + ev_init(read_evb_cb1); + send(fd[writer], ns, 2, 0); + next = ns+2; + event_base_dispatch(evbase); +} + +void test_read_evbuffer_one_chunk() +{ + ns = "13:foobar-bizbaz,"; + ev_init(read_evb_cb3); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +// +// Abnormal test scenarios for netstring_read_evbuffer +// + +void read_evb_leading_zero_cb() +{ + assert_int_equal(NETSTRING_ERROR_LEADING_ZERO, netstring_read_evbuffer(bev, &ns_buffer)); + int res = event_base_loopbreak(evbase); +} + +void test_read_evbuffer_leading_zero() +{ + ns = "0001:abbbbbbb,"; + ev_init(read_evb_leading_zero_cb); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +void read_evb_no_length_cb() +{ + assert_int_equal(NETSTRING_ERROR_NO_LENGTH, netstring_read_evbuffer(bev, &ns_buffer)); + int res = event_base_loopbreak(evbase); +} + +void test_read_evbuffer_no_length() +{ + ns = "a:......................b,"; + ev_init(read_evb_no_length_cb); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +void read_evb_too_long_cb() +{ + assert_int_equal(NETSTRING_ERROR_TOO_LONG, netstring_read_evbuffer(bev, &ns_buffer)); + int res = event_base_loopbreak(evbase); +} + +void test_read_evbuffer_too_long() +{ + ns = "999999999999999999999:...,"; + ev_init(read_evb_too_long_cb); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +void read_evb_no_colon_cb() +{ + assert_int_equal(NETSTRING_ERROR_NO_COLON, netstring_read_evbuffer(bev, &ns_buffer)); + int res = event_base_loopbreak(evbase); +} + +void test_read_evbuffer_no_colon() +{ + ns = "999abcc,"; + ev_init(read_evb_no_colon_cb); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +void read_evb_no_comma_cb() +{ + assert_int_equal(NETSTRING_ERROR_NO_COMMA, netstring_read_evbuffer(bev, &ns_buffer)); + int res = event_base_loopbreak(evbase); +} + +void test_read_evbuffer_no_comma() +{ + ns = "2:ab."; + ev_init(read_evb_no_comma_cb); + send(fd[writer], ns, strlen(ns), 0); + event_base_dispatch(evbase); +} + +/* *********************************************************************** + * tests for netstring_read_fd * + * ***********************************************************************/ + +// +// Test normal operation of netstring_read_fd, with data received in three chunks. +// +void test_read_fd() +{ + char* ns = "13:foobar-bizbaz,"; + char* temp; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, 4, 0); + assert_int_equal(NETSTRING_INCOMPLETE, netstring_read_fd(fd[reader], &buffer)); + temp = ns+4; + send(fd[writer], temp, 3, 0); + assert_int_equal(NETSTRING_INCOMPLETE, netstring_read_fd(fd[reader], &buffer)); + temp = temp+3; + send(fd[writer], temp, 10, 0); + int r = netstring_read_fd(fd[reader], &buffer); + assert_int_equal(0, r); + if (r == 0) + assert_string_equal("foobar-bizbaz", buffer->string); +} + + +// +// Abnormal test scenarios for netstring_read_fd +// +void test_read_fd_leading_zero() +{ + char *ns = "0001:a,"; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, 7, 0); + assert_int_equal(NETSTRING_ERROR_LEADING_ZERO, netstring_read_fd(fd[reader], &buffer)); +} + +void test_read_fd_no_length() +{ + char *ns = "ab,"; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, 3, 0); + assert_int_equal(NETSTRING_ERROR_NO_LENGTH, netstring_read_fd(fd[reader], &buffer)); +} + +void test_read_fd_too_long() +{ + char *ns = "999999999999999999999:...,"; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, strlen(ns), 0); + assert_int_equal(NETSTRING_ERROR_TOO_LONG, netstring_read_fd(fd[reader], &buffer)); +} + +void test_read_fd_no_colon() +{ + char *ns = "999ab,"; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, strlen(ns), 0); + assert_int_equal(NETSTRING_ERROR_NO_COLON, netstring_read_fd(fd[reader], &buffer)); +} + +void test_read_fd_no_comma() +{ + char *ns = "5:......"; + int fd[2]; + const int writer = 0; + const int reader = 1; + socketpair(PF_LOCAL, SOCK_STREAM, 0, fd); + netstring_t* buffer = NULL; + + send(fd[writer], ns, strlen(ns), 0); + assert_int_equal(NETSTRING_ERROR_NO_COMMA, netstring_read_fd(fd[reader], &buffer)); +} + +/// +// Test for netstring_encode_new +// +void test_encode_new() +{ + char *ns; + char *data = "foobar-bizbaz"; + int len = netstring_encode_new(&ns, data, strlen(data)); + assert_int_equal(17, len); + char *temp = malloc(len+1); + memcpy(temp, ns, len); + temp[len] = '\0'; + assert_string_equal("13:foobar-bizbaz,", temp); +} + +// put the tests into a fixture... +// +void test_fixture_read_fd(void) +{ + printf("Running read_fd tests...\n"); + test_fixture_start(); + run_test(test_read_fd); + run_test(test_read_fd_leading_zero); + run_test(test_read_fd_no_length); + run_test(test_read_fd_too_long); + run_test(test_read_fd_no_colon); + run_test(test_read_fd_no_comma); + test_fixture_end(); +} + +void test_fixture_read_evbuffer(void) +{ + printf("Running read_evbuffer tests...\n"); + test_fixture_start(); + run_test(test_read_evbuffer); + run_test(test_read_evbuffer_one_chunk); + run_test(test_read_evbuffer_leading_zero); + run_test(test_read_evbuffer_no_length); + run_test(test_read_evbuffer_too_long); +// run_test(test_read_evbuffer_no_colon); +// ...skip due to TODO in netstring.c + run_test(test_read_evbuffer_no_comma); + test_fixture_end(); +} + +void test_fixture_encode(void) +{ + printf("Running encode_new tests...\n"); + test_fixture_start(); + run_test(test_encode_new); + test_fixture_end(); +} + +// +// put the fixture into a suite... +// +void all_tests(void) +{ + test_fixture_read_fd(); + test_fixture_read_evbuffer(); + test_fixture_encode(); +} + +// +// run the suite! +// +int main(int argc, char** argv) +{ + return run_tests(all_tests); +} + diff --git a/modules/janssonrpc-c/unit_tests/seatest/license.txt b/modules/janssonrpc-c/unit_tests/seatest/license.txt new file mode 100644 index 00000000000..3fbf632a0a7 --- /dev/null +++ b/modules/janssonrpc-c/unit_tests/seatest/license.txt @@ -0,0 +1,19 @@ + Copyright (c) 2010 Keith Nicholas + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. \ No newline at end of file diff --git a/modules/janssonrpc-c/unit_tests/seatest/seatest.c b/modules/janssonrpc-c/unit_tests/seatest/seatest.c new file mode 100644 index 00000000000..cb68fb5c65c --- /dev/null +++ b/modules/janssonrpc-c/unit_tests/seatest/seatest.c @@ -0,0 +1,248 @@ +#include "seatest.h" +#include +#ifdef WIN32 +#include +#include "windows.h" +#else +unsigned int GetTickCount() { return 0;} +void _getch( void ) { } +#endif + +static int sea_tests_run = 0; +static int sea_tests_passed = 0; +static int sea_tests_failed = 0; +static char* seatest_current_fixture; + +static void (*seatest_suite_setup_func)( void ) = 0; +static void (*seatest_suite_teardown_func)( void ) = 0; +static void (*seatest_fixture_setup)( void ) = 0; +static void (*seatest_fixture_teardown)( void ) = 0; + + + +void suite_setup(void (*setup)( void )) +{ + seatest_suite_setup_func = setup; +} +void suite_teardown(void (*teardown)( void )) +{ + seatest_suite_teardown_func = teardown; +} + +void seatest_suite_setup( void ) +{ + if(seatest_suite_setup_func != 0) seatest_suite_setup_func(); +} + +void seatest_suite_teardown( void ) +{ + if(seatest_suite_teardown_func != 0) seatest_suite_teardown_func(); +} + +void fixture_setup(void (*setup)( void )) +{ + seatest_fixture_setup = setup; +} +void fixture_teardown(void (*teardown)( void )) +{ + seatest_fixture_teardown = teardown; +} + +void seatest_setup( void ) +{ + if(seatest_fixture_setup != 0) seatest_fixture_setup(); +} + +void seatest_teardown( void ) +{ + if(seatest_fixture_teardown != 0) seatest_fixture_teardown(); +} + +char* test_file_name(char* path) +{ + char* file = path + strlen(path); + while(file != path && *file!= '\\' ) file--; + if(*file == '\\') file++; + return file; +} + +static int seatest_fixture_tests_run; +static int seatest_fixture_tests_failed; + +void seatest_simple_test_result(int passed, char* reason, const char* function, unsigned int line) +{ + if (!passed) + { + printf("%-20s Line %-5d %s\r\n", function, line, reason ); + sea_tests_failed++; + } + else + { + sea_tests_passed++; + } +} + +void seatest_assert_true(int test, const char* function, unsigned int line) +{ + seatest_simple_test_result(test, "Should of been true", function, line); + +} + +void seatest_assert_false(int test, const char* function, unsigned int line) +{ + seatest_simple_test_result(!test, "Should of been false", function, line); +} + + +void seatest_assert_int_equal(int expected, int actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %d but was %d", expected, actual); + seatest_simple_test_result(expected==actual, s, function, line); +} + +void seatest_assert_ulong_equal(unsigned long expected, unsigned long actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %lu but was %lu", expected, actual); + seatest_simple_test_result(expected==actual, s, function, line); +} + +void seatest_assert_float_equal( float expected, float actual, float delta, const char* function, unsigned int line ) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + float result = expected-actual; + sprintf(s, "Expected %f but was %f", expected, actual); + if(result < 0.0) result = 0.0f - result; + seatest_simple_test_result( result <= delta, s, function, line); +} + +void seatest_assert_double_equal( double expected, double actual, double delta, const char* function, unsigned int line ) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + double result = expected-actual; + sprintf(s, "Expected %f but was %f", expected, actual); + if(result < 0.0) result = 0.0 - result; + seatest_simple_test_result( result <= delta, s, function, line); +} + +void seatest_assert_string_equal(char* expected, char* actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %s but was %s", expected, actual); + seatest_simple_test_result(strcmp(expected, actual)==0, s, function, line); +} + +void seatest_assert_string_ends_with(char* expected, char* actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %s to end with %s", actual, expected); + seatest_simple_test_result(strcmp(expected, actual+(strlen(actual)-strlen(expected)))==0, s, function, line); +} + +void seatest_assert_string_starts_with(char* expected, char* actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %s to start with %s", actual, expected); + seatest_simple_test_result(strncmp(expected, actual, strlen(expected))==0, s, function, line); +} + +void seatest_assert_string_contains(char* expected, char* actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %s to be in %s", expected, actual); + seatest_simple_test_result(strstr(actual, expected)!=0, s, function, line); +} + +void seatest_assert_string_doesnt_contain(char* expected, char* actual, const char* function, unsigned int line) +{ + char s[SEATEST_PRINT_BUFFER_SIZE]; + sprintf(s, "Expected %s not to have %s in it", actual, expected); + seatest_simple_test_result(strstr(actual, expected)==0, s, function, line); +} + +void seatest_run_test(void) +{ + sea_tests_run++; +} + +void seatest_header_printer(char* s, int length, char f) +{ + int l = strlen(s); + int d = (length- (l + 2)) / 2; + int i; + for(i = 0; i 0) { + printf(" Failed\r\n"); + } + else { + printf(" ALL TESTS PASSED\r\n"); + } + printf(" %d tests run\r\n", sea_tests_run); + printf(" in %lu ms\r\n",end - start); + printf("==================================================\r\n"); + + _getch(); + return sea_tests_failed == 0; +} + diff --git a/modules/janssonrpc-c/unit_tests/seatest/seatest.h b/modules/janssonrpc-c/unit_tests/seatest/seatest.h new file mode 100644 index 00000000000..964eaf91ba5 --- /dev/null +++ b/modules/janssonrpc-c/unit_tests/seatest/seatest.h @@ -0,0 +1,74 @@ +#ifndef SEATEST_H +#define SEATEST_H +#include + +/* +Defines +*/ + +#define SEATEST_VERSION "0.5" +#define SEATEST_PROJECT_HOME "http://code.google.com/p/seatest/" +#define SEATEST_PRINT_BUFFER_SIZE 100000 + +/* +Declarations +*/ + +void seatest_test_fixture_start(char* filepath); +void seatest_test_fixture_end( void ); +void seatest_simple_test_result(int passed, char* reason, const char* function, unsigned int line); +void seatest_assert_true(int test, const char* function, unsigned int line); +void seatest_assert_false(int test, const char* function, unsigned int line); +void seatest_assert_int_equal(int expected, int actual, const char* function, unsigned int line); +void seatest_assert_ulong_equal(unsigned long expected, unsigned long actual, const char* function, unsigned int line); +void seatest_assert_float_equal(float expected, float actual, float delta, const char* function, unsigned int line); +void seatest_assert_double_equal(double expected, double actual, double delta, const char* function, unsigned int line); +void seatest_assert_string_equal(char* expected, char* actual, const char* function, unsigned int line); +void seatest_assert_string_ends_with(char* expected, char* actual, const char* function, unsigned int line); +void seatest_assert_string_starts_with(char* expected, char* actual, const char* function, unsigned int line); +void seatest_assert_string_contains(char* expected, char* actual, const char* function, unsigned int line); +void seatest_assert_string_doesnt_contain(char* expected, char* actual, const char* function, unsigned int line); +int seatest_should_run( char* fixture, char* test); +void seatest_run_test(void); +void seatest_setup( void ); +void seatest_teardown( void ); +void seatest_suite_teardown( void ); +void seatest_suite_setup( void ); + +/* +Assert Macros +*/ + +#define assert_true(test) do { seatest_assert_true(test, __FUNCTION__, __LINE__); } while (0) +#define assert_false(test) do { seatest_assert_false(test, __FUNCTION__, __LINE__); } while (0) +#define assert_int_equal(expected, actual) do { seatest_assert_int_equal(expected, actual, __FUNCTION__, __LINE__); } while (0) +#define assert_ulong_equal(expected, actual) do { seatest_assert_ulong_equal(expected, actual, __FUNCTION__, __LINE__); } while (0) +#define assert_string_equal(expected, actual) do { seatest_assert_string_equal(expected, actual, __FUNCTION__, __LINE__); } while (0) +#define assert_n_array_equal(expected, actual, n) do { int seatest_count; for(seatest_count=0; seatest_count +#include +#include "seatest/seatest.h" + +struct _str{ + char* s; + int len; +}; + +typedef struct _str str; + +#define pkg_malloc malloc +#define shm_malloc malloc + +#define pkg_free free +#define shm_free free + +#define ERR printf +#define ALERT printf + +#endif /* _TEST_H_ */