7) There is an optional `mod_time` uint32_t member in the generic fop_fd. If you are able to set it during open, you
should indicate it by setting `LWS_FOP_FLAG_MOD_TIME_VALID` on the flags.
+@section rawfd RAW file descriptor polling
+
+LWS allows you to include generic platform file descriptors in the lws service / poll / event loop.
+
+Open your fd normally and then
+
+```
+ lws_sock_file_fd_type u;
+
+ u.filefd = your_open_file_fd;
+
+ if (!lws_adopt_descriptor_vhost(vhost, 0, u,
+ "protocol-name-to-bind-to",
+ optional_wsi_parent_or_NULL)) {
+ // failed
+ }
+
+ // OK
+```
+
+A wsi is created for the file fd that acts like other wsi, you will get these
+callbacks on the named protocol
+
+```
+ LWS_CALLBACK_RAW_ADOPT_FILE
+ LWS_CALLBACK_RAW_RX_FILE
+ LWS_CALLBACK_RAW_WRITEABLE_FILE
+ LWS_CALLBACK_RAW_CLOSE_FILE
+```
+
+starting with LWS_CALLBACK_RAW_ADOPT_FILE.
+
+`protocol-lws-raw-test` plugin provides a method for testing this with
+`libwebsockets-test-server-v2.0`:
+
+The plugin creates a FIFO on your system called "/tmp/lws-test-raw"
+
+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.
+
+@section rawsrvsocket RAW server socket descriptor polling
+
+You can also enable your vhost to accept RAW socket connections, in addition to
+HTTP[s] and WS[s]. If the first bytes written on the connection are not a
+valid HTTP method, then the connection switches to RAW mode.
+
+This is disabled by default, you enable it by setting the `.options` flag
+LWS_SERVER_OPTION_FALLBACK_TO_RAW when creating the vhost.
+
+RAW mode socket connections receive the following callbacks
+
+```
+ LWS_CALLBACK_RAW_ADOPT
+ LWS_CALLBACK_RAW_RX
+ LWS_CALLBACK_RAW_WRITEABLE
+ LWS_CALLBACK_RAW_CLOSE
+```
+
+You can control which protocol on your vhost handles these RAW mode
+incoming connections by marking the selected protocol with a pvo `raw`, eg
+
+```
+ "protocol-lws-raw-test": {
+ "status": "ok",
+ "raw": "1"
+ },
+```
+
+The "raw" pvo marks this protocol as being used for RAW connections.
+
+`protocol-lws-raw-test` plugin provides a method for testing this with
+`libwebsockets-test-server-v2.0`:
+
+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.
+
+@section rawclientsocket RAW client socket descriptor polling
+
+You can now also open RAW socket connections in client mode.
+
+Follow the usual method for creating a client connection, but set the
+`info.method` to "RAW". When the connection is made, the wsi will be
+converted to RAW mode and operate using the same callbacks as the
+server RAW sockets described above.
+
+The libwebsockets-test-client supports this using raw:// URLS. To
+test, open a netcat listener in one window
+
+```
+ $ nc -l 9999
+```
+
+and in another window, connect to it using the test client
+
+```
+ $ libwebsockets-test-client raw://127.0.0.1:9999
+```
+
+The connection should succeed, and text typed in the netcat window (including a CRLF)
+will be received in the client.
+
@section ecdh ECDH Support
ECDH Certs are now supported. Enable the CMake option
case LWSCM_WSCL_ISSUE_HANDSHAKE2:
p = lws_generate_client_handshake(wsi, p);
if (p == NULL) {
+ if (wsi->mode == LWSCM_RAW)
+ return 0;
+
lwsl_err("Failed to generate handshake for client\n");
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
return 0;
} else
wsi->do_ws = 0;
+ if (!strcmp(meth, "RAW")) {
+ const char *pp = lws_hdr_simple_ptr(wsi,
+ _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+ lwsl_notice("client transition to raw\n");
+ if (pp) {
+ const struct lws_protocols *pr;
+
+ pr = lws_vhost_name_to_protocol(wsi->vhost, pp);
+
+ if (!pr) {
+ lwsl_err("protocol %s not enabled on vhost\n",
+ pp);
+ return NULL;
+ }
+
+ lws_bind_protocol(wsi, pr);
+ }
+ if ((wsi->protocol->callback)(wsi,
+ LWS_CALLBACK_RAW_ADOPT,
+ wsi->user_space, NULL, 0))
+ return NULL;
+
+ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
+ lws_union_transition(wsi, LWSCM_RAW);
+ lws_header_table_detach(wsi, 1);
+
+ return NULL;
+ }
+
if (wsi->do_ws) {
/*
* create the random key
vh->protocols[n].name);
vh->default_protocol_index = n;
}
+ if (!strcmp(pvo->name, "raw")) {
+ lwsl_notice("Setting raw "
+ "protocol for vh %s to %s\n",
+ vh->name,
+ vh->protocols[n].name);
+ vh->raw_protocol_index = n;
+ }
pvo = pvo->next;
}
/* Handshake indicates this session is done. */
goto bail;
+ /* we might have transitioned to RAW */
+ if (wsi->mode == LWSCM_RAW)
+ /* we gave the read buffer to RAW handler already */
+ goto read_ok;
+
/*
* It's possible that we've exhausted our data already, or
* rx flow control has stopped us dealing with this early,
}
}
+int
+lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p)
+{
+// if (wsi->protocol == p)
+// return 0;
+
+ if (wsi->protocol)
+ wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL,
+ wsi->user_space, NULL, 0);
+ if (!wsi->user_space_externally_allocated)
+ lws_free_set_NULL(wsi->user_space);
+
+ wsi->protocol = p;
+ if (!p)
+ return 0;
+
+ if (lws_ensure_user_space(wsi))
+ return 1;
+
+ if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL,
+ wsi->user_space, NULL, 0))
+ return 1;
+
+ return 0;
+}
+
void
lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
{
* the context, only the string you give in the client connect
* info for .origin (if any) will be used directly.
*/
+ LWS_SERVER_OPTION_FALLBACK_TO_RAW = (1 << 20),
+ /**< (VH) if invalid http is coming in the first line, */
/****** add new things just above ---^ ******/
};
}
/*
* hm it's an unknown http method from a client in fact,
- * treat as dangerous
+ * it cannot be valid http
*/
if (m == ARRAY_SIZE(methods)) {
+ /*
+ * are we set up to accept raw in these cases?
+ */
+ if (lws_check_opt(wsi->vhost->options,
+ LWS_SERVER_OPTION_FALLBACK_TO_RAW))
+ return 2; /* transition to raw */
+
lwsl_info("Unknown method - dropping\n");
goto forbid;
}
unsigned int created_vhost_protocols:1;
unsigned char default_protocol_index;
+ unsigned char raw_protocol_index;
};
/*
if (p)
colon = p - servername;
- /* first try exact matches */
+ /* Priotity 1: first try exact matches */
while (vhost) {
if (port == vhost->listen_port &&
}
/*
- * if no exact matches, try matching *.vhost-name
+ * Priority 2: if no exact matches, try matching *.vhost-name
* unintentional matches are possible but resolve to x.com for *.x.com
* which is reasonable. If exact match exists we already chose it and
* never reach here. SSL will still fail it if the cert doesn't allow
vhost = vhost->vhost_next;
}
+ /* Priority 3: match the first vhost on our port */
+
+ vhost = context->vhost_list;
+ while (vhost) {
+ if (port == vhost->listen_port) {
+ lwsl_info("vhost match to %s based on port %d\n",
+ vhost->name, port);
+ return vhost;
+ }
+ vhost = vhost->vhost_next;
+ }
+
+ /* no match */
+
return NULL;
}
#endif
}
-int
-lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p)
-{
-// if (wsi->protocol == p)
-// return 0;
-
- if (wsi->protocol)
- wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL,
- wsi->user_space, NULL, 0);
- if (!wsi->user_space_externally_allocated)
- lws_free_set_NULL(wsi->user_space);
-
- wsi->protocol = p;
- if (!p)
- return 0;
-
- if (lws_ensure_user_space(wsi))
- return 1;
-
- if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL,
- wsi->user_space, NULL, 0))
- return 1;
-
- return 0;
-}
-
int
lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len)
{
+ int protocol_len, n = 0, hit, non_space_char_found = 0, m;
struct lws_context *context = lws_get_context(wsi);
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
struct _lws_header_related hdr;
struct allocated_headers *ah;
- int protocol_len, n = 0, hit, non_space_char_found = 0;
+ unsigned char *obuf = *buf;
char protocol_list[128];
char protocol_name[64];
+ size_t olen = len;
char *p;
if (len >= 10000000) {
goto bail_nuke_ah;
}
- if (lws_parse(wsi, *(*buf)++)) {
+ m = lws_parse(wsi, *(*buf)++);
+ if (m) {
+ if (m == 2) {
+ /*
+ * we are transitioning from http with
+ * an AH, to raw. Drop the ah and set
+ * the mode.
+ */
+raw_transition:
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+ lws_bind_protocol(wsi, &wsi->vhost->protocols[
+ wsi->vhost->
+ raw_protocol_index]);
+ lwsl_info("transition to raw vh %s prot %d\n",
+ wsi->vhost->name,
+ wsi->vhost->raw_protocol_index);
+ if ((wsi->protocol->callback)(wsi,
+ LWS_CALLBACK_RAW_ADOPT,
+ wsi->user_space, NULL, 0))
+ goto bail_nuke_ah;
+
+ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
+ lws_union_transition(wsi, LWSCM_RAW);
+ lws_header_table_detach(wsi, 1);
+
+ if (m == 2 && (wsi->protocol->callback)(wsi,
+ LWS_CALLBACK_RAW_RX,
+ wsi->user_space, obuf, olen))
+ return 1;
+
+ return 0;
+ }
lwsl_info("lws_parse failed\n");
goto bail_nuke_ah;
}
if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECT)) {
lwsl_info("Changing to RAW mode\n");
- lws_union_transition(wsi, LWSCM_RAW);
- if (!wsi->more_rx_waiting) {
- wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
-
- //lwsl_notice("%p: dropping ah EST\n", wsi);
- lws_header_table_detach(wsi, 1);
- }
+ m = 0;
+ goto raw_transition;
}
wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT;
case LWSCM_HTTP_SERVING:
case LWSCM_HTTP_SERVING_ACCEPTED:
case LWSCM_HTTP2_SERVING:
+ case LWSCM_RAW:
/* handle http headers coming in */
/* these states imply we MUST have an ah attached */
- if (wsi->state == LWSS_HTTP ||
+ if (wsi->mode != LWSCM_RAW && (wsi->state == LWSS_HTTP ||
wsi->state == LWSS_HTTP_ISSUING_FILE ||
- wsi->state == LWSS_HTTP_HEADERS) {
+ wsi->state == LWSS_HTTP_HEADERS)) {
if (!wsi->u.hdr.ah) {
//lwsl_err("wsi %p: missing ah\n", wsi);
len = lws_ssl_capable_read(wsi, pt->serv_buf,
context->pt_serv_buf_size);
-// lwsl_notice("%s: wsi %p read %d\r\n", __func__, wsi, len);
+ lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len);
switch (len) {
case 0:
lwsl_info("%s: read 0 len\n", __func__);
wsi, LWS_CALLBACK_RAW_RX,
wsi->user_space, pt->serv_buf, len);
if (n < 0) {
- lwsl_info("raw writeable_fail\n");
+ lwsl_info("LWS_CALLBACK_RAW_RX_fail\n");
goto fail;
}
- break;
+ goto try_pollout;
}
/* just ignore incoming if waiting for close */
goto fail;
}
+ if (wsi->mode == LWSCM_RAW) {
+ n = user_callback_handle_rxflow(wsi->protocol->callback,
+ wsi, LWS_CALLBACK_RAW_WRITEABLE,
+ wsi->user_space, NULL, 0);
+ if (n < 0) {
+ lwsl_info("writeable_fail\n");
+ goto fail;
+ }
+ break;
+ }
+
if (!wsi->hdr_parsing_completed)
break;
goto handled;
}
#endif
+ /* fallthru */
+ case LWSCM_RAW:
n = lws_server_socket_service(context, wsi, pollfd);
if (n) /* closed by above */
return 1;
goto handled;
case LWSCM_RAW_FILEDESC:
- case LWSCM_RAW:
+
if (pollfd->revents & LWS_POLLOUT) {
n = lws_calllback_as_writeable(wsi);
if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
goto close_and_handled;
}
}
+
+ if (pollfd->revents & LWS_POLLHUP)
+ goto close_and_handled;
n = 0;
goto handled;
* 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": {
*
* $ 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.
+ * 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_RAW
+ *
+ * 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)
struct per_session_data__raw_test {
int number;
+ unsigned char buf[128];
+ int len;
};
static int
pvo = pvo->next;
}
if (vhd->fifo_path[0] == '\0') {
- lwsl_err("Missing pvo \"fifo-path\"\n");
- return 1;
+ lwsl_err("%s: Missing pvo \"fifo-path\", raw file fd testing disabled\n", __func__);
+ break;
}
}
unlink(vhd->fifo_path);
}
lwsl_notice("FIFO %s created\n", vhd->fifo_path);
u.filefd = vhd->fifo;
- if (!lws_adopt_descriptor_vhost(vhd->vhost, 0, u, "protocol-lws-raw-test", NULL)) {
+ 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);
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;
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;
}
+static int
+callback_test_raw_client(struct lws *wsi, enum lws_callback_reasons reason,
+ void *user, void *in, size_t len)
+{
+ switch (reason) {
+ 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);
+ puts(in);
+ 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");
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
/* list of supported protocols and callbacks */
-static struct lws_protocols protocols[] = {
+static const struct lws_protocols protocols[] = {
{
"dumb-increment-protocol",
callback_dumb_increment,
callback_lws_mirror,
0,
128,
+ }, {
+ "lws-test-raw-client",
+ callback_test_raw_client,
+ 0,
+ 128
},
{ NULL, NULL, 0, 0 } /* end */
};
i.method = "GET";
do_ws = 0;
} else
- lwsl_notice("using %s mode (ws)\n", prot);
+ if (!strcmp(prot, "raw")) {
+ i.method = "RAW";
+ i.protocol = "lws-test-raw-client";
+ lwsl_notice("using RAW mode connection\n");
+ do_ws = 0;
+ } else
+ lwsl_notice("using %s mode (ws)\n", prot);
/*
* sit there servicing the websocket context to handle incoming
"1"
};
+static const struct lws_protocol_vhost_options pvo_opt4a = {
+ NULL,
+ NULL,
+ "raw", /* indicate we are the protocol that gets raw connections */
+ "1"
+};
+
+static const struct lws_protocol_vhost_options pvo_opt4 = {
+ &pvo_opt4a,
+ NULL,
+ "fifo-path", /* tell the raw test plugin to open a raw file here */
+ "/tmp/lws-test-raw"
+};
+
/*
* We must enable the plugin protocols we want into our vhost with a
* linked-list. We can also give the plugin per-vhost options here.
*/
-static const struct lws_protocol_vhost_options pvo_3 = {
+static const struct lws_protocol_vhost_options pvo_4 = {
NULL,
+ &pvo_opt4, /* set us as the protocol who gets raw connections */
+ "protocol-lws-raw-test",
+ "" /* ignored, just matches the protocol name above */
+};
+
+static const struct lws_protocol_vhost_options pvo_3 = {
+ &pvo_4,
NULL,
"protocol-post-demo",
"" /* ignored, just matches the protocol name above */
info.gid = gid;
info.uid = uid;
info.max_http_header_pool = 16;
- info.options = opts |
+ info.options = opts | LWS_SERVER_OPTION_FALLBACK_TO_RAW |
LWS_SERVER_OPTION_VALIDATE_UTF8 |
LWS_SERVER_OPTION_LIBUV; /* plugins require this */