Browse Source

evapi: new module to push event details via tcp to external applications

- uses libev for handling connections
Daniel-Constantin Mierla 11 years ago
parent
commit
0acecd065e

+ 36 - 0
modules/evapi/Makefile

@@ -0,0 +1,36 @@
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=evapi.so
+
+ifeq ($(CROSS_COMPILE),)
+	BUILDER = $(shell which pkg-config)
+ifneq ($(BUILDER),)
+		PKGLIBUV = $(shell $(BUILDER) --exists libuv > /dev/null 2>&1 ; echo $$? )
+ifneq ($(PKGLIBUV),0)
+			BUILDER =
+endif
+endif
+endif
+
+ifneq ($(BUILDER),)
+	DEFS += $(shell $(BUILDER) --cflags libev)
+	LIBS += $(shell $(BUILDER) --libs libev)
+else
+ifeq ($(OS), darwin)
+	DEFS += -I/opt/local/include
+	LIBS += -L/opt/local/lib
+endif
+	DEFS += -I$(LOCALBASE)/include -I$(SYSBASE)/include
+	LIBS += -L$(LOCALBASE)/lib -L$(SYSBASE)/lib -lev
+endif
+
+
+DEFS+=-DKAMAILIO_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srutils/srutils
+
+include ../../Makefile.modules

+ 133 - 0
modules/evapi/README

@@ -0,0 +1,133 @@
+EVAPI Module
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+Edited by
+
+Daniel-Constantin Mierla
+
+   <[email protected]>
+
+   Copyright © 2014 asipto.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. workers (int)
+              3.2. bind_addr (str)
+
+        4. Functions
+
+              4.1. evapi_relay(event, data)
+
+   List of Examples
+
+   1.1. Set workers parameter
+   1.2. Set bind_addr parameter
+   1.3. evapi_relay usage
+   1.4. TCP message
+
+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. workers (int)
+        3.2. bind_addr (str)
+
+   4. Functions
+
+        4.1. evapi_relay(event, data)
+
+1. Overview
+
+   This module pushes event details to remote applications, via TCP.
+
+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:
+     * none
+
+2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running Kamailio with this module loaded:
+     * libev
+
+3. Parameters
+
+   3.1. workers (int)
+   3.2. bind_addr (str)
+
+3.1. workers (int)
+
+   Number of worker processes to be started to handle incoming messages
+   from remote applications.
+
+   Default value is 1.
+
+   Example 1.1. Set workers parameter
+...
+modparam("async", "workers", 2)
+...
+
+3.2. bind_addr (str)
+
+   Local IP and port to listen on for incoming TCP connections.
+
+   Default value is "127.0.0.1:8448".
+
+   Example 1.2. Set bind_addr parameter
+...
+modparam("async", "bind_addr", "1.2.3.4:8228")
+...
+
+4. Functions
+
+   4.1. evapi_relay(event, data)
+
+4.1. evapi_relay(event, data)
+
+   Relay the event to connected applications. The format is netstring with
+   json payload.
+
+   This function can be used from ANY_ROUTE.
+
+   Example 1.3. evapi_relay usage
+...
+evapi_relay("test", "{ \"fU\": \"$fU\" }");
+...
+
+   The above exaple will send the following message over tcp:
+
+   Example 1.4. TCP message
+...
+45:{
+ "event":"test",
+ "data":{ "fU": "test" }
+},
+...

+ 4 - 0
modules/evapi/doc/Makefile

@@ -0,0 +1,4 @@
+docs = evapi.xml
+
+docbook_dir = ../../../docbook
+include $(docbook_dir)/Makefile.module

+ 37 - 0
modules/evapi/doc/evapi.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+	<title>EVAPI Module</title>
+	<productname class="trade">kamailio.org</productname>
+	<authorgroup>
+	    <author>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </author>
+	    <editor>
+		<firstname>Daniel-Constantin</firstname>
+		<surname>Mierla</surname>
+		<email>[email protected]</email>
+	    </editor>
+	</authorgroup>
+	<copyright>
+	    <year>2014</year>
+	    <holder>asipto.com</holder>
+	</copyright>
+    </bookinfo>
+    <toc></toc>
+    
+    <xi:include href="evapi_admin.xml"/>
+    
+    
+</book>

