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