1# Http fallback and raw proxying 2 3Lws has several interesting options and features that can be applied to get 4some special behaviours... this article discusses them and how they work. 5 6## Overview of normal vhost selection 7 8Lws supports multiple http or https vhosts sharing a listening socket on the 9same port. 10 11For unencrypted http, the Host: header is used to select which vhost the 12connection should bind to, by comparing what is given there against the 13names the server was configured with for the various vhosts. If no match, it 14selects the first configured vhost. 15 16For TLS, it has an extension called SNI (Server Name Indication) which tells 17the server early in the TLS handshake the host name the connection is aimed at. 18That allows lws to select the vhost early, and use vhost-specific TLS certs 19so everything is happy. Again, if there is no match the connection proceeds 20using the first configured vhost and its certs. 21 22## Http(s) fallback options 23 24What happens if you try to connect, eg, an ssh client to the http server port 25(this is not an idle question...)? Obviously the http server part or the tls 26part of lws will fail the connection and close it. (We will look at that flow 27in a moment in detail for both unencrypted and tls listeners.) 28 29However if the first configured vhost for the port was created with the 30vhost creation info struct `.options` flag `LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`, 31then instead of the error, the connection transitions to whatever role was 32given in the vhost creation info struct `.listen_accept_role` and `.listen_accept_protocol`. 33 34With lejp-conf / lwsws, the options can be applied to the first vhost using: 35 36``` 37 "listen-accept-role": "the-role-name", 38 "listen-accept-protocol": "the-protocol-name", 39 "fallback-listen-accept": "1" 40``` 41 42See `./minimal-examples/raw/minimal-raw-fallback-http-server` for examples of 43all the options in use via commandline flags. 44 45So long as the first packet for the protocol doesn't look like GET, POST, or 46a valid tls packet if connection to an https vhost, this allows the one listen 47socket to handle both http(s) and a second protocol, as we will see, like ssh. 48 49Notice there is a restriction that no vhost selection processing is possible, 50neither for tls listeners nor plain http ones... the packet belonging to a 51different protocol will not send any Host: header nor tls SNI. 52 53Therefore although the flags and settings are applied to the first configured 54vhost, actually their effect is global for a given listen port. If enabled, 55all vhosts on the same listen port will do the fallback action. 56 57### Plain http flow 58 59![plain http flow](/doc-assets/accept-flow-1.svg) 60 61Normally, if the first received packet does not contain a valid HTTP method, 62then the connection is dropped. Which is what you want from an http server. 63 64However if enabled, the connection can transition to the defined secondary 65role / protocol. 66 67|Flag|lejp-conf / lwsws|Function| 68|---|---|---| 69|`LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`|`"fallback-listen-accept": "1"`|Enable fallback processing| 70 71### TLS https flow 72 73![tls https flow](/doc-assets/accept-flow-2.svg) 74 75If the port is listening with tls, the point that a packet from a different 76protocol will fail is earlier, when the tls tunnel is being set up. 77 78|Flag|lejp-conf / lwsws|Function| 79|---|---|---| 80|`LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG`|`"fallback-listen-accept": "1"`|Enable fallback processing| 81|`LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS`|`"redirect-http": "1"`|Treat invalid tls packet as http, issue http redirect to https://| 82|`LWS_SERVER_OPTION_ALLOW_HTTP_ON_HTTPS_LISTENER`|`"allow-http-on-https": "1"`|Accept unencrypted http connections on this tls port (dangerous)| 83 84The latter two options are higher priority than, and defeat, the first one. 85 86### Non-http listener 87 88![non-http flow](/doc-assets/accept-flow-3.svg) 89 90It's also possible to skip the fallback processing and just force the first 91vhost on the port to use the specified role and protocol in the first place. 92 93|Flag|lejp-conf / lwsws|Function| 94|---|---|---| 95|LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG|`"apply-listen-accept": "1"`|Force vhost to use listen-accept-role / listen-accept-protocol| 96 97## Using http(s) fallback with raw-proxy 98 99If enabled for build with `cmake .. -DLWS_ROLE_RAW_PROXY=1 -DLWS_WITH_PLUGINS=1` 100then lws includes ready-to-use support for raw tcp proxying. 101 102This can be used standalone on the first vhost on a port, but most intriguingly 103it can be specified as the fallback for http(s)... 104 105See `./minimal-examples/raw/minimal-raw-proxy-fallback.c` for a working example. 106 107### fallback with raw-proxy in code 108 109On the first vhost for the port, specify the required "onward" pvo to configure 110the raw-proxy protocol...you can adjust the "ipv4:127.0.0.1:22" to whatever you 111want... 112 113``` 114 static struct lws_protocol_vhost_options pvo1 = { 115 NULL, 116 NULL, 117 "onward", /* pvo name */ 118 "ipv4:127.0.0.1:22" /* pvo value */ 119 }; 120 121 static const struct lws_protocol_vhost_options pvo = { 122 NULL, /* "next" pvo linked-list */ 123 &pvo1, /* "child" pvo linked-list */ 124 "raw-proxy", /* protocol name we belong to on this vhost */ 125 "" /* ignored */ 126 }; 127``` 128 129... and set up the fallback enable and bindings... 130 131``` 132 info.options |= LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG; 133 info.listen_accept_role = "raw_proxy"; 134 info.listen_accept_proxy = "raw_proxy"; 135 info.pvo = &pvo; 136``` 137 138### fallback with raw-proxy in JSON conf 139 140On the first vhost for the port, enable the raw-proxy protocol on the vhost and 141set the pvo config 142 143``` 144 "ws-protocols": [{ 145 "raw-proxy": { 146 "status": "ok", 147 "onward": "ipv4:127.0.0.1:22" 148 } 149 }], 150``` 151 152Enable the fallback behaviour on the vhost and the role / protocol binding 153 154``` 155 "listen-accept-role": "raw-proxy", 156 "listen-accept-protocol": "raw-proxy", 157 "fallback-listen-accept": "1" 158``` 159 160### Testing 161 162With this configured, the listen port will function normally for http or https 163depending on how it was set up. 164 165But if you try to connect to it with an ssh client, that will also work fine. 166 167The libwebsockets.org server is set up in this way, you can confirm it by 168visiting `https://libwebsockets.org` on port 443 as usual, but also trying 169`ssh -p 443 invalid@libwebsockets.org`... you will get permission denied from 170your ssh client. With valid credentials in fact that works perfectly for 171ssh, scp, git-over-ssh etc all on port 443... 172 173