Browse code

lwsc: new module adding websocket client connector

- targeting interaction with external system, not for usual SIP forwarding
- uses libwebsockets

Daniel-Constantin Mierla authored on 24/03/2021 08:20:13
Showing 6 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,25 @@
1
+#
2
+#
3
+# WARNING: do not run this directly, it should be run by the main Makefile
4
+
5
+include ../../Makefile.defs
6
+auto_gen=
7
+NAME=lwsc.so
8
+
9
+ifeq ($(CROSS_COMPILE),)
10
+LWSC_BUILDER=$(shell \
11
+	if pkg-config --exists libwebsockets; then \
12
+		echo 'pkg-config libwebsockets'; \
13
+	fi)
14
+endif
15
+
16
+ifneq ($(LWSC_BUILDER),)
17
+	DEFS += $(shell $(LWSC_BUILDER) --cflags)
18
+	LIBS += $(shell $(LWSC_BUILDER) --libs)
19
+else
20
+	DEFS += -I$(LOCALBASE)/include
21
+	LIBS += -L$(LOCALBASE)/lib -lwebsockets
22
+endif
23
+
24
+include ../../Makefile.modules
25
+
0 26
new file mode 100644
... ...
@@ -0,0 +1,201 @@
1
+LWSC Module
2
+
3
+Daniel-Constantin Mierla
4
+
5
+   <miconda@gmail.com>
6
+
7
+Edited by
8
+
9
+Daniel-Constantin Mierla
10
+
11
+   <miconda@gmail.com>
12
+
13
+   Copyright � 2021 asipto.com
14
+     __________________________________________________________________
15
+
16
+   Table of Contents
17
+
18
+   1. Admin Guide
19
+
20
+        1. Overview
21
+        2. Dependencies
22
+
23
+              2.1. Kamailio Modules
24
+              2.2. External Libraries or Applications
25
+
26
+        3. Parameters
27
+
28
+              3.1. protocol (str)
29
+              3.2. timeout_init (int)
30
+              3.3. timeout_read (int)
31
+
32
+        4. Functions
33
+
34
+              4.1. lwsc_notify(wsurl, data)
35
+              4.2. lwsc_request(wsurl, data)
36
+
37
+        5. Variables
38
+
39
+              5.1. $lwsc(key)
40
+
41
+   List of Examples
42
+
43
+   1.1. Set protocol parameter
44
+   1.2. Set timeout_init parameter
45
+   1.3. Set timeout_read parameter
46
+   1.4. lwsc_notify usage
47
+   1.5. lwsc_request usage
48
+   1.6. $lwsc(name) usage
49
+
50
+Chapter 1. Admin Guide
51
+
52
+   Table of Contents
53
+
54
+   1. Overview
55
+   2. Dependencies
56
+
57
+        2.1. Kamailio Modules
58
+        2.2. External Libraries or Applications
59
+
60
+   3. Parameters
61
+
62
+        3.1. protocol (str)
63
+        3.2. timeout_init (int)
64
+        3.3. timeout_read (int)
65
+
66
+   4. Functions
67
+
68
+        4.1. lwsc_notify(wsurl, data)
69
+        4.2. lwsc_request(wsurl, data)
70
+
71
+   5. Variables
72
+
73
+        5.1. $lwsc(key)
74
+
75
+1. Overview
76
+
77
+   This module provides a websocket client implementation to interact with
78
+   external systems, similar to http client. It is not for routing SIP
79
+   traffic.
80
+
81
+   It relies on libwebsockets (tested with v3.1.0) library
82
+   (https://libwebsockets.org/).
83
+
84
+2. Dependencies
85
+
86
+   2.1. Kamailio Modules
87
+   2.2. External Libraries or Applications
88
+
89
+2.1. Kamailio Modules
90
+
91
+   The following modules must be loaded before this module:
92
+     * none.
93
+
94
+2.2. External Libraries or Applications
95
+
96
+   The following libraries or applications must be installed before
97
+   running Kamailio with this module loaded:
98
+     * libwebsockets - probably v3.1.0 or newer.
99
+
100
+3. Parameters
101
+
102
+   3.1. protocol (str)
103
+   3.2. timeout_init (int)
104
+   3.3. timeout_read (int)
105
+
106
+3.1. protocol (str)
107
+
108
+   The websocket sub-protocol.
109
+
110
+   Default value is "kmsg".
111
+
112
+   Example 1.1. Set protocol parameter
113
+...
114
+modparam("lwsc", "protocol", "ksr")
115
+...
116
+
117
+3.2. timeout_init (int)
118
+
119
+   The interval in microseconds to wait for websocket connection to be
120
+   initialized.
121
+
122
+   Default value is 2000000 (2 seconds).
123
+
124
+   Example 1.2. Set timeout_init parameter
125
+...
126
+modparam("lwsc", "timeout_init", 4000000)
127
+...
128
+
129
+3.3. timeout_read (int)
130
+
131
+   The interval in microseconds to wait for the response of the
132
+   lwsc_request() function.
133
+
134
+   Default value is 2000000 (2 seconds).
135
+
136
+   Example 1.3. Set timeout_read parameter
137
+...
138
+modparam("lwsc", "timeout_init", 1000000)
139
+...
140
+
141
+4. Functions
142
+
143
+   4.1. lwsc_notify(wsurl, data)
144
+   4.2. lwsc_request(wsurl, data)
145
+
146
+4.1. lwsc_notify(wsurl, data)
147
+
148
+   Send data via websockets to the address specified by wsurl, no response
149
+   is expected.
150
+
151
+   The parameters are:
152
+     * wsurl - websocket url
153
+     * data - what to send
154
+
155
+   The parameters can contain pseudo-variables.
156
+
157
+   This function can be used from ANY_ROUTE.
158
+
159
+   Example 1.4. lwsc_notify usage
160
+...
161
+    jwt_notify("ws://10.1.1.10:8080/log",
162
+        "caller=$fU;callee=$tU;callid=$ci");
163
+...
164
+
165
+4.2. lwsc_request(wsurl, data)
166
+
167
+   Send data via websockets to the address specified by wsurl, a response
168
+   is expected and made available in $lwsc(rdata).
169
+
170
+   The parameters are:
171
+     * wsurl - websocket url
172
+     * data - what to send
173
+
174
+   The parameters can contain pseudo-variables.
175
+
176
+   This function can be used from ANY_ROUTE.
177
+
178
+   Example 1.5. lwsc_request usage
179
+...
180
+    jwt_request("ws://10.1.1.10:8080/log",
181
+        "caller=$fU;callee=$tU;srcip=$si");
182
+...
183
+
184
+5. Variables
185
+
186
+   5.1. $lwsc(key)
187
+
188
+5.1. $lwsc(key)
189
+
190
+   Get the values and attributes after using LWSC functions.
191
+
192
+   The key can be:
193
+     * rdata - the response retrieved after lwsc_request().
194
+     * status - the status of verification after a failed jwt_verify().
195
+
196
+   Example 1.6. $lwsc(name) usage
197
+...
198
+  jwt_request("ws://10.1.1.10:8080/log",
199
+        "caller=$fU;callee=$tU;srcip=$si");
200
+  xinfo("jwt is: $lwsc(rdata)");
201
+...
0 202
new file mode 100644
... ...
@@ -0,0 +1,4 @@
1
+docs = lwsc.xml
2
+
3
+docbook_dir = ../../../../doc/docbook
4
+include $(docbook_dir)/Makefile.module
0 5
new file mode 100644
... ...
@@ -0,0 +1,37 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+
11
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
12
+    <bookinfo>
13
+	<title>LWSC Module</title>
14
+	<productname class="trade">kamailio.org</productname>
15
+	<authorgroup>
16
+	    <author>
17
+		<firstname>Daniel-Constantin</firstname>
18
+		<surname>Mierla</surname>
19
+		<email>miconda@gmail.com</email>
20
+	    </author>
21
+	    <editor>
22
+		<firstname>Daniel-Constantin</firstname>
23
+		<surname>Mierla</surname>
24
+		<email>miconda@gmail.com</email>
25
+	    </editor>
26
+	</authorgroup>
27
+	<copyright>
28
+	    <year>2021</year>
29
+	    <holder>asipto.com</holder>
30
+	</copyright>
31
+    </bookinfo>
32
+    <toc></toc>
33
+
34
+    <xi:include href="lwsc_admin.xml"/>
35
+
36
+
37
+</book>
0 38
new file mode 100644
... ...
@@ -0,0 +1,241 @@
1
+<?xml version="1.0" encoding='ISO-8859-1'?>
2
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
3
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
4
+
5
+<!-- Include general documentation entities -->
6
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
7
+%docentities;
8
+
9
+]>
10
+<!-- Module User's Guide -->
11
+
12
+<chapter>
13
+
14
+	<title>&adminguide;</title>
15
+
16
+	<section>
17
+	<title>Overview</title>
18
+	<para>
19
+		This module provides a websocket client implementation to interact with
20
+		external systems, similar to http client. It is not for routing SIP traffic.
21
+	</para>
22
+	<para>
23
+		It relies on libwebsockets (tested with v3.1.0) library (https://libwebsockets.org/).
24
+	</para>
25
+	</section>
26
+
27
+	<section>
28
+	<title>Dependencies</title>
29
+	<section>
30
+		<title>&kamailio; Modules</title>
31
+		<para>
32
+		The following modules must be loaded before this module:
33
+			<itemizedlist>
34
+			<listitem>
35
+			<para>
36
+				<emphasis>none</emphasis>.
37
+			</para>
38
+			</listitem>
39
+			</itemizedlist>
40
+		</para>
41
+	</section>
42
+	<section>
43
+		<title>External Libraries or Applications</title>
44
+		<para>
45
+		The following libraries or applications must be installed before running
46
+		&kamailio; with this module loaded:
47
+			<itemizedlist>
48
+			<listitem>
49
+			<para>
50
+				<emphasis>libwebsockets</emphasis> - probably v3.1.0 or newer.
51
+			</para>
52
+			</listitem>
53
+			</itemizedlist>
54
+		</para>
55
+	</section>
56
+	</section>
57
+
58
+	<section>
59
+	<title>Parameters</title>
60
+	<section id="lwsc.p.protocol">
61
+		<title><varname>protocol</varname> (str)</title>
62
+		<para>
63
+			The websocket sub-protocol.
64
+		</para>
65
+		<para>
66
+		<emphasis>
67
+			Default value is "kmsg".
68
+		</emphasis>
69
+		</para>
70
+		<example>
71
+		<title>Set <varname>protocol</varname> parameter</title>
72
+		<programlisting format="linespecific">
73
+...
74
+modparam("lwsc", "protocol", "ksr")
75
+...
76
+</programlisting>
77
+		</example>
78
+	</section>
79
+	<section id="lwsc.p.timeout_init">
80
+		<title><varname>timeout_init</varname> (int)</title>
81
+		<para>
82
+			The interval in microseconds to wait for websocket connection to
83
+			be initialized.
84
+		</para>
85
+		<para>
86
+		<emphasis>
87
+			Default value is 2000000 (2 seconds).
88
+		</emphasis>
89
+		</para>
90
+		<example>
91
+		<title>Set <varname>timeout_init</varname> parameter</title>
92
+		<programlisting format="linespecific">
93
+...
94
+modparam("lwsc", "timeout_init", 4000000)
95
+...
96
+</programlisting>
97
+		</example>
98
+	</section>
99
+	<section id="lwsc.p.timeout_read">
100
+		<title><varname>timeout_read</varname> (int)</title>
101
+		<para>
102
+			The interval in microseconds to wait for the response of the
103
+			lwsc_request() function.
104
+		</para>
105
+		<para>
106
+		<emphasis>
107
+			Default value is 2000000 (2 seconds).
108
+		</emphasis>
109
+		</para>
110
+		<example>
111
+		<title>Set <varname>timeout_read</varname> parameter</title>
112
+		<programlisting format="linespecific">
113
+...
114
+modparam("lwsc", "timeout_init", 1000000)
115
+...
116
+</programlisting>
117
+		</example>
118
+	</section>
119
+
120
+	</section>
121
+
122
+	<section>
123
+	<title>Functions</title>
124
+	<section id="lwsc.f.lwsc_notify">
125
+	    <title>
126
+		<function moreinfo="none">lwsc_notify(wsurl, data)</function>
127
+	    </title>
128
+	    <para>
129
+		Send data via websockets to the address specified by wsurl, no response
130
+		is expected.
131
+		</para>
132
+		<para>
133
+		The parameters are:
134
+		</para>
135
+		<itemizedlist>
136
+			<listitem>
137
+			<para>
138
+			wsurl - websocket url
139
+			</para>
140
+			</listitem>
141
+			<listitem>
142
+			<para>
143
+			data - what to send
144
+			</para>
145
+			</listitem>
146
+		</itemizedlist>
147
+		<para>
148
+		The parameters can contain pseudo-variables.
149
+		</para>
150
+		<para>
151
+		This function can be used from ANY_ROUTE.
152
+		</para>
153
+		<example>
154
+		<title><function>lwsc_notify</function> usage</title>
155
+		<programlisting format="linespecific">
156
+...
157
+    jwt_notify("ws://10.1.1.10:8080/log",
158
+        "caller=$fU;callee=$tU;callid=$ci");
159
+...
160
+</programlisting>
161
+	    </example>
162
+	</section>
163
+	<section id="lwsc.f.lwsc_request">
164
+	    <title>
165
+		<function moreinfo="none">lwsc_request(wsurl, data)</function>
166
+	    </title>
167
+	    <para>
168
+		Send data via websockets to the address specified by wsurl, a response
169
+		is expected and made available in $lwsc(rdata).
170
+		</para>
171
+		<para>
172
+		The parameters are:
173
+		</para>
174
+		<itemizedlist>
175
+			<listitem>
176
+			<para>
177
+			wsurl - websocket url
178
+			</para>
179
+			</listitem>
180
+			<listitem>
181
+			<para>
182
+			data - what to send
183
+			</para>
184
+			</listitem>
185
+		</itemizedlist>
186
+		<para>
187
+		The parameters can contain pseudo-variables.
188
+		</para>
189
+		<para>
190
+		This function can be used from ANY_ROUTE.
191
+		</para>
192
+		<example>
193
+		<title><function>lwsc_request</function> usage</title>
194
+		<programlisting format="linespecific">
195
+...
196
+    jwt_request("ws://10.1.1.10:8080/log",
197
+        "caller=$fU;callee=$tU;srcip=$si");
198
+...
199
+</programlisting>
200
+	    </example>
201
+	</section>
202
+
203
+	</section>
204
+	<section>
205
+	<title>Variables</title>
206
+	<section id="lwsc.v.lwsc">
207
+	    <title>
208
+		<function moreinfo="none">$lwsc(key)</function>
209
+	    </title>
210
+	    <para>
211
+	    Get the values and attributes after using LWSC functions.
212
+		</para>
213
+		<para>
214
+		The key can be:
215
+		</para>
216
+		<itemizedlist>
217
+			<listitem>
218
+			<para>
219
+			rdata - the response retrieved after lwsc_request().
220
+			</para>
221
+			</listitem>
222
+			<listitem>
223
+			<para>
224
+			status - the status of verification after a failed jwt_verify().
225
+			</para>
226
+			</listitem>
227
+		</itemizedlist>
228
+		<example>
229
+		<title><function>$lwsc(name)</function> usage</title>
230
+		<programlisting format="linespecific">
231
+...
232
+  jwt_request("ws://10.1.1.10:8080/log",
233
+        "caller=$fU;callee=$tU;srcip=$si");
234
+  xinfo("jwt is: $lwsc(rdata)");
235
+...
236
+</programlisting>
237
+	    </example>
238
+	</section>
239
+	</section>
240
+
241
+</chapter>
0 242
new file mode 100644
... ...
@@ -0,0 +1,767 @@
1
+/**
2
+ * Copyright (C) 2021 Daniel-Constantin Mierla (asipto.com)
3
+ *
4
+ * This file is part of Kamailio, a free SIP server.
5
+ *
6
+ * This file is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version
10
+ *
11
+ *
12
+ * This file is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ * GNU General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU General Public License
18
+ * along with this program; if not, write to the Free Software
19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
+ *
21
+ */
22
+
23
+#include <stdio.h>
24
+#include <unistd.h>
25
+#include <stdlib.h>
26
+#include <string.h>
27
+#include <unistd.h>
28
+#include <pthread.h>
29
+
30
+#include <libwebsockets.h>
31
+
32
+#include "../../core/sr_module.h"
33
+#include "../../core/dprint.h"
34
+#include "../../core/mod_fix.h"
35
+#include "../../core/lvalue.h"
36
+#include "../../core/kemi.h"
37
+#include "../../core/parser/parse_param.h"
38
+
39
+
40
+MODULE_VERSION
41
+
42
+static int  mod_init(void);
43
+static int  child_init(int);
44
+static void mod_destroy(void);
45
+
46
+static int w_lwsc_request(sip_msg_t* msg, char* pwsurl, char* pdata);
47
+static int w_lwsc_notify(sip_msg_t* msg, char* pwsurl, char* pdata);
48
+
49
+static int _lwsc_timeout_connect = 0;
50
+static int _lwsc_timeout_send = 0;
51
+static int _lwsc_timeout_read = 2000000;
52
+static int _lwsc_timeout_init = 2000000;
53
+static str _lwsc_protocol = str_init("kmsg");
54
+
55
+static cmd_export_t cmds[]={
56
+	{"lwsc_request", (cmd_function)w_lwsc_request, 2,
57
+		fixup_spve_all, 0, ANY_ROUTE},
58
+	{"lwsc_notify", (cmd_function)w_lwsc_notify, 2,
59
+		fixup_spve_all, 0, ANY_ROUTE},
60
+	{0, 0, 0, 0, 0, 0}
61
+};
62
+
63
+static param_export_t params[]={
64
+	{ "timeout_connect", PARAM_INT, &_lwsc_timeout_connect },
65
+	{ "timeout_send",    PARAM_INT, &_lwsc_timeout_send },
66
+	{ "timeout_read",    PARAM_INT, &_lwsc_timeout_read },
67
+	{ "timeout_init",    PARAM_INT, &_lwsc_timeout_init },
68
+	{ "protocol",        PARAM_STR, &_lwsc_protocol },
69
+
70
+	{ 0, 0, 0 }
71
+};
72
+
73
+static int lwsc_pv_get(sip_msg_t *msg, pv_param_t *param, pv_value_t *res);
74
+static int lwsc_pv_parse_name(pv_spec_t *sp, str *in);
75
+
76
+static pv_export_t mod_pvs[] = {
77
+	{ {"lwsc",  sizeof("lwsc")-1}, PVT_OTHER,  lwsc_pv_get,    0,
78
+			lwsc_pv_parse_name, 0, 0, 0 },
79
+	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
80
+};
81
+
82
+struct module_exports exports = {
83
+	"lwsc",          /* module name */
84
+	DEFAULT_DLFLAGS, /* dlopen flags */
85
+	cmds,            /* cmd (cfg function) exports */
86
+	params,          /* param exports */
87
+	0,               /* RPC method exports */
88
+	mod_pvs,         /* pseudo-variables exports */
89
+	0,               /* response handling function */
90
+	mod_init,        /* module init function */
91
+	child_init,      /* per-child init function */
92
+	mod_destroy      /* module destroy function */
93
+};
94
+
95
+
96
+/**
97
+ * @brief Initialize crypto module function
98
+ */
99
+static int mod_init(void)
100
+{
101
+	return 0;
102
+}
103
+
104
+/**
105
+ * @brief Initialize crypto module children
106
+ */
107
+static int child_init(int rank)
108
+{
109
+	return 0;
110
+}
111
+
112
+/**
113
+ * destroy module function
114
+ */
115
+static void mod_destroy(void)
116
+{
117
+	return;
118
+}
119
+
120
+#define WSURL_PATH_SIZE 64
121
+
122
+/**
123
+ *
124
+ */
125
+typedef struct lwsc_endpoint {
126
+	str wsurl;
127
+	/* clone of wsurl for libwebsockets parsing with dropping in zeros */
128
+	str wsurlparse;
129
+	char wsurlpath[WSURL_PATH_SIZE];
130
+	/* first LWS_PRE bytes must preserved for headers */
131
+	str wbuf;
132
+	str rbuf;
133
+	int tlson;
134
+	struct lws_context_creation_info crtinfo;
135
+	struct lws_client_connect_info coninfo;
136
+	struct lws_context *wsctx;
137
+	struct lws *wsi;
138
+
139
+	pthread_mutex_t wslock;
140
+	pthread_t wsthread;
141
+
142
+	int wsready;
143
+	int status;
144
+
145
+	struct lwsc_endpoint *next;
146
+} lwsc_endpoint_t;
147
+
148
+/**
149
+ *
150
+ */
151
+static lwsc_endpoint_t *_lwsc_endpoints = NULL;
152
+
153
+/**
154
+ *
155
+ */
156
+static str _lwsc_rdata_buf = STR_NULL;
157
+
158
+/**
159
+ *
160
+ */
161
+static lwsc_endpoint_t* lwsc_get_endpoint_by_wsi(struct lws *wsi)
162
+{
163
+	lwsc_endpoint_t *ep;
164
+
165
+	for(ep=_lwsc_endpoints; ep!=NULL; ep=ep->next) {
166
+		if(ep->wsi==wsi) {
167
+			return ep;
168
+		}
169
+	}
170
+	return NULL;
171
+}
172
+
173
+/**
174
+ *
175
+ */
176
+static void lwsc_print_log(int llevel, const char* lmsg)
177
+{
178
+	if(llevel&LLL_ERR) {
179
+		LM_ERR("libwebsockets: %s\n", lmsg);
180
+	} else if(llevel&LLL_WARN) {
181
+		LM_INFO("libwebsockets: %s\n", lmsg);
182
+	} else if(llevel&LLL_NOTICE) {
183
+		LM_INFO("libwebsockets: %s\n", lmsg);
184
+	} else {
185
+		LM_INFO("libwebsockets(%d): %s\n", llevel, lmsg);
186
+	}
187
+}
188
+
189
+/**
190
+ *
191
+ */
192
+static void lwsc_set_logging(void)
193
+{
194
+	if(_lwsc_endpoints==NULL) {
195
+		lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE, lwsc_print_log);
196
+	}
197
+}
198
+
199
+/**
200
+ *
201
+ */
202
+static int ksr_lwsc_callback(struct lws *wsi, enum lws_callback_reasons reason,
203
+		void *user, void *in, size_t len)
204
+{
205
+	int m = 0;
206
+	size_t remain = 0;
207
+	int first = 0;
208
+	int final = 0;
209
+	lwsc_endpoint_t *ep = NULL;
210
+	str rbuf = STR_NULL;
211
+	str wbuf = STR_NULL;
212
+
213
+	LM_DBG("callback called with reason %d\n", reason);
214
+
215
+	switch (reason) {
216
+
217
+		case LWS_CALLBACK_PROTOCOL_INIT:
218
+			LM_DBG("LWS_CALLBACK_PROTOCOL_INIT\n");
219
+			break;
220
+
221
+		case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
222
+			LM_ERR("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)");
223
+			ep = lwsc_get_endpoint_by_wsi(wsi);
224
+			if(ep==NULL) {
225
+				LM_ERR("no endpoint for wsi %p\n", wsi);
226
+				goto done;
227
+			}
228
+			ep->wsready = 0;
229
+			ep->wsi = NULL;
230
+			break;
231
+
232
+		case LWS_CALLBACK_CLOSED:
233
+			LM_DBG("LWS_CALLBACK_CLOSED - wsi: %p\n", wsi);
234
+			ep = lwsc_get_endpoint_by_wsi(wsi);
235
+			if(ep==NULL) {
236
+				LM_ERR("no endpoint for wsi %p\n", wsi);
237
+				goto done;
238
+			}
239
+			ep->wsready = 0;
240
+			ep->wsi = NULL;
241
+			break;
242
+
243
+		case LWS_CALLBACK_CLIENT_ESTABLISHED:
244
+			LM_DBG("LWS_CALLBACK_CLIENT_ESTABLISHED - wsi: %p\n", wsi);
245
+			ep = lwsc_get_endpoint_by_wsi(wsi);
246
+			if(ep==NULL) {
247
+				LM_ERR("no endpoint for wsi %p\n", wsi);
248
+				goto done;
249
+			}
250
+			ep->wsready = 1;
251
+			lws_callback_on_writable(wsi);
252
+			break;
253
+
254
+		case LWS_CALLBACK_CLIENT_CLOSED:
255
+			LM_DBG("LWS_CALLBACK_CLIENT_CLOSED - wsi: %p\n", wsi);
256
+			ep = lwsc_get_endpoint_by_wsi(wsi);
257
+			if(ep==NULL) {
258
+				LM_ERR("no endpoint for wsi %p\n", wsi);
259
+				goto done;
260
+			}
261
+			ep->wsready = 0;
262
+			ep->wsi = NULL;
263
+			break;
264
+
265
+		case LWS_CALLBACK_CLIENT_WRITEABLE:
266
+			ep = lwsc_get_endpoint_by_wsi(wsi);
267
+			if(ep==NULL) {
268
+				LM_ERR("no endpoint for wsi %p\n", wsi);
269
+				goto done;
270
+			}
271
+			pthread_mutex_lock(&ep->wslock);
272
+			if(ep->wbuf.s!=NULL && ep->wbuf.len>LWS_PRE) {
273
+				wbuf = ep->wbuf;
274
+				ep->wbuf.s = NULL;
275
+				ep->wbuf.len = 0;
276
+			}
277
+			pthread_mutex_unlock(&ep->wslock);
278
+			if(wbuf.s!=NULL) {
279
+				m = lws_write(wsi, (unsigned char*)wbuf.s + LWS_PRE,
280
+						wbuf.len - LWS_PRE, LWS_WRITE_TEXT);
281
+				if (m < wbuf.len - LWS_PRE) {
282
+					LM_ERR("sending message failed: %d < %d\n", m,
283
+							wbuf.len - LWS_PRE);
284
+				}
285
+				pkg_free(wbuf.s);
286
+			}
287
+			break;
288
+
289
+		case LWS_CALLBACK_TIMER:
290
+			LM_DBG("LWS_CALLBACK_TIMER - wsi: %p\n", wsi);
291
+			// lws_callback_on_writable(wsi);
292
+			break;
293
+
294
+		case LWS_CALLBACK_CLIENT_RECEIVE:
295
+			first = lws_is_first_fragment(wsi);
296
+			final = lws_is_final_fragment(wsi);
297
+			remain = lws_remaining_packet_payload(wsi);
298
+			LM_DBG("LWS_CALLBACK_RECEIVE - wsi: %p len: %lu, first: %d, "
299
+					"final = %d, remains = %lu\n", wsi,
300
+					(unsigned long)len, first, final,
301
+					(unsigned long)remain);
302
+			if(len>0) {
303
+				ep = lwsc_get_endpoint_by_wsi(wsi);
304
+				if(ep==NULL) {
305
+					LM_ERR("no endpoint for wsi %p\n", wsi);
306
+					goto done;
307
+				}
308
+				rbuf.s = (char*)pkg_malloc(len + 1);
309
+				if(rbuf.s==NULL) {
310
+					PKG_MEM_ERROR;
311
+				} else {
312
+					memcpy(rbuf.s, in, len);
313
+					rbuf.len = len;
314
+					rbuf.s[rbuf.len] = '\0';
315
+					pthread_mutex_lock(&ep->wslock);
316
+					if(ep->rbuf.s!=NULL) {
317
+						LM_ERR("losing read buffer of %d bytes\n", ep->rbuf.len);
318
+						pkg_free(ep->rbuf.s);
319
+					}
320
+					ep->rbuf = rbuf;
321
+					pthread_mutex_unlock(&ep->wslock);
322
+				}
323
+			}
324
+			break;
325
+
326
+		case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE:
327
+			LM_INFO("server initiated connection close - wsi: %p len: %lu, "
328
+					"in: %s\n", wsi, (unsigned long)len, (in)?(char*)in:"");
329
+			ep = lwsc_get_endpoint_by_wsi(wsi);
330
+			if(ep==NULL) {
331
+				LM_ERR("no endpoint for wsi %p\n", wsi);
332
+				goto done;
333
+			}
334
+			ep->wsready = 0;
335
+			ep->wsi = NULL;
336
+			break;
337
+		default:
338
+			LM_DBG("unhandled reason %d\n", reason);
339
+			break;
340
+	}
341
+
342
+done:
343
+	return lws_callback_http_dummy(wsi, reason, user, in, len);
344
+}
345
+
346
+/**
347
+ *
348
+ */
349
+static struct lws_protocols _lwsc_protocols[] = {
350
+	{
351
+		"kmsg", ksr_lwsc_callback,
352
+		0, 0, 0, NULL, 0
353
+	},
354
+	{ NULL, NULL, 0, 0, 0, NULL, 0}
355
+};
356
+
357
+/**
358
+ *
359
+ */
360
+static int ksr_shoud_reconnect(unsigned int *last, unsigned int secs)
361
+{
362
+	struct timeval tv;
363
+
364
+	gettimeofday(&tv, NULL);
365
+
366
+	if ((unsigned long)tv.tv_sec-(unsigned long)(*last)<(unsigned long)secs) {
367
+		return 0;
368
+	}
369
+
370
+	*last = (unsigned int)tv.tv_sec;
371
+
372
+	return 1;
373
+}
374
+
375
+/**
376
+ *
377
+ */
378
+static void* ksr_lwsc_thread(void *arg)
379
+{
380
+	lwsc_endpoint_t *ep;
381
+	int rcount = 0;
382
+	unsigned int ltime = 0;
383
+
384
+	ep = (lwsc_endpoint_t*)arg;
385
+
386
+	/* wait 2secs for initial connect */
387
+	while(ep->wsi==NULL && rcount<200) {
388
+		usleep(10000);
389
+		rcount++;
390
+	}
391
+	/* try to connect every 2secs */
392
+	while(ep->wsi==NULL) {
393
+		usleep(2000000);
394
+		if(ep->wsi==NULL) {
395
+			ep->coninfo.pwsi = &ep->wsi;
396
+			lws_client_connect_via_info(&ep->coninfo);
397
+		}
398
+	}
399
+
400
+	while(ep->status==0) {
401
+		if((ep->wsi==NULL) && ksr_shoud_reconnect(&ltime, 2)) {
402
+			LM_DBG("attempting to reconnect: %u\n", ltime);
403
+			ep->coninfo.pwsi = &ep->wsi;
404
+			lws_client_connect_via_info(&ep->coninfo);
405
+		}
406
+		lws_service(ep->wsctx, 100);
407
+	}
408
+
409
+	return NULL;
410
+}
411
+
412
+/**
413
+ *
414
+ */
415
+static lwsc_endpoint_t* lwsc_get_endpoint(str *wsurl)
416
+{
417
+	lwsc_endpoint_t *ep;
418
+	int ssize = 0;
419
+	const char *urlproto = NULL;
420
+	const char *urlpath = NULL;
421
+	int s = 0;
422
+
423
+	for(ep=_lwsc_endpoints; ep!=NULL; ep=ep->next) {
424
+		if(ep->wsurl.len==wsurl->len
425
+				&& strncmp(ep->wsurl.s, wsurl->s, wsurl->len)==0) {
426
+			return ep;
427
+		}
428
+	}
429
+	ssize = sizeof(lwsc_endpoint_t) + 2*(wsurl->len + 1);
430
+	ep = (lwsc_endpoint_t*)pkg_malloc(ssize);
431
+	if(ep==NULL) {
432
+		PKG_MEM_ERROR;
433
+		return NULL;
434
+	}
435
+	memset(ep, 0, ssize);
436
+	ep->wsurl.s = (char*)ep + sizeof(lwsc_endpoint_t);
437
+	memcpy(ep->wsurl.s, wsurl->s, wsurl->len);
438
+	ep->wsurl.len = wsurl->len;
439
+	ep->wsurlparse.s = ep->wsurl.s + wsurl->len + 1;
440
+	memcpy(ep->wsurlparse.s, wsurl->s, wsurl->len);
441
+	ep->wsurlparse.len = wsurl->len;
442
+
443
+	if (lws_parse_uri(ep->wsurlparse.s, &urlproto, &ep->coninfo.address,
444
+				&ep->coninfo.port, &urlpath)) {
445
+		LM_ERR("cannot parse ws url [%.*s]\n", wsurl->len, wsurl->s);
446
+		goto error;
447
+	}
448
+	if(strlen(urlpath) > WSURL_PATH_SIZE - 4) {
449
+		LM_ERR("url path is too long [%s]\n", urlpath);
450
+		goto error;
451
+	}
452
+	ep->wsurlpath[0] = '/';
453
+	strcpy(ep->wsurlpath+1, urlpath);
454
+	ep->coninfo.path = (const char*)ep->wsurlpath;
455
+
456
+	if (strcmp(urlproto, "wss")==0 || strcmp(urlproto, "https")==0) {
457
+		ep->tlson = 1;
458
+	}
459
+
460
+	ep->crtinfo.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
461
+	if(ep->tlson==1) {
462
+		ep->crtinfo.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
463
+	}
464
+	ep->crtinfo.protocols = _lwsc_protocols;
465
+	ep->crtinfo.gid = -1;
466
+	ep->crtinfo.uid = -1;
467
+	ep->crtinfo.ws_ping_pong_interval = 5; /*secs*/
468
+	/* apparently only in newer versions of libwebsockets */
469
+	//ep->crtinfo.timeout_secs = _lwsc_timeout_send;
470
+	//ep->crtinfo.connect_timeout_secs = _lwsc_timeout_connect;
471
+	/* 1 internal and 1 (+ 1 http2 nwsi) */
472
+	ep->crtinfo.fd_limit_per_thread = 1 + 1 + 1;
473
+
474
+	ep->wsctx = lws_create_context(&ep->crtinfo);
475
+	if (!ep->wsctx) {
476
+		LM_ERR("failed to intialize context for ws url [%.*s]\n",
477
+				wsurl->len, wsurl->s);
478
+		goto error;
479
+	}
480
+
481
+	ep->coninfo.context = ep->wsctx;
482
+	ep->coninfo.ssl_connection = 0;
483
+	ep->coninfo.host = ep->coninfo.address;
484
+	ep->coninfo.origin = ep->coninfo.address;
485
+	ep->coninfo.ietf_version_or_minus_one = -1;
486
+	ep->coninfo.protocol = _lwsc_protocols[0].name;
487
+	if(ep->tlson==1) {
488
+		ep->coninfo.ssl_connection = LCCSCF_USE_SSL | LCCSCF_ALLOW_SELFSIGNED
489
+			| LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
490
+	}
491
+	ep->coninfo.pwsi = &ep->wsi;
492
+	pthread_mutex_init(&ep->wslock, NULL);
493
+
494
+	LM_DBG("connecting to [%.*s]\n", wsurl->len, wsurl->s);
495
+
496
+	ep->next = _lwsc_endpoints;
497
+	_lwsc_endpoints = ep;
498
+
499
+	lws_client_connect_via_info(&ep->coninfo);
500
+	if(ep->wsi==NULL) {
501
+		LM_ERR("failed to creating the ws client instance [%.*s]\n",
502
+				wsurl->len, wsurl->s);
503
+		/* unlink - first item */
504
+		_lwsc_endpoints = ep->next;
505
+		goto error;
506
+	}
507
+	LM_DBG("ws connection instance [%p]\n", ep->wsi);
508
+
509
+	s = pthread_create(&ep->wsthread, NULL, &ksr_lwsc_thread, (void*)ep);
510
+	if (s != 0) {
511
+		LM_ERR("failed to create the event loop thread\n");
512
+		goto error;
513
+	}
514
+	pthread_detach(ep->wsthread);
515
+
516
+	return ep;
517
+
518
+error:
519
+	pkg_free(ep);
520
+	return NULL;
521
+}
522
+
523
+/**
524
+ *
525
+ */
526
+static int ki_lwsc_request(sip_msg_t* msg, str* wsurl, str* data)
527
+{
528
+	lwsc_endpoint_t *ep = NULL;
529
+	str wbuf = STR_NULL;
530
+	int rcount = 0;
531
+	int icount = 0;
532
+
533
+	if(wsurl==NULL || wsurl->s==NULL || wsurl->len<=0
534
+			|| data==NULL || data->s==NULL || data->len<=0) {
535
+		LM_ERR("invalid parameters\n");
536
+		return -1;
537
+	}
538
+
539
+	lwsc_set_logging();
540
+	ep = lwsc_get_endpoint(wsurl);
541
+	if(ep==NULL) {
542
+		LM_ERR("endpoint not available\n");
543
+		return -1;
544
+	}
545
+
546
+	while(ep->wsready==0) {
547
+		usleep(10000);
548
+		icount += 10000;
549
+		if(icount>=_lwsc_timeout_init) {
550
+			LM_ERR("connection not ready after init timeout\n");
551
+			return -1;
552
+		}
553
+	}
554
+
555
+	wbuf.s = (char*)pkg_malloc(LWS_PRE + data->len + 1);
556
+	if(wbuf.s==NULL) {
557
+		PKG_MEM_ERROR;
558
+		return -1;
559
+	}
560
+	memset(wbuf.s, 0, LWS_PRE + data->len + 1);
561
+	memcpy(wbuf.s + LWS_PRE, data->s, data->len);
562
+	wbuf.len = LWS_PRE + data->len;
563
+
564
+	/* clear local receive buffer */
565
+	if(_lwsc_rdata_buf.s!=NULL) {
566
+		pkg_free(_lwsc_rdata_buf.s);
567
+		_lwsc_rdata_buf.s = NULL;
568
+		_lwsc_rdata_buf.len = 0;
569
+	}
570
+
571
+	pthread_mutex_lock(&ep->wslock);
572
+	if(ep->rbuf.s!=NULL) {
573
+		/* clear ws receive buffer */
574
+		LM_ERR("losing read buffer content of %d bytes\n", ep->rbuf.len);
575
+		pkg_free(ep->rbuf.s);
576
+		ep->rbuf.s = NULL;
577
+		ep->rbuf.len = 0;
578
+	}
579
+	if(ep->wbuf.s!=NULL) {
580
+		LM_ERR("losing write buffer content of %d bytes\n", ep->wbuf.len);
581
+		pkg_free(ep->wbuf.s);
582
+	}
583
+	ep->wbuf = wbuf;
584
+	pthread_mutex_unlock(&ep->wslock);
585
+
586
+	/* notify main loop another message should be sent */
587
+    lws_callback_on_writable(ep->wsi);
588
+
589
+	do {
590
+		pthread_mutex_lock(&ep->wslock);
591
+		if(ep->rbuf.s!=NULL) {
592
+			_lwsc_rdata_buf = ep->rbuf;
593
+			ep->rbuf.s = NULL;
594
+			ep->rbuf.len = 0;
595
+		}
596
+		pthread_mutex_unlock(&ep->wslock);
597
+		if(_lwsc_rdata_buf.s==NULL) {
598
+			usleep(10000);
599
+		}
600
+		rcount += 10000;
601
+	} while(rcount<_lwsc_timeout_read && _lwsc_rdata_buf.s==NULL);
602
+
603
+	if(_lwsc_rdata_buf.s==NULL) {
604
+		LM_DBG("no response data received before timeout\n");
605
+		return -2;
606
+	}
607
+
608
+	return 1;
609
+}
610
+
611
+/**
612
+ *
613
+ */
614
+static int w_lwsc_request(sip_msg_t* msg, char* pwsurl, char* pdata)
615
+{
616
+	str swsurl = STR_NULL;
617
+	str sdata = STR_NULL;
618
+
619
+	if (fixup_get_svalue(msg, (gparam_t*)pwsurl, &swsurl) != 0) {
620
+		LM_ERR("cannot get ws url file\n");
621
+		return -1;
622
+	}
623
+	if (fixup_get_svalue(msg, (gparam_t*)pdata, &sdata) != 0) {
624
+		LM_ERR("cannot get data value\n");
625
+		return -1;
626
+	}
627
+
628
+	return ki_lwsc_request(msg, &swsurl, &sdata);
629
+}
630
+
631
+/**
632
+ *
633
+ */
634
+static int ki_lwsc_notify(sip_msg_t* msg, str* wsurl, str* data)
635
+{
636
+	lwsc_endpoint_t *ep = NULL;
637
+	str wbuf = STR_NULL;
638
+	int icount = 0;
639
+
640
+	if(wsurl==NULL || wsurl->s==NULL || wsurl->len<=0
641
+			|| data==NULL || data->s==NULL || data->len<=0) {
642
+		LM_ERR("invalid parameters\n");
643
+		return -1;
644
+	}
645
+
646
+	lwsc_set_logging();
647
+
648
+	ep = lwsc_get_endpoint(wsurl);
649
+	if(ep==NULL) {
650
+		LM_ERR("endpoint not available\n");
651
+		return -1;
652
+	}
653
+
654
+	while(ep->wsready==0) {
655
+		usleep(10000);
656
+		icount += 10000;
657
+		if(icount>=_lwsc_timeout_init) {
658
+			LM_ERR("connection not ready after init timeout\n");
659
+			return -1;
660
+		}
661
+	}
662
+
663
+	wbuf.s = (char*)pkg_malloc(LWS_PRE + data->len + 1);
664
+	if(wbuf.s==NULL) {
665
+		PKG_MEM_ERROR;
666
+		return -1;
667
+	}
668
+	memset(wbuf.s, 0, LWS_PRE + data->len + 1);
669
+	memcpy(wbuf.s + LWS_PRE, data->s, data->len);
670
+	wbuf.len = LWS_PRE + data->len;
671
+
672
+	pthread_mutex_lock(&ep->wslock);
673
+	if(ep->wbuf.s!=NULL) {
674
+		LM_ERR("losing write buffer content of %d bytes\n", ep->wbuf.len);
675
+		pkg_free(ep->wbuf.s);
676
+	}
677
+	ep->wbuf = wbuf;
678
+	pthread_mutex_unlock(&ep->wslock);
679
+
680
+	/* notify main loop another message should be sent */
681
+    lws_callback_on_writable(ep->wsi);
682
+
683
+	LM_DBG("notification prepared for delivery\n");
684
+
685
+	return 1;
686
+}
687
+
688
+/**
689
+ *
690
+ */
691
+static int w_lwsc_notify(sip_msg_t* msg, char* pwsurl, char* pdata)
692
+{
693
+	str swsurl = STR_NULL;
694
+	str sdata = STR_NULL;
695
+
696
+	if (fixup_get_svalue(msg, (gparam_t*)pwsurl, &swsurl) != 0) {
697
+		LM_ERR("cannot get ws url file\n");
698
+		return -1;
699
+	}
700
+	if (fixup_get_svalue(msg, (gparam_t*)pdata, &sdata) != 0) {
701
+		LM_ERR("cannot get data value\n");
702
+		return -1;
703
+	}
704
+
705
+	return ki_lwsc_notify(msg, &swsurl, &sdata);
706
+}
707
+
708
+
709
+/**
710
+ *
711
+ */
712
+static int lwsc_pv_get(sip_msg_t *msg, pv_param_t *param, pv_value_t *res)
713
+{
714
+	switch(param->pvn.u.isname.name.n)
715
+	{
716
+		case 0:
717
+			if(_lwsc_rdata_buf.s==NULL)
718
+				return pv_get_null(msg, param, res);
719
+			return pv_get_strval(msg, param, res, &_lwsc_rdata_buf);
720
+		case 1:
721
+			return pv_get_uintval(msg, param, res, 0);
722
+		default:
723
+			return pv_get_null(msg, param, res);
724
+	}
725
+}
726
+
727
+/**
728
+ *
729
+ */
730
+static int lwsc_pv_parse_name(pv_spec_t *sp, str *in)
731
+{
732
+	if(in->len==5 && strncmp(in->s, "rdata", 5)==0) {
733
+		sp->pvp.pvn.u.isname.name.n = 0;
734
+	} else if(in->len==6 && strncmp(in->s, "status", 6)==0) {
735
+		sp->pvp.pvn.u.isname.name.n = 1;
736
+	} else {
737
+		LM_ERR("unknown inner name [%.*s]\n", in->len, in->s);
738
+		return -1;
739
+	}
740
+	return 0;
741
+}
742
+
743
+/**
744
+ *
745
+ */
746
+/* clang-format off */
747
+static sr_kemi_t sr_kemi_lwsc_exports[] = {
748
+	{ str_init("lwsc"), str_init("lwsc_request"),
749
+		SR_KEMIP_INT, ki_lwsc_request,
750
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
751
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
752
+	},
753
+	{ str_init("lwsc"), str_init("lwsc_notify"),
754
+		SR_KEMIP_INT, ki_lwsc_notify,
755
+		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
756
+			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
757
+	},
758
+
759
+	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
760
+};
761
+/* clang-format on */
762
+
763
+int mod_register(char *path, int *dlflags, void *p1, void *p2)
764
+{
765
+	sr_kemi_modules_add(sr_kemi_lwsc_exports);
766
+	return 0;
767
+}