Procházet zdrojové kódy

modules/websocket: finished off WebSocket connection management

Peter Dunkley před 13 roky
rodič
revize
5d8239f5da

+ 198 - 72
modules/websocket/ws_conn.c

@@ -33,10 +33,18 @@
 /* Maximum number of connections to display when using the ws.dump MI command */
 #define MAX_WS_CONNS_DUMP	50
 
-struct ws_connection **wsconn_hash = NULL;
+ws_connection_t **wsconn_id_hash = NULL;
+#define wsconn_listadd	tcpconn_listadd
+#define wsconn_listrm	tcpconn_listrm
+
 gen_lock_t *wsconn_lock = NULL;
+#define WSCONN_LOCK	lock_get(wsconn_lock)
+#define WSCONN_UNLOCK	lock_release(wsconn_lock)
+
 gen_lock_t *wsstat_lock = NULL;
 
+ws_connection_used_list_t *wsconn_used_list = NULL;
+
 stat_var *ws_current_connections;
 stat_var *ws_max_concurrent_connections;
 
@@ -48,7 +56,10 @@ char *wsconn_state_str[] =
 	"CLOSED"	/* WS_S_CLOSED */
 };
 
-static inline void _wsconn_rm(ws_connection_t *wsc);
+/* MI command status text */
+static str str_status_empty_param = str_init("Empty display order parameter");
+static str str_status_bad_param = str_init("Bad display order parameter");
+static str str_status_too_many_params = str_init("Too many parameters");
 
 int wsconn_init(void)
 {
@@ -76,17 +87,26 @@ int wsconn_init(void)
 		goto error;
 	}
 
-	wsconn_hash =
+	wsconn_id_hash =
 		(ws_connection_t **) shm_malloc(TCP_ID_HASH_SIZE *
 						sizeof(ws_connection_t));
-	if (wsconn_hash == NULL)
+	if (wsconn_id_hash == NULL)
 	{
 		LM_ERR("allocating WebSocket hash-table\n");
 		goto error;
 	}
-	memset((void *) wsconn_hash, 0,
+	memset((void *) wsconn_id_hash, 0,
 		TCP_ID_HASH_SIZE * sizeof(ws_connection_t *));
 
+	wsconn_used_list = (ws_connection_used_list_t *) shm_malloc(
+					sizeof(ws_connection_used_list_t));
+	if (wsconn_used_list == NULL)
+	{
+		LM_ERR("allocating WebSocket used list\n");
+		goto error;
+	}
+	memset((void *) wsconn_used_list, 0, sizeof(ws_connection_used_list_t));
+
 	return 0;
 
 error:
@@ -94,20 +114,39 @@ error:
 	if (wsstat_lock) lock_dealloc((void *) wsstat_lock);
 	wsconn_lock = wsstat_lock = NULL;
 
+	if (wsconn_id_hash) shm_free(wsconn_id_hash);
+	if (wsconn_used_list) shm_free(wsconn_used_list);
+	wsconn_id_hash = NULL;
+	wsconn_used_list = NULL;
+
 	return -1;
 }
 