+ 135 - 0
modules/evapi/doc/evapi_admin.xml

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+	
+	<title>&adminguide;</title>
+	
+	<section>
+	<title>Overview</title>
+	<para>
+		This module pushes event details to remote applications, via TCP.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>none</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>libev</emphasis>
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section>
+		<title><varname>workers</varname> (int)</title>
+		<para>
+			Number of worker processes to be started to handle incoming messages
+			from remote applications.
+		</para>
+		<para>
+		<emphasis>
+			Default value is 1.
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>workers</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async", "workers", 2)
+...
+</programlisting>
+		</example>
+	</section>
+	<section>
+		<title><varname>bind_addr</varname> (str)</title>
+		<para>
+		Local IP and port to listen on for incoming TCP connections.
+		</para>
+		<para>
+		<emphasis>
+			Default value is "127.0.0.1:8448".
+		</emphasis>
+		</para>
+		<example>
+		<title>Set <varname>bind_addr</varname> parameter</title>
+		<programlisting format="linespecific">
+...
+modparam("async", "bind_addr", "1.2.3.4:8228")
+...
+</programlisting>
+		</example>
+	</section>
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section>
+	    <title>
+		<function moreinfo="none">evapi_relay(event, data)</function>
+	    </title>
+	    <para>
+		Relay the event to connected applications. The format is netstring
+		with json payload.
+		</para>
+		<para>
+		This function can be used from ANY_ROUTE.
+		</para>
+		<example>
+		<title><function>evapi_relay</function> usage</title>
+		<programlisting format="linespecific">
+...
+evapi_relay("test", "{ \"fU\": \"$fU\" }");
+...
+</programlisting>
+	    </example>
+		<para>
+		The above exaple will send the following message over tcp:
+		</para>
+		<example>
+		<title>TCP message</title>
+		<programlisting format="linespecific">
+...
+45:{
+ "event":"test",
+ "data":{ "fU": "test" }
+},
+...
+</programlisting>
+	    </example>
+	</section>
+
+	</section>
+</chapter>
+

+ 334 - 0
modules/evapi/evapi_dispatch.c

