PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/mod_cdr_rabbitmq.c

https://gitlab.com/bratner/mod_cdr_rabbitmq
C | 350 lines | 249 code | 53 blank | 48 comment | 42 complexity | a8c16b5bf7898a94db7d1963dcce9c0a MD5 | raw file
  1. /*
  2. * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  3. * Copyright (C) 2005-2012, Anthony Minessale II <anthm@freeswitch.org>
  4. *
  5. * Version: MPL 1.1
  6. *
  7. * The contents of this file are subject to the Mozilla Public License Version
  8. * 1.1 (the "License"); you may not use this file except in compliance with
  9. * the License. You may obtain a copy of the License at
  10. * http://www.mozilla.org/MPL/
  11. *
  12. * Software distributed under the License is distributed on an "AS IS" basis,
  13. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. * for the specific language governing rights and limitations under the
  15. * License.
  16. *
  17. * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  18. *
  19. * The Initial Developer of the Original Code is
  20. * Anthony Minessale II <anthm@freeswitch.org>
  21. * Portions created by the Initial Developer are Copyright (C)
  22. * the Initial Developer. All Rights Reserved.
  23. *
  24. * mod_cdr_rabbitmqr module developers:
  25. * Vadim Senderovich <daddyzgm@gmail.com>
  26. * Boris Ratner <ratner2@gmail.com>
  27. *
  28. * made for http://commpeak.com
  29. *
  30. * mod_cdr_rabbitmq.c -- Rabbitmq CDR Module
  31. *
  32. * Derived from:
  33. * mod_cdr_mongodb.c -- CDR Module to mongodb
  34. *
  35. */
  36. #include <switch.h>
  37. #include <amqp.h>
  38. #include <amqp_framing.h>
  39. #include <amqp_tcp_socket.h>
  40. static struct {
  41. switch_memory_pool_t *pool;
  42. int shutdown;
  43. char *rabbitmq_host;
  44. int rabbitmq_port;
  45. char *rabbitmq_username;
  46. char *rabbitmq_password;
  47. char *rabbitmq_vhost;
  48. char *rabbitmq_exchange;
  49. char *rabbitmq_exchange_b;
  50. char *rabbitmq_routing_key;
  51. char *rabbitmq_routing_key_b;
  52. amqp_connection_state_t conn;
  53. amqp_socket_t* socket;
  54. switch_mutex_t *rabbitmq_mutex;
  55. switch_bool_t log_b;
  56. switch_bool_t encode_values;
  57. } globals;
  58. static switch_xml_config_item_t config_settings[] = {
  59. /* key, flags, ptr, default_value, syntax, helptext */
  60. SWITCH_CONFIG_ITEM_STRING_STRDUP("host", CONFIG_RELOADABLE, &globals.rabbitmq_host, "127.0.0.1", NULL, "Rabbitmq server host address"),
  61. SWITCH_CONFIG_ITEM("port", SWITCH_CONFIG_INT, CONFIG_RELOADABLE, &globals.rabbitmq_port, 5672, NULL, NULL, "Rabbitmq server TCP port"),
  62. SWITCH_CONFIG_ITEM_STRING_STRDUP("username", CONFIG_RELOADABLE, &globals.rabbitmq_username, NULL, NULL, "Rabbitmq server username"),
  63. SWITCH_CONFIG_ITEM_STRING_STRDUP("password", CONFIG_RELOADABLE, &globals.rabbitmq_password, NULL, NULL, "Rabbitmq server password"),
  64. SWITCH_CONFIG_ITEM_STRING_STRDUP("vhost", CONFIG_RELOADABLE, &globals.rabbitmq_vhost, NULL, NULL, "Rabbitmq server vhost"),
  65. SWITCH_CONFIG_ITEM_STRING_STRDUP("exchange", CONFIG_RELOADABLE, &globals.rabbitmq_exchange, NULL, NULL, "Rabbitmq server exchange"),
  66. SWITCH_CONFIG_ITEM_STRING_STRDUP("exchange_b", CONFIG_RELOADABLE, &globals.rabbitmq_exchange_b, NULL, NULL, "Rabbitmq server exchange for leg_b"),
  67. SWITCH_CONFIG_ITEM_STRING_STRDUP("routing_key", CONFIG_RELOADABLE, &globals.rabbitmq_routing_key, NULL, NULL, "Rabbitmq server routing key"),
  68. SWITCH_CONFIG_ITEM_STRING_STRDUP("routing_key_b", CONFIG_RELOADABLE, &globals.rabbitmq_routing_key_b, NULL, NULL, "Rabbitmq server routing key for leg_b"),
  69. SWITCH_CONFIG_ITEM("log-b-leg", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &globals.log_b, SWITCH_TRUE, NULL, NULL, "Log B-leg in addition to A-leg"),
  70. SWITCH_CONFIG_ITEM("encode", SWITCH_CONFIG_BOOL, CONFIG_RELOADABLE, &globals.encode_values, SWITCH_TRUE, NULL, NULL, "URL Encode values in JSON"),
  71. SWITCH_CONFIG_ITEM_END()
  72. };
  73. SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_rabbitmq_load);
  74. SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_rabbitmq_shutdown);
  75. SWITCH_MODULE_DEFINITION(mod_cdr_rabbitmq, mod_cdr_rabbitmq_load, mod_cdr_rabbitmq_shutdown, NULL);
  76. static switch_status_t check_amqp_error(amqp_rpc_reply_t x, char const *context) {
  77. switch (x.reply_type) {
  78. case AMQP_RESPONSE_NORMAL:
  79. return SWITCH_STATUS_SUCCESS;
  80. break;
  81. case AMQP_RESPONSE_NONE:
  82. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: missing RPC reply type!\n", context);
  83. return SWITCH_STATUS_FALSE;
  84. break;
  85. case AMQP_RESPONSE_LIBRARY_EXCEPTION:
  86. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: %s\n", context, amqp_error_string2(x.library_error));
  87. return SWITCH_STATUS_FALSE;
  88. break;
  89. case AMQP_RESPONSE_SERVER_EXCEPTION:
  90. switch (x.reply.id) {
  91. case AMQP_CONNECTION_CLOSE_METHOD: {
  92. amqp_connection_close_t *m = (amqp_connection_close_t *) x.reply.decoded;
  93. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: server connection error %d, message: %.*s\n",
  94. context,
  95. m->reply_code,
  96. (int) m->reply_text.len, (char *) m->reply_text.bytes);
  97. return SWITCH_STATUS_FALSE;
  98. break;
  99. }
  100. case AMQP_CHANNEL_CLOSE_METHOD: {
  101. amqp_channel_close_t *m = (amqp_channel_close_t *) x.reply.decoded;
  102. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: server channel error %d, message: %.*s\n",
  103. context,
  104. m->reply_code,
  105. (int) m->reply_text.len, (char *) m->reply_text.bytes);
  106. return SWITCH_STATUS_FALSE;
  107. break;
  108. }
  109. default:
  110. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "%s: unknown server error, method id 0x%08X\n", context, x.reply.id);
  111. return SWITCH_STATUS_FALSE;
  112. break;
  113. }
  114. break;
  115. }
  116. return SWITCH_STATUS_FALSE;
  117. }
  118. static switch_status_t rabbitmq_publish_data(char *cdr, int leg_b)
  119. {
  120. amqp_basic_properties_t props;
  121. char * exchange = globals.rabbitmq_exchange;
  122. char * routing_key = globals.rabbitmq_routing_key;
  123. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Publishing leg %c CDR to rabbitmq. Total of %d bytes\n", leg_b?'b':'a', (int) strlen(cdr));
  124. /* use leg_a exchange and routing key for leg_b if not specified */
  125. if(leg_b && globals.rabbitmq_exchange_b)
  126. exchange = globals.rabbitmq_exchange_b;
  127. if(leg_b && globals.rabbitmq_routing_key_b)
  128. routing_key = globals.rabbitmq_routing_key_b;
  129. props._flags = AMQP_BASIC_CONTENT_TYPE_FLAG | AMQP_BASIC_DELIVERY_MODE_FLAG;
  130. props.content_type = amqp_cstring_bytes("text/plain");
  131. props.delivery_mode = 1;
  132. if (0 != amqp_basic_publish(globals.conn,
  133. 1,
  134. amqp_cstring_bytes(exchange),
  135. amqp_cstring_bytes(routing_key),
  136. 0,
  137. 0,
  138. &props,
  139. amqp_cstring_bytes(cdr))) {
  140. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Publish failed\n");
  141. return SWITCH_STATUS_FALSE;
  142. }
  143. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Publish succeeded\n");
  144. return SWITCH_STATUS_SUCCESS;
  145. }
  146. static switch_status_t connect_to_rabbitmq()
  147. {
  148. int status;
  149. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connecting to Rabbitmq server %s:%d\n", globals.rabbitmq_host, globals.rabbitmq_port);
  150. /* if reconnecting then cleanup previous connection */
  151. if(globals.conn)
  152. amqp_destroy_connection(globals.conn);
  153. globals.conn = amqp_new_connection();
  154. if(!globals.conn){
  155. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create Rabbitmq connection\n");
  156. goto return_error;
  157. }
  158. globals.socket = amqp_tcp_socket_new(globals.conn);
  159. if(!globals.socket){
  160. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to create Rabbitmq socket\n");
  161. amqp_destroy_connection(globals.conn);
  162. goto return_error;
  163. }
  164. status = amqp_socket_open(globals.socket, globals.rabbitmq_host, globals.rabbitmq_port);
  165. if(status){
  166. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Failed to establish Rabbitmq connection\n");
  167. amqp_destroy_connection(globals.conn);
  168. }
  169. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Connection established, trying authentication\n");
  170. if (check_amqp_error(amqp_login(globals.conn, globals.rabbitmq_vhost, 0, 131072, 0, AMQP_SASL_METHOD_PLAIN, globals.rabbitmq_username, globals.rabbitmq_password), "connect_to_rabbitmq") == SWITCH_STATUS_FALSE) {
  171. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Rabbitmq Authentication failed\n");
  172. goto return_error;
  173. }
  174. amqp_channel_open(globals.conn, 1);
  175. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Authenticated, openning channel\n");
  176. if (check_amqp_error(amqp_get_rpc_reply(globals.conn), "connect_to_rabbitmq") == SWITCH_STATUS_FALSE) {
  177. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Rabbitmq openning channel failed\n");
  178. goto return_error;
  179. }
  180. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Successfully connected to rabbitmq\n");
  181. return SWITCH_STATUS_SUCCESS;
  182. return_error:
  183. return SWITCH_STATUS_FALSE;
  184. }
  185. static switch_status_t my_on_reporting(switch_core_session_t *session)
  186. {
  187. cJSON *json_cdr = NULL;
  188. char *json_text = NULL;
  189. switch_channel_t *channel = switch_core_session_get_channel(session);
  190. switch_status_t status = SWITCH_STATUS_FALSE;
  191. int is_b;
  192. if (globals.shutdown) {
  193. return SWITCH_STATUS_SUCCESS;
  194. }
  195. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Starting on_reproting function, creating json\n");
  196. is_b = channel && switch_channel_get_originator_caller_profile(channel);
  197. if (!globals.log_b && is_b) {
  198. const char *force_cdr = switch_channel_get_variable(channel, SWITCH_FORCE_PROCESS_CDR_VARIABLE);
  199. if (!switch_true(force_cdr)) {
  200. return SWITCH_STATUS_SUCCESS;
  201. }
  202. }
  203. if (switch_ivr_generate_json_cdr(session, &json_cdr, globals.encode_values?1:0) != SWITCH_STATUS_SUCCESS) {
  204. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Generating JSON Data!\n");
  205. return SWITCH_STATUS_FALSE;
  206. }
  207. json_text = cJSON_PrintUnformatted(json_cdr);
  208. if (!json_text) {
  209. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");
  210. goto finish;
  211. }
  212. switch_mutex_lock(globals.rabbitmq_mutex);
  213. status = SWITCH_STATUS_SUCCESS;
  214. if (rabbitmq_publish_data(json_text, is_b) == SWITCH_STATUS_FALSE) {
  215. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Rabbitmq connection failed; attempting reconnect...\n");
  216. if (connect_to_rabbitmq() == SWITCH_STATUS_SUCCESS) {
  217. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Rabbitmq connection re-established.\n");
  218. if (rabbitmq_publish_data(json_text, is_b) == SWITCH_STATUS_SUCCESS) {
  219. goto finish;
  220. }
  221. } else {
  222. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Rabbitmq reconnect failed\n");
  223. }
  224. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Rabbitmq failed to publish message\n");
  225. }
  226. finish:
  227. cJSON_Delete(json_cdr);
  228. switch_safe_free(json_text);
  229. switch_mutex_unlock(globals.rabbitmq_mutex);
  230. return status;
  231. }
  232. static switch_state_handler_table_t state_handlers = {
  233. /*.on_init */ NULL,
  234. /*.on_routing */ my_on_reporting,
  235. /*.on_execute */ NULL,
  236. /*.on_hangup */ NULL,
  237. /*.on_exchange_media */ NULL,
  238. /*.on_soft_execute */ NULL,
  239. /*.on_consume_media */ NULL,
  240. /*.on_hibernate */ NULL,
  241. /*.on_reset */ NULL,
  242. /*.on_park */ NULL,
  243. /*.on_reporting */ my_on_reporting
  244. };
  245. static switch_status_t load_config(switch_memory_pool_t *pool)
  246. {
  247. switch_status_t status = SWITCH_STATUS_SUCCESS;
  248. if (switch_xml_config_parse_module_settings("cdr_rabbitmq.conf", SWITCH_FALSE, config_settings) != SWITCH_STATUS_SUCCESS) {
  249. return SWITCH_STATUS_FALSE;
  250. }
  251. return status;
  252. }
  253. SWITCH_MODULE_LOAD_FUNCTION(mod_cdr_rabbitmq_load)
  254. {
  255. memset(&globals, 0, sizeof(globals));
  256. globals.pool = pool;
  257. if (load_config(pool) != SWITCH_STATUS_SUCCESS) {
  258. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to load or parse config!\n");
  259. return SWITCH_STATUS_FALSE;
  260. }
  261. if (connect_to_rabbitmq() == SWITCH_STATUS_FALSE) {
  262. return SWITCH_STATUS_FALSE;
  263. }
  264. switch_mutex_init(&globals.rabbitmq_mutex, SWITCH_MUTEX_NESTED, pool);
  265. switch_core_add_state_handler(&state_handlers);
  266. *module_interface = switch_loadable_module_create_module_interface(pool, modname);
  267. return SWITCH_STATUS_SUCCESS;
  268. }
  269. SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_cdr_rabbitmq_shutdown)
  270. {
  271. globals.shutdown = 1;
  272. switch_core_remove_state_handler(&state_handlers);
  273. if (check_amqp_error(amqp_channel_close(globals.conn, 1, AMQP_REPLY_SUCCESS), "mod_cdr_rabbitmq_shutdown") == SWITCH_STATUS_FALSE) {
  274. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Channel close error\n");
  275. goto return_success;
  276. }
  277. if (check_amqp_error(amqp_connection_close(globals.conn, AMQP_REPLY_SUCCESS), "mod_cdr_rabbitmq_shutdown") == SWITCH_STATUS_FALSE) {
  278. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Connection close error\n");
  279. goto return_success;
  280. }
  281. if (amqp_destroy_connection(globals.conn) < 0) {
  282. switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Connection destroy error\n");
  283. goto return_success;
  284. }
  285. return_success:
  286. return SWITCH_STATUS_SUCCESS;
  287. }
  288. /* For Emacs:
  289. * Local Variables:
  290. * mode:c
  291. * indent-tabs-mode:t
  292. * tab-width:4
  293. * c-basic-offset:4
  294. * End:
  295. * For VIM:
  296. * vim:set softtabstop=4 shiftwidth=4 tabstop=4:
  297. */