+static inline void _wsconn_rm(ws_connection_t *wsc)
+{
+	wsconn_listrm(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
+	shm_free(wsc);
+	wsc = NULL;
+	update_stat(ws_current_connections, -1);
+}
+
 void wsconn_destroy(void)
 {
 	int h;
 
-	if (wsconn_hash)
+	if (wsconn_used_list)
+	{
+		shm_free(wsconn_used_list);
+		wsconn_used_list = NULL;
+	}
+
+	if (wsconn_id_hash)
 	{
-		lock_release(wsconn_lock);
-		lock_get(wsconn_lock);
+		WSCONN_UNLOCK;
+		WSCONN_LOCK;
 		for (h = 0; h < TCP_ID_HASH_SIZE; h++)
 		{
-			ws_connection_t *wsc = wsconn_hash[h];
+			ws_connection_t *wsc = wsconn_id_hash[h];
 			while (wsc)
 			{
 				ws_connection_t *next = wsc->id_next;
@@ -115,10 +154,10 @@ void wsconn_destroy(void)
 				wsc = next;
 			}
 		}
-		lock_release(wsconn_lock);
+		WSCONN_UNLOCK;
 
-		shm_free(wsconn_hash);
-		wsconn_hash = NULL;
+		shm_free(wsconn_id_hash);
+		wsconn_id_hash = NULL;
 	}
 
 	if (wsconn_lock)
@@ -150,16 +189,25 @@ int wsconn_add(int id)
 		return -1;
 	}
 	memset(wsc, 0, sizeof(ws_connection_t));
-
 	wsc->id = id;
 	wsc->id_hash = id_hash;
-	wsc->last_used = (int)time(NULL);
 	wsc->state = WS_S_OPEN;
 
+	WSCONN_LOCK;
 	/* Add to WebSocket connection table */
-	lock_get(wsconn_lock);
-	wsconn_listadd(wsconn_hash[wsc->id_hash], wsc, id_next, id_prev);
-	lock_release(wsconn_lock);
+	wsconn_listadd(wsconn_id_hash[wsc->id_hash], wsc, id_next, id_prev);
+
+	/* Add to the end of the WebSocket used list */
+	wsc->last_used = (int)time(NULL);
+	if (wsconn_used_list->head == NULL)
+		wsconn_used_list->head = wsconn_used_list->tail = wsc;
+	else
+	{
+		wsc->used_prev = wsconn_used_list->tail;
+		wsconn_used_list->tail->used_next = wsc;
+		wsconn_used_list->tail = wsc;
+	}
+	WSCONN_UNLOCK;
 
 	/* Update connection statistics */
 	lock_get(wsstat_lock);
@@ -173,14 +221,6 @@ int wsconn_add(int id)
 	return 0;
 }
 
-static inline void _wsconn_rm(ws_connection_t *wsc)
-{
-	wsconn_listrm(wsconn_hash[wsc->id_hash], wsc, id_next, id_prev);
-	shm_free(wsc);
-	wsc = NULL;
-	update_stat(ws_current_connections, -1);
-}
-
 int wsconn_rm(ws_connection_t *wsc)
 {
 	if (!wsc)
@@ -189,9 +229,19 @@ int wsconn_rm(ws_connection_t *wsc)
 		return -1;
 	}
 
-	lock_get(wsconn_lock);
+	WSCONN_LOCK;
 	_wsconn_rm(wsc);
-	lock_release(wsconn_lock);
+
+	/* Remove from the WebSocket used list */
+	if (wsconn_used_list->head == wsc)
+		wsconn_used_list->head = wsc->used_next;
+	if (wsconn_used_list->tail == wsc)
+		wsconn_used_list->tail = wsc->used_prev;
+	if (wsc->used_prev)
+		wsc->used_prev->used_next = wsc->used_next;
+	if (wsc->used_next)
+		wsc->used_next->used_prev = wsc->used_prev;
+	WSCONN_UNLOCK;
 
 	return 0;
 }
@@ -200,11 +250,28 @@ int wsconn_update(ws_connection_t *wsc)
 {
 	if (!wsc)
 	{
-		LM_ERR("wsconn_rm: null pointer\n");
+		LM_ERR("wsconn_update: null pointer\n");
 		return -1;
 	}
 
+	WSCONN_LOCK;
 	wsc->last_used = (int) time(NULL);
+	if (wsconn_used_list->tail == wsc)
+		/* Already at the end of the list */
+		goto end;
+	if (wsconn_used_list->head == wsc)
+		wsconn_used_list->head = wsc->used_next;
+	if (wsc->used_prev)
+		wsc->used_prev->used_next = wsc->used_next;
+	if (wsc->used_next)
+		wsc->used_next->used_prev = wsc->used_prev;
+	wsc->used_prev = wsconn_used_list->tail;
+	wsc->used_next = NULL;
+	wsconn_used_list->tail->used_next = wsc;
+	wsconn_used_list->tail = wsc;
+
+end:
+	WSCONN_UNLOCK;
 	return 0;
 }
 