@@ -0,0 +1,334 @@
+/**
+ * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <ev.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+
+static int _evapi_notify_sockets[2];
+
+typedef struct _evapi_client {
+	int connected;
+	int sock;
+} evapi_client_t;
+
+#define EVAPI_MAX_CLIENTS	8
+static evapi_client_t _evapi_clients[EVAPI_MAX_CLIENTS];
+
+/**
+ *
+ */
+int evapi_init_notify_sockets(void)
+{
+	if (socketpair(PF_UNIX, SOCK_STREAM, 0, _evapi_notify_sockets) < 0) {
+		LM_ERR("opening notify stream socket pair\n");
+		return -1;
+	}
+	LM_DBG("inter-process event notification sockets initialized\n");
+	return 0;
+}
+
+/**
+ *
+ */
+void evapi_close_notify_sockets_child(void)
+{
+	LM_DBG("closing the notification socket used by children\n");
+	close(_evapi_notify_sockets[1]);
+}
+
+/**
+ *
+ */
+void evapi_close_notify_sockets_parent(void)
+{
+	LM_DBG("closing the notification socket used by parent\n");
+	close(_evapi_notify_sockets[0]);
+}
+
+/**
+ *
+ */
+int evapi_dispatch_notify(char *obuf, int olen)
+{
+	int i;
+	int n;
+	int wlen;
+
+	n = 0;
+	for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
+		if(_evapi_clients[i].connected==1 && _evapi_clients[i].sock>0) {
+			wlen = write(_evapi_clients[i].sock, obuf, olen);
+			if(wlen!=olen) {
+				LM_DBG("failed to write all packet (%d out of %d) on socket %d index [%d]\n",
+						wlen, olen, _evapi_clients[i].sock, i);
+			}
+			n++;
+		}
+	}
+
+	return n;
+}
+
+/**
+ *
+ */
+void evapi_recv_client(struct ev_loop *loop, struct ev_io *watcher, int revents)
+{
+#define CLIENT_BUFFER_SIZE	1024
+	char rbuffer[CLIENT_BUFFER_SIZE];
+	ssize_t rlen;
+	int i;
+
+	if(EV_ERROR & revents) {
+		perror("received invalid event\n");
+		return;
+	}
+
+	/* read message from client */
+	rlen = recv(watcher->fd, rbuffer, CLIENT_BUFFER_SIZE, 0);
+
+	if(rlen < 0) {
+		LM_ERR("cannot read the client message\n");
+		return;
+	}
+
+	if(rlen == 0) {
+		/* client is gone */
+		for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
+			if(_evapi_clients[i].connected==1 && _evapi_clients[i].sock==watcher->fd) {
+				_evapi_clients[i].connected = 0;
+				_evapi_clients[i].sock = 0;
+				break;
+			}
+		}
+		ev_io_stop(loop, watcher);
+		free(watcher);
+		LM_INFO("client closing connection\n");
+		return;
+	}
+
+	LM_NOTICE("received [%.*s]\n", (int)rlen, rbuffer);
+}
+
+/**
+ *
+ */
+void evapi_accept_client(struct ev_loop *loop, struct ev_io *watcher, int revents)
+{
+	struct sockaddr_in caddr;
+	socklen_t clen = sizeof(caddr);
+	int csock;
+	struct ev_io *evapi_client;
+	int i;
+	
+	evapi_client = (struct ev_io*) malloc (sizeof(struct ev_io));
+	if(evapi_client==NULL) {
+		perror("no more memory\n");
+		return;
+	}
+
+	if(EV_ERROR & revents) {
+		perror("received invalid event\n");
+		return;
+	}
+
+	/* accept new client connection */
+	csock = accept(watcher->fd, (struct sockaddr *)&caddr, &clen);
+
+	if (csock < 0) {
+		LM_ERR("cannot accept the client\n");
+		return;
+	}
+	for(i=0; i<EVAPI_MAX_CLIENTS; i++) {
+		if(_evapi_clients[i].connected==0) {
+			_evapi_clients[i].connected = 1;
+			_evapi_clients[i].sock = csock;
+			break;
+		}
+	}
+	if(i==EVAPI_MAX_CLIENTS) {
+		LM_ERR("too many clients\n");
+		close(csock);
+		return;
+	}
+
+	/* start watcher to read messages from whatchers */
+	ev_io_init(evapi_client, evapi_recv_client, csock, EV_READ);
+	ev_io_start(loop, evapi_client);
+}
+
+/**
+ *
+ */
+void evapi_recv_notify(struct ev_loop *loop, struct ev_io *watcher, int revents)
+{
+	str *sbuf;
+	int rlen;
+
+	if(EV_ERROR & revents) {
+		perror("received invalid event\n");
+		return;
+	}
+
+	/* read message from client */
+	rlen = read(watcher->fd, &sbuf, sizeof(str*));
+
+	if(rlen != sizeof(str*)) {
+		LM_ERR("cannot read the sip worker message\n");
+		return;
+	}
+
+	LM_DBG("received [%p] [%.*s] (%d)\n", sbuf, sbuf->len, sbuf->s, sbuf->len);
+	evapi_dispatch_notify(sbuf->s, sbuf->len);
+	shm_free(sbuf);
+}
+
+/**
+ *
+ */
+int evapi_run_dispatcher(char *laddr, int lport)
+{
+	int evapi_srv_sock;
+	struct sockaddr_in evapi_srv_addr;
+	struct ev_loop *loop;
+	struct hostent *h = NULL;
+	struct ev_io io_server;
+	struct ev_io io_notify;
+
+	LM_DBG("starting dispatcher processing\n");
+
+	memset(_evapi_clients, 0, sizeof(evapi_client_t) * EVAPI_MAX_CLIENTS);
+
+	loop = ev_default_loop(0);
+
+	if(loop==NULL) {
+		LM_ERR("cannot get libev loop\n");
+		return -1;
+	}
+
+    h = gethostbyname(laddr);
+    if (h == NULL || (h->h_addrtype != AF_INET && h->h_addrtype != AF_INET6)) {
+    	LM_ERR("cannot resolve local server address [%s]\n", laddr);
+        return -1;
+    }
+    if(h->h_addrtype == AF_INET) {
+    	evapi_srv_sock = socket(PF_INET, SOCK_STREAM, 0);
+	} else {
+		evapi_srv_sock = socket(PF_INET6, SOCK_STREAM, 0);
+	}
+	if( evapi_srv_sock < 0 )
+	{
+		LM_ERR("cannot create server socket (family %d)\n", h->h_addrtype);
+		return -1;
+	}
+	/* set non-blocking flag */
+	fcntl(evapi_srv_sock, F_SETFL, fcntl(evapi_srv_sock, F_GETFL) | O_NONBLOCK);
+
+	bzero(&evapi_srv_addr, sizeof(evapi_srv_addr));
+	evapi_srv_addr.sin_family = h->h_addrtype;
+	evapi_srv_addr.sin_port   = htons((short)lport);
+	evapi_srv_addr.sin_addr  = *(struct in_addr*)h->h_addr;
+
+	if (bind(evapi_srv_sock, (struct sockaddr*)&evapi_srv_addr,
+				sizeof(evapi_srv_addr)) < 0) {
+		LM_ERR("cannot bind to local address and port [%s:%d]\n", laddr, lport);
+		close(evapi_srv_sock);
+		return -1;
+	}
+	if (listen(evapi_srv_sock, 4) < 0) {
+		LM_ERR("listen error\n");
+		close(evapi_srv_sock);
+		return -1;
+	}
+	ev_io_init(&io_server, evapi_accept_client, evapi_srv_sock, EV_READ);
+	ev_io_start(loop, &io_server);
+	ev_io_init(&io_notify, evapi_recv_notify, _evapi_notify_sockets[0], EV_READ);
+	ev_io_start(loop, &io_notify);
+
+	while(1) {
+		ev_loop (loop, 0);
+	}
+
+	return 0;
+}
+
+/**
+ *
+ */
+int evapi_run_worker(int prank)
+{
+	LM_DBG("started worker process: %d\n", prank);
+	while(1) {
+		sleep(3);
+	}
+}
+
+/**
+ *
+ */
+int evapi_relay(str *event, str *data)
+{
+#define EVAPI_RELAY_FORMAT "%d:{\n \"event\":\"%.*s\",\n \"data\":%.*s\n},"
+
+	int len;
+	int sbsize;
+	str *sbuf;
+
+	LM_DBG("relaying event [%.*s] data [%.*s]\n",
+			event->len, event->s, data->len, data->s);
+
+	sbsize = sizeof(EVAPI_RELAY_FORMAT) + event->len + data->len - 13;
+	sbuf = (str*)shm_malloc(sizeof(str) + ((sbsize+32) * sizeof(char)));
+	if(sbuf==NULL) {
+		LM_ERR("no more shared memory\n");
+		return -1;
+	}
+	sbuf->s = (char*)sbuf + sizeof(str);
+	sbuf->len = snprintf(sbuf->s, sbsize+32,
+			EVAPI_RELAY_FORMAT,
+			sbsize, event->len, event->s, data->len, data->s);
+	if(sbuf->len<=0 || sbuf->len>sbsize+32) {
+		shm_free(sbuf);
+		LM_ERR("cannot serialize event\n");
+		return -1;
+	}
+
+	len = write(_evapi_notify_sockets[1], &sbuf, sizeof(str*));
+	if(len<=0) {
+		LM_ERR("failed to pass the pointer to evapi dispatcher\n");
+		return -1;
+	}
+	LM_DBG("sent [%p] [%.*s] (%d)\n", sbuf, sbuf->len, sbuf->s, sbuf->len);
+	return 0;
+}

