瀏覽代碼

modules/websocket: First attempt a module for websocket support

- So far this is:
  - Module boiler-plate
  - WebSocket handshake
  - Example/test kamailio.cfg
Peter Dunkley 13 年之前
父節點
當前提交
48ba74772c

+ 27 - 0
modules/websocket/Makefile

@@ -0,0 +1,27 @@
+# $Id$
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=websocket.so
+
+BUILDER = $(shell which pkg-config)
+ifeq ($(BUILDER),)
+        DEFS+= -I$(LOCALBASE)/ssl/include
+        LIBS=  -L$(LOCALBASE)/lib -L$(LOCALBASE)/ssl/lib \
+			-L$(LOCALBASE)/lib64 -L$(LOCALBASE)/ssl/lib64 \
+			-lssl
+else
+	DEFS+= $(shell pkg-config --cflags libssl)
+	LIBS=  $(shell pkg-config --libs libssl)
+endif
+
+DEFS+=-DOPENSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/kcore/kcore
+
+include ../../Makefile.modules
+

+ 252 - 0
modules/websocket/example/kamailio.cfg

@@ -0,0 +1,252 @@
+#!KAMAILIO
+
+#!define DBURL "sqlite:////etc/kamailio/db.sqlite"
+
+####### Global Parameters #########
+
+debug=2
+fork=yes
+children=4
+
+alias="example.com"
+listen=192.168.111.12
+port=5060
+listen=192.168.111.12
+port=80
+
+tcp_connection_lifetime=3604
+tcp_accept_no_cl=yes
+
+syn_branch=0
+
+#mpath="/usr/lib64/kamailio/modules_k/:/usr/lib64/kamailio/modules/"
+mpath="modules_k:modules"
+
+loadmodule "db_sqlite.so"
+loadmodule "tm.so"
+loadmodule "sl.so"
+loadmodule "rr.so"
+loadmodule "pv.so"
+loadmodule "maxfwd.so"
+loadmodule "usrloc.so"
+loadmodule "registrar.so"
+loadmodule "textops.so"
+loadmodule "siputils.so"
+loadmodule "xlog.so"
+loadmodule "sanity.so"
+loadmodule "ctl.so"
+loadmodule "auth.so"
+loadmodule "auth_db.so"
+loadmodule "xhttp.so"
+loadmodule "kex.so"
+loadmodule "websocket.so"
+
+# ----------------- setting module-specific parameters ---------------
+
+# ----- tm params -----
+# auto-discard branches from previous serial forking leg
+modparam("tm", "failure_reply_mode", 3)
+# default retransmission timeout: 30sec
+modparam("tm", "fr_timer", 30000)
+# default invite retransmission timeout after 1xx: 120sec
+modparam("tm", "fr_inv_timer", 120000)
+
+# ----- rr params -----
+# add value to ;lr param to cope with most of the UAs
+modparam("rr", "enable_full_lr", 1)
+# do not append from tag to the RR (no need for this script)
+modparam("rr", "append_fromtag", 0)
+
+# ----- registrar params -----
+modparam("registrar", "method_filtering", 1)
+modparam("registrar", "max_expires", 3600)
+modparam("registrar", "gruu_enabled", 0)
+
+# ----- usrloc params -----
+modparam("usrloc", "db_url", DBURL)
+modparam("usrloc", "db_mode", 0)
+
+# ----- auth_db params -----
+modparam("auth_db", "db_url", DBURL)
+modparam("auth_db", "calculate_ha1", yes)
+modparam("auth_db", "password_column", "password")
+modparam("auth_db", "load_credentials", "")
+
+
+####### Routing Logic ########
+
+
+# Main SIP request routing logic
+# - processing of any incoming SIP request starts with this route
+# - note: this is the same as route { ... }
+request_route {
+
+	# per request initial checks
+	route(REQINIT);
+
+	# handle requests within SIP dialogs
+	route(WITHINDLG);
+
+	### only initial requests (no To tag)
+
+	# CANCEL processing
+	if (is_method("CANCEL"))
+	{
+		if (t_check_trans())
+			t_relay();
+		exit;
+	}
+
+	t_check_trans();
+
+	# authentication
+	route(AUTH);
+
+	# record routing for dialog forming requests (in case they are routed)
+	# - remove preloaded route headers
+	remove_hf("Route");
+	if (is_method("INVITE|SUBSCRIBE"))
+		record_route();
+
+	# handle registrations
+	route(REGISTRAR);
+
+	if ($rU==$null)
+	{
+		# request with no Username in RURI
+		sl_send_reply("484","Address Incomplete");
+		exit;
+	}
+
+	# user location service
+	route(LOCATION);
+
+	route(RELAY);
+}
+
+route[RELAY] {
+	if (!t_relay()) {
+		sl_reply_error();
+	}
+	exit;
+}
+
+# Per SIP request initial checks
+route[REQINIT] {
+	if (!mf_process_maxfwd_header("10")) {
+		sl_send_reply("483","Too Many Hops");
+		exit;
+	}
+
+	if(!sanity_check("1511", "7"))
+	{
+		xlog("Malformed SIP message from $si:$sp\n");
+		exit;
+	}
+}
+
+# Handle requests within SIP dialogs
+route[WITHINDLG] {
+	if (has_totag()) {
+		# sequential request withing a dialog should
+		# take the path determined by record-routing
+		if (loose_route()) {
+			route(RELAY);
+		} else {
+			if ( is_method("ACK") ) {
+				if ( t_check_trans() ) {
+					# no loose-route, but stateful ACK;
+					# must be an ACK after a 487
+					# or e.g. 404 from upstream server
+					t_relay();
+					exit;
+				} else {
+					# ACK without matching transaction...
+					# ignore and discard
+					exit;
+				}
+			}
+			sl_send_reply("404","Not here");
+		}
+		exit;
+	}
+}
+
+# Handle SIP registrations
+route[REGISTRAR] {
+	if (is_method("REGISTER"))
+	{
+		if (!save("location"))
+			sl_reply_error();
+
+		exit;
+	}
+}
+
+# USER location service
+route[LOCATION] {
+	if (!lookup("location")) {
+		$var(rc) = $rc;
+		t_newtran();
+		switch ($var(rc)) {
+			case -1:
+			case -3:
+				send_reply("404", "Not Found");
+				exit;
+			case -2:
+				send_reply("405", "Method Not Allowed");
+				exit;
+		}
+	}
+}
+
+# Authentication route
+route[AUTH] {
+	if (is_method("REGISTER") || from_uri==myself)
+	{
+		# authenticate requests
+		if (!auth_check("$fd", "subscriber", "1")) {
+			auth_challenge("$fd", "0");
+			exit;
+		}
+		# user authenticated - remove auth header
+		if(!is_method("REGISTER|PUBLISH"))
+			consume_credentials();
+	}
+	# if caller is not local subscriber, then check if it calls
+	# a local destination, otherwise deny, not an open relay here
+	if (from_uri!=myself && uri!=myself)
+	{
+		sl_send_reply("403","Not relaying");
+		exit;
+	}
+}
+
+event_route[xhttp:request] {
+	if ($Rp != "80") {
+		xlog("L_WARN", "HTTP request received on $Rp\n");
+		xhttp_reply("403", "Forbidden", "", "");
+		exit;
+	}
+
+	if ($hdr(Upgrade)=~"websocket"
+			&& $hdr(Connection)=~"Upgrade"
+			&& $rm=~"GET") {
+		xlog("L_INFO", "WebSocket\n");
+		xlog("L_INFO", " Host: $hdr(Host)\n");
+		xlog("L_INFO", " Origin: $hdr(Origin)\n");
+
+		if ($hdr(Host) == $null || !is_myself($hdr(Host))) {
+			xlog("L_WARN", "Bad host $hdr(Host)\n");
+			xhttp_reply("403", "Forbidden", "", "");
+			exit;
+		}
+
+		# Optional... validate Origin
+		# Optional... perform HTTP authentication
+
+		ws_handle_handshake();
+	}
+
+	xhttp_reply("404", "Not found", "", "");
+}