@@ -231,69 +298,108 @@ ws_connection_t *wsconn_get(int id)
 	int id_hash = tcp_id_hash(id);
 	ws_connection_t *wsc;
 
-	lock_get(wsconn_lock);
-	for (wsc = wsconn_hash[id_hash]; wsc; wsc = wsc->id_next)
+	WSCONN_LOCK;
+	for (wsc = wsconn_id_hash[id_hash]; wsc; wsc = wsc->id_next)
 	{
 		if (wsc->id == id)
 		{
-			lock_release(wsconn_lock);
+			WSCONN_UNLOCK;
 			return wsc;
 		}
 	}
+	WSCONN_UNLOCK;
 
-	lock_release(wsconn_lock);
 	return NULL;
 }
 
-struct mi_root *ws_mi_dump(struct mi_root *cmd, void *param)
+static int add_node(struct mi_root *tree, ws_connection_t *wsc)
 {
-	int h, connections = 0, truncated = 0, interval;
-	char *src_proto, *dst_proto;
+	int interval;
+	char *src_proto, *dst_proto, *pong;
 	char src_ip[IP6_MAX_STR_SIZE + 1], dst_ip[IP6_MAX_STR_SIZE + 1];
+	struct tcp_connection *con = tcpconn_get(wsc->id, 0, 0, 0, 0);
+
+	if (con)
+	{
+		src_proto = (con->rcv.proto== PROTO_TCP) ? "ws" : "wss";
+		memset(src_ip, 0, IP6_MAX_STR_SIZE + 1);
+		ip_addr2sbuf(&con->rcv.src_ip, src_ip, IP6_MAX_STR_SIZE);
+
+		dst_proto = (con->rcv.proto == PROTO_TCP) ? "ws" : "wss";
+		memset(dst_ip, 0, IP6_MAX_STR_SIZE + 1);
+		ip_addr2sbuf(&con->rcv.dst_ip, src_ip, IP6_MAX_STR_SIZE);
+
+		pong = wsc->awaiting_pong ? "awaiting Pong, " : "";
+
+		interval = (int)time(NULL) - wsc->last_used;
+
+		if (addf_mi_node_child(&tree->node, 0, 0, 0,
+					"%d: %s:%s:%hu -> %s:%s:%hu (state: %s"
+					", %slast used %ds ago)",
+					wsc->id,
+					src_proto,
+					strlen(src_ip) ? src_ip : "*",
+					con->rcv.src_port,
+					dst_proto,
+					strlen(dst_ip) ? dst_ip : "*",
+					con->rcv.dst_port,
+					wsconn_state_str[wsc->state],
+					pong,
+					interval) == 0)
+			return -1;
+	}
+
+	return 0;
+}
+
+struct mi_root *ws_mi_dump(struct mi_root *cmd, void *param)
+{
+	int h, connections = 0, truncated = 0, order = 0;
 	ws_connection_t *wsc;
+	struct mi_node *node = NULL;
 	struct mi_root *rpl_tree = init_mi_tree(200, MI_OK_S, MI_OK_LEN);
-
+	
 	if (!rpl_tree)
 		return 0;
 
-	lock_get(wsconn_lock);
-	for (h = 0; h < TCP_ID_HASH_SIZE; h++)
+	node = cmd->node.kids;
+	if (node != NULL)
 	{
-		wsc = wsconn_hash[h];
-		while(wsc)
+		if (node->value.s == NULL || node->value.len == 0)
+		{
+			LM_ERR("empty display order parameter\n");
+			return init_mi_tree(400, str_status_empty_param.s,
+						str_status_empty_param.len);
+		}
+		strlower(&node->value);
+		if (strncmp(node->value.s, "id", 2) == 0)
+			order = 0;
+		else if (strncmp(node->value.s, "used", 4) == 0)
+			order = 1;
+		else
+		{
+			LM_ERR("bad display order parameter\n");
+			return init_mi_tree(400, str_status_bad_param.s,
+						str_status_bad_param.len);
+		}
+
+		if (node->next != NULL)
 		{
-			struct tcp_connection *con =
-					tcpconn_get(wsc->id, 0, 0, 0, 0);
+			LM_ERR("too many parameters\n");
+			return init_mi_tree(400, str_status_too_many_params.s,
+						str_status_too_many_params.len);
+		}
+	}
 
-			if (con)
+	WSCONN_LOCK;
+	if (order == 0)
+	{
+		for (h = 0; h < TCP_ID_HASH_SIZE; h++)
+		{
+			wsc = wsconn_id_hash[h];
+			while(wsc)
 			{
-				src_proto = (con->rcv.proto== PROTO_TCP)
-						? "ws" : "wss";
-				memset(src_ip, 0, IP6_MAX_STR_SIZE + 1);
-				ip_addr2sbuf(&con->rcv.src_ip, src_ip,
-						IP6_MAX_STR_SIZE);
-
-				dst_proto = (con->rcv.proto == PROTO_TCP)
-						? "ws" : "wss";
-				memset(dst_ip, 0, IP6_MAX_STR_SIZE + 1);
-				ip_addr2sbuf(&con->rcv.dst_ip, src_ip,
-						IP6_MAX_STR_SIZE);
-
-				interval = (int)time(NULL) - wsc->last_used;
-
-				if (addf_mi_node_child(&rpl_tree->node, 0, 0, 0,
-						"%d: %s:%s:%hu -> %s:%s:%hu "
-						"(state: %s, "
-						"last used %ds ago)",
-						wsc->id,
-						src_proto,
-						strlen(src_ip) ? src_ip : "*",
-						con->rcv.src_port,
-						dst_proto,
-						strlen(dst_ip) ? dst_ip : "*",
-						con->rcv.dst_port,
-						wsconn_state_str[wsc->state],
-						interval) == 0)
+				if (add_node(rpl_tree, wsc) < 0)
 					return 0;
 
 				if (++connections == MAX_WS_CONNS_DUMP)
@@ -301,12 +407,32 @@ struct mi_root *ws_mi_dump(struct mi_root *cmd, void *param)
 					truncated = 1;
 					break;
 				}
+
+				wsc = wsc->id_next;
+			}
+
+			if (truncated == 1)
+				break;
+		}
+	}
+	else
+	{
+		wsc = wsconn_used_list->head;
+		while (wsc)
+		{
+			if (add_node(rpl_tree, wsc) < 0)
+				return 0;
+
+			if (++connections == MAX_WS_CONNS_DUMP)
+			{
+				truncated = 1;
+				break;
 			}
 
-			wsc = wsc->id_next;
+			wsc = wsc->used_next;
 		}
 	}
