1 /*
2 * ws protocol handler plugin for testing raw file and raw socket
3 *
4 * Written in 2010-2019 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 * The person who associated a work with this deed has dedicated
10 * the work to the public domain by waiving all of his or her rights
11 * to the work worldwide under copyright law, including all related
12 * and neighboring rights, to the extent allowed by law. You can copy,
13 * modify, distribute and perform the work, even for commercial purposes,
14 * all without asking permission.
15 *
16 * These test plugins are intended to be adapted for use in your code, which
17 * may be proprietary. So unlike the library itself, they are licensed
18 * Public Domain.
19 *
20 * This plugin test both raw file descriptors and raw socket descriptors. It
21 * can test both or just one depending on how you configure it. libwebsockets-
22 * test-server-v2.0 is set up to test both.
23 *
24 * RAW File Descriptor Testing
25 * ===========================
26 *
27 * Enable on a vhost like this
28 *
29 * "protocol-lws-raw-test": {
30 * "status": "ok",
31 * "fifo-path": "/tmp/lws-test-raw"
32 * },
33 *
34 * Then you can feed it data through the FIFO like this
35 *
36 * $ sudo sh -c "echo hello > /tmp/lws-test-raw"
37 *
38 * This plugin simply prints the data. But it does it through the lws event
39 * loop / service poll.
40 *
41 *
42 * RAW Socket Descriptor Testing
43 * =============================
44 *
45 * 1) You must give the vhost the option flag
46 * LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG
47 *
48 * 2) Enable on a vhost like this
49 *
50 * "protocol-lws-raw-test": {
51 * "status": "ok",
52 * "raw": "1"
53 * },
54 *
55 * The "raw" pvo marks this protocol as being used for RAW connections.
56 *
57 * 3) Run libwebsockets-test-server-v2.0 and connect to it by telnet, eg
58 *
59 * telnet 127.0.0.1 7681
60 *
61 * type something that isn't a valid HTTP method and enter, before the
62 * connection times out. The connection will switch to RAW mode using this
63 * protocol, and pass the unused rx as a raw RX callback.
64 *
65 * The test protocol echos back what was typed on telnet to telnet.
66 */
67
68 #if !defined (LWS_PLUGIN_STATIC)
69 #if !defined(LWS_DLL)
70 #define LWS_DLL
71 #endif
72 #if !defined(LWS_INTERNAL)
73 #define LWS_INTERNAL
74 #endif
75 #include <libwebsockets.h>
76 #endif
77
78 #include <string.h>
79 #include <fcntl.h>
80
81 #include <sys/stat.h>
82
83 struct per_vhost_data__raw_test {
84 struct lws_context *context;
85 struct lws_vhost *vhost;
86 const struct lws_protocols *protocol;
87 char fifo_path[100];
88 int fifo;
89 char zero_length_read;
90 };
91
92 struct per_session_data__raw_test {
93 int number;
94 unsigned char buf[128];
95 int len;
96 };
97
98 static int
callback_raw_test(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)99 callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, void *user,
100 void *in, size_t len)
101 {
102 struct per_session_data__raw_test *pss =
103 (struct per_session_data__raw_test *)user;
104 struct per_vhost_data__raw_test *vhd =
105 (struct per_vhost_data__raw_test *)
106 lws_protocol_vh_priv_get(lws_get_vhost(wsi),
107 lws_get_protocol(wsi));
108 lws_sock_file_fd_type u;
109
110 (void)pss;
111
112 switch (reason) {
113 case LWS_CALLBACK_PROTOCOL_INIT:
114 vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
115 lws_get_protocol(wsi),
116 sizeof(struct per_vhost_data__raw_test));
117 if (!vhd)
118 return 0;
119 vhd->context = lws_get_context(wsi);
120 vhd->protocol = lws_get_protocol(wsi);
121 vhd->vhost = lws_get_vhost(wsi);
122 {
123 const struct lws_protocol_vhost_options *pvo =
124 (const struct lws_protocol_vhost_options *)in;
125 while (pvo) {
126 if (!strcmp(pvo->name, "fifo-path"))
127 lws_strncpy(vhd->fifo_path, pvo->value,
128 sizeof(vhd->fifo_path));
129 pvo = pvo->next;
130 }
131 if (vhd->fifo_path[0] == '\0') {
132 lwsl_warn("%s: Missing pvo \"fifo-path\", "
133 "raw file fd testing disabled\n",
134 __func__);
135 break;
136 }
137 }
138 unlink(vhd->fifo_path);
139 if (mkfifo(vhd->fifo_path, 0666)) {
140 lwsl_err("mkfifo failed\n");
141 return 1;
142 }
143 vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY);
144 if (vhd->fifo == -1) {
145 lwsl_err("opening fifo failed\n");
146 unlink(vhd->fifo_path);
147 return 1;
148 }
149 lwsl_notice("FIFO %s created\n", vhd->fifo_path);
150 u.filefd = vhd->fifo;
151 if (!lws_adopt_descriptor_vhost(vhd->vhost,
152 LWS_ADOPT_RAW_FILE_DESC, u,
153 "protocol-lws-raw-test",
154 NULL)) {
155 lwsl_err("Failed to adopt fifo descriptor\n");
156 close(vhd->fifo);
157 unlink(vhd->fifo_path);
158 return 1;
159 }
160 break;
161
162 case LWS_CALLBACK_PROTOCOL_DESTROY:
163 if (!vhd)
164 break;
165 if (vhd->fifo >= 0) {
166 close(vhd->fifo);
167 unlink(vhd->fifo_path);
168 }
169 break;
170
171
172 /*
173 * Callbacks related to Raw file descriptor testing
174 */
175
176 case LWS_CALLBACK_RAW_ADOPT_FILE:
177 lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
178 break;
179
180
181 case LWS_CALLBACK_RAW_RX_FILE:
182 lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n");
183 {
184 char buf[256];
185 int n;
186
187 n = (int)read(vhd->fifo, buf, sizeof(buf) - 1);
188 if (n < 0) {
189 lwsl_err("FIFO read failed\n");
190 return 1;
191 }
192 /*
193 * When nobody opened the other side of the FIFO, the
194 * FIFO fd acts well and only signals POLLIN when
195 * somebody opened and wrote to it.
196 *
197 * But if the other side of the FIFO closed it, we will
198 * see an endless POLLIN and 0 available to read.
199 *
200 * The only way to handle it is to reopen the FIFO our
201 * side and wait for a new peer. This is a quirk of
202 * FIFOs not of LWS.
203 */
204 if (n == 0) { /* peer closed - reopen in close processing */
205 vhd->zero_length_read = 1;
206 return 1;
207 }
208 buf[n] = '\0';
209 lwsl_info("read %d\n", n);
210 puts(buf);
211 }
212 break;
213
214 case LWS_CALLBACK_RAW_CLOSE_FILE:
215 lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n");
216 if (vhd->zero_length_read) {
217 vhd->zero_length_read = 0;
218 close(vhd->fifo);
219 /* the wsi that adopted the fifo file is closing...
220 * reopen the fifo and readopt
221 */
222 vhd->fifo = lws_open(vhd->fifo_path,
223 O_NONBLOCK | O_RDONLY);
224 if (vhd->fifo == -1) {
225 lwsl_err("opening fifo failed\n");
226 return 1;
227 }
228 lwsl_notice("FIFO %s reopened\n", vhd->fifo_path);
229 u.filefd = vhd->fifo;
230 if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u,
231 "protocol-lws-raw-test", NULL)) {
232 lwsl_err("Failed to adopt fifo descriptor\n");
233 close(vhd->fifo);
234 return 1;
235 }
236 }
237 break;
238
239 case LWS_CALLBACK_RAW_WRITEABLE_FILE:
240 lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n");
241 break;
242
243 /*
244 * Callbacks related to Raw socket descriptor testing
245 */
246
247 case LWS_CALLBACK_RAW_ADOPT:
248 lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
249 break;
250
251 case LWS_CALLBACK_RAW_RX:
252 lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
253 if (len > sizeof(pss->buf))
254 len = sizeof(pss->buf);
255 memcpy(pss->buf, in, len);
256 pss->len = (int)len;
257 lws_callback_on_writable(wsi);
258 break;
259
260 case LWS_CALLBACK_RAW_CLOSE:
261 lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
262 break;
263
264 case LWS_CALLBACK_RAW_WRITEABLE:
265 lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
266 lws_write(wsi, pss->buf, (size_t)pss->len, LWS_WRITE_HTTP);
267 break;
268
269 default:
270 break;
271 }
272
273 return 0;
274 }
275
276 #define LWS_PLUGIN_PROTOCOL_RAW_TEST \
277 { \
278 "protocol-lws-raw-test", \
279 callback_raw_test, \
280 sizeof(struct per_session_data__raw_test), \
281 1024, /* rx buf size must be >= permessage-deflate rx size */ 0, NULL, 0\
282 }
283
284 #if !defined (LWS_PLUGIN_STATIC)
285
286 LWS_VISIBLE const struct lws_protocols lws_raw_test_protocols[] = {
287 LWS_PLUGIN_PROTOCOL_RAW_TEST
288 };
289
290 LWS_VISIBLE const lws_plugin_protocol_t lws_raw_test = {
291 .hdr = {
292 "lws raw test",
293 "lws_protocol_plugin",
294 LWS_BUILD_HASH,
295 LWS_PLUGIN_API_MAGIC
296 },
297
298 .protocols = lws_raw_test_protocols,
299 .count_protocols = LWS_ARRAY_SIZE(lws_raw_test_protocols),
300 .extensions = NULL,
301 .count_extensions = 0,
302 };
303
304 #endif
305