raw: enable server and client raw sockets

This commit is contained in:
Andy Green 2017-03-07 16:06:05 +08:00
parent 4578036b53
commit 205ccedf6e
13 changed files with 410 additions and 53 deletions

View file

@ -440,6 +440,122 @@ on the flags during open.
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

View file

@ -196,6 +196,9 @@ lws_client_socket_service(struct lws_context *context, struct lws *wsi,
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;
@ -1027,6 +1030,36 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
} 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

View file

@ -175,6 +175,13 @@ lws_protocol_init(struct lws_context *context)
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;
}

View file

@ -121,6 +121,11 @@ lws_read(struct lws *wsi, unsigned char *buf, size_t len)
/* 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,

View file

@ -165,6 +165,32 @@ lws_remove_child_from_any_parent(struct lws *wsi)
}
}
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)
{

View file

@ -1612,6 +1612,8 @@ enum lws_context_options {
* 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 ---^ ******/
};

View file

@ -813,9 +813,16 @@ swallow:
}
/*
* 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;
}

View file

@ -795,6 +795,7 @@ struct lws_vhost {
unsigned int created_vhost_protocols:1;
unsigned char default_protocol_index;
unsigned char raw_protocol_index;
};
/*

View file

@ -210,7 +210,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername)
if (p)
colon = p - servername;
/* first try exact matches */
/* Priotity 1: first try exact matches */
while (vhost) {
if (port == vhost->listen_port &&
@ -222,7 +222,7 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername)
}
/*
* 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
@ -243,6 +243,20 @@ lws_select_vhost(struct lws_context *context, int port, const char *servername)
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;
}
@ -1137,43 +1151,19 @@ transaction_result_n:
#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) {
@ -1195,7 +1185,38 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len)
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;
}
@ -1253,13 +1274,8 @@ lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len)
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;
@ -1991,6 +2007,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi,
case LWSCM_HTTP_SERVING:
case LWSCM_HTTP_SERVING_ACCEPTED:
case LWSCM_HTTP2_SERVING:
case LWSCM_RAW:
/* handle http headers coming in */
@ -2033,9 +2050,9 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi,
/* 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);
@ -2105,7 +2122,7 @@ lws_server_socket_service(struct lws_context *context, struct lws *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__);
@ -2124,10 +2141,10 @@ lws_server_socket_service(struct lws_context *context, struct lws *wsi,
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 */
@ -2162,6 +2179,17 @@ try_pollout:
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;

View file

@ -863,13 +863,15 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
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)) {
@ -892,6 +894,9 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
goto close_and_handled;
}
}
if (pollfd->revents & LWS_POLLHUP)
goto close_and_handled;
n = 0;
goto handled;

View file

@ -17,6 +17,13 @@
* 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": {
@ -28,8 +35,33 @@
*
* $ 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)
@ -51,6 +83,8 @@ struct per_vhost_data__raw_test {
struct per_session_data__raw_test {
int number;
unsigned char buf[128];
int len;
};
static int
@ -84,8 +118,8 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
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);
@ -101,7 +135,9 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
}
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);
@ -118,6 +154,11 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
}
break;
/*
* Callbacks related to Raw file descriptor testing
*/
case LWS_CALLBACK_RAW_ADOPT_FILE:
lwsl_notice("LWS_CALLBACK_RAW_ADOPT_FILE\n");
break;
@ -179,6 +220,32 @@ callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
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;
}

View file

@ -334,10 +334,38 @@ callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
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,
@ -349,6 +377,11 @@ static struct lws_protocols protocols[] = {
callback_lws_mirror,
0,
128,
}, {
"lws-test-raw-client",
callback_test_raw_client,
0,
128
},
{ NULL, NULL, 0, 0 } /* end */
};
@ -597,7 +630,13 @@ int main(int argc, char **argv)
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

View file

@ -155,13 +155,34 @@ static const struct lws_protocol_vhost_options pvo_opt = {
"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 */
@ -367,7 +388,7 @@ int main(int argc, char **argv)
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 */