-	lock_release(wsconn_lock);
+	WSCONN_UNLOCK;
 
 	if (addf_mi_node_child(&rpl_tree->node, 0, 0, 0,
 				"%d WebSocket connection%s found%s",

+ 12 - 3
modules/websocket/ws_conn.h

@@ -24,7 +24,7 @@
 #ifndef _WS_CONN_H
 #define _WS_CONN_H
 
-#include "../../locking.h"
+#include "../../lib/kcore/kstats_wrapper.h"
 #include "../../lib/kmi/tree.h"
 
 typedef enum
@@ -38,7 +38,11 @@ typedef enum
 typedef struct ws_connection
 {
 	ws_conn_state_t state;
+	int awaiting_pong;
+
 	int last_used;
+	struct ws_connection *used_prev;
+	struct ws_connection *used_next;
 
 	int id;			/* id and id_hash are identical to the values */
 	unsigned id_hash;	/* for the corresponding TCP/TLS connection */
@@ -46,8 +50,13 @@ typedef struct ws_connection
 	struct ws_connection *id_next;
 } ws_connection_t;
 
-#define wsconn_listadd	tcpconn_listadd
-#define wsconn_listrm	tcpconn_listrm
+typedef struct
+{
+	ws_connection_t *head;
+	ws_connection_t *tail;
+} ws_connection_used_list_t;
+
+extern ws_connection_used_list_t *wsconn_used_list;
 
 extern char *wsconn_state_str[];
 

+ 36 - 2
modules/websocket/ws_frame.c

@@ -87,6 +87,11 @@ typedef enum
 #define OPCODE_PONG		(0xa)
 /* 0xb - 0xf are reserved for further control frames */
 
+/* Time (in seconds) after which to send a keepalive on an idle connection */
+int ws_keepalive_timeout = DEFAULT_KEEPALIVE_TIMEOUT;
+int ws_keepalive_mechanism = DEFAULT_KEEPALIVE_MECHANISM;
+str ws_ping_application_data = {0, 0};
+
 stat_var *ws_failed_connections;
 stat_var *ws_local_closed_connections;
 stat_var *ws_received_frames;
@@ -506,6 +511,10 @@ static int handle_pong(ws_frame_t *frame)
 {
 	LM_INFO("Pong: %.*s\n", frame->payload_len, frame->payload_data);
 
+	if (strncmp(frame->payload_data, ws_ping_application_data.s,
+			ws_ping_application_data.len) == 0)
+		frame->wsc->awaiting_pong = 0;
+
 	return 0;
 }
 
@@ -572,8 +581,8 @@ static int ping_pong(ws_connection_t *wsc, int opcode)
 	memset(&frame, 0, sizeof(frame));
 	frame.fin = 1;
 	frame.opcode = opcode;
-	frame.payload_len = server_hdr.len;
-	frame.payload_data = server_hdr.s;
+	frame.payload_len = ws_ping_application_data.len;
+	frame.payload_data = ws_ping_application_data.s;
 	frame.wsc = wsc;
 
 	if (encode_and_send_ws_frame(&frame, CONN_CLOSE_DONT) < 0)
@@ -582,6 +591,9 @@ static int ping_pong(ws_connection_t *wsc, int opcode)
 		return -1;
 	}
 
+	if (opcode == OPCODE_PING)
+		wsc->awaiting_pong = 1;
+
 	return 0;
 }
 