+ 38 - 0
modules/evapi/evapi_dispatch.h

@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.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 _EVAPI_DISPATCH_
+#define _EVAPI_DISPATCH_
+
+int evapi_init_notify_sockets(void);
+
+void evapi_close_notify_sockets_child(void);
+
+void evapi_close_notify_sockets_parent(void);
+
+int evapi_run_dispatcher(char *laddr, int lport);
+
+int evapi_run_worker(int prank);
+
+int evapi_relay(str *event, str *data);
+
+#endif

+ 214 - 0
modules/evapi/evapi_mod.c

@@ -0,0 +1,214 @@
+/**
+ * Copyright (C) 2014 Daniel-Constantin Mierla (asipto.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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <ev.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../pt.h"
+#include "../../pvar.h"
+#include "../../mem/shm_mem.h"
+#include "../../mod_fix.h"
+#include "../../cfg/cfg_struct.h"
+
+#include "evapi_dispatch.h"
+
+MODULE_VERSION
+
+static int   _evapi_workers = 1;
+static char *_evapi_bind_addr = "127.0.0.1";
+static int   _evapi_bind_port = 8448;
+static char *_evapi_bind_param = NULL;
+
+
+static int  mod_init(void);
+static int  child_init(int);
+static void mod_destroy(void);
+
+static int w_evapi_relay(struct sip_msg* msg, char* event, char* data);
+static int fixup_evapi_relay(void** param, int param_no);
+
+static cmd_export_t cmds[]={
+	{"evapi_relay", (cmd_function)w_evapi_relay, 2, fixup_evapi_relay,
+		0, ANY_ROUTE},
+	{0, 0, 0, 0, 0, 0}
+};
+
+static param_export_t params[]={
+	{"workers",     INT_PARAM,   &_evapi_workers},
+	{"bind_addr",   STR_PARAM,   &_evapi_bind_param},
+	{0, 0, 0}
+};
+
+struct module_exports exports = {
+	"evapi",
+	DEFAULT_DLFLAGS, /* dlopen flags */
+	cmds,
+	params,
+	0,
+	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 */
+};
+
+
+
+/**
+ * init module function
+ */
+static int mod_init(void)
+{
+	char *p;
+	if(_evapi_bind_param!=NULL) {
+		p = strchr(_evapi_bind_param, ':');
+		if(p!=NULL) {
+			*p++ = '\0';
+			_evapi_bind_port = (short)atoi(p);
+			if (_evapi_bind_port <= 0) {
+				LM_ERR("invalid port: %d\n", _evapi_bind_port);
+				return -1;
+			}
+		}
+		_evapi_bind_addr = _evapi_bind_param;
+	}
+
+	/* add space for one extra process */
+	register_procs(1 + _evapi_workers);
+
+	/* add child to update local config framework structures */
+	cfg_register_child(1 + _evapi_workers);
+
+	return 0;
+}
+
+/**
+ * @brief Initialize async module children
+ */
+static int child_init(int rank)
+{
+	int pid;
+	int i;
+
+	if (rank==PROC_INIT) {
+		if(evapi_init_notify_sockets()<0) {
+			LM_ERR("failed to initialize notify sockets\n");
+			return -1;
+		}
+		return 0;
+	}
+
+	if (rank!=PROC_MAIN) {
+		evapi_close_notify_sockets_parent();
+		return 0;
+	}
+
+	pid=fork_process(PROC_NOCHLDINIT, "EvAPI Dispatcher", 1);
+	if (pid<0)
+		return -1; /* error */
+	if(pid==0) {
+		/* child */
+
+		/* initialize the config framework */
+		if (cfg_child_init())
+			return -1;
+		/* main function for dispatcher */
+		evapi_close_notify_sockets_child();
+		if(evapi_run_dispatcher(_evapi_bind_addr, _evapi_bind_port)<0) {
+			LM_ERR("failed to initialize disptacher process\n");
+			return -1;
+		}
+	}
+
+	for(i=0; i<_evapi_workers; i++) {
+		pid=fork_process(PROC_RPC, "EvAPI Worker", 1);
+		if (pid<0)
+			return -1; /* error */
+		if(pid==0) {
+			/* child */
+
+			/* initialize the config framework */
+			if (cfg_child_init())
+				return -1;
+			/* main function for workers */
+			if(evapi_run_worker(i+1)<0) {
+				LM_ERR("failed to initialize worker process: %d\n", i);
+				return -1;
+			}
+		}
+	}
+
+	return 0;
+}
+/**
+ * destroy module function
+ */
+static void mod_destroy(void)
+{
+}
+
+static int w_evapi_relay(sip_msg_t *msg, char *event, char *data)
+{
+	str sevent;
+	str sdata;
+
+	if(event==0 || data==0) {
+		LM_ERR("invalid parameters\n");
+		return -1;
+	}
+
+	if(fixup_get_svalue(msg, (gparam_t*)event, &sevent)!=0) {
+		LM_ERR("unable to get event\n");
+		return -1;
+	}
+	if(sevent.s==NULL || sevent.len == 0) {
+		LM_ERR("invalid event parameter\n");
+		return -1;
+	}
+	if(fixup_get_svalue(msg, (gparam_t*)data, &sdata)!=0) {
+		LM_ERR("unable to get data\n");
+		return -1;
+	}
+	if(sdata.s==NULL || sdata.len == 0) {
+		LM_ERR("invalid data parameter\n");
+		return -1;
+	}
+	if(evapi_relay(&sevent, &sdata)<0) {
+		LM_ERR("failed to relay event: %.*s\n", sevent.len, sevent.s);
+		return -1;
+	}
+	return 1;
+}
+
+static int fixup_evapi_relay(void** param, int param_no)
+{
+	return fixup_spve_spve(param, param_no);
+}