/* * ws protocol handler plugin for testing raw file and raw socket * * Written in 2010-2019 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. * * The person who associated a work with this deed has dedicated * the work to the public domain by waiving all of his or her rights * to the work worldwide under copyright law, including all related * and neighboring rights, to the extent allowed by law. You can copy, * modify, distribute and perform the work, even for commercial purposes, * all without asking permission. * * These test plugins are intended to be adapted for use in your code, which * may be proprietary. So unlike the library itself, they are licensed * Public Domain. * * This plugin test both raw file descriptors and raw socket descriptors. It * can test both or just one depending on how you configure it. libwebsockets- * test-server-v2.0 is set up to test both. * * RAW File Descriptor Testing * =========================== * * Enable on a vhost like this * * "protocol-lws-raw-test": { * "status": "ok", * "fifo-path": "/tmp/lws-test-raw" * }, * * Then you can feed it data through the FIFO like this * * $ sudo sh -c "echo hello > /tmp/lws-test-raw" * * This plugin simply prints the data. But it does it through the lws event * loop / service poll. * * * RAW Socket Descriptor Testing * ============================= * * 1) You must give the vhost the option flag * LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG * * 2) Enable on a vhost like this * * "protocol-lws-raw-test": { * "status": "ok", * "raw": "1" * }, * * The "raw" pvo marks this protocol as being used for RAW connections. * * 3) Run libwebsockets-test-server-v2.0 and connect to it by telnet, eg * * telnet 127.0.0.1 7681 * * type something that isn't a valid HTTP method and enter, before the * connection times out. The connection will switch to RAW mode using this * protocol, and pass the unused rx as a raw RX callback. * * The test protocol echos back what was typed on telnet to telnet. */ #if !defined (LWS_PLUGIN_STATIC) #define LWS_DLL #define LWS_INTERNAL #include #endif #include struct per_vhost_data__raw_test { struct lws_context *context; struct lws_vhost *vhost; const struct lws_protocols *protocol; char fifo_path[100]; int fifo; char zero_length_read; }; struct per_session_data__raw_test { int number; unsigned char buf[128]; int len; }; static int callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__raw_test *pss = (struct per_session_data__raw_test *)user; struct per_vhost_data__raw_test *vhd = (struct per_vhost_data__raw_test *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); lws_sock_file_fd_type u; (void)pss; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__raw_test)); vhd->context = lws_get_context(wsi); vhd->protocol = lws_get_protocol(wsi); vhd->vhost = lws_get_vhost(wsi); { const struct lws_protocol_vhost_options *pvo = (const struct lws_protocol_vhost_options *)in; while (pvo) { if (!strcmp(pvo->name, "fifo-path")) lws_strncpy(vhd->fifo_path, pvo->value, sizeof(vhd->fifo_path)); pvo = pvo->next; } if (vhd->fifo_path[0] == '\0') { lwsl_err("%s: Missing pvo \"fifo-path\", " "raw file fd testing disabled\n", __func__); break; } } unlink(vhd->fifo_path); if (mkfifo(vhd->fifo_path, 0666)) { lwsl_err("mkfifo failed\n"); return 1; } vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY); if (vhd->fifo == -1) { lwsl_err("opening fifo failed\n"); unlink(vhd->fifo_path); return 1; } lwsl_notice("FIFO %s created\n", vhd->fifo_path); u.filefd = vhd->fifo; if (!lws_adopt_descriptor_vhost(vhd->vhost, LWS_ADOPT_RAW_FILE_DESC, u, "protocol-lws-raw-test", NULL)) { lwsl_err("Failed to adopt fifo descriptor\n"); close(vhd->fifo); unlink(vhd->fifo_path); return 1; } break; case LWS_CALLBACK_PROTOCOL_DESTROY: if (!vhd) break; if (vhd->fifo >= 0) { close(vhd->fifo); unlink(vhd->fifo_path); } break; /* * Callbacks related to Raw file descriptor testing */ case LWS_CALLBACK_RAW_ADOPT_FILE: lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n"); break; case LWS_CALLBACK_RAW_RX_FILE: lwsl_notice("LWS_CALLBACK_RAW_RX_FILE\n"); { char buf[256]; int n; n = read(vhd->fifo, buf, sizeof(buf) - 1); if (n < 0) { lwsl_err("FIFO read failed\n"); return 1; } /* * When nobody opened the other side of the FIFO, the * FIFO fd acts well and only signals POLLIN when * somebody opened and wrote to it. * * But if the other side of the FIFO closed it, we will * see an endless POLLIN and 0 available to read. * * The only way to handle it is to reopen the FIFO our * side and wait for a new peer. This is a quirk of * FIFOs not of LWS. */ if (n == 0) { /* peer closed - reopen in close processing */ vhd->zero_length_read = 1; return 1; } buf[n] = '\0'; lwsl_info("read %d\n", n); puts(buf); } break; case LWS_CALLBACK_RAW_CLOSE_FILE: lwsl_notice("LWS_CALLBACK_RAW_CLOSE_FILE\n"); if (vhd->zero_length_read) { vhd->zero_length_read = 0; close(vhd->fifo); /* the wsi that adopted the fifo file is closing... * reopen the fifo and readopt */ vhd->fifo = lws_open(vhd->fifo_path, O_NONBLOCK | O_RDONLY); if (vhd->fifo == -1) { lwsl_err("opening fifo failed\n"); return 1; } lwsl_notice("FIFO %s reopened\n", vhd->fifo_path); u.filefd = vhd->fifo; if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test", NULL)) { lwsl_err("Failed to adopt fifo descriptor\n"); close(vhd->fifo); return 1; } } break; case LWS_CALLBACK_RAW_WRITEABLE_FILE: lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE_FILE\n"); break; /* * Callbacks related to Raw socket descriptor testing */ case LWS_CALLBACK_RAW_ADOPT: lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n"); break; case LWS_CALLBACK_RAW_RX: lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len); if (len > sizeof(pss->buf)) len = sizeof(pss->buf); memcpy(pss->buf, in, len); pss->len = len; lws_callback_on_writable(wsi); break; case LWS_CALLBACK_RAW_CLOSE: lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n"); break; case LWS_CALLBACK_RAW_WRITEABLE: lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n"); lws_write(wsi, pss->buf, pss->len, LWS_WRITE_HTTP); break; default: break; } return 0; } #define LWS_PLUGIN_PROTOCOL_RAW_TEST \ { \ "protocol-lws-raw-test", \ callback_raw_test, \ sizeof(struct per_session_data__raw_test), \ 1024, /* rx buf size must be >= permessage-deflate rx size */ \ } #if !defined (LWS_PLUGIN_STATIC) static const struct lws_protocols protocols[] = { LWS_PLUGIN_PROTOCOL_RAW_TEST }; LWS_VISIBLE int init_protocol_lws_raw_test(struct lws_context *context, struct lws_plugin_capability *c) { if (c->api_magic != LWS_PLUGIN_API_MAGIC) { lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC, c->api_magic); return 1; } c->protocols = protocols; c->count_protocols = LWS_ARRAY_SIZE(protocols); c->extensions = NULL; c->count_extensions = 0; return 0; } LWS_VISIBLE int destroy_protocol_lws_raw_test(struct lws_context *context) { return 0; } #endif