@@ -683,3 +695,25 @@ struct mi_root *ws_mi_pong(struct mi_root *cmd, void *param)
 {
 	return mi_ping_pong(cmd, param, OPCODE_PONG);
 }
+
+void ws_keepalive(unsigned int ticks, void *param)
+{
+	int check_time = (int) time(NULL) - ws_keepalive_timeout;
+	ws_connection_t *wsc = wsconn_used_list->head;
+
+	while (wsc && wsc->last_used < check_time)
+	{
+		if (wsc->state == WS_S_CLOSING
+			|| wsc->awaiting_pong)
+		{
+			LM_WARN("forcibly closing connection\n");
+			wsconn_close_now(wsc);
+		}
+		else
+			ping_pong(wsconn_used_list->head,
+			  ws_keepalive_mechanism == KEEPALIVE_MECHANISM_PING
+					? OPCODE_PING : OPCODE_PONG);
+		wsc = wsconn_used_list->head;
+	}
+	
+}

+ 18 - 0
modules/websocket/ws_frame.h

@@ -24,6 +24,7 @@
 #ifndef _WS_FRAME_H
 #define _WS_FRAME_H
 
+#include "../../config.h"
 #include "../../sr_module.h"
 #include "../../str.h"
 #include "../../lib/kmi/tree.h"
@@ -35,6 +36,22 @@ typedef enum
 	REMOTE_CLOSE
 } ws_close_type_t;
 
+#define DEFAULT_KEEPALIVE_TIMEOUT		180 /* seconds */
+extern int ws_keepalive_timeout;
+
+enum
+{
+	KEEPALIVE_MECHANISM_NONE = 0,
+	KEEPALIVE_MECHANISM_PING = 1,
+	KEEPALIVE_MECHANISM_PONG = 2
+};
+#define DEFAULT_KEEPALIVE_MECHANISM		KEEPALIVE_MECHANISM_PING
+extern int ws_keepalive_mechanism;
+
+extern str ws_ping_application_data;
+#define DEFAULT_PING_APPLICATION_DATA		SERVER_HDR
+#define DEFAULT_PING_APPLICATION_DATA_LEN	SERVER_HDR_LEN
+
 extern stat_var *ws_failed_connections;
 extern stat_var *ws_local_closed_connections;
 extern stat_var *ws_received_frames;
