• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * lws-minimal-ws-raw-proxy
3  *
4  * Written in 2010-2021 by Andy Green <andy@warmcat.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * This demonstrates a ws (server) -> raw (client) proxy,  it's a ws server
10  * that accepts connections, creates an onward client connection to some other
11  * no-protocol server, eg, nc -l 127.0.0.1 1234
12  *
13  * The idea is to show the general approach for making async proxies using lws
14  * that are robust and valgrind-clean.
15  *
16  * There's no vhd or pss on either side.  Instead when the ws server gets an
17  * incoming connection and negotiates the ws link, he creates an object
18  * representing the proxied connection, it is not destroyed automatically when
19  * any particular wsi is closed, instead the last wsi that is part of the
20  * proxied connection destroys it when he is closed.
21  */
22 
23 #include <libwebsockets.h>
24 #include <string.h>
25 #include <signal.h>
26 #include <string.h>
27 
28 /* one of these created for each pending message that is to be forwarded */
29 
30 typedef struct proxy_msg {
31 	lws_dll2_t		list;
32 	size_t			len;
33 	/*
34 	 * the packet content is overallocated here, if p is a pointer to
35 	 * this struct, you can get a pointer to the message contents by
36 	 * ((uint8_t)&p[1]) + LWS_PRE.
37 	 *
38 	 * Notice we additionally take care to overallocate LWS_PRE before the
39 	 * actual message data, so we can simplify sending it.
40 	 */
41 } proxy_msg_t;
42 
43 /*
44  * One of these is created when a inbound ws connection joins, it represents
45  * the proxy action provoked by that.
46  */
47 
48 typedef struct proxy_conn {
49 	struct lws		*wsi_ws; /* wsi for the inbound ws conn */
50 	struct lws		*wsi_raw; /* wsi for the outbound raw conn */
51 
52 	lws_dll2_owner_t	pending_msg_to_ws;
53 	lws_dll2_owner_t	pending_msg_to_raw;
54 } proxy_conn_t;
55 
56 
57 static int
proxy_ws_raw_msg_destroy(struct lws_dll2 * d,void * user)58 proxy_ws_raw_msg_destroy(struct lws_dll2 *d, void *user)
59 {
60 	proxy_msg_t *msg = lws_container_of(d, proxy_msg_t, list);
61 
62 	lws_dll2_remove(d);
63 	free(msg);
64 
65 	return 0;
66 }
67 
68 /*
69  * First the ws server side
70  */
71 
72 static int
callback_proxy_ws_server(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)73 callback_proxy_ws_server(struct lws *wsi, enum lws_callback_reasons reason,
74 			 void *user, void *in, size_t len)
75 {
76 	proxy_conn_t *pc = (proxy_conn_t *)lws_get_opaque_user_data(wsi);
77 	struct lws_client_connect_info i;
78 	proxy_msg_t *msg;
79 	uint8_t *data;
80 	int m, a;
81 
82 	switch (reason) {
83 	case LWS_CALLBACK_ESTABLISHED:
84 		/* so let's create the proxy connection object */
85 		pc = malloc(sizeof(*pc));
86 		memset(pc, 0, sizeof(*pc));
87 
88 		/* mark this accepted ws connection with the proxy conn obj */
89 		lws_set_opaque_user_data(wsi, pc);
90 		/* tell the proxy conn object that we are the ws side of it */
91 		pc->wsi_ws = wsi;
92 
93 		/*
94 		 * For this example proxy, our job is to create a new, onward,
95 		 * raw client connection to proxy stuff on to
96 		 */
97 
98 		memset(&i, 0, sizeof(i));
99 
100 		i.method = "RAW";
101 		i.context = lws_get_context(wsi);
102 		i.port = 1234;
103 		i.address = "127.0.0.1";
104 		i.ssl_connection = 0;
105 		i.local_protocol_name = "lws-ws-raw-raw";
106 
107 		/* also mark the onward, raw client conn with the proxy_conn */
108 		i.opaque_user_data = pc;
109 		/* if it succeeds, set the wsi into the proxy_conn */
110 		i.pwsi = &pc->wsi_raw;
111 
112 		if (!lws_client_connect_via_info(&i)) {
113 			lwsl_warn("%s: onward connection failed\n", __func__);
114 			return -1; /* hang up on the ws client, triggering
115 				    * _CLOSE flow */
116 		}
117 
118 		break;
119 
120 	case LWS_CALLBACK_CLOSED:
121 		/*
122 		 * Clean up any pending messages to us that are never going
123 		 * to get delivered now, we are in the middle of closing
124 		 */
125 		lws_dll2_foreach_safe(&pc->pending_msg_to_ws, NULL,
126 				      proxy_ws_raw_msg_destroy);
127 
128 		/*
129 		 * Remove our pointer from the proxy_conn... we are about to
130 		 * be destroyed.
131 		 */
132 		pc->wsi_ws = NULL;
133 		lws_set_opaque_user_data(wsi, NULL);
134 
135 		if (!pc->wsi_raw) {
136 			/*
137 			 * The onward raw conn either never got started or is
138 			 * already closed... then we are the last guy still
139 			 * holding on to the proxy_conn... and we're going away
140 			 * so let's destroy it
141 			 */
142 
143 			free(pc);
144 			break;
145 		}
146 
147 		/*
148 		 * Onward conn still alive...
149 		 * does he have stuff left to deliver?
150 		 */
151 		if (pc->pending_msg_to_raw.count) {
152 			/*
153 			 * Yes, let him get on with trying to send
154 			 * the remaining pieces... but put a time limit
155 			 * on how hard he will try now the ws part is
156 			 * disappearing... give him 3s
157 			 */
158 			lws_set_timeout(pc->wsi_raw,
159 				PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE, 3);
160 			break;
161 		}
162 		/*
163 		 * Onward raw client conn doesn't have anything left
164 		 * to do, let's close him right after this, he will take care to
165 		 * destroy the proxy_conn when he goes down after he sees we
166 		 * have already been closed
167 		 */
168 
169 		lws_wsi_close(pc->wsi_raw, LWS_TO_KILL_ASYNC);
170 		break;
171 
172 	case LWS_CALLBACK_SERVER_WRITEABLE:
173 		if (!pc || !pc->pending_msg_to_ws.count)
174 			break;
175 
176 		msg = lws_container_of(pc->pending_msg_to_ws.head,
177 				       proxy_msg_t, list);
178 		data = (uint8_t *)&msg[1] + LWS_PRE;
179 
180 		/* notice we allowed for LWS_PRE in the payload already */
181 		m = lws_write(wsi, data, msg->len, LWS_WRITE_TEXT);
182 		a = (int)msg->len;
183 		lws_dll2_remove(&msg->list);
184 		free(msg);
185 
186 		if (m < a) {
187 			lwsl_err("ERROR %d writing to ws\n", m);
188 			return -1;
189 		}
190 
191 		/*
192 		 * If more to do...
193 		 */
194 		if (pc->pending_msg_to_ws.count)
195 			lws_callback_on_writable(wsi);
196 		break;
197 
198 	case LWS_CALLBACK_RECEIVE:
199 		if (!pc || !pc->wsi_raw)
200 			break;
201 
202 		/* notice we over-allocate by LWS_PRE + rx len */
203 		msg = (proxy_msg_t *)malloc(sizeof(*msg) + LWS_PRE + len);
204 		data = (uint8_t *)&msg[1] + LWS_PRE;
205 
206 		if (!msg) {
207 			lwsl_user("OOM: dropping\n");
208 			break;
209 		}
210 
211 		memset(msg, 0, sizeof(*msg));
212 		msg->len = len;
213 		memcpy(data, in, len);
214 
215 		/* add us on to the list of packets to send to the onward conn */
216 		lws_dll2_add_tail(&msg->list, &pc->pending_msg_to_raw);
217 
218 		/* ask to send on the onward proxy client conn */
219 		lws_callback_on_writable(pc->wsi_raw);
220 		break;
221 
222 	default:
223 		break;
224 	}
225 
226 	return 0;
227 }
228 
229 /*
230  * Then the onward, raw client side
231  */
232 
233 static int
callback_proxy_raw_client(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)234 callback_proxy_raw_client(struct lws *wsi, enum lws_callback_reasons reason,
235 			  void *user, void *in, size_t len)
236 {
237 	proxy_conn_t *pc = (proxy_conn_t *)lws_get_opaque_user_data(wsi);
238 	proxy_msg_t *msg;
239 	uint8_t *data;
240 	int m, a;
241 
242 	switch (reason) {
243 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
244 		lwsl_warn("%s: onward raw connection failed\n", __func__);
245 		pc->wsi_raw = NULL;
246 		break;
247 
248 	case LWS_CALLBACK_RAW_ADOPT:
249 		lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
250 		pc->wsi_raw = wsi;
251 		lws_callback_on_writable(wsi);
252 		break;
253 
254 	case LWS_CALLBACK_RAW_CLOSE:
255 		lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
256 		/*
257 		 * Clean up any pending messages to us that are never going
258 		 * to get delivered now, we are in the middle of closing
259 		 */
260 		lws_dll2_foreach_safe(&pc->pending_msg_to_raw, NULL,
261 				      proxy_ws_raw_msg_destroy);
262 
263 		/*
264 		 * Remove our pointer from the proxy_conn... we are about to
265 		 * be destroyed.
266 		 */
267 		pc->wsi_raw = NULL;
268 		lws_set_opaque_user_data(wsi, NULL);
269 
270 		if (!pc->wsi_ws) {
271 			/*
272 			 * The original ws conn is already closed... then we are
273 			 * the last guy still holding on to the proxy_conn...
274 			 * and we're going away, so let's destroy it
275 			 */
276 
277 			free(pc);
278 			break;
279 		}
280 
281 		/*
282 		 * Original ws conn still alive...
283 		 * does he have stuff left to deliver?
284 		 */
285 		if (pc->pending_msg_to_ws.count) {
286 			/*
287 			 * Yes, let him get on with trying to send
288 			 * the remaining pieces... but put a time limit
289 			 * on how hard he will try now the raw part is
290 			 * disappearing... give him 3s
291 			 */
292 			lws_set_timeout(pc->wsi_ws,
293 				PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE, 3);
294 			break;
295 		}
296 		/*
297 		 * Original ws client conn doesn't have anything left
298 		 * to do, let's close him right after this, he will take care to
299 		 * destroy the proxy_conn when he goes down after he sees we
300 		 * have already been closed
301 		 */
302 
303 		lws_wsi_close(pc->wsi_ws, LWS_TO_KILL_ASYNC);
304 		break;
305 
306 	case LWS_CALLBACK_RAW_RX:
307 		lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
308 		if (!pc || !pc->wsi_ws)
309 			break;
310 
311 		/* notice we over-allocate by LWS_PRE + rx len */
312 		msg = (proxy_msg_t *)malloc(sizeof(*msg) + LWS_PRE + len);
313 		data = (uint8_t *)&msg[1] + LWS_PRE;
314 
315 		if (!msg) {
316 			lwsl_user("OOM: dropping\n");
317 			break;
318 		}
319 
320 		memset(msg, 0, sizeof(*msg));
321 		msg->len = len;
322 		memcpy(data, in, len);
323 
324 		/* add us on to the list of packets to send to the onward conn */
325 		lws_dll2_add_tail(&msg->list, &pc->pending_msg_to_ws);
326 
327 		/* ask to send on the onward proxy client conn */
328 		lws_callback_on_writable(pc->wsi_ws);
329 		break;
330 
331 	case LWS_CALLBACK_RAW_WRITEABLE:
332 		lwsl_user("LWS_CALLBACK_RAW_WRITEABLE\n");
333 		if (!pc || !pc->pending_msg_to_raw.count)
334 			break;
335 
336 		msg = lws_container_of(pc->pending_msg_to_raw.head,
337 				       proxy_msg_t, list);
338 		data = (uint8_t *)&msg[1] + LWS_PRE;
339 
340 		/* notice we allowed for LWS_PRE in the payload already */
341 		m = lws_write(wsi, data, msg->len, LWS_WRITE_TEXT);
342 		a = (int)msg->len;
343 		lws_dll2_remove(&msg->list);
344 		free(msg);
345 
346 		if (m < a) {
347 			lwsl_err("ERROR %d writing to raw\n", m);
348 			return -1;
349 		}
350 
351 		/*
352 		 * If more to do...
353 		 */
354 		if (pc->pending_msg_to_raw.count)
355 			lws_callback_on_writable(wsi);
356 		break;
357 	default:
358 		break;
359 	}
360 
361 	return 0;
362 }
363 
364 static struct lws_protocols protocols[] = {
365 	{ "http", lws_callback_http_dummy, 0, 0, 0, NULL, 0 },
366 	{ "lws-ws-raw-ws", callback_proxy_ws_server, 0, 1024, 0, NULL, 0 },
367 	{ "lws-ws-raw-raw", callback_proxy_raw_client, 0, 1024, 0, NULL, 0 },
368 	LWS_PROTOCOL_LIST_TERM
369 };
370 
371 static const lws_retry_bo_t retry = {
372 	.secs_since_valid_ping = 3,
373 	.secs_since_valid_hangup = 10,
374 };
375 
376 static int interrupted;
377 
378 static const struct lws_http_mount mount = {
379 	/* .mount_next */		NULL,		/* linked-list "next" */
380 	/* .mountpoint */		"/",		/* mountpoint URL */
381 	/* .origin */			"./mount-origin",  /* serve from dir */
382 	/* .def */			"index.html",	/* default filename */
383 	/* .protocol */			NULL,
384 	/* .cgienv */			NULL,
385 	/* .extra_mimetypes */		NULL,
386 	/* .interpret */		NULL,
387 	/* .cgi_timeout */		0,
388 	/* .cache_max_age */		0,
389 	/* .auth_mask */		0,
390 	/* .cache_reusable */		0,
391 	/* .cache_revalidate */		0,
392 	/* .cache_intermediaries */	0,
393 	/* .origin_protocol */		LWSMPRO_FILE,	/* files in a dir */
394 	/* .mountpoint_len */		1,		/* char count */
395 	/* .basic_auth_login_file */	NULL,
396 };
397 
sigint_handler(int sig)398 void sigint_handler(int sig)
399 {
400 	interrupted = 1;
401 }
402 
main(int argc,const char ** argv)403 int main(int argc, const char **argv)
404 {
405 	struct lws_context_creation_info info;
406 	struct lws_context *context;
407 	const char *p;
408 	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
409 			/* for LLL_ verbosity above NOTICE to be built into lws,
410 			 * lws must have been configured and built with
411 			 * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
412 			/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
413 			/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
414 			/* | LLL_DEBUG */;
415 
416 	signal(SIGINT, sigint_handler);
417 
418 	if ((p = lws_cmdline_option(argc, argv, "-d")))
419 		logs = atoi(p);
420 
421 	lws_set_log_level(logs, NULL);
422 	lwsl_user("LWS minimal ws-raw proxy | visit http://localhost:7681 (-s = use TLS / https)\n");
423 
424 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
425 	info.port = 7681;
426 	info.mounts = &mount;
427 	info.protocols = protocols;
428 	info.vhost_name = "localhost";
429 	info.options =
430 		LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
431 
432 #if defined(LWS_WITH_TLS)
433 	if (lws_cmdline_option(argc, argv, "-s")) {
434 		lwsl_user("Server using TLS\n");
435 		info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
436 		info.ssl_cert_filepath = "localhost-100y.cert";
437 		info.ssl_private_key_filepath = "localhost-100y.key";
438 	}
439 #endif
440 
441 	if (lws_cmdline_option(argc, argv, "-h"))
442 		info.options |= LWS_SERVER_OPTION_VHOST_UPG_STRICT_HOST_CHECK;
443 
444 	if (lws_cmdline_option(argc, argv, "-v"))
445 		info.retry_and_idle_policy = &retry;
446 
447 	context = lws_create_context(&info);
448 	if (!context) {
449 		lwsl_err("lws init failed\n");
450 		return 1;
451 	}
452 
453 	while (n >= 0 && !interrupted)
454 		n = lws_service(context, 0);
455 
456 	lws_context_destroy(context);
457 
458 	return 0;
459 }
460