+ 186 - 0
modules/websocket/ws_handshake.c

@@ -0,0 +1,186 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Crocodile RCS Ltd
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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 <openssl/sha.h>
+
+#include "../../basex.h"
+#include "../../data_lump_rpl.h"
+#include "../../dprint.h"
+#include "../../lib/kcore/cmpapi.h"
+#include "../../parser/msg_parser.h"
+#include "../sl/sl.h"
+#include "ws_handshake.h"
+#include "ws_mod.h"
+
+#define WS_VERSION		(13)
+
+#define SEC_WEBSOCKET_KEY	(1<<0)
+#define SEC_WEBSOCKET_PROTOCOL	(1<<1)
+#define SEC_WEBSOCKET_VERSION	(1<<2)
+
+#define REQUIRED_HEADERS	(SEC_WEBSOCKET_KEY | SEC_WEBSOCKET_PROTOCOL\
+					| SEC_WEBSOCKET_VERSION)
+
+static str str_sip = str_init("sip");
+static str str_ws_guid = str_init("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
+
+static str str_switching_protocols = str_init("Switching Protocols");
+static str str_bad_request = str_init("Bad Request");
+static str str_upgrade_required = str_init("Upgrade Required");
+static str str_internal_server_error = str_init("Internal Server Error");
+
+#define HDR_BUF_LEN		(256)
+static char headers_buf[HDR_BUF_LEN];
+
+#define KEY_BUF_LEN		(28)
+static char key_buf[KEY_BUF_LEN];
+
+static int ws_send_reply(sip_msg_t *msg, int code, str *reason, str *hdrs)
+{
+	if (hdrs && hdrs->len > 0)
+	{
+		if (add_lump_rpl(msg, hdrs->s, hdrs->len, LUMP_RPL_HDR) == 0)
+		{
+			LM_ERR("inserting extra-headers lump\n");
+			return -1;
+		}
+	}
+
+	if (ws_slb.freply(msg, code, reason) < 0)
+	{
+		LM_ERR("sending reply\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+int ws_handle_handshake(struct sip_msg *msg)
+{
+	str key = {0, 0}, headers = {0, 0}, reply_key = {0, 0};
+	unsigned char sha1[20];
+	unsigned int hdr_flags = 0;
+	int version;
+	struct hdr_field *hdr = msg->headers;
+
+	while (hdr != NULL)
+	{
+		/* Decode and validate Sec-WebSocket-Key */
+		if (cmp_hdrname_strzn(&hdr->name,
+				"Sec-WebSocket-Key", 17) == 0) 
+		{
+			if (hdr_flags & SEC_WEBSOCKET_KEY)
+			{
+				LM_WARN("%.*s found multiple times\n",
+					hdr->name.len, hdr->name.s);
+				ws_send_reply(msg, 400, &str_bad_request, NULL);
+				return 0;
+			}
+
+			key = hdr->body;
+			hdr_flags |= SEC_WEBSOCKET_KEY;
+		}
+		/* Decode and validate Sec-WebSocket-Protocol */
+		else if (cmp_hdrname_strzn(&hdr->name,
+				"Sec-WebSocket-Protocol", 22) == 0)
+		{
+			if (str_search(&hdr->body, &str_sip) != NULL)
+				hdr_flags |= SEC_WEBSOCKET_PROTOCOL;
+		}
+		/* Decode and validate Sec-WebSocket-Version */
+		else if (cmp_hdrname_strzn(&hdr->name,
+				"Sec-WebSocket-Version", 21) == 0)
+		{
+			if (hdr_flags & SEC_WEBSOCKET_VERSION)
+			{
+				LM_WARN("%.*s found multiple times\n",
+					hdr->name.len, hdr->name.s);
+				ws_send_reply(msg, 400, &str_bad_request, NULL);
+				return 0;
+			}
+
+			str2sint(&hdr->body, &version);
+
+			if (version != WS_VERSION)
+			{
+				LM_WARN("Unsupported protocol version %.*s\n",
+					hdr->body.len, hdr->body.s);
+				headers.s = headers_buf;
+				headers.len = snprintf(headers.s, HDR_BUF_LEN,
+					"Sec-WebSocket-Version: %u\r\n",
+					WS_VERSION);
+				ws_send_reply(msg, 426, &str_upgrade_required,
+						&headers);
+				return 0;
+			}
+
+			hdr_flags |= SEC_WEBSOCKET_VERSION;
+		}
+
+		hdr = hdr->next;
+	}
+
+	/* Final check that all required headers/values were found */
+	if (hdr_flags != REQUIRED_HEADERS)
+	{
+		LM_WARN("all required headers not present\n");
+		ws_send_reply(msg, 400, &str_bad_request, NULL);
+		return 0;
+	}
+
+	/* Construct reply_key */
+	reply_key.s = (char *) pkg_malloc(
+				(key.len + str_ws_guid.len) * sizeof(char)); 
+	if (reply_key.s == NULL)
+	{
+		LM_ERR("allocating pkg memory\n");
+		ws_send_reply(msg, 500, &str_internal_server_error, NULL);
+		return 0;
+	}
+	memcpy(reply_key.s, key.s, key.len);
+	memcpy(reply_key.s + key.len, str_ws_guid.s, str_ws_guid.len);
+	reply_key.len = key.len + str_ws_guid.len;
+	SHA1((const unsigned char *) reply_key.s, reply_key.len, sha1);
+	pkg_free(reply_key.s);
+	reply_key.s = key_buf;
+	reply_key.len = base64_enc(sha1, 20,
+				(unsigned char *) reply_key.s, KEY_BUF_LEN);
+
+	/* Build headers for reply */
+	headers.s = headers_buf;
+	headers.len = snprintf(headers.s, HDR_BUF_LEN,
+			"Sec-WebSocket-Key: %.*s\r\n"
+			"Sec-WebSocket-Protocol: %.*s\r\n"
+			"Sec-WebSocket-Version: %u\r\n",
+			reply_key.len, reply_key.s,
+			str_sip.len, str_sip.s,
+			WS_VERSION);
+
+	/* TODO: make sure Kamailio core sends future requests on this
+		 connection directly to this module */
+
+	/* Send reply */
+	ws_send_reply(msg, 101, &str_switching_protocols, &headers);
+
+	return 0;
+}

+ 31 - 0
modules/websocket/ws_handshake.h

@@ -0,0 +1,31 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Crocodile RCS Ltd
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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 _WS_HANDSHAKE_H
+#define _WS_HANDSHAKE_H
+
+#include "../../parser/msg_parser.h"
+
+int ws_handle_handshake(struct sip_msg *msg);
+
+#endif /* _WS_HANDSHAKE_H */

+ 82 - 0
modules/websocket/ws_mod.c

@@ -0,0 +1,82 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Crocodile RCS Ltd
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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 "../../dprint.h"
+#include "../../sr_module.h"
+#include "../../parser/msg_parser.h"
+#include "ws_handshake.h"
+#include "ws_mod.h"
+
+MODULE_VERSION
+
+static int mod_init(void);
+
+sl_api_t ws_slb;
+int ws_ping_interval = 25;	/* time (in seconds) after which a Ping will be
+				   sent on an idle connection */
+int ws_ping_timeout = 1;	/* time (in seconds) to wait for a Pong in
+				   response to a Ping before closing a
+				   connection */
+
+static param_export_t params[]=
+{
+	{"ping_interval",	INT_PARAM, &ws_ping_interval},
+	{"ping_timeout",	INT_PARAM, &ws_ping_timeout},
+};
+
+static cmd_export_t cmds[]= 
+{
+    {"ws_handle_handshake", (cmd_function)ws_handle_handshake, 0,
+	0, 0,
+	ANY_ROUTE},
+    {0, 0, 0, 0, 0, 0}
+};
+
+struct module_exports exports= 
+{
+	"websocket",
+	DEFAULT_DLFLAGS,	/* dlopen flags */
+	cmds,			/* Exported functions */
+	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 */
+	0,			/* destroy function */
+	0			/* per-child initialization function */
+};
+
+static int mod_init(void)
+{
+	if (sl_load_api(&ws_slb) != 0)
+	{
+		LM_ERR("cannot bind to SL\n");
+		return -1;
+	}
+
+	/* TODO: register module with core to receive WS/WSS messages */
+
+	return 0;
+}

+ 35 - 0
modules/websocket/ws_mod.h

@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2012 Crocodile RCS Ltd
+ *
+ * This file is part of Kamailio, a free SIP server.
+ *
+ * Kamailio 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
+ *
+ * Kamailio 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 _WS_MOD_H
+#define _WS_MOD_H
+
+#include "../sl/sl.h"
+
+extern sl_api_t ws_slb;
+extern int ws_ping_interval;	/* time (in seconds) after which a Ping will be
+				   sent on an idle connection */
+extern int ws_ping_timeout;	/* time (in seconds) to wait for a Pong in
+				   response to a Ping before closing a
+				   connection */
+#endif /* _WS_MOD_H */