@@ -45,5 +62,6 @@ int ws_frame_received(void *data);
 struct mi_root *ws_mi_close(struct mi_root *cmd, void *param);
 struct mi_root *ws_mi_ping(struct mi_root *cmd, void *param);
 struct mi_root *ws_mi_pong(struct mi_root *cmd, void *param);
+void ws_keepalive(unsigned int ticks, void *param);
 
 #endif /* _WS_FRAME_H */

+ 101 - 15
modules/websocket/ws_mod.c

@@ -27,6 +27,7 @@
 #include "../../locking.h"
 #include "../../sr_module.h"
 #include "../../tcp_conn.h"
+#include "../../timer_proc.h"
 #include "../../lib/kcore/kstats_wrapper.h"
 #include "../../lib/kmi/mi.h"
 #include "../../mem/mem.h"
@@ -39,27 +40,42 @@
 MODULE_VERSION
 
 /* Maximum number of connections to display when using the ws.dump MI command */
-#define MAX_WS_CONNS_DUMP	50
+#define MAX_WS_CONNS_DUMP		50
 
 static int mod_init(void);
+static int child_init(int rank);
 static void destroy(void);
 
 sl_api_t ws_slb;
 int *ws_enabled;
 
-int ws_ping_interval = 180;	/* time (in seconds) between sending Pings */
+#define DEFAULT_KEEPALIVE_INTERVAL	1
+static int ws_keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL;
+
+#define DEFAULT_KEEPALIVE_PROCESSES	1
+static int ws_keepalive_processes = DEFAULT_KEEPALIVE_PROCESSES;
 
 static cmd_export_t cmds[]= 
 {
-    { "ws_handle_handshake", (cmd_function)ws_handle_handshake,
-	0, 0, 0,
-	ANY_ROUTE },
-    { 0, 0, 0, 0, 0, 0 }
+	/* ws_handshake.c */
+	{ "ws_handle_handshake", (cmd_function) ws_handle_handshake,
+	  0, 0, 0,
+	  ANY_ROUTE },
+
+	{ 0, 0, 0, 0, 0, 0 }
 };
 
 static param_export_t params[]=
 {
-	{ "ping_interval",	INT_PARAM, &ws_ping_interval },
+	/* ws_frame.c */
+	{ "keepalive_mechanism",	INT_PARAM, &ws_keepalive_mechanism },
+	{ "keepalive_timeout",		INT_PARAM, &ws_keepalive_timeout },
+	{ "ping_application_data",	STR_PARAM, &ws_ping_application_data.s},
+
+	/* ws_mod.c */
+	{ "keepalive_interval",		INT_PARAM, &ws_keepalive_interval },
+	{ "keepalive_processes",	INT_PARAM, &ws_keepalive_processes },
+
 	{ 0, 0, 0 }
 };
 
@@ -69,27 +85,34 @@ static stat_export_t stats[] =
 	{ "ws_current_connections",       0, &ws_current_connections },
 	{ "ws_max_concurrent_connections",0, &ws_max_concurrent_connections },
 
-	/* ws_handshake.c */
-	{ "ws_failed_handshakes",         0, &ws_failed_handshakes },
-	{ "ws_successful_handshakes",     0, &ws_successful_handshakes },
-
 	/* ws_frame.c */
 	{ "ws_failed_connections",        0, &ws_failed_connections },
 	{ "ws_local_closed_connections",  0, &ws_local_closed_connections },
 	{ "ws_received_frames",           0, &ws_received_frames },
 	{ "ws_remote_closed_connections", 0, &ws_remote_closed_connections },
 	{ "ws_transmitted_frames",        0, &ws_transmitted_frames },
+
+	/* ws_handshake.c */
+	{ "ws_failed_handshakes",         0, &ws_failed_handshakes },
+	{ "ws_successful_handshakes",     0, &ws_successful_handshakes },
+
 	{ 0, 0, 0 }
 };
 
 static mi_export_t mi_cmds[] =
 {
-	{ "ws.close",   ws_mi_close,   0, 0, 0 },
-	{ "ws.disable", ws_mi_disable, 0, 0, 0 },
+	/* ws_conn.c */
 	{ "ws.dump",	ws_mi_dump,    0, 0, 0 },
-	{ "ws.enable",	ws_mi_enable,  0, 0, 0 },
+
+	/* ws_frame.c */
+	{ "ws.close",   ws_mi_close,   0, 0, 0 },
 	{ "ws.ping",    ws_mi_ping,    0, 0, 0 },
 	{ "ws.pong",	ws_mi_pong,    0, 0, 0 },
+
+	/* ws_handshake.c */
+	{ "ws.disable", ws_mi_disable, 0, 0, 0 },
+	{ "ws.enable",	ws_mi_enable,  0, 0, 0 },
+
 	{ 0, 0, 0, 0, 0 }
 };
 
@@ -106,7 +129,7 @@ struct module_exports exports=
 	mod_init,		/* module initialization function */
 	0,			/* response function */
 	destroy,		/* destroy function */
-	0			/* per-child initialization function */
+	child_init		/* per-child initialization function */
 };
 
 static int mod_init(void)
@@ -148,6 +171,43 @@ static int mod_init(void)
 	}
 	*ws_enabled = 1;
 
+	
+	if (ws_ping_application_data.s != 0)
+		ws_ping_application_data.len =
+					strlen(ws_ping_application_data.s);
+	if (ws_ping_application_data.len < 1
+		|| ws_ping_application_data.len > 125)
+	{
+		ws_ping_application_data.s = DEFAULT_PING_APPLICATION_DATA + 8;
+		ws_ping_application_data.len =
+					DEFAULT_PING_APPLICATION_DATA_LEN - 8;
+	}
+
+	if (ws_keepalive_mechanism != KEEPALIVE_MECHANISM_NONE)
+	{
+		if (ws_keepalive_timeout < 1 || ws_keepalive_timeout > 3600)
+			ws_keepalive_timeout = DEFAULT_KEEPALIVE_TIMEOUT;
+
+		switch(ws_keepalive_mechanism)
+		{
+		case KEEPALIVE_MECHANISM_PING:
+		case KEEPALIVE_MECHANISM_PONG:
+			break;
+		default:
+			ws_keepalive_mechanism = DEFAULT_KEEPALIVE_MECHANISM;
+			break;
+		}
+
+		if (ws_keepalive_interval < 1 || ws_keepalive_interval > 60)
+			ws_keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL;
+
+		if (ws_keepalive_processes < 1 || ws_keepalive_processes > 16)
+			ws_keepalive_processes = DEFAULT_KEEPALIVE_PROCESSES;
+
+		/* Add extra process/timer for the keepalive process */
+		register_sync_timers(ws_keepalive_processes);
+	}
+
 	return 0;
 
 error:
@@ -157,6 +217,32 @@ error:
 	return -1;
 }
 
+static int child_init(int rank)
+{
+	int i;
+
+	if (rank == PROC_INIT || rank == PROC_TCP_MAIN)
+		return 0;
+
+	if (rank == PROC_MAIN
+		&& ws_keepalive_mechanism != KEEPALIVE_MECHANISM_NONE)
+	{
+		for (i = 0; i < ws_keepalive_processes; i++)
+		{
+			if (fork_sync_timer(PROC_TIMER, "WEBSOCKET KEEPALIVE",
+						1, ws_keepalive, NULL,
+						ws_keepalive_interval) < 0)
+			{
+				LM_ERR("starting keepalive process\n");
+				return -1;
+			}
+		}
+
+	}
+
+	return 0;
+}
+
 static void destroy(void)
 {
 	wsconn_destroy();