Update Files

This commit is contained in:
2025-01-22 17:22:38 +01:00
parent 89b9349629
commit 4c5e729485
5132 changed files with 1195369 additions and 0 deletions

View File

@ -0,0 +1,154 @@
#
# libwebsockets - small server side websockets and web server implementation
#
# Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
include_directories(.)
if (LWS_WITH_CLIENT)
list(APPEND SOURCES
secure-streams/secure-streams.c
secure-streams/policy-common.c
secure-streams/system/captive-portal-detect/captive-portal-detect.c
secure-streams/protocols/ss-raw.c
)
if (NOT LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY)
list(APPEND SOURCES
secure-streams/policy-json.c
secure-streams/system/fetch-policy/fetch-policy.c
)
endif()
if (LWS_ROLE_H1)
list(APPEND SOURCES
secure-streams/protocols/ss-h1.c
)
endif()
if (LWS_ROLE_H2)
list(APPEND SOURCES
secure-streams/protocols/ss-h2.c
)
endif()
if (LWS_ROLE_WS)
list(APPEND SOURCES
secure-streams/protocols/ss-ws.c
)
endif()
if (LWS_ROLE_MQTT)
list(APPEND SOURCES
secure-streams/protocols/ss-mqtt.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
list(APPEND SOURCES
secure-streams/serialized/proxy/proxy.c
secure-streams/serialized/proxy/proxy-transport.c
secure-streams/serialized/proxy/proxy-transport-wsi.c
secure-streams/serialized/proxy/proxy-deserialize.c
secure-streams/serialized/client/sspc.c
secure-streams/serialized/client/sspc-transport.c
secure-streams/serialized/client/sspc-transport-wsi.c
secure-streams/serialized/client/sspc-deserialize.c
core-net/transport-mux-client.c
core-net/transport-mux-common.c
core-net/transport-mux-proxy.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM AND
LWS_WITH_SYS_STATE)
list(APPEND SOURCES
secure-streams/system/auth-api.amazon.com/auth.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_AUTH_SIGV4)
list(APPEND SOURCES
secure-streams/system/auth-sigv4/sign.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_CPP)
list(APPEND SOURCES secure-streams/cpp/lss.cxx)
if (LWS_ROLE_H1 OR LWS_ROLE_H2)
list(APPEND SOURCES secure-streams/cpp/lssFile.cxx)
endif()
if (LWS_ROLE_WS)
list(APPEND SOURCES secure-streams/cpp/lssMsg.cxx)
endif()
endif()
#
# Helper function for adding a secure stream plugin
#
macro(create_ss_plugin NAME S2 S3 S4 S5 S6)
set(SSP_SRCS)
set(SSP_PUBLIC_HDR)
set(SSP_HDR)
if ("${S2}" STREQUAL "")
else()
list(APPEND SSP_SRCS plugins/${NAME}/${S2})
endif()
if ("${S3}" STREQUAL "")
else()
list(APPEND SSP_SRCS plugins/${NAME}/${S3})
endif()
if ("${S4}" STREQUAL "")
else()
list(APPEND SSP_SRCS plugins/${NAME}/${S4})
endif()
if ("${S5}" STREQUAL "")
else()
list(APPEND SSP_SRCS plugins/${NAME}/${S5})
endif()
if ("${S6}" STREQUAL "")
else()
list(APPEND SSP_SRCS plugins/${NAME}/${S6})
endif()
source_group("Headers Private" FILES ${SSP_HDR})
source_group("Sources" FILES ${SSP_SRCS})
add_library( ${NAME} STATIC
${SSP_HDR} ${SSP_PUBLIC_HDR} ${SSP_SRCS} )
target_include_directories(${NAME} PRIVATE "${LWS_LIB_INCLUDES}" ${LWS_LIB_BUILD_INC_PATHS})
if (NOT LWS_PLAT_FREERTOS)
add_dependencies(${NAME} websockets_shared)
endif()
list(APPEND SS_PLUGINS_LIST ${NAME})
endmacro()
# create_ss_plugin(ssp-h1url "h1url.c" "" "" "" "")
endif()
#
# Keep explicit parent scope exports at end
#
exports_to_parent_scope()

View File

@ -0,0 +1,924 @@
# Secure Streams
Secure Streams is a networking api that strictly separates payload from any
metadata. That includes the client endpoint address for the connection, the tls
trust chain and even the protocol used to connect to the endpoint.
The user api just receives and transmits payload, and receives advisory
connection state information.
The details about how the connections for different types of secure stream should
be made are held in JSON "policy database" initially passed in to the context
creation, but able to be updated from a remote copy.
Both client and server networking can be handled using Secure Streams APIS.
![overview](/doc-assets/ss-operation-modes.png)
## Secure Streams CLIENT State lifecycle
![overview](/doc-assets/ss-state-flow.png)
Secure Streams are created using `lws_ss_create()`, after that they may acquire
underlying connections, and lose them, but the lifecycle of the Secure Stream
itself is not directly related to any underlying connection.
Once created, Secure Streams may attempt connections, these may fail and once
the number of failures exceeds the count of attempts to conceal in the retry /
backoff policy, the stream reaches `LWSSSCS_ALL_RETRIES_FAILED`. The stream becomes
idle again until another explicit connection attempt is given.
Once connected, the user code can use `lws_ss_request_tx()` to ask for a slot
to write to the peer, when this if forthcoming the tx handler can send a message.
If the underlying protocol gives indications of transaction success, such as,
eg, a 200 for http, or an ACK from MQTT, the stream state is called back with
an `LWSSSCS_QOS_ACK_REMOTE` or `LWSSSCS_QOS_NACK_REMOTE`.
## SS Callback return handling
SS state(), rx() and tx() can indicate with their return code some common
situations that should be handled by the caller.
Constant|Scope|Meaning
---|---|---
LWSSSSRET_TX_DONT_SEND|tx|This opportunity to send something was passed on
LWSSSSRET_OK|state, rx, tx|No error, continue doing what we're doing
LWSSSSRET_DISCONNECT_ME|state, rx|assertively disconnect from peer
LWSSSSRET_DESTROY_ME|state, rx|Caller should now destroy the stream itself
LWSSSSRET_SS_HANDLE_DESTROYED|state|Something handled a request to destroy the stream
Destruction of the stream we're calling back on inside the callback is tricky,
it's preferable to return `LWSSSSRET_DESTROY_ME` if it is required, and let the
caller handle it. But in some cases, helpers called from the callbacks may
destroy the handle themselves, in that case the handler should return
`LWSSSSRET_SS_HANDLE_DESTROYED` indicating that the handle is already destroyed.
## Secure Streams SERVER State lifecycle
![overview](/doc-assets/ss-state-flow-server.png)
You can also run servers defined using Secure Streams, the main difference is
that the user code must assertively create a secure stream of the server type
in order to create the vhost and listening socket. When this stream is
destroyed, the vhost is destroyed and the listen socket closed, otherwise it
does not perform any rx or tx, it just represents the server lifecycle.
When client connections randomly arrive at the listen socket, new Secure Stream
objects are created along with accept sockets to represent each client
connection. As they represent the incoming connection, their lifecycle is the
same as that of the underlying connection. There is no retry concept since as
with eg, http servers, the clients may typically not be routable for new
connections initiated by the server.
Since connections at socket level are already established, new connections are
immediately taken through CREATING, CONNECTING, CONNECTED states for
consistency.
Some underlying protocols like http are "transactional", the server receives
a logical request and must reply with a logical response. The additional
state `LWSSSCS_SERVER_TXN` provides a point where the user code can set
transaction metadata before or in place of sending any payload. It's also
possible to defer this until any rx related to the transaction was received,
but commonly with http requests, there is no rx / body. Configuring the
response there may look like
```
/*
* We do want to ack the transaction...
*/
lws_ss_server_ack(m->ss, 0);
/*
* ... it's going to be text/html...
*/
lws_ss_set_metadata(m->ss, "mime", "text/html", 9);
/*
* ...it's going to be 128 byte (and request tx)
*/
lws_ss_request_tx_len(m->ss, 128);
```
Otherwise the general api usage is very similar to client usage.
## Convention for rx and tx callback return
Function|Return|Meaning
---|---|---
tx|`LWSSSSRET_OK`|Send the amount of `buf` stored in `*len`
tx|`LWSSSSRET_TX_DONT_SEND`|Do not send anything
tx|`LWSSSSRET_DISCONNECT_ME`|Close the current connection
tx|`LWSSSSRET_DESTROY_ME`|Destroy the Secure Stream
rx|>=0|accepted
rx|<0|Close the current connection
# JSON Policy Database
Example JSON policy... formatting is shown for clarity but whitespace can be
omitted in the actual policy.
Ordering is not critical in itself, but forward references are not allowed,
things must be defined before they are allowed to be referenced later in the
JSON.
```
{
"release": "01234567",
"product": "myproduct",
"schema-version": 1,
"retry": [{
"default": {
"backoff": [1000, 2000, 3000, 5000, 10000],
"conceal": 5,
"jitterpc": 20
}
}],
"certs": [{
"isrg_root_x1": "MIIFazCCA1OgAw...AnX5iItreGCc="
}, {
"LEX3_isrg_root_x1": "MIIFjTCCA3WgAwIB...WEsikxqEt"
}],
"trust_stores": [{
"le_via_isrg": ["isrg_root_x1", "LEX3_isrg_root_x1"]
}],
"s": [{
"mintest": {
"endpoint": "warmcat.com",
"port": 4443,
"protocol": "h1get",
"aux": "index.html",
"plugins": [],
"tls": true,
"opportunistic": true,
"retry": "default",
"tls_trust_store": "le_via_isrg"
}
}]
}
```
### `Release`
Identifies the policy version
### `Product`
Identifies the product the policy should apply to
### `Schema-version`
The minimum version of the policy parser required to parse this policy
### `via-socks5`
Optional redirect for Secure Streams client traffic through a socks5
proxy given in the format `address:port`, eg, `127.0.0.1:12345`.
### `retry`
A list of backoff schemes referred to in the policy
### `backoff`
An array of ms delays for each retry in turn
### `conceal`
The number of retries to conceal from higher layers before giving errors. If
this is larger than the number of times in the backoff array, then the last time
is used for the extra delays. 65535 means never stop trying.
### `jitterpc`
Percentage of the delay times mentioned in the backoff array that may be
randomly added to the figure from the array. For example with an array entry of
1000ms, and jitterpc of 20%, actual delays will be chosen randomly from 1000ms
through 1200ms. This is to stop retry storms triggered by a single event like
an outage becoming synchronized into a DoS.
### `certs`
Certificates needed for validation should be listed here each with a name. The
format is base64 DER, which is the same as the part of PEM that is inside the
start and end lines.
### `trust_stores`
Chains of certificates given in the `certs` section may be named and described
inside the `trust_stores` section. Each entry in `trust_stores` is created as
a vhost + tls context with the given name. Stream types can later be associated
with one of these to enforce validity checking of the remote server.
Entries should be named using "name" and the stack array defined using "stack"
### `auth`
Optional section describing a map of available authentication streamtypes to
auth token blob indexes.
```
...
"auth": [{"name":"newauth","type":"sigv4", "blob":0}]
...
```
Streams can indicate they depend on a valid auth token from one of these schemes
by using the `"use_auth": "name"` member in the streamtype definition, where name
is, eg, "sigv4" in the example above. If "use_auth" is not in the streamtype
definition, default auth is lwa if "http_auth_header" is there.
### `auth[].name`
This is the name of the authentication scheme used by other streamtypes
### `auth[].type`
Indicate the auth type, e.g. sigv4
### `auth[].streamtype`
This is the auth streamtype to be used to refresh the authentication token
### `auth[].blob`
This is the auth blob index the authentication token is stored into and retreived
from system blob, currently up to 4 blobs.
### `s`
These are an array of policies for the supported stream type names.
### `server`
**SERVER ONLY**: if set to `true`, the policy describes a secure streams
server.
### `endpoint`
**CLIENT**: The DNS address the secure stream should connect to.
This may contain string symbols which will be replaced with the
corresponding streamtype metadata value at runtime. Eg, if the
streamtype lists a metadata name "region", it's then possible to
define the endpoint as, eg, `${region}.mysite.com`, and before
attempting the connection setting the stream's metadata item
"region" to the desired value, eg, "uk".
If the endpoint string begins with `+`, then it's understood to
mean a connection to a Unix Domain Socket, for Linux `+@` means
the following Unix Domain Socket is in the Linux Abstract
Namespace and doesn't have a filesystem footprint. This is only
supported on unix-type and windows platforms and when lws was
configured with `-DLWS_UNIX_SOCK=1`
**SERVER**: If given, the network interface name or IP address the listen socket
should bind to.
**SERVER**: If begins with '!', the rest of the endpoint name is the
vhost name of an existing vhost to bind to, instead of creating a new
one. This is useful when the vhost layout is already being managed by
lejp-conf JSON and it's more convenient to put the details in there.
### `port`
**CLIENT**: The port number as an integer on the endpoint to connect to
**SERVER**: The port number the server will listen on
### `protocol`
**CLIENT**: The wire protocol to connect to the endpoint with. Currently
supported streamtypes are
|Wire protocol|Description|
|---|---|
|h1|http/1|
|h2|http/2|
|ws|http/1 Websockets|
|mqtt|mqtt 3.1.1|
|raw||
Raw protocol is a bit different than the others in that there is no protocol framing,
whatever is received on the connection is passed to the user rx callback and whatever
the tx callback provides is issued on to the connection. Because tcp can be
arbitrarily fragmented by any intermediary, such streams have to be regarded as an
ordered bytestream that may be fragmented at any byte without any meaning in terms
of message boundaries, for that reason SOM and EOM are ignored with raw.
### `allow_redirects`
By default redirects are not followed, if you wish a streamtype to observe them, eg,
because that's how it responds to a POST, set `"allow_redirects": true`
### `tls`
Set to `true` to enforce the stream travelling in a tls tunnel
### `client cert`
Set if the stream needs to authenticate itself using a tls client certificate.
Set to the certificate index counting from 0+. The certificates are managed
using lws_sytstem blobs.
### `opportunistic`
Set to `true` if the connection may be left dropped except when in use
### `nailed_up`
Set to `true` to have lws retry if the connection carrying this stream should
ever drop.
### `retry`
The name of the policy described in the `retry` section to apply to this
connection for retry + backoff
### `timeout_ms`
Optional timeout associated with streams of this streamtype.
If user code applies the `lws_ss_start_timeout()` api on a stream with a
timeout of LWSSS_TIMEOUT_FROM_POLICY, the `timeout_ms` entry given in the
policy is applied.
### `perf`
If set to true, and lws was built with `LWS_WITH_CONMON`, causes this streamtype
to receive additional rx payload with the `LWSSS_FLAG_PERF_JSON` flag set on it,
that is JSON representing the onward connection performance information.
These are based on the information captured in the struct defined in
libwebsockets/lws-conmon.h, represented in JSON
```
{
"peer": "46.105.127.147",
"dns_us": 1234,
"sockconn_us": 1234,
"tls_us": 1234,
"txn_resp_us": 1234,
"dns":["46.105.127.147", "2001:41d0:2:ee93::1"]
}
```
Streamtypes without "perf": true will never see the special rx payloads.
Notice that the `LWSSS_FLAG_PERF_JSON` payloads must be handled out of band
for the normal payloads, as they can appear inside normal payload messages.
### `tls_trust_store`
The name of the trust store described in the `trust_stores` section to apply
to validate the remote server cert.
If missing and tls is enabled on the streamtype, then validation is
attempted using the OS trust store, otherwise the connection fails.
### `use_auth`
Indicate that the streamtype should use the named auth type from the `auth`
array in the policy
### `aws_region`
Indicate which metadata should be used to set aws region for certain streamtype
### `aws_service`
Indicate which metadata should be used to set aws service for certain streamtype
### `direct_proto_str`
If set to `true`, application can use `lws_ss_set_metadata()` to directly set protocol related string and use `lws_ss_get_metadata` to fetch certain protocol related string. Please note that currently HTTP header is the supported protocol string. The `name` parameter is the name of HTTP header name (**with ':'**, e.g. `"Content-Type:"`) and `value` is the header's value. `LWS_WITH_SS_DIRECT_PROTOCOL_STR` flag needs to be configured during compilation for this. Currently it's only work for non-proxy case.
### `server_cert`
**SERVER ONLY**: subject to change... the name of the x.509 cert that is the
server's tls certificate
### `server_key`
**SERVER ONLY**: subject to change... the name of the x.509 cert that is the
server's tls key
### `swake_validity`
Set to `true` if this streamtype is important enough for the functioning of the
device that its locally-initiated periodic connection validity checks of the
interval described in the associated retry / backoff selection, are important
enough to wake the whole system from low power suspend so they happen on
schedule.
### `proxy_buflen`
Only used when the streamtype is proxied... sets the maximum size of the
payload buffering (in bytes) the proxy will hold for this type of stream. If
the endpoint dumps a lot of data without any flow control, this may need to
be correspondingly large. Default is 32KB.
### `proxy_buflen_rxflow_on_above`, `proxy_buflen_rxflow_off_below`
When `proxy_buflen` is set, you can also wire up the amount of buffered
data intended for the client held at the proxy, to the onward ss wsi
rx flow control state. If more than `proxy_buflen_rxflow_on_above`
bytes are buffered, rx flow control is set stopping further rx. Once
the dsh is drained below `proxy_buflen_rxflow_off_below`, the rx flow
control is released and RX resumes.
### `client_buflen`
Only used when the streamtype is proxied... sets the maximum size of the
payload buffering (in bytes) the client will hold for this type of stream. If
the client sends a lot of data without any flow control, this may need to
be correspondingly large. Default is 32KB.
### `attr_priority`
A number between 0 (normal priority) and 6 (very high priority). 7 is also
possible, but requires CAP_NET_ADMIN on Linux and is reserved for network
administration packets. Normally default priority is fine, but under some
conditions when transporting over IP packets, you may want to control the
IP packet ToS priority for the streamtype by using this.
### `attr_low_latency`
This is a flag indicating that the streamtype packets should be transported
in a way that results in lower latency where there is a choice. For IP packets,
this sets the ToS "low delay" flag on packets from this streamtype.
### `attr_high_throughput`
This is a flag indicating that this streamtype should be expected to produce
bulk content that requires high throughput. For IP packets,
this sets the ToS "high throughput" flag on packets from this streamtype.
### `attr_high_reliability`
This is a flag indicating that extra efforts should be made to deliver packets
from this streamtype where possible. For IP packets, this sets the ToS "high
reliability" flag on packets from this streamtype.
### `attr_low_cost`
This is a flag indicating that packets from this streamtype should be routed as
inexpensively as possible by trading off latency and reliability where there is
a choice. For IP packets, this sets the ToS "low cost" flag on packets from
this streamtype.
### `metadata`
This allows declaring basically dynamic symbol names to be used by the streamtype,
along with an optional mapping to a protocol-specific entity such as a given
http header. Eg:
```
"metadata": [ { "myname": "" }, { "ctype": "content-type:" } ],
```
In this example "ctype" is associated with the http header "content-type" while
"myname" doesn't have any association to a header.
Symbol names may be used in the other policy for the streamtype for string
substitution using the syntax like `xxx${myname}yyy`, forward references are
valid but the scope of the symbols is just the streamtype the metadata is
defined for.
Client code can set metadata by name, using the `lws_ss_set_metadata()` api, this
should be done before a transaction. And for metadata associated with a
protocol-specific entity, like http headers, if incoming responses contain the
mentioned header, the metadata symbol is set to that value at the client before
any rx proceeds.
Metadata continues to work the same for the client in the case it is proxying its
connectivity, metadata is passed in both directions serialized over the proxy link.
## http transport
### `http_method`
HTTP method to use with http-related protocols, like GET or POST.
Not required for ws.
### `http_expect`
Optionally indicates that success for HTTP transactions using this
streamtype is different than the default 200 - 299.
Eg, you may choose to set this to 204 for Captive Portal Detect usage
if that's what you expect the server to reply with to indicate
success. In that case, anything other than 204 will be treated as a
connection failure.
### `http_fail_redirect`
Set to `true` if you want to fail the connection on meeting an
http redirect. This is needed to, eg, detect Captive Portals
correctly. Normally, if on https, you would want the default behaviour
of following the redirect.
### `http_url`
Url path to use with http-related protocols
The URL path can include metatadata like this
"/mypath?whatever=${metadataname}"
${metadataname} will be replaced by the current value of the
same metadata name. The metadata names must be listed in the
"metadata": [ ] section.
### `http_resp_map`
If your server overloads the meaning of the http transport response code with
server-custom application codes, you can map these to discrete Secure Streams
state callbacks using a JSON map, eg
```
"http_resp_map": [ { "530": 1530 }, { "531": 1531 } ],
```
It's not recommended to abuse the transport layer http response code by
mixing it with application state information like this, but if it's dealing
with legacy serverside that takes this approach, it's possible to handle it
in SS this way while removing the dependency on http.
### `http_auth_header`
The name of the header that takes the auth token, with a trailing ':', eg
```
"http_auth_header": "authorization:"
```
### `http_dsn_header`
The name of the header that takes the dsn token, with a trailing ':', eg
```
"http_dsn_header": "x-dsn:"
```
### `http_fwv_header`
The name of the header that takes the firmware version token, with a trailing ':', eg
```
"http_fwv_header": "x-fw-version:"
```
### `http_devtype_header`
The name of the header that takes the device type token, with a trailing ':', eg
```
"http_devtype_header": "x-device-type:"
```
### `http_auth_preamble`
An optional string that precedes the auth token, eg
```
"http_auth_preamble": "bearer "
```
### `auth_hexify`
Convert the auth token to hex ('A' -> "41") before transporting. Not necessary if the
auth token is already in printable string format suitable for transport. Needed if the
auth token is a chunk of 8-bit binary.
### `nghttp2_quirk_end_stream`
Set this to `true` if the peer server has the quirk it won't send a response until we have
sent an `END_STREAM`, even though we have sent headers with `END_HEADERS`.
### `h2q_oflow_txcr`
Set this to `true` if the peer server has the quirk it sends an maximum initial tx credit
of 0x7fffffff and then later increments it illegally.
### `http_multipart_ss_in`
Indicates that SS should parse any incoming multipart mime on this stream
### `http_multipart_name`
Indicates this stream goes out using multipart mime, and provides the name part of the
multipart header
### `http_multipart_filename`
Indicates this stream goes out using multipart mime, and provides the filename part of the
multipart header
### `http_multipart_content_type`
The `content-type` to mark up the multipart mime section with if present
### `http_www_form_urlencoded`
Indicate the data is sent in `x-www-form-urlencoded` form
### `http_cookies`
This streamtype should store and bring out http cookies from the peer.
### `rideshare`
For special cases where one logically separate stream travels with another when using this
protocol. Eg, a single multipart mime transaction carries content from two or more streams.
## ws transport
### `ws_subprotocol`
** CLIENT **: Name of the ws subprotocol to request from the server
** SERVER **: Name of the subprotocol we will accept
### `ws_binary`
Use if the ws messages are binary
### `ws_prioritize_reads`
Set `true` if the event loop should prioritize keeping up with input at the
potential expense of output latency.
## MQTT transport
### `mqtt_topic`
Set the topic this streamtype uses for writes
### `mqtt_subscribe`
Set the topic this streamtype subscribes to
### `mqtt qos`
Set the QOS level for this streamtype
### `mqtt_retain`
Set to true if this streamtype should use MQTT's "retain" feature.
### `mqtt_keep_alive`
16-bit number representing MQTT keep alive for the stream.
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
### `mqtt_clean_start`
Set to true if the connection should use MQTT's "clean start" feature.
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
### `mqtt_will_topic`
Set the topic of the connection's will message, if any (there is none by default).
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
### `mqtt_will_message`
Set the content of the connect's will message, if any (there is none by default).
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
### `mqtt_will_qos`
Set the QoS of the will message, if any (there is none by default).
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
### `mqtt_will_retain`
Set to true if the connection should use MQTT's "will retain" feature, if there
is a will message (there is none by default).
This is applied at connection time... where different streams may bind to the
same underlying MQTT connection, all the streams should have an identical
setting for this.
## Loading and using updated remote policy
If the default, hardcoded policy includes a streamtype `fetch_policy`,
during startup when lws_system reaches the POLICY state, lws will use
a Secure Stream of type `fetch_policy` to download, parse and update
the policy to use it.
The secure-streams-proxy minimal example shows how this is done and
fetches its real policy from warmcat.com at startup using the built-in
one.
## Applying streamtype policy overlays
This is intended for modifying policies at runtime for testing, eg, to
force error paths to be taken. After the main policy is processed, you
may parse additional, usually smaller policy fragments on top of it.
Where streamtype names in the new fragment already exist in the current
parsed policy, the settings in the fragment are applied over the parsed
policy, overriding settings. There's a simple api to enable this by
giving it the override JSON in one string
```
int
lws_ss_policy_overlay(struct lws_context *context, const char *overlay);
```
but there are also other apis available that can statefully process
larger overlay fragments if needed.
An example overlay fragment looks like this
```
{ "s": [{ "captive_portal_detect": {
"endpoint": "google.com",
"http_url": "/",
"port": 80
}}]}
```
ie the overlay fragment completely follows the structure of the main policy,
just misses out anything it doesn't override.
Currently ONLY streamtypes may be overridden.
You can see an example of this in use in `minimal-secure-streams` example
where `--force-portal` and `--force-no-internet` options cause the captive
portal detect streamtype to be overridden to force the requested kind of
outcome.
## Captive Portal Detection
If the policy contains a streamtype `captive_portal_detect` then the
type of transaction described there is automatically performed after
acquiring a DHCP address to try to determine the captive portal
situation.
```
"captive_portal_detect": {
"endpoint": "connectivitycheck.android.com",
"port": 80,
"protocol": "h1",
"http_method": "GET",
"http_url": "generate_204",
"opportunistic": true,
"http_expect": 204,
"http_fail_redirect": true
}
```
## Stream serialization and proxying
By default Secure Streams expects to make the outgoing connection described in
the policy in the same process / thread, this suits the case where all the
participating clients are in the same statically-linked image.
In this case the `lws_ss_` apis are fulfilled locally by secure-streams.c and
policy.c for policy lookups.
However it also supports serialization, where the SS api can be streamed over
another transport such as a Unix Domain Socket connection. This suits the case
where the clients are actually in different processes in, eg, Linux or Android.
In those cases, you run a proxy process (minimal-secure-streams-proxy) that
listens on a Unix Domain Socket and is connected to by one or more other
processes that pass their SS API activity to the proxy for fulfilment (or
onward proxying).
Each Secure Stream that is created then in turn creates a private Unix Domain
Socket connection to the proxy for each stream.
In this case the proxy uses secure-streams.c and policy.c as before to fulfil
the inbound proxy streams, but uses secure-streams-serialize.c to serialize and
deserialize the proxied SS API activity. The proxy clients define
LWS_SS_USE_SSPC either very early in their sources before the includes, or on
the compiler commandline... this causes the lws_ss_ apis to be replaced at
preprocessor time with lws_sspc_ equivalents. These serialize the api action
and pass it to the proxy over a Unix Domain Socket for fulfilment, the results
and state changes etc are streamed over the Unix Domain Socket and presented to
the application exactly the same as if it was being fulfilled locally.
To demonstrate this, some minimal examples, eg, minimal-secure-streams and
mimimal-secure-streams-avs build themselves both ways, once with direct SS API
fulfilment and once with Unix Domain Socket proxying and -client appended on the
executable name. To test the -client variants, run minimal-secure-streams-proxy
on the same machine.
## Complicated scenarios with secure streams proxy
As mentioned above, Secure Streams has two modes, by default the application
directly parses the policy and makes the outgoing connections itself.
However when configured at cmake with
```
-DLWS_WITH_SOCKS5=1 -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_SECURE_STREAMS_PROXY_API=1 -DLWS_WITH_MINIMAL_EXAMPLES=1
```
and define `LWS_SS_USE_SSPC` when building the application, applications forward
their network requests to a local or remote SS proxy for fulfilment... and only
the SS proxy has the system policy. By default, the SS proxy is on the local
machine and is connected to via a Unix Domain Socket, but tcp links are also
possible. (Note the proxied traffic is not encrypyed by default.)
Using the configuration above, the example SS applications are built two ways,
once for direct connection fulfilment (eg, `./bin/lws-minimal-secure-streams`),
and once with `LWS_SS_USE_SSPC` also defined so it connects via an SS proxy,
(eg, `./bin/lws-minimal-secure-streams-client`).
## Testing an example scenario with SS Proxy and socks5 proxy
```
[ SS application ] --- tcp --- [ socks 5 proxy ] --- tcp --- [ SS proxy ] --- internet
```
In this scenario, everything is on localhost, the socks5 proxy listens on :1337 and
the SS proxy listens on :1234. The SS application connects to the socks5
proxy to get to the SS proxy, which then goes out to the internet
### 1 Start the SS proxy
Tell it to listen on lo interface on port 1234
```
$ ./bin/lws-minimal-secure-streams-proxy -p 1234 -i lo
```
### 2 Start the SOCKS5 proxy
```
$ ssh -D 1337 -N -v localhost
```
The -v makes connections to the proxy visible in the terminal for testing
### 3 Run the SS application
The application is told to make all connections via the socks5 proxy at
127.0.0.1:1337, and to fulfil its SS connections via an SS proxy, binding
connections to 127.0.0.1 (ipv4 lo interface, -1), to 127.0.0.1:1234 (-a/-p).
```
socks_proxy=127.0.0.1:1337 ./bin/lws-minimal-secure-streams-client -p 1234 -i 127.0.0.1 -a 127.0.0.1
```
You can confirm this goes through the ssh socks5 proxy to get to the SS proxy
and fulfil the connection.
## Using static policies
If one of your targets is too constrained to make use of dynamic JSON policies, but
using SS and the policies is attractive for wider reasons, you can use a static policy
built into the firmware for the constrained target.
The secure-streams example "policy2c" (which runs on the build machine, not the device)
https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/secure-streams/minimal-secure-streams-policy2c
accepts a normal JSON policy on stdin, and emits a C code representation that can be
included directly in the firmware.
https://libwebsockets.org/git/libwebsockets/tree/minimal-examples/secure-streams/minimal-secure-streams-staticpolicy/static-policy.h
Using this technique it's possible to standardize on maintaining JSON policies across a
range of devices with different contraints, and use the C conversion of the policy on devices
that are too small.
The Cmake option `LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY` should be enabled to use this
mode, it will not build the JSON parser (and the option for LEJP can also be disabled if
you're not otherwise using it, saving an additional couple of KB).
Notice policy2c example tool must be built with `LWS_ROLE_H1`, `LWS_ROLE_H2`, `LWS_ROLE_WS`
and `LWS_ROLE_MQTT` enabled so it can handle any kind of policy.
## HTTP and ws serving
All ws servers start out as http servers... for that reason ws serving is
handled as part of http serving, if you give the `ws_subprotocol` entry to the
streamtype additionally, the server will also accept upgrades to ws.
To help the user code understand if the upgrade occurred, there's a special
state `LWSSSCS_SERVER_UPGRADE`, so subsequent rx and tx can be understood to
have come from the upgraded protocol. To allow separation of rx and tx
handling between http and ws, there's a ss api `lws_ss_change_handlers()`
which allows dynamically setting SS handlers.
Since the http and ws upgrade identity is encapsulated in one streamtype, the
user object for the server streamtype should contain related user data for both
http and ws underlying protocol identity.

View File

@ -0,0 +1,29 @@
## Secure Streams client C++ API
Enable for build by selecting `-DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_SECURE_STREAMS_CPP=1` at
cmake.
Because it's designed for OpenSSL + system trust bundle, the minimal
example minimal-secure-streams-cpp requires `-DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_MBEDTLS=0`
By default the -cpp example downloads https://warmcat.com/test-a.bin to the local
file /tmp/test-a.bin. By giving, eg, -c 4, you can run four concurrent downloads of
files test-a.bin through test-d.bin... up to 12 files may be downloaded concurrently.
By default it will connect over h2 and share the single connection between all the
downloads.
### File level api
```
#include <libwebsockets.hxx>
...
new lssFile(context, "https://warmcat.com/index.html",
"/tmp/index.html", lss_completion, 0);
```
This will copy the remote url to the given local file, and call the
completion callback when it has succeeded or failed.

View File

@ -0,0 +1,154 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* C++ classes for Secure Streams
*/
#include <libwebsockets.hxx>
static const char *pcols[] = {
"http://", /* LWSSSP_H1 */
"https://",
"h2://", /* LWSSSP_H2 */
"h2s://",
"ws://", /* LWSSSP_WS */
"wss://",
"mqtt://", /* LWSSSP_MQTT */
"mqtts://",
"raw://", /* LWSSSP_RAW */
"raws://",
};
static const uint8_t pcols_len[] = {
7, 8, 5, 6, 5, 6, 7, 8, 6, 7
};
static const uint16_t pcols_port[] = {
80, 443, 443, 443, 80, 443, 1883, 8883, 80, 443
};
lss::lss(lws_ctx_t _ctx, std::string _uri, lsscomp_t _comp, bool _psh,
lws_sscb_rx rx, lws_sscb_tx tx, lws_sscb_state state)
{
const char *p, *urlpath;
lws_ss_info_t ssi;
int n, port;
memset(&ssi, 0, sizeof(ssi));
memset(&pol, 0, sizeof(pol));
ctx = _ctx;
comp = _comp;
comp_done = 0;
rxlen = 0;
/*
* We have a common stub userdata, our "real" userdata is in the
* derived class members. The Opaque user pointer points to the
* lss itself.
*/
ssi.handle_offset = offsetof(lssPriv, lssPriv::m_ss);
ssi.opaque_user_data_offset = offsetof(lssPriv, lssPriv::m_plss);
ssi.user_alloc = sizeof(lssPriv);
ssi.rx = rx;
ssi.tx = tx;
ssi.state = state;
ssi.policy = &pol; /* we will provide our own policy */
/*
* _uri is like "https://warmcat.com:443/index.html"... we need to
* deconstruct it into its policy implications
*/
uri = strdup(_uri.c_str());
for (n = 0; n < LWS_ARRAY_SIZE(pcols); n++)
if (!strncmp(uri, pcols[n], pcols_len[n]))
break;
if (n == LWS_ARRAY_SIZE(pcols))
throw lssException("unknown uri protocol://");
pol.protocol = n >> 1;
if (n & 1)
pol.flags |= LWSSSPOLF_TLS;
n = pcols_port[n];
if (lws_parse_uri(uri, &p, &pol.endpoint, &n, &urlpath))
throw lssException("unable to parse uri://");
pol.port = (uint16_t)n;
if (pol.protocol <= LWSSSP_WS) {
pol.u.http.url = urlpath;
/*
* These are workarounds for common h2 server noncompliances
*/
pol.flags |= LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM |
LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR |
LWSSSPOLF_H2_QUIRK_UNCLEAN_HPACK_STATE;
if (pol.protocol < LWSSSP_WS)
pol.u.http.method = _psh ? "POST" : "GET";
}
us_start = lws_now_usecs();
if (lws_ss_create(ctx, 0, &ssi, (void *)this, &m_ss, NULL, NULL))
goto blow;
if (pol.protocol <= LWSSSP_WS)
lws_ss_client_connect(m_ss);
return;
blow:
if (uri)
free(uri);
throw lssException("ss creation failed");
}
lss::~lss()
{
if (uri)
free(uri);
if (m_ss)
lws_ss_destroy(&m_ss);
}
int lss::call_completion(lws_ss_constate_t state)
{
if (comp_done)
return 0;
if (!comp)
return 0;
comp_done = 1;
return comp(this, state, NULL);
}

View File

@ -0,0 +1,132 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* C++ classes for Secure Streams - file transaction
*/
#include <libwebsockets.hxx>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
static lws_ss_state_return_t
lssfile_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
lssFile *lf = (lssFile *)userobj_to_lss(userobj);
return lf->write(buf, len, flags);
}
static lws_ss_state_return_t
lssfile_tx(void *userobj, lws_ss_tx_ordinal_t ord,uint8_t *buf, size_t *len,
int *flags)
{
/*
* TODO: we don't know how to send things yet
*/
return LWSSSSRET_TX_DONT_SEND;
}
static lws_ss_state_return_t
lssfile_state(void *userobj, void *h_src, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
lssFile *lf = (lssFile *)userobj_to_lss(userobj);
lwsl_info("%s: state %s\n", __func__, lws_ss_state_name(state));
switch (state) {
/*
* These reflect some kind of final disposition for the transaction,
* that we want to report along with the completion. If no other chance
* we'll report DESTROYING
*/
case LWSSSCS_DESTROYING:
case LWSSSCS_ALL_RETRIES_FAILED:
case LWSSSCS_QOS_ACK_REMOTE:
case LWSSSCS_QOS_NACK_REMOTE:
lf->call_completion(state);
if (state == LWSSSCS_DESTROYING) {
/*
* we get DESTROYING because we are already in the
* middle of destroying the m_ss, unlink the C++ lss
* from the ss handle so it won't recursively try to
* destroy it
*/
lf->m_ss = NULL;
delete lf;
}
break;
}
return LWSSSSRET_OK;
}
lws_ss_state_return_t lssFile::write(const uint8_t *buf, size_t len, int flags)
{
if (fd == LWS_INVALID_FILE) {
fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0640);
if (fd == LWS_INVALID_FILE)
return LWSSSSRET_DESTROY_ME;
}
if (::write(fd, buf, len) != len) {
close(fd);
fd = LWS_INVALID_FILE;
return LWSSSSRET_DESTROY_ME;
}
rxlen += len;
if (flags & LWSSS_FLAG_EOM) {
close(fd);
fd = LWS_INVALID_FILE;
}
return LWSSSSRET_OK;
}
lssFile::lssFile(lws_ctx_t ctx, std::string uri, std::string _path,
lsscomp_t comp, bool _psh) :
lss(ctx, uri, comp, _psh, lssfile_rx, lssfile_tx, lssfile_state)
{
path = _path;
push = _psh;
fd = LWS_INVALID_FILE;
}
lssFile::~lssFile()
{
if (fd == LWS_INVALID_FILE)
return;
close(fd);
fd = LWS_INVALID_FILE;
}

View File

@ -0,0 +1,60 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* C++ classes for Secure Streams - atomic heap messages
*/
#include <libwebsockets.hxx>
static lws_ss_state_return_t
lssmsg_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
lssmsg_tx(void *userobj, lws_ss_tx_ordinal_t ord,uint8_t *buf, size_t *len,
int *flags)
{
/*
* TODO: we don't know how to send things yet
*/
return LWSSSSRET_TX_DONT_SEND;
}
static lws_ss_state_return_t
lssmsg_state(void *userobj, void *h_src, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
return LWSSSSRET_OK;
}
lssMsg::lssMsg(lws_ctx_t ctx, lsscomp_t _comp, std::string uri) :
lss(ctx, uri, comp, 0, lssmsg_rx, lssmsg_tx, lssmsg_state)
{
}
lssMsg::~lssMsg()
{
}

View File

@ -0,0 +1,40 @@
/*
* ssp-h1url plugin
*
* Written in 2010-2020 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* CC0 so it can be used as a template for your own secure streams plugins
* licensed how you like.
*/
#include <libwebsockets.h>
static int
ssp_h1url_create(struct lws_ss_handle *ss, void *info, plugin_auth_status_cb status)
{
return 0;
}
static int
ssp_h1url_destroy(struct lws_ss_handle *ss)
{
return 0;
}
static int
ssp_h1url_munge(struct lws_ss_handle *ss, char *path, size_t path_len)
{
return 0;
}
/* this is the only exported symbol */
const lws_ss_plugin_t ssp_h1url = {
.name = "h1url",
.alloc = 0,
.create = ssp_h1url_create,
.destroy = ssp_h1url_destroy,
.munge = ssp_h1url_munge
};

View File

@ -0,0 +1,599 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* This file contains the stuff related to secure streams policy, it's always
* built if LWS_WITH_SECURE_STREAMS enabled.
*/
#include <private-lib-core.h>
#if defined(LWS_WITH_SYS_SMD)
const lws_ss_policy_t pol_smd = {
.flags = 0, /* have to set something for windows */
};
#endif
const lws_ss_policy_t *
lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype)
{
const lws_ss_policy_t *p = context->pss_policies;
if (!streamtype)
return NULL;
#if defined(LWS_WITH_SYS_SMD)
if (!strcmp(streamtype, LWS_SMD_STREAMTYPENAME))
return &pol_smd;
#endif
while (p) {
if (!strcmp(p->streamtype, streamtype))
return p;
p = p->next;
}
return NULL;
}
int
_lws_ss_set_metadata(lws_ss_metadata_t *omd, const char *name,
const void *value, size_t len)
{
/*
* If there was already a heap-based value, it's about to go out of
* scope due to us trashing the pointer. So free it first and clear
* its flag indicating it's heap-based.
*/
if (omd->value_on_lws_heap) {
lws_free_set_NULL(omd->value__may_own_heap);
omd->value_on_lws_heap = 0;
}
// lwsl_notice("%s: %s %s\n", __func__, name, (const char *)value);
omd->name = name;
omd->value__may_own_heap = (void *)value;
omd->length = len;
return 0;
}
int
lws_ss_set_metadata(struct lws_ss_handle *h, const char *name,
const void *value, size_t len)
{
lws_ss_metadata_t *omd = lws_ss_get_handle_metadata(h, name);
lws_service_assert_loop_thread(h->context, h->tsi);
if (omd)
return _lws_ss_set_metadata(omd, name, value, len);
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
if (h->policy->flags & LWSSSPOLF_DIRECT_PROTO_STR) {
omd = lws_ss_get_handle_instant_metadata(h, name);
if (!omd) {
omd = lws_zalloc(sizeof(*omd), "imetadata");
if (!omd) {
lwsl_err("%s OOM\n", __func__);
return 1;
}
omd->name = name;
omd->next = h->instant_metadata;
h->instant_metadata = omd;
}
omd->value__may_own_heap = (void *)value;
omd->length = len;
return 0;
}
#endif
lwsl_info("%s: unknown metadata %s\n", __func__, name);
return 1;
}
int
_lws_ss_alloc_set_metadata(lws_ss_metadata_t *omd, const char *name,
const void *value, size_t len)
{
uint8_t *p;
int n;
if (omd->value_on_lws_heap) {
lws_free_set_NULL(omd->value__may_own_heap);
omd->value_on_lws_heap = 0;
}
p = lws_malloc(len, __func__);
if (!p)
return 1;
n = _lws_ss_set_metadata(omd, name, p, len);
if (n) {
lws_free(p);
return n;
}
memcpy(p, value, len);
omd->value_on_lws_heap = 1;
return 0;
}
int
lws_ss_alloc_set_metadata(struct lws_ss_handle *h, const char *name,
const void *value, size_t len)
{
lws_ss_metadata_t *omd = lws_ss_get_handle_metadata(h, name);
lws_service_assert_loop_thread(h->context, h->tsi);
if (!omd) {
lwsl_info("%s: unknown metadata %s\n", __func__, name);
return 1;
}
return _lws_ss_alloc_set_metadata(omd, name, value, len);
}
int
lws_ss_get_metadata(struct lws_ss_handle *h, const char *name,
const void **value, size_t *len)
{
lws_ss_metadata_t *omd = lws_ss_get_handle_metadata(h, name);
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
int n;
#endif
lws_service_assert_loop_thread(h->context, h->tsi);
if (omd) {
*value = omd->value__may_own_heap;
*len = omd->length;
return 0;
}
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
if (!(h->policy->flags & LWSSSPOLF_DIRECT_PROTO_STR) || !h->wsi)
goto bail;
n = lws_http_string_to_known_header(name, strlen(name));
if (n != LWS_HTTP_NO_KNOWN_HEADER) {
*len = (size_t)lws_hdr_total_length(h->wsi, n);
if (!*len)
goto bail;
*value = lws_hdr_simple_ptr(h->wsi, n);
if (!*value)
goto bail;
return 0;
}
#if defined(LWS_WITH_CUSTOM_HEADERS)
n = lws_hdr_custom_length(h->wsi, (const char *)name,
(int)strlen(name));
if (n <= 0)
goto bail;
*value = lwsac_use(&h->imd_ac, (size_t)(n+1), (size_t)(n+1));
if (!*value) {
lwsl_err("%s ac OOM\n", __func__);
return 1;
}
if (lws_hdr_custom_copy(h->wsi, (char *)(*value), n+1, name,
(int)strlen(name))) {
/* waste n+1 bytes until ss is destryed */
goto bail;
}
*len = (size_t)n;
return 0;
#endif
bail:
#endif
lwsl_info("%s: unknown metadata %s\n", __func__, name);
return 1;
}
lws_ss_metadata_t *
lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name)
{
int n;
lws_service_assert_loop_thread(h->context, h->tsi);
for (n = 0; n < h->policy->metadata_count; n++)
if (!strcmp(name, h->metadata[n].name))
return &h->metadata[n];
return NULL;
}
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
lws_ss_metadata_t *
lws_ss_get_handle_instant_metadata(struct lws_ss_handle *h, const char *name)
{
lws_ss_metadata_t *imd = h->instant_metadata;
while (imd) {
if (!strcmp(name, imd->name))
return imd;
imd = imd->next;
}
return NULL;
}
#endif
lws_ss_metadata_t *
lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name)
{
lws_ss_metadata_t *pmd = p->metadata;
while (pmd) {
if (pmd->name && !strcmp(name, pmd->name))
return pmd;
pmd = pmd->next;
}
return NULL;
}
lws_ss_metadata_t *
lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index)
{
lws_ss_metadata_t *pmd = p->metadata;
while (pmd) {
if (pmd->length == index)
return pmd;
pmd = pmd->next;
}
return NULL;
}
#if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY)
static int
fe_lws_ss_destroy(struct lws_dll2 *d, void *user)
{
lws_ss_handle_t *h = lws_container_of(d, lws_ss_handle_t, list);
lws_ss_destroy(&h);
return 0;
}
#endif
/*
* Dynamic policy: we want to one-time create the vhost for the policy and the
* trust store behind it.
*
* Static policy: We want to make use of a trust store / vhost from the policy and add to its
* ss-refcount.
*/
struct lws_vhost *
lws_ss_policy_ref_trust_store(struct lws_context *context,
const lws_ss_policy_t *pol, char doref)
{
struct lws_context_creation_info i;
struct lws_vhost *v;
int n;
memset(&i, 0, sizeof(i));
if (!pol->trust.store) {
v = lws_get_vhost_by_name(context, "_ss_default");
if (!v) {
/* corner case... there's no trust store used */
i.options = context->options;
i.vhost_name = "_ss_default";
i.port = CONTEXT_PORT_NO_LISTEN;
v = lws_create_vhost(context, &i);
if (!v) {
lwsl_err("%s: failed to create vhost %s\n",
__func__, i.vhost_name);
return NULL;
}
}
goto accepted;
}
v = lws_get_vhost_by_name(context, pol->trust.store->name);
if (v) {
lwsl_debug("%s: vh already exists\n", __func__);
goto accepted;
}
i.options = context->options;
i.vhost_name = pol->trust.store->name;
lwsl_debug("%s: %s\n", __func__, i.vhost_name);
#if defined(LWS_WITH_TLS) && defined(LWS_WITH_CLIENT)
i.client_ssl_ca_mem = pol->trust.store->ssx509[0]->ca_der;
i.client_ssl_ca_mem_len = (unsigned int)
pol->trust.store->ssx509[0]->ca_der_len;
#endif
i.port = CONTEXT_PORT_NO_LISTEN;
lwsl_info("%s: %s trust store initial '%s'\n", __func__,
i.vhost_name, pol->trust.store->ssx509[0]->vhost_name);
v = lws_create_vhost(context, &i);
if (!v) {
lwsl_err("%s: failed to create vhost %s\n",
__func__, i.vhost_name);
return NULL;
} else
v->from_ss_policy = 1;
for (n = 1; v && n < pol->trust.store->count; n++) {
lwsl_info("%s: add '%s' to trust store\n", __func__,
pol->trust.store->ssx509[n]->vhost_name);
#if defined(LWS_WITH_TLS)
if (lws_tls_client_vhost_extra_cert_mem(v,
pol->trust.store->ssx509[n]->ca_der,
pol->trust.store->ssx509[n]->ca_der_len)) {
lwsl_err("%s: add extra cert failed\n",
__func__);
return NULL;
}
#endif
}
accepted:
#if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) || defined(LWS_WITH_SECURE_STREAMS_CPP)
if (doref)
v->ss_refcount++;
#endif
return v;
}
#if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) || defined(LWS_WITH_SECURE_STREAMS_CPP)
int
lws_ss_policy_unref_trust_store(struct lws_context *context,
const lws_ss_policy_t *pol)
{
struct lws_vhost *v;
const char *name = "_ss_default";
if (pol->trust.store)
name = pol->trust.store->name;
v = lws_get_vhost_by_name(context, name);
if (!v || !v->from_ss_policy)
return 0;
assert(v->ss_refcount);
v->ss_refcount--;
if (!v->ss_refcount) {
lwsl_notice("%s: destroying vh %s\n", __func__, name);
lws_vhost_destroy(v);
}
return 1;
}
#endif
int
lws_ss_policy_set(struct lws_context *context, const char *name)
{
int ret = 0;
#if !defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY)
struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args;
const lws_ss_policy_t *pol;
struct lws_vhost *v;
lws_ss_x509_t *x;
char buf[16];
int m;
/*
* Parsing seems to have succeeded, and we're going to use the new
* policy that's laid out in args->ac
*/
if (!args)
return 1;
lejp_destruct(&args->jctx);
if (context->ac_policy) {
int n;
#if defined(LWS_WITH_SYS_METRICS)
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
context->owner_mtr_dynpol.head) {
lws_metric_policy_dyn_t *dm =
lws_container_of(d, lws_metric_policy_dyn_t, list);
lws_metric_policy_dyn_destroy(dm, 1); /* keep */
} lws_end_foreach_dll_safe(d, d1);
#endif
/*
* any existing ss created with the old policy have to go away
* now, since they point to the shortly-to-be-destroyed old
* policy
*/
for (n = 0; n < context->count_threads; n++) {
struct lws_context_per_thread *pt = &context->pt[n];
lws_dll2_foreach_safe(&pt->ss_owner, NULL, fe_lws_ss_destroy);
}
/*
* So this is a bit fun-filled, we already had a policy in
* force, perhaps it was the default policy that's just good for
* fetching the real policy, and we're doing that now.
*
* We can destroy all the policy-related direct allocations
* easily because they're cleanly in a single lwsac...
*/
lwsac_free(&context->ac_policy);
/*
* ...but when we did the trust stores, we created vhosts for
* each. We need to destroy those now too, and recreate new
* ones from the new policy, perhaps with different X.509s.
*
* Vhost destruction is inherently async, it can't be destroyed
* until all of the wsi bound to it have closed, and, eg, libuv
* means their closure is deferred until a later go around the
* event loop. SMP means we also have to wait for all the pts
* to close their wsis that are bound on the vhost too.
*
* This marks the vhost as being destroyed so new things won't
* use it, and starts the close of all wsi on this pt that are
* bound to the wsi, and deals with the listen socket if any.
* "being-destroyed" vhosts can't be found using get_vhost_by_
* name(), so if a new vhost of the same name exists that isn't
* being destroyed that will be the one found.
*
* When the number of wsi bound to the vhost gets to zero a
* short time later, the vhost is actually destroyed.
*/
v = context->vhost_list;
while (v) {
if (v->from_ss_policy) {
struct lws_vhost *vh = v->vhost_next;
lwsl_debug("%s: destroying %s\n", __func__, lws_vh_tag(v));
lws_vhost_destroy(v);
v = vh;
continue;
}
v = v->vhost_next;
}
}
context->pss_policies = args->heads[LTY_POLICY].p;
context->ac_policy = args->ac;
lws_humanize(buf, sizeof(buf), lwsac_total_alloc(args->ac),
humanize_schema_si_bytes);
if (lwsac_total_alloc(args->ac))
m = (int)((lwsac_total_overhead(args->ac) * 100) /
lwsac_total_alloc(args->ac));
else
m = 0;
(void)m;
lwsl_info("%s: %s, pad %d%c: %s\n", __func__, buf, m, '%', name);
/* Create vhosts for each type of trust store */
/*
* We get called from context creation... instantiates
* vhosts with client tls contexts set up for each unique CA.
*
* We create the vhosts by walking streamtype list and create vhosts
* using trust store name if it's a client connection that doesn't
* already exist.
*/
pol = context->pss_policies;
while (pol) {
if (!(pol->flags & LWSSSPOLF_SERVER)) {
v = lws_ss_policy_ref_trust_store(context, pol,
0 /* no refcount inc */);
if (!v)
ret = 1;
}
pol = pol->next;
}
#if defined(LWS_WITH_SOCKS5)
/*
* ... we need to go through every vhost updating its understanding of
* which socks5 proxy to use...
*/
v = context->vhost_list;
while (v) {
lws_set_socks(v, args->socks5_proxy);
v = v->vhost_next;
}
if (context->vhost_system)
lws_set_socks(context->vhost_system, args->socks5_proxy);
if (args->socks5_proxy)
lwsl_notice("%s: global socks5 proxy: %s\n", __func__,
args->socks5_proxy);
#endif
/*
* For dynamic policy case, now we processed the x.509 CAs, we can free
* all of our originals. For static policy, they're in .rodata, nothing
* to free.
*/
x = args->heads[LTY_X509].x;
while (x) {
/*
* Free all the client DER buffers now they have been parsed
* into tls library X.509 objects
*/
if (!x->keep) { /* used for server */
lws_free((void *)x->ca_der);
x->ca_der = NULL;
}
x = x->next;
}
context->last_policy = time(NULL);
#if defined(LWS_WITH_SYS_METRICS)
if (context->pss_policies)
((lws_ss_policy_t *)context->pss_policies)->metrics =
args->heads[LTY_METRICS].m;
#endif
/* and we can discard the parsing args object now, invalidating args */
lws_free_set_NULL(context->pol_args);
#endif
#if defined(LWS_WITH_SYS_METRICS)
lws_metric_rebind_policies(context);
#endif
#if defined(LWS_WITH_SYS_SMD)
(void)lws_smd_msg_printf(context, LWSSMDCL_SYSTEM_STATE,
"{\"policy\":\"updated\",\"ts\":%lu}",
(long)context->last_policy);
#endif
return ret;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,742 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#if !defined(__LWS_PRIVATE_SS_H__)
#define __LWS_PRIVATE_SS_H__
/* current SS Serialization protocol version */
#define LWS_SSS_CLIENT_PROTOCOL_VERSION 1
#if defined(STANDALONE)
#define lws_context lws_context_standalone
struct lws_context_standalone;
#endif
/*
* Secure Stream state
*/
typedef enum {
SSSEQ_IDLE,
SSSEQ_TRY_CONNECT,
SSSEQ_TRY_CONNECT_NAUTH,
SSSEQ_TRY_CONNECT_SAUTH,
SSSEQ_RECONNECT_WAIT,
SSSEQ_DO_RETRY,
SSSEQ_CONNECTED,
} lws_ss_seq_state_t;
struct lws_sss_proxy_conn;
/**
* lws_ss_handle_t: publicly-opaque secure stream object implementation
*/
typedef struct lws_ss_handle {
lws_ss_info_t info; /**< copy of stream creation info */
lws_lifecycle_t lc;
#if defined(LWS_WITH_SYS_METRICS)
lws_metrics_caliper_compose(cal_txn)
#endif
struct lws_dll2 list; /**< pt lists active ss */
struct lws_dll2 to_list; /**< pt lists ss with pending to-s */
#if defined(LWS_WITH_SERVER)
struct lws_dll2 cli_list; /**< same server clients list */
#endif
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
lws_fi_ctx_t fic; /**< Fault Injection context */
#endif
struct lws_dll2_owner src_list; /**< server's list of bound sources */
struct lws_context *context; /**< lws context we are created on */
const lws_ss_policy_t *policy; /**< system policy for stream */
struct lws *wsi; /**< the stream wsi if any */
struct lws_sss_proxy_conn *conn_if_sspc_onw;
lws_ss_metadata_t *metadata;
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
lws_ss_metadata_t *instant_metadata; /**< for set instant metadata */
struct lwsac *imd_ac; /**< for get custom header */
#endif
const lws_ss_policy_t *rideshare;
struct lws_ss_handle *h_in_svc;
#if defined(LWS_WITH_CONMON)
char *conmon_json;
#endif
#if defined(LWS_WITH_SERVER)
lws_dll2_t sink_bind; /* if bound to / owned by a sink */
lws_sorted_usec_list_t sul_txreq; /* pending tx req to peer */
struct lws_ss_handle *sink_local_bind; /* nonproxy sink peer */
#endif
lws_sorted_usec_list_t sul_timeout;
lws_sorted_usec_list_t sul;
#if defined(LWS_WITH_FILE_OPS)
lws_sorted_usec_list_t fops_sul;
lws_fop_fd_t fop_fd;
#endif
lws_ss_tx_ordinal_t txord;
/* protocol-specific connection helpers */
union {
/* ...for http-related protocols... */
struct {
/* common to all http-related protocols */
/* incoming multipart parsing */
char boundary[24]; /* --boundary from headers */
uint8_t boundary_len; /* length of --boundary */
uint8_t boundary_seq; /* current match amount */
uint8_t boundary_dashes; /* check for -- after */
uint8_t boundary_post; /* swallow post CRLF */
uint8_t som:1; /* SOM has been sent */
uint8_t eom:1; /* EOM has been sent */
uint8_t any:1; /* any content has been sent */
uint8_t good_respcode:1; /* 200 type response code */
union {
struct { /* LWSSSP_H1 */
#if defined(WIN32)
uint8_t dummy;
#endif
} h1;
struct { /* LWSSSP_H2 */
#if defined(WIN32)
uint8_t dummy;
#endif
} h2;
struct { /* LWSSSP_WS */
#if defined(WIN32)
uint8_t dummy;
#endif
} ws;
} u;
} http;
/* details for non-http related protocols... */
#if defined(LWS_ROLE_MQTT)
struct {
lws_mqtt_topic_elem_t topic_qos;
lws_mqtt_topic_elem_t sub_top;
lws_mqtt_subscribe_param_t sub_info;
lws_mqtt_subscribe_param_t shadow_sub;
/* allocation that must be destroyed with conn */
void *heap_baggage;
const char *subscribe_to;
size_t subscribe_to_len;
struct lws_buflist *buflist_unacked;
uint32_t unacked_size;
uint8_t retry_count;
uint8_t send_unacked:1;
} mqtt;
#endif
#if defined(LWS_WITH_SYS_SMD)
struct {
struct lws_smd_peer *smd_peer;
lws_sorted_usec_list_t sul_write;
} smd;
#endif
} u;
unsigned long writeable_len;
lws_ss_constate_t connstate;/**< public connection state */
lws_ss_seq_state_t seqstate; /**< private connection state */
lws_ss_state_return_t pending_ret; /**< holds desired disposition
* for ss during CCE */
#if defined(LWS_WITH_SERVER)
int txn_resp;
#endif
uint16_t retry; /**< retry / backoff tracking */
#if defined(LWS_WITH_CONMON)
uint16_t conmon_len;
#endif
int16_t temp16;
uint8_t tsi; /**< service thread idx, usually 0 */
uint8_t subseq; /**< emulate SOM tracking */
uint8_t txn_ok; /**< 1 = transaction was OK */
uint8_t prev_ss_state;
uint8_t txn_resp_set:1; /**< user code set one */
uint8_t txn_resp_pending:1; /**< we have yet to send */
uint8_t hanging_som:1;
uint8_t inside_msg:1;
uint8_t being_serialized:1; /* we are not the consumer */
uint8_t destroying:1;
uint8_t ss_dangling_connected:1;
uint8_t proxy_onward:1; /* opaque is conn */
uint8_t inside_connect:1; /* set if we are currently
* creating the onward
* connect */
} lws_ss_handle_t;
/* connection helper that doesn't need to hang around after connection starts */
union lws_ss_contemp {
#if defined(LWS_ROLE_MQTT)
lws_mqtt_client_connect_param_t ccp;
#else
#if defined(WIN32)
uint8_t dummy;
#endif
#endif
};
/*
* When allocating the opaque handle, we overallocate for:
*
* 1) policy->nauth_plugin->alloc (.nauthi) if any
* 2) policy->sauth_plugin->alloc (.sauthi) if any
* 3) copy of creation info stream type pointed to by info.streamtype... this
* may be arbitrarily long and since it may be coming from socket ipc and be
* temporary at creation time, we need a place for the copy to stay in scope
* 4) copy of info->streamtype contents
*/
/* the user object allocation is immediately after the ss object allocation */
#define ss_to_userobj(ss) ((void *)&(ss)[1])
/*
* serialization parser state
*/
enum {
KIND_C_TO_P,
KIND_SS_TO_P,
};
typedef enum {
RPAR_TYPE,
RPAR_LEN_MSB,
RPAR_LEN_LSB,
RPAR_FLAG_B3,
RPAR_FLAG_B2,
RPAR_FLAG_B1,
RPAR_FLAG_B0,
RPAR_LATA3,
RPAR_LATA2,
RPAR_LATA1,
RPAR_LATA0,
RPAR_LATB7,
RPAR_LATB6,
RPAR_LATB5,
RPAR_LATB4,
RPAR_LATB3,
RPAR_LATB2,
RPAR_LATB1,
RPAR_LATB0,
RPAR_RIDESHARE_LEN,
RPAR_RIDESHARE,
RPAR_PERF,
RPAR_RESULT_CREATION_DSH,
RPAR_RESULT_CREATION_RIDESHARE,
RPAR_METADATA_NAMELEN,
RPAR_METADATA_NAME,
RPAR_METADATA_VALUE,
RPAR_PAYLOAD,
RPAR_RX_TXCR_UPDATE,
RPAR_STREAMTYPE,
RPAR_INIT_PROVERS,
RPAR_INIT_PID,
RPAR_INITTXC0,
RPAR_TXCR0,
RPAR_TIMEOUT0,
RPAR_PAYLEN0,
RPAR_RESULT_CREATION,
RPAR_STATEINDEX,
RPAR_ORD3,
RPAR_ORD2,
RPAR_ORD1,
RPAR_ORD0,
} rx_parser_t;
struct lws_ss_serialization_parser {
char streamtype[32];
char rideshare[32];
char metadata_name[32];
uint64_t ust_pwait;
lws_ss_metadata_t *ssmd;
uint8_t *rxmetaval;
int ps;
int ctr;
uint32_t usd_phandling;
uint32_t flags;
uint32_t client_pid;
int32_t temp32;
int32_t txcr_out;
int32_t txcr_in;
uint16_t rem;
uint8_t type;
uint8_t frag1;
uint8_t slen;
uint8_t rsl_pos;
uint8_t rsl_idx;
uint8_t protocol_version;
};
/*
* Unlike locally-fulfilled SS, SSS doesn't have to hold metadata on client side
* but pass it through to the proxy. The client side doesn't know the real
* metadata names that are available in the policy (since it's hardcoded in code
* no point passing them back to the client from the policy). Because of that,
* it doesn't know how many to allocate when we create the sspc_handle either.
*
* So we use a linked-list of changed-but-not-yet-proxied metadata allocated
* on the heap and items removed as they are proxied out. Anything on the list
* is sent to the proxy before any requested tx is handled.
*
* This is also used to queue tx credit changes
*/
typedef struct lws_sspc_metadata {
lws_dll2_t list;
char name[32]; /* empty string, then actually TCXR */
size_t len;
int tx_cr_adjust;
/* the value of length .len is overallocated after this */
} lws_sspc_metadata_t;
/* state of the upstream proxy onward connection */
enum {
LWSSSPC_ONW_NONE,
LWSSSPC_ONW_REQ,
LWSSSPC_ONW_ONGOING,
LWSSSPC_ONW_CONN,
};
typedef struct ss_proxy_onward {
lws_ss_handle_t *ss;
struct lws_sss_proxy_conn *conn;
} ss_proxy_t;
extern const lws_transport_client_ops_t txp_ops_sspc_wsi;
extern const lws_transport_proxy_ops_t txp_ops_ssproxy_wsi;
typedef struct lws_sspc_handle {
char rideshare_list[128];
lws_lifecycle_t lc;
lws_ss_info_t ssi;
lws_sorted_usec_list_t sul_retry;
lws_txp_path_client_t txp_path;
struct lws_ss_serialization_parser parser;
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
lws_fi_ctx_t fic; /**< Fault Injection context */
#endif
lws_dll2_owner_t metadata_owner;
lws_dll2_owner_t metadata_owner_rx;
struct lws_dll2 client_list;
struct lws_tx_credit txc;
#if defined(LWS_WITH_SYS_METRICS)
lws_metrics_caliper_compose(cal_txn)
#endif
struct lws_dsh *dsh;
struct lws_context *context;
struct lws_sspc_handle *h_in_svc;
/*
* Used to detect illegal lws_sspc_destroy() calls while still
* being serviced
*/
lws_usec_t us_earliest_write_req;
lws_usec_t us_start_upstream;
unsigned long writeable_len;
lws_ss_conn_states_t state;
uint32_t timeout_ms;
uint32_t ord;
int16_t temp16;
uint8_t rideshare_ofs[4];
uint8_t rsidx;
uint8_t prev_ss_state;
uint8_t conn_req_state:2;
uint8_t destroying:1;
uint8_t non_wsi:1;
uint8_t ignore_txc:1;
uint8_t pending_timeout_update:1;
uint8_t pending_writeable_len:1;
uint8_t creating_cb_done:1;
uint8_t ss_dangling_connected:1;
} lws_sspc_handle_t;
typedef struct backoffs {
struct backoffs *next;
const char *name;
lws_retry_bo_t r;
} backoff_t;
union u {
backoff_t *b;
lws_ss_x509_t *x;
lws_ss_trust_store_t *t;
lws_ss_policy_t *p;
lws_ss_auth_t *a;
lws_metric_policy_t *m;
};
enum {
LTY_BACKOFF,
LTY_X509,
LTY_TRUSTSTORE,
LTY_POLICY,
LTY_AUTH,
LTY_METRICS,
_LTY_COUNT /* always last */
};
struct policy_cb_args {
struct lejp_ctx jctx;
struct lws_context *context;
struct lwsac *ac;
const char *socks5_proxy;
struct lws_b64state b64;
lws_ss_http_respmap_t respmap[16];
struct lws_protocol_vhost_options *pvostack[4];
union u heads[_LTY_COUNT];
union u curr[_LTY_COUNT];
uint8_t *p;
int count;
int pvosp;
char pending_respmap;
uint8_t parse_data:1;
};
#if defined(LWS_WITH_SYS_SMD)
extern const lws_ss_policy_t pol_smd;
#endif
/*
* returns one of
*
* LWSSSSRET_OK
* LWSSSSRET_DISCONNECT_ME
* LWSSSSRET_DESTROY_ME
*/
int
lws_ss_proxy_deserialize_parse(struct lws_ss_serialization_parser *par,
struct lws_context *context,
struct lws_dsh *dsh, const uint8_t *cp,
size_t len, lws_ss_conn_states_t *state,
void *parconn, lws_ss_handle_t **pss,
lws_ss_info_t *ssi);
int
lws_sspc_deserialize_parse(lws_sspc_handle_t *hh, const uint8_t *cp, size_t len,
lws_ss_handle_t **pss);
int
lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,
lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags);
void
lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul);
const lws_ss_policy_t *
lws_ss_policy_lookup(const struct lws_context *context, const char *streamtype);
/* can be used as a cb from lws_dll2_foreach_safe() to destroy ss */
int
lws_ss_destroy_dll(struct lws_dll2 *d, void *user);
int
lws_sspc_destroy_dll(struct lws_dll2 *d, void *user);
void
lws_sspc_rxmetadata_destroy(lws_sspc_handle_t *h);
int
lws_ss_policy_set(struct lws_context *context, const char *name);
int
lws_ss_sys_fetch_policy(struct lws_context *context);
lws_ss_state_return_t
lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs);
lws_ss_state_return_t
_lws_ss_backoff(lws_ss_handle_t *h, lws_usec_t us_override);
lws_ss_state_return_t
lws_ss_backoff(lws_ss_handle_t *h);
int
_lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(lws_ss_state_return_t r, struct lws *wsi,
lws_ss_handle_t **ph);
int
lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us);
void
ss_proxy_onward_txcr(void *userobj, int bump);
int
lws_ss_sys_auth_api_amazon_com(struct lws_context *context);
lws_ss_metadata_t *
lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name);
lws_ss_metadata_t *
lws_ss_policy_metadata_index(const lws_ss_policy_t *p, size_t index);
#if defined(LWS_WITH_SS_DIRECT_PROTOCOL_STR)
lws_ss_metadata_t *
lws_ss_get_handle_instant_metadata(struct lws_ss_handle *h, const char *name);
#endif
lws_ss_metadata_t *
lws_ss_policy_metadata(const lws_ss_policy_t *p, const char *name);
int
lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,
size_t olen, size_t *exp_ofs);
int
_lws_ss_set_metadata(lws_ss_metadata_t *omd, const char *name,
const void *value, size_t len);
int
_lws_ss_alloc_set_metadata(lws_ss_metadata_t *omd, const char *name,
const void *value, size_t len);
lws_ss_state_return_t
_lws_ss_client_connect(lws_ss_handle_t *h, int is_retry, void *conn_if_sspc_onw);
lws_ss_state_return_t
_lws_ss_request_tx(lws_ss_handle_t *h);
int
__lws_ss_proxy_bind_ss_to_conn_wsi(void *parconn, size_t dsh_size);
struct lws_vhost *
lws_ss_policy_ref_trust_store(struct lws_context *context,
const lws_ss_policy_t *pol, char doref);
lws_ss_state_return_t
lws_sspc_event_helper(lws_sspc_handle_t *h, lws_ss_constate_t cs,
lws_ss_tx_ordinal_t flags);
int
lws_ss_check_next_state(lws_lifecycle_t *lc, uint8_t *prevstate,
lws_ss_constate_t cs);
int
lws_ss_check_next_state_ss(lws_ss_handle_t *ss, uint8_t *prevstate,
lws_ss_constate_t cs);
int
lws_ss_check_next_state_sspc(lws_sspc_handle_t *ss, uint8_t *prevstate,
lws_ss_constate_t cs);
void
lws_proxy_clean_conn_ss(struct lws *wsi);
int
lws_ss_cancel_notify_dll(struct lws_dll2 *d, void *user);
int
lws_sspc_cancel_notify_dll(struct lws_dll2 *d, void *user);
#if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) || defined(LWS_WITH_SECURE_STREAMS_CPP)
int
lws_ss_policy_unref_trust_store(struct lws_context *context,
const lws_ss_policy_t *pol);
#endif
int
lws_ss_sys_cpd(struct lws_context *cx);
#if defined(LWS_WITH_SECURE_STREAMS_AUTH_SIGV4)
int lws_ss_apply_sigv4(struct lws *wsi, struct lws_ss_handle *h,
unsigned char **p, unsigned char *end);
#endif
#if defined(_DEBUG)
void
lws_ss_assert_extant(struct lws_context *cx, int tsi, struct lws_ss_handle *h);
#else
#define lws_ss_assert_extant(_a, _b, _c)
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
typedef int (* const secstream_protocol_connect_munge_t)(lws_ss_handle_t *h,
char *buf, size_t len, struct lws_client_connect_info *i,
union lws_ss_contemp *ct);
#endif
typedef int (* const secstream_protocol_add_txcr_t)(lws_ss_handle_t *h, int add);
typedef int (* const secstream_protocol_get_txcr_t)(lws_ss_handle_t *h);
#if defined(LWS_WITH_SECURE_STREAMS)
struct ss_pcols {
const char *name;
const char *alpn;
const struct lws_protocols *protocol;
secstream_protocol_connect_munge_t munge;
secstream_protocol_add_txcr_t tx_cr_add;
secstream_protocol_get_txcr_t tx_cr_est;
};
#endif
/*
* Because both sides of the connection share the conn, we allocate it
* during accepted adoption, and both sides point to it.
*
* When .ss or .wsi close, they must NULL their entry here so no dangling
* refereneces.
*
* The last one of the accepted side and the onward side to close frees it.
*/
lws_ss_state_return_t
lws_conmon_ss_json(lws_ss_handle_t *h);
void
ss_proxy_onward_link_req_writeable(lws_ss_handle_t *h_onward);
#define LWS_PROXY_CONN_MAGIC LWS_FOURCC('C', 'o', 'N', 'N')
#define assert_is_conn(_conn) lws_assert_fourcc(_conn->magic, LWS_PROXY_CONN_MAGIC)
struct lws_sss_proxy_conn {
#if defined(_DEBUG)
uint32_t magic;
#endif
struct lws_ss_serialization_parser parser;
lws_dsh_t *dsh; /* unified buffer for both sides */
lws_txp_path_proxy_t txp_path;
lws_ss_handle_t *ss; /* the onward, ss side */
lws_ss_conn_states_t state;
struct lws_context *cx;
char onward_in_flow_control;
};
/*
* Handlers for onward SS that divert the events and data into serialized
* secure streams proxy.
*/
lws_ss_state_return_t
lws_sss_proxy_onward_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack);
lws_ss_state_return_t
lws_sss_proxy_onward_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags);
lws_ss_state_return_t
lws_sss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags);
void
lws_transport_set_link(lws_transport_mux_t *tm, int link_state);
lws_ss_state_return_t
lws_ss_proxy_destroy(struct lws_context *cx);
extern const struct ss_pcols ss_pcol_h1;
extern const struct ss_pcols ss_pcol_h2;
extern const struct ss_pcols ss_pcol_ws;
extern const struct ss_pcols ss_pcol_mqtt;
extern const struct ss_pcols ss_pcol_raw;
extern const struct lws_protocols protocol_secstream_h1;
extern const struct lws_protocols protocol_secstream_h2;
extern const struct lws_protocols protocol_secstream_ws;
extern const struct lws_protocols protocol_secstream_mqtt;
extern const struct lws_protocols protocol_secstream_raw;
#if defined(STANDALONE)
#undef lws_context
#endif
#endif

View File

@ -0,0 +1,38 @@
# Lws Protocol bindings for Secure Streams
This directory contains the code wiring up normal lws protocols
to Secure Streams.
## The lws_protocols callback
This is the normal lws struct lws_protocols callback that handles events and
traffic on the lws protocol being supported.
The various events and traffic are converted into calls using the Secure
Streams api, and Secure Streams events.
## The connect_munge helper
Different protocols have different semantics in the arguments to the client
connect function, this protocol-specific helper is called to munge the
connect_info struct to match the details of the protocol selected.
The `ss->policy->aux` string is used to hold protocol-specific information
passed in the from the policy, eg, the URL path or websockets subprotocol
name.
## The (library-private) ss_pcols export
Each protocol binding exports two things to other parts of lws (they
are not exported to user code)
- a struct lws_protocols, including a pointer to the callback
- a struct ss_pcols describing how secure_streams should use, including
a pointer to the related connect_munge helper.
In ./lib/core-net/vhost.c, enabled protocols are added to vhost protcols
lists so they may be used. And in ./lib/secure-streams/secure-streams.c,
enabled struct ss_pcols are listed and checked for matches when the user
creates a new Secure Stream.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,226 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
extern int
secstream_h1(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len);
static int
secstream_h2(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
lws_ss_state_return_t r;
int n;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
if (!h)
return -1;
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
if (h->being_serialized) {
/*
* We are the proxy-side SS for a remote client... we
* need to inform the client about the initial tx credit
* to write to it that the remote h2 server set up
*/
lwsl_info("%s: reporting initial tx cr from server %d\n",
__func__, wsi->txc.tx_cr);
ss_proxy_onward_txcr((void *)(h + 1), wsi->txc.tx_cr);
}
#endif
n = secstream_h1(wsi, reason, user, in, len);
if (!n && (h->policy->flags & LWSSSPOLF_LONG_POLL)) {
lwsl_notice("%s: h2 client %s entering LONG_POLL\n",
__func__, lws_wsi_tag(wsi));
lws_h2_client_stream_long_poll_rxonly(wsi);
}
return n;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
/*
* Only allow the wsi that the handle believes is representing
* him to report closure up to h1
*/
if (!h || h->wsi != wsi)
return 0;
break;
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
if (!h)
return -1;
// lwsl_err("%s: h2 COMPLETED_CLIENT_HTTP\n", __func__);
r = 0;
if (h->hanging_som)
r = h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM);
h->txn_ok = 1;
lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
if (h->hanging_som && r == LWSSSSRET_DESTROY_ME)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
h->hanging_som = 0;
break;
case LWS_CALLBACK_WSI_TX_CREDIT_GET:
if (!h)
return -1;
/*
* The peer has sent us additional tx credit...
*/
lwsl_info("%s: LWS_CALLBACK_WSI_TX_CREDIT_GET: %d\n",
__func__, (int)len);
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
if (h->being_serialized)
/* we are the proxy-side SS for a remote client */
ss_proxy_onward_txcr((void *)(h + 1), (int)len);
#endif
break;
default:
break;
}
return secstream_h1(wsi, reason, user, in, len);
}
const struct lws_protocols protocol_secstream_h2 = {
"lws-secstream-h2",
secstream_h2,
0, 0, 0, NULL, 0
};
/*
* Munge connect info according to protocol-specific considerations... this
* usually means interpreting aux in a protocol-specific way and using the
* pieces at connection setup time, eg, http url pieces.
*
* len bytes of buf can be used for things with scope until after the actual
* connect.
*/
int
secstream_connect_munge_h2(lws_ss_handle_t *h, char *buf, size_t len,
struct lws_client_connect_info *i,
union lws_ss_contemp *ct)
{
const char *pbasis = h->policy->u.http.url;
size_t used_in, used_out;
lws_strexp_t exp;
/* i.path on entry is used to override the policy urlpath if not "" */
if (i->path[0])
pbasis = i->path;
if (h->policy->flags & LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM)
i->ssl_connection |= LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM;
if (h->policy->flags & LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR)
i->ssl_connection |= LCCSCF_H2_QUIRK_OVERFLOWS_TXCR;
if (h->policy->flags & LWSSSPOLF_HTTP_MULTIPART)
i->ssl_connection |= LCCSCF_HTTP_MULTIPART_MIME;
if (h->policy->flags & LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED)
i->ssl_connection |= LCCSCF_HTTP_X_WWW_FORM_URLENCODED;
if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
i->ssl_connection |= LCCSCF_CACHE_COOKIES;
i->ssl_connection |= LCCSCF_PIPELINE;
i->alpn = "h2";
/* initial peer tx credit */
if (h->info.manual_initial_tx_credit) {
i->ssl_connection |= LCCSCF_H2_MANUAL_RXFLOW;
i->manual_initial_tx_credit = h->info.manual_initial_tx_credit;
lwsl_info("%s: initial txcr %d\n", __func__,
i->manual_initial_tx_credit);
}
if (!pbasis)
return 0;
/* protocol aux is the path part */
i->path = buf;
buf[0] = '/';
lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1);
if (lws_strexp_expand(&exp, pbasis, strlen(pbasis),
&used_in, &used_out) != LSTRX_DONE)
return 1;
return 0;
}
static int
secstream_tx_credit_add_h2(lws_ss_handle_t *h, int add)
{
lwsl_info("%s: %s: add %d\n", __func__, lws_ss_tag(h), add);
if (h->wsi)
return lws_h2_update_peer_txcredit(h->wsi, (unsigned int)LWS_H2_STREAM_SID, add);
return 0;
}
static int
secstream_tx_credit_est_h2(lws_ss_handle_t *h)
{
if (h->wsi) {
lwsl_info("%s: %s: est %d\n", __func__, lws_ss_tag(h),
lws_h2_get_peer_txcredit_estimate(h->wsi));
return lws_h2_get_peer_txcredit_estimate(h->wsi);
}
lwsl_info("%s: %s: Unknown (0)\n", __func__, lws_ss_tag(h));
return 0;
}
const struct ss_pcols ss_pcol_h2 = {
"h2",
"h2",
&protocol_secstream_h2,
secstream_connect_munge_h2,
secstream_tx_credit_add_h2,
secstream_tx_credit_est_h2
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,202 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* This is the glue that wires up raw-socket to Secure Streams.
*/
#include <private-lib-core.h>
int
secstream_raw(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
#if defined(LWS_WITH_SERVER)
struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
#endif
lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
uint8_t buf[LWS_PRE + 1520], *p = &buf[LWS_PRE],
*end = &buf[sizeof(buf) - 1];
lws_ss_state_return_t r;
size_t buflen;
int f = 0;
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
assert(h);
assert(h->policy);
lwsl_info("%s: %s, %s CLIENT_CONNECTION_ERROR: %s\n", __func__,
lws_ss_tag(h), h->policy->streamtype, in ? (char *)in : "(null)");
#if defined(LWS_WITH_CONMON)
lws_conmon_ss_json(h);
#endif
r = lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
if (r == LWSSSSRET_DESTROY_ME)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
h->wsi = NULL;
r = lws_ss_backoff(h);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
break;
case LWS_CALLBACK_RAW_CLOSE:
if (!h)
break;
lws_sul_cancel(&h->sul_timeout);
#if defined(LWS_WITH_CONMON)
lws_conmon_ss_json(h);
#endif
lwsl_info("%s: %s, %s RAW_CLOSE\n", __func__, lws_ss_tag(h),
h->policy ? h->policy->streamtype : "no policy");
h->wsi = NULL;
#if defined(LWS_WITH_SERVER)
lws_pt_lock(pt, __func__);
lws_dll2_remove(&h->cli_list);
lws_pt_unlock(pt);
#endif
/* wsi is going down anyway */
r = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);
if (r == LWSSSSRET_DESTROY_ME)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
#if defined(LWS_WITH_SERVER)
!(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not server */
#endif
!h->txn_ok && !wsi->a.context->being_destroyed) {
r = lws_ss_backoff(h);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
break;
}
break;
case LWS_CALLBACK_RAW_CONNECTED:
lwsl_info("%s: RAW_CONNECTED\n", __func__);
h->retry = 0;
h->seqstate = SSSEQ_CONNECTED;
lws_sul_cancel(&h->sul);
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
r = lws_ss_event_helper(h, LWSSSCS_CONNECTED);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
lws_validity_confirmed(wsi);
break;
case LWS_CALLBACK_RAW_ADOPT:
lwsl_info("%s: RAW_ADOPT\n", __func__);
break;
/* chunks of chunked content, with header removed */
case LWS_CALLBACK_RAW_RX_FILE:
in = p;
f = (int)read((int)(intptr_t)wsi->desc.filefd, p, sizeof(buf) - LWS_PRE);
if (f < 0)
return 0;
len = (unsigned int)f;
/* fallthru */
case LWS_CALLBACK_RAW_RX:
if (!h || !h->info.rx)
return 0;
r = h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, 0);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
return 0; /* don't passthru */
case LWS_CALLBACK_RAW_WRITEABLE:
lwsl_info("%s: RAW_WRITEABLE\n", __func__);
if (!h || !h->info.tx)
return 0;
buflen = lws_ptr_diff_size_t(end, p);
r = h->info.tx(ss_to_userobj(h), h->txord++, p, &buflen, &f);
if (r == LWSSSSRET_TX_DONT_SEND)
return 0;
if (r < 0)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
/*
* flags are ignored with raw, there are no protocol payload
* boundaries, just an arbitrarily-fragmented bytestream
*/
p += buflen;
if (lws_write(wsi, buf + LWS_PRE, lws_ptr_diff_size_t(p, buf + LWS_PRE),
LWS_WRITE_HTTP) != lws_ptr_diff(p, buf + LWS_PRE)) {
lwsl_err("%s: write failed\n", __func__);
return -1;
}
lws_set_timeout(wsi, 0, 0);
break;
default:
break;
}
return 0;
}
static int
secstream_connect_munge_raw(lws_ss_handle_t *h, char *buf, size_t len,
struct lws_client_connect_info *i,
union lws_ss_contemp *ct)
{
i->method = "RAW";
return 0;
}
const struct lws_protocols protocol_secstream_raw = {
"lws-secstream-raw",
secstream_raw,
0,
0,
0, NULL, 0
};
const struct ss_pcols ss_pcol_raw = {
"raw",
"",
&protocol_secstream_raw,
secstream_connect_munge_raw,
NULL, NULL
};

View File

@ -0,0 +1,249 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
static int
secstream_ws(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
#if defined(LWS_WITH_SERVER)
struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
#endif
lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
uint8_t buf[LWS_PRE + 1400];
lws_ss_state_return_t r;
int f = 0, f1, n;
size_t buflen;
switch (reason) {
/* because we are protocols[0] ... */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_info("%s: CLIENT_CONNECTION_ERROR: %s\n", __func__,
in ? (char *)in : "(null)");
if (!h)
break;
#if defined(LWS_WITH_CONMON)
lws_conmon_ss_json(h);
#endif
r = lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
if (r == LWSSSSRET_DESTROY_ME)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
h->wsi = NULL;
r = lws_ss_backoff(h);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
break;
case LWS_CALLBACK_CLOSED: /* server */
case LWS_CALLBACK_CLIENT_CLOSED:
if (!h)
break;
lws_sul_cancel(&h->sul_timeout);
#if defined(LWS_WITH_CONMON)
lws_conmon_ss_json(h);
#endif
r = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);
if (r == LWSSSSRET_DESTROY_ME)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
if (h->wsi)
lws_set_opaque_user_data(h->wsi, NULL);
h->wsi = NULL;
#if defined(LWS_WITH_SERVER)
lws_pt_lock(pt, __func__);
lws_dll2_remove(&h->cli_list);
lws_pt_unlock(pt);
#endif
if (reason == LWS_CALLBACK_CLIENT_CLOSED) {
if (h->policy &&
!(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
#if defined(LWS_WITH_SERVER)
!(h->info.flags & LWSSSINFLAGS_ACCEPTED) && /* not server */
#endif
!wsi->a.context->being_destroyed) {
r = lws_ss_backoff(h);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
break;
}
#if defined(LWS_WITH_SERVER)
if (h->info.flags & LWSSSINFLAGS_ACCEPTED) {
/*
* was an accepted client connection to
* our server, so the stream is over now
*/
lws_ss_destroy(&h);
return 0;
}
#endif
}
break;
case LWS_CALLBACK_ESTABLISHED:
case LWS_CALLBACK_CLIENT_ESTABLISHED:
h->retry = 0;
h->seqstate = SSSEQ_CONNECTED;
lws_sul_cancel(&h->sul);
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
r = lws_ss_event_helper(h, LWSSSCS_CONNECTED);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
break;
case LWS_CALLBACK_RECEIVE:
case LWS_CALLBACK_CLIENT_RECEIVE:
// lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len);
if (!h || !h->info.rx)
return 0;
if (lws_is_first_fragment(wsi))
f |= LWSSS_FLAG_SOM;
if (lws_is_final_fragment(wsi))
f |= LWSSS_FLAG_EOM;
// lws_frame_is_binary(wsi);
h->subseq = 1;
r = h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
return 0; /* don't passthru */
case LWS_CALLBACK_SERVER_WRITEABLE:
case LWS_CALLBACK_CLIENT_WRITEABLE:
// lwsl_notice("%s: %s: WRITEABLE\n", __func__, lws_ss_tag(h));
if (!h || !h->info.tx)
return 0;
if (h->seqstate != SSSEQ_CONNECTED) {
lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate);
break;
}
buflen = sizeof(buf) - LWS_PRE;
r = h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE,
&buflen, &f);
if (r == LWSSSSRET_TX_DONT_SEND)
return 0;
if (r != LWSSSSRET_OK)
return _lws_ss_handle_state_ret_CAN_DESTROY_HANDLE(r, wsi, &h);
f1 = lws_write_ws_flags(h->policy->u.http.u.ws.binary ?
LWS_WRITE_BINARY : LWS_WRITE_TEXT,
!!(f & LWSSS_FLAG_SOM),
!!(f & LWSSS_FLAG_EOM));
n = lws_write(wsi, buf + LWS_PRE, buflen, (enum lws_write_protocol)f1);
if (n < (int)buflen) {
lwsl_info("%s: write failed %d %d\n", __func__,
n, (int)buflen);
return -1;
}
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols protocol_secstream_ws = {
"lws-secstream-ws",
secstream_ws,
0, 0, 0, NULL, 0
};
/*
* Munge connect info according to protocol-specific considerations... this
* usually means interpreting aux in a protocol-specific way and using the
* pieces at connection setup time, eg, http url pieces.
*
* len bytes of buf can be used for things with scope until after the actual
* connect.
*
* For ws, protocol aux is <url path>;<ws subprotocol name>
*/
static int
secstream_connect_munge_ws(lws_ss_handle_t *h, char *buf, size_t len,
struct lws_client_connect_info *i,
union lws_ss_contemp *ct)
{
const char *pbasis = h->policy->u.http.url;
size_t used_in, used_out;
lws_strexp_t exp;
/* i.path on entry is used to override the policy urlpath if not "" */
if (i->path[0])
pbasis = i->path;
if (!pbasis)
return 0;
if (h->policy->flags & LWSSSPOLF_HTTP_CACHE_COOKIES)
i->ssl_connection |= LCCSCF_CACHE_COOKIES;
if (h->policy->flags & LWSSSPOLF_PRIORITIZE_READS)
i->ssl_connection |= LCCSCF_PRIORITIZE_READS;
/* protocol aux is the path part ; ws subprotocol name */
i->path = buf;
buf[0] = '/';
lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, buf + 1, len - 1);
if (lws_strexp_expand(&exp, pbasis, strlen(pbasis),
&used_in, &used_out) != LSTRX_DONE)
return 1;
i->protocol = h->policy->u.http.u.ws.subprotocol;
lwsl_ss_info(h, "url %s, ws subprotocol %s", buf, i->protocol);
return 0;
}
const struct ss_pcols ss_pcol_ws = {
"ws", "http/1.1", &protocol_secstream_ws, secstream_connect_munge_ws, 0, 0
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
include_directories(../../../core
../../../core-net
../../../plat/unix
../../../plat/freertos
../../../tls
../../../event-libs
../../../roles
../../../roles/http
../../../roles/h1
../../../roles/h2
../../../roles/ws
../../../roles/mqtt
../../../roles/raw
../../../system
../../../system/smd
../../../system/fault-injection
../../../system/metrics
${LIBUV_INCLUDE_DIRS}
)
foreach(libpath ${LWS_LIB_BUILD_INC_PATHS})
include_directories(${libpath})
endforeach()

View File

@ -0,0 +1,89 @@
# SSPC client support
## Full vs LWS_ONLY_SSPC lws builds
SSPC (Secure Stream Proxy Client) apis are built into libwebsockets as you would
expect if built with `LWS_WITH_SECURE_STREAMS_PROXY_API`. These are the client
SS api implementation, the serialization and deserialization of SS protocol, and
generic transport interface.
You can also choose to build lws so it ONLY contains the SSPC pieces suitable
for the client side, with the libwebsockets build option `LWS_ONLY_SSPC`.
![lws-sspc build](../../../../doc-assets/lws-sspc-1.png)
Identical sources are used in both cases, but with `LWS_ONLY_SSPC` only the
parts related to mux and SSPC are included, coming to less than 25KB .text on
armv7 while providing the full proxied SS API. Also excluded are the normal
`lws_context` and event loop facilities, the provided apis expect to work with
an existing event loop.
`LWS_ONLY_SSPC` facilitates using full libwebsockets SSPC client support on very
small devices that don't need the rest of lws. Such devices do not need to have
tls, an IP stack or any network, yet can use full Secure Streams capabilities
via the proxy (eg, h2, including stream binding, or ws over tls).
![lws-sspc build](../../../../doc-assets/lws-sspc-2.png)
`LWS_ONLY_SSPC` does not need a normal `struct lws_context` nor the lws event
loop or other apis. To maintain compatibility with the SS apis the user code
provides a simple, static stub lws_context with a couple of members and no
creation or destruction.
You can find a minimal example that builds with `LWS_ONLY_SSPC` in
at `minimal-examples/secure-streams/minimal-secure-streams-custom-client-transport`,
implementing full SS apis over a UART link, where the client does not link to
lws nor need any network stack, tls library or internet protocol implementation.
It expects to use two USB serial adapters in loopback, one for this example and
one for the related proxy, minimal-secure-streams-custom-proxy-transport.
These two examples need different build dirs (but use the same libwebsockets
source) since the client requires lws built with `LWS_ONLY_SSPC`.
## Communication layers
Different transports have quite different semantics for connectivity. Serialized
SS communication has three abstract layers:
- link: connectivity is established. The serial cable is plugged in, the
RF link is established, or the Posix Socket has connected.
- mux: On some transports, eg, UART, there is a single logical link that we
want to be able to carry multiple SS connections, so the transport must
provide a way to negotiate multiplexing over the link.
- Serialized SS: the SS activity serialized into a stream in both directions
How these look depend on the nature of the underlying transport, some examples:
|layer|RF|UART|Unix Domain Socket|
|---|---|---|---|
|link|managed at RF stack|detect by periodic PING/PONG|Socket Connection semantics|
|mux|extra framing|extra framing|not needed, one UDS client socket for each SS|
|Serialized SS|bytestream|bytestream|Datagram stream|
SSPC can directly hook up to an lws_transport_ops structure, this is used in the
case where multiplexing is handled by the transport like Unix Domain Socket link
to the proxy. In that case, each SS makes its own personal Unix Domain Socket
link to the proxy.
The lws_transport_mux support is designed to interpose between SSPC layter and
the lws_transport itself, to provide its own multiplexing layer. This is needed
in the case where there is just a reliable bytestream link to the proxy, and no
suitable channelization or link detection, for example a simple UART transport.
lws_transport_mux provides 250 mux channels over the transport, with link
detection by three-way PING handshakes at the mux layer.
## LWS_ONLY_SSPC imports
Four system integration imports are needed by the library.
|prototype|function|
|---|---|
|lws_usec_t **lws_now_usecs**(void)|get us-resolution monotonic time|
|void **__lws_logv**(lws_log_cx_t *ignore1, lws_log_prepend_cx_t ignore2, void *ignore3, int filter, const char *_fun, const char *format, va_list ap)|log emit|
|void **lws_sul_schedule**(struct lws_context_standalone *ctx, int tsi, lws_sorted_usec_list_t *sul, sul_cb_t _cb, lws_usec_t _us)|schedule sul callback|
|void **lws_sul_cancel**(lws_sorted_usec_list_t *sul)|Cancel scheduled callback|

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* Client SSPC where the connectivity is implemented by a wsi
*/
#include <private-lib-core.h>
static int
lws_sss_transport_wsi_cb(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi);
size_t pktsize = wsi->a.context->max_http_header_data;
lws_ss_state_return_t r;
switch (reason) {
case LWS_CALLBACK_CONNECTING:
/*
* In our particular case, we want CCEs even inside the
* initial connect loop time
*/
wsi->client_suppress_CONNECTION_ERROR = 0;
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_warn("%s: CCE: %s\n", __func__,
in ? (const char *)in : "null");
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free
* any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
lws_set_opaque_user_data(wsi, NULL);
h->txp_path.ops_in->event_connect_disposition(h, 1);
break;
case LWS_CALLBACK_RAW_CONNECTED:
lwsl_user("%s: CONNECTED\n", __func__);
if (h->txp_path.ops_in->event_connect_disposition(h, 0))
return -1;
/*
* We create the dsh at the response to the initial tx, which
* will let us know the policy's max size for it... let's
* protect the connection with a promise to complete the
* SS serialization streamtype negotation within a short period,
* we will cancel this timeout when we have the proxy's ack
* of the streamtype serialization, eg, it exists in the proxy
* policy etc
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
break;
case LWS_CALLBACK_RAW_CLOSE:
/*
* our ss proxy Unix Domain socket has closed...
*/
lwsl_sspc_info(h, "LWS_CALLBACK_RAW_CLOSE: proxy conn down, wsi %s",
lws_wsi_tag(wsi));
if (h) {
r = h->txp_path.ops_in->event_closed(h);
h->txp_path.priv_in = NULL;
if (r == LWSSSSRET_DESTROY_ME) {
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
}
}
break;
case LWS_CALLBACK_RAW_RX:
/*
* ie, the proxy has sent us something
*/
if (!h || !h->txp_path.priv_in) {
lwsl_info("%s: rx when client ss destroyed\n", __func__);
return -1;
}
lwsl_sspc_info(h, "%s: RAW_RX: rx %d\n", __func__, (int)len);
if (!len) {
lwsl_sspc_notice(h, "RAW_RX: zero len");
return -1;
}
r = h->txp_path.ops_in->event_read((lws_transport_priv_t)h,
(const uint8_t *)in, len);
switch (r) {
default:
break;
case LWSSSSRET_DISCONNECT_ME:
lwsl_info("%s: proxlicent RX ended with DISCONNECT_ME\n",
__func__);
return -1;
case LWSSSSRET_DESTROY_ME:
lwsl_info("%s: proxlicent RX ended with DESTROY_ME\n",
__func__);
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
return -1;
}
if (h->state == LPCSCLI_LOCAL_CONNECTED ||
h->state == LPCSCLI_ONWARD_CONNECT)
lws_set_timeout(wsi, 0, 0);
break;
case LWS_CALLBACK_RAW_WRITEABLE:
/*
* We can transmit something to the proxy...
*/
if (!h)
break;
lwsl_sspc_debug(h, "WRITEABLE %s, state %d",
wsi->lc.gutag, h->state);
if (h->txp_path.ops_in->event_can_write(h, pktsize))
return -1;
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols lws_sspc_protocols[] = {
{
"ssproxy-protocol",
lws_sss_transport_wsi_cb,
0,
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
/*
* lws_sss_transport ops for wsi transport
*/
static int
lws_sss_transport_wsi_retry_connect(lws_txp_path_client_t *path, lws_sspc_handle_t *h)
{
struct lws_client_connect_info i;
/*
* We may have started up before the system proxy, so be prepared with
* a sul to retry at 1Hz
*/
memset(&i, 0, sizeof i);
i.context = h->context;
if (h->context->ss_proxy_port) { /* tcp */
i.address = h->context->ss_proxy_address;
i.port = h->context->ss_proxy_port;
i.iface = h->context->ss_proxy_bind;
} else {
if (h->context->ss_proxy_bind)
i.address = h->context->ss_proxy_bind;
else
#if defined(__linux__)
i.address = "+@proxy.ss.lws";
#else
i.address = "+/tmp/proxy.ss.lws";
#endif
}
i.host = i.address;
i.origin = i.address;
i.method = "RAW";
i.protocol = lws_sspc_protocols[0].name;
i.local_protocol_name = lws_sspc_protocols[0].name;
i.path = "";
i.pwsi = (struct lws **)&h->txp_path.priv_onw;
i.opaque_user_data = (void *)h;
i.ssl_connection = LCCSCF_SECSTREAM_PROXY_LINK;
lws_metrics_caliper_bind(h->cal_txn, h->context->mt_ss_cliprox_conn);
#if defined(LWS_WITH_SYS_METRICS)
lws_metrics_tag_add(&h->cal_txn.mtags_owner, "ss", h->ssi.streamtype);
#endif
/* this wsi is the link to the proxy */
if (!lws_client_connect_via_info(&i)) {
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
return 1; /* going to need to retry */
}
lwsl_sspc_notice(h, "%s", ((struct lws *)(h->txp_path.priv_onw))->lc.gutag);
return 0; /* in progress */
}
static void
lws_sss_transport_wsi_req_write(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (wsi)
lws_callback_on_writable(wsi);
}
static int
lws_sss_transport_wsi_write(lws_transport_priv_t priv, uint8_t *buf, size_t len)
{
struct lws *wsi = (struct lws *)priv;
if (lws_write(wsi, buf, len, LWS_WRITE_RAW) != (ssize_t)len) {
lwsl_wsi_notice(wsi, "failed");
return -1;
}
return 0;
}
static void
lws_sss_transport_wsi_close(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (!wsi)
return;
lws_set_opaque_user_data(wsi, NULL);
lws_wsi_close(wsi, LWS_TO_KILL_ASYNC);
}
static void
lws_sss_transport_wsi_stream_up(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
}
const lws_transport_client_ops_t txp_ops_sspc_wsi = {
.name = "txp_sspc_wsi",
.event_retry_connect = lws_sss_transport_wsi_retry_connect,
.req_write = lws_sss_transport_wsi_req_write,
._write = lws_sss_transport_wsi_write,
._close = lws_sss_transport_wsi_close,
.event_stream_up = lws_sss_transport_wsi_stream_up,
.dsh_splitat = 1300,
};

View File

@ -0,0 +1,515 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* These are helpers used by the transport implementation. They contain the
* generic sspc actions to handle events that happen at the transport.
*/
#include <private-lib-core.h>
#if defined(STANDALONE)
#define lws_context lws_context_standalone
void
lws_ser_wu16be(uint8_t *b, uint16_t u)
{
*b++ = (uint8_t)(u >> 8);
*b = (uint8_t)u;
}
void
lws_ser_wu32be(uint8_t *b, uint32_t u32)
{
*b++ = (uint8_t)(u32 >> 24);
*b++ = (uint8_t)(u32 >> 16);
*b++ = (uint8_t)(u32 >> 8);
*b = (uint8_t)u32;
}
void
lws_ser_wu64be(uint8_t *b, uint64_t u64)
{
lws_ser_wu32be(b, (uint32_t)(u64 >> 32));
lws_ser_wu32be(b + 4, (uint32_t)u64);
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
#endif
static size_t
lws_sspc_serialize_metadata(lws_sspc_handle_t *h, lws_sspc_metadata_t *md,
uint8_t *p, uint8_t *end)
{
size_t n, txc;
if (md->name[0] == '\0') {
lwsl_sspc_info(h, "sending tx credit update %d",
md->tx_cr_adjust);
p[0] = LWSSS_SER_TXPRE_TXCR_UPDATE;
lws_ser_wu16be(&p[1], 4);
lws_ser_wu32be(&p[3], (uint32_t)md->tx_cr_adjust);
n = 7;
} else {
lwsl_sspc_info(h, "sending metadata");
p[0] = LWSSS_SER_TXPRE_METADATA;
txc = strlen(md->name);
n = txc + 1 + md->len;
if (n > 0xffff)
/* we can't serialize this metadata in 16b length */
return 0;
if (n > lws_ptr_diff_size_t(end, &p[4]))
/* we don't have space for this metadata */
return 0;
lws_ser_wu16be(&p[1], (uint16_t)n);
p[3] = (uint8_t)txc;
memcpy(&p[4], md->name, (unsigned int)txc);
memcpy(&p[4 + txc], &md[1], md->len);
n = 4 + txc + md->len;
}
lws_dll2_remove(&md->list);
lws_free(md);
return n;
}
/*
* An attempt to establish a link to the SS proxy has failed
*/
lws_ss_state_return_t
lws_sspc_txp_connect_disposition(lws_sspc_handle_t *h, int disposition)
{
lws_ss_state_return_t r;
uint64_t i;
if (!disposition) {
if (!h
#if !defined(STANDALONE)
|| lws_fi(&h->fic, "sspc_fail_on_linkup")
#endif
)
return 1;
lwsl_sspc_info(h, "CONNECTED (%s), %s", h->ssi.streamtype, h->txp_path.ops_onw->name);
h->state = LPCSCLI_SENDING_INITIAL_TX;
h->us_start_upstream = 0;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
h->txp_path.priv_onw = NULL;
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
if (!h->ssi.state)
return LWSSSSRET_OK;
i = (uint64_t)(lws_now_usecs() - h->us_start_upstream) / LWS_US_PER_MS;
if (i > 0xffffffffull)
i = 0xffffffffull;
r = h->ssi.state(lws_sspc_to_user_object(h), NULL,
LWSSSCS_UPSTREAM_LINK_RETRY, (uint32_t)i);
if (r == LWSSSSRET_DESTROY_ME)
lws_sspc_destroy(&h);
return LWSSSSRET_OK;
}
void
lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul)
{
lws_sspc_handle_t *h = lws_container_of(sul, lws_sspc_handle_t,
sul_retry);
if (h->txp_path.ops_onw->event_retry_connect(&h->txp_path, h))
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
}
/*
* The transport connection has closed
*/
lws_ss_state_return_t
lws_sspc_txp_event_closed(lws_transport_priv_t priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)priv;
lws_ss_state_return_t r = LWSSSSRET_OK;
if (!h) {
lwsl_sspc_info(h, "No sspc on client proxy link close");
return LWSSSSRET_OK;
}
h->parser.ps = RPAR_TYPE;
lws_dsh_empty(h->dsh);
h->txp_path.priv_onw = NULL;
h->conn_req_state = LWSSSPC_ONW_NONE;
if (h->ss_dangling_connected && h->ssi.state) {
lwsl_sspc_notice(h, "setting _DISCONNECTED");
h->ss_dangling_connected = 0;
h->prev_ss_state = LWSSSCS_DISCONNECTED;
r = h->ssi.state(ss_to_userobj(h), NULL,
LWSSSCS_DISCONNECTED, 0);
}
if (r != LWSSSSRET_DESTROY_ME)
/*
* schedule a reconnect in 1s
*/
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
return r;
}
/*
* We received rx from the proxy... caller must do destroy on DESTROY_ME
*/
lws_ss_state_return_t
lws_sspc_txp_rx_from_proxy(lws_transport_priv_t txp_priv, const uint8_t *in,
size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
void *m = (void *)((uint8_t *)(h + 1));
assert(h);
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fake_rxparse_disconnect_me"))
return LWSSSSRET_DISCONNECT_ME;
if (lws_fi(&h->fic, "sspc_fake_rxparse_destroy_me"))
return LWSSSSRET_DESTROY_ME;
#endif
return lws_sspc_deserialize_parse(h, in, len, (lws_ss_handle_t **)m);
}
lws_ss_state_return_t
lws_sspc_txp_tx(lws_sspc_handle_t *h, size_t metadata_limit)
{
uint8_t *pkt = NULL, *p = NULL, *end = NULL;
void *m = (void *)((uint8_t *)(h + 1));
lws_ss_state_return_t r;
uint8_t _s[64 + LWS_PRE], *s = _s + LWS_PRE, *cp = s;
size_t txl, len;
lws_usec_t us;
int flags;
/*
* Management of ss timeout can happen any time and doesn't
* depend on wsi existence or state
*/
if (h->pending_timeout_update) {
cp = s;
*s = LWSSS_SER_TXPRE_TIMEOUT_UPDATE;
*(s + 1) = 0;
*(s + 2) = 4;
/*
* 0: use policy timeout value
* 0xffffffff: cancel the timeout
*/
lws_ser_wu32be(s + 3, h->timeout_ms);
/* in case anything else to write */
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
h->pending_timeout_update = 0;
txl = 7;
goto do_write;
}
*(s + 1) = 0;
/*
* This is the state of the link that connects us to the onward
* proxy
*/
switch (h->state) {
case LPCSCLI_SENDING_INITIAL_TX:
/*
* We are negotating the opening of a particular
* streamtype
*/
// lwsl_sspc_notice(h, "LPCSCLI_SENDING_INITIAL_TX");
txl = strlen(h->ssi.streamtype) + 1 + 4 + 4;
cp = s;
*s = LWSSS_SER_TXPRE_STREAMTYPE;
lws_ser_wu16be(s + 1, (uint16_t)txl);
/* SSSv1: add protocol version byte (initially 1) */
*(s + 3) = (uint8_t)LWS_SSS_CLIENT_PROTOCOL_VERSION;
#if defined(WIN32) || defined(LWS_PLAT_BAREMETAL)
lws_ser_wu32be(s + 4, (uint32_t)0);
#else
lws_ser_wu32be(s + 4, (uint32_t)getpid());
#endif
lws_ser_wu32be(s + 8, (uint32_t)h->txc.peer_tx_cr_est);
lws_strncpy((char *)(s + 12), h->ssi.streamtype,
(sizeof(_s) - LWS_PRE) - 12);
txl += 3;
h->state = LPCSCLI_WAITING_CREATE_RESULT;
goto do_write;
case LPCSCLI_LOCAL_CONNECTED:
// lwsl_sspc_notice(h, "LPCSCLI_LOCAL_CONNECTED");
/*
* Do we need to prioritize sending any metadata
* changes?
*/
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
size_t n;
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
n = lws_sspc_serialize_metadata(h, md, p, end);
if (!n)
goto metadata_hangup;
txl = (size_t)n;
lwsl_sspc_debug(h, "(local_conn) metadata");
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_debug(h, "(local_conn) PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
if (h->conn_req_state >= LWSSSPC_ONW_ONGOING) {
lwsl_sspc_info(h, "conn_req_state %d",
h->conn_req_state);
break;
}
lwsl_sspc_info(h, "(local_conn) onward connect");
h->conn_req_state = LWSSSPC_ONW_ONGOING;
cp = s;
*s = LWSSS_SER_TXPRE_ONWARD_CONNECT;
*(s + 1) = 0;
*(s + 2) = 0;
txl = 3;
goto do_write;
case LPCSCLI_OPERATIONAL:
/*
*
* - Do we need to prioritize sending any metadata
* changes? (includes txcr updates)
*
* - Do we need to forward a hint about the payload
* length?
*/
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
txl = lws_sspc_serialize_metadata(h, md, p, end);
if (!txl)
goto metadata_hangup;
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_info(h, "PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
/* we can't write anything if we don't have credit */
if (!h->ignore_txc && h->txc.tx_cr <= 0)
lwsl_sspc_info(h, "WRITEABLE / OPERATIONAL:"
" lack credit (%d)",
(int)h->txc.tx_cr);
len = metadata_limit - LWS_PRE - 19;
flags = 0;
if (!h->ssi.tx) {
txl = 0;
goto do_write_nz;
}
r = h->ssi.tx(m, h->ord++, pkt + LWS_PRE + 19, &len, &flags);
switch (r) {
case LWSSSSRET_TX_DONT_SEND:
txl = 0;
goto do_write_nz;
case LWSSSSRET_DISCONNECT_ME:
case LWSSSSRET_DESTROY_ME:
lwsl_sspc_warn(h, "sspc tx DISCONNECT/DESTROY TBD");
break;
default:
break;
}
h->txc.tx_cr = h->txc.tx_cr - (int)len;
cp = p;
txl = len + 19;
us = lws_now_usecs();
p[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], (uint16_t)(len + 19 - 3));
lws_ser_wu32be(&p[3], (uint32_t)flags);
/* time spent here waiting to send this */
lws_ser_wu32be(&p[7], (uint32_t)(us - h->us_earliest_write_req));
/* ust that the client write happened */
lws_ser_wu64be(&p[11], (uint64_t)us);
h->us_earliest_write_req = 0;
if (flags & LWSSS_FLAG_EOM)
if (h->rsidx + 1 < (int)LWS_ARRAY_SIZE(h->rideshare_ofs) &&
h->rideshare_ofs[h->rsidx + 1])
h->rsidx++;
goto do_write;
default:
break;
}
return LWSSSSRET_OK;
req_write_and_issue:
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
do_write_nz:
if (!txl) {
lws_free(pkt);
return LWSSSSRET_OK;
}
do_write:
if (
#if !defined(STANDALONE)
!lws_fi(&h->fic, "sspc_link_write_fail") &&
#endif
!h->txp_path.ops_onw->_write(h->txp_path.priv_onw, cp, txl)) {
if (pkt)
lws_free(pkt);
return LWSSSSRET_OK;
}
goto hangup;
metadata_hangup:
lwsl_sspc_err(h, "metadata too large");
hangup:
lws_free(pkt);
lwsl_sspc_warn(h, "hangup");
/* hang up on the proxy link */
return LWSSSSRET_DISCONNECT_ME;
}
void
lws_sspc_txp_lost_coherence(lws_transport_priv_t txp_priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
lwsl_sspc_warn(h, "Lost Coherence");
h->conn_req_state = LWSSSPC_ONW_NONE;
/* pass thru to lower layer, eg, mux */
h->txp_path.ops_onw->lost_coherence(h->txp_path.priv_onw);
}
/*
* The actual client transports bind to this transport ops for "inside sspc".
* It's like this so we can transparently interpose the mux.
*
* Only the apis the transport needs to call on the inside need timplementing
* for this
*/
const lws_transport_client_ops_t lws_txp_inside_sspc = {
.name = "txp_inside_sspc",
.event_connect_disposition = lws_sspc_txp_connect_disposition,
.event_read = lws_sspc_txp_rx_from_proxy,
.event_can_write = lws_sspc_txp_tx,
.event_closed = lws_sspc_txp_event_closed,
.lost_coherence = lws_sspc_txp_lost_coherence,
};

View File

@ -0,0 +1,851 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
extern const uint32_t ss_state_txn_validity[17];
#if defined(STANDALONE)
#define lws_context lws_context_standalone
static const char *state_names[] = {
"(unset)",
"LWSSSCS_CREATING",
"LWSSSCS_DISCONNECTED",
"LWSSSCS_UNREACHABLE",
"LWSSSCS_AUTH_FAILED",
"LWSSSCS_CONNECTED",
"LWSSSCS_CONNECTING",
"LWSSSCS_DESTROYING",
"LWSSSCS_POLL",
"LWSSSCS_ALL_RETRIES_FAILED",
"LWSSSCS_QOS_ACK_REMOTE",
"LWSSSCS_QOS_NACK_REMOTE",
"LWSSSCS_QOS_ACK_LOCAL",
"LWSSSCS_QOS_NACK_LOCAL",
"LWSSSCS_TIMEOUT",
"LWSSSCS_SERVER_TXN",
"LWSSSCS_SERVER_UPGRADE",
"LWSSSCS_EVENT_WAIT_CANCELLED",
"LWSSSCS_UPSTREAM_LINK_RETRY",
};
const char *
lws_ss_state_name(int state)
{
if (state >= LWSSSCS_USER_BASE)
return "user state";
if (state >= (int)LWS_ARRAY_SIZE(state_names))
return "unknown";
return state_names[state];
}
const uint32_t ss_state_txn_validity[] = {
/* if we was last in this state... we can legally go to these states */
[0] = (1 << LWSSSCS_CREATING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CREATING] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DISCONNECTED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_UNREACHABLE] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_CONNECTING) |
/* win conn failure > retry > succ */
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_AUTH_FAILED] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTED] = (1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_QOS_ACK_LOCAL) |
(1 << LWSSSCS_QOS_NACK_LOCAL) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTING] = (1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DESTROYING] = 0,
[LWSSSCS_POLL] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_ALL_RETRIES_FAILED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
#if defined(LWS_ROLE_MQTT)
(1 << LWSSSCS_QOS_ACK_REMOTE) |
#endif
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_LOCAL] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_LOCAL] = (1 << LWSSSCS_DESTROYING) |
(1 << LWSSSCS_TIMEOUT),
/* he can get the timeout at any point and take no action... */
[LWSSSCS_TIMEOUT] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_TXN] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_UPGRADE] = (1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_DESTROYING),
};
char *
lws_strncpy(char *dest, const char *src, size_t size)
{
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
extern void
__lws_logv(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, va_list ap);
void _lws_logv(int filter, const char *format, va_list ap)
{
__lws_logv(NULL, NULL, NULL, filter, NULL, format, ap);
}
void
_lws_log(int filter, const char *format, ...)
{
va_list ap;
va_start(ap, format);
_lws_logv(filter, format, ap);
va_end(ap);
}
void
_lws_log_cx(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, ...)
{
va_list ap;
va_start(ap, format);
__lws_logv(cx, prep, obj, filter, _fun, format, ap);
va_end(ap);
}
#endif
int
lws_ss_check_next_state_sspc(lws_sspc_handle_t *ss, uint8_t *prevstate,
lws_ss_constate_t cs)
{
if (cs >= LWSSSCS_USER_BASE || cs == LWSSSCS_EVENT_WAIT_CANCELLED)
/*
* we can't judge user or transient states, leave the old state
* and just wave them through
*/
return 0;
if (cs >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* we don't recognize this state as usable */
lwsl_sspc_err(ss, "bad new state %u", cs);
assert(0);
return 1;
}
if (*prevstate >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* existing state is broken */
lwsl_sspc_err(ss, "bad existing state %u",
(unsigned int)*prevstate);
assert(0);
return 1;
}
if (ss_state_txn_validity[*prevstate] & (1u << cs)) {
lwsl_sspc_notice(ss, "%s -> %s",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
/* this is explicitly allowed, update old state to new */
*prevstate = (uint8_t)cs;
return 0;
}
lwsl_sspc_err(ss, "transition from %s -> %s is illegal",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
assert(0);
return 1;
}
lws_ss_state_return_t
lws_sspc_event_helper(lws_sspc_handle_t *h, lws_ss_constate_t cs,
lws_ss_tx_ordinal_t flags)
{
lws_ss_state_return_t ret;
if (!h)
return LWSSSSRET_OK;
if (lws_ss_check_next_state_sspc(h, &h->prev_ss_state, cs))
return LWSSSSRET_DESTROY_ME;
if (!h->ssi.state)
return LWSSSSRET_OK;
h->h_in_svc = h;
ret = h->ssi.state((void *)((uint8_t *)(h + 1)), NULL, cs, flags);
h->h_in_svc = NULL;
return ret;
}
int
lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, lws_sspc_handle_t **ppss,
void *reserved, const char **ppayload_fmt)
{
lws_sspc_handle_t *h;
uint8_t *ua;
char *p;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(context, tsi);
#endif
/* allocate the handle (including ssi), the user alloc,
* and the streamname */
h = malloc(sizeof(lws_sspc_handle_t) + ssi->user_alloc +
strlen(ssi->streamtype) + 1);
if (!h)
return 1;
memset(h, 0, sizeof(*h));
#if !defined(STANDALONE)
h->lc.log_cx = context->log_cx;
#endif
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
h->fic.name = "sspc";
lws_xos_init(&h->fic.xos, lws_xos(&context->fic.xos));
if (ssi->fic.fi_owner.count)
lws_fi_import(&h->fic, &ssi->fic);
lws_fi_inherit_copy(&h->fic, &context->fic, "ss", ssi->streamtype);
if (lws_fi(&h->fic, "sspc_create_oom")) {
/*
* We have to do this a little later, so we can cleanly inherit
* the OOM pieces and drain the info fic
*/
lws_fi_destroy(&h->fic);
free(h);
return 1;
}
#endif
#if !defined(STANDALONE)
__lws_lc_tag(context, &context->lcg[LWSLCG_SSP_CLIENT], &h->lc,
ssi->streamtype);
#else
snprintf(h->lc.gutag, sizeof(h->lc.gutag), "[sspc|%s|%x]",
ssi->streamtype,
(unsigned int)(context->ssidx++));
#endif
h->txp_path = context->txp_cpath;
h->txp_path.ops_in = &lws_txp_inside_sspc;
h->txp_path.priv_in = (lws_transport_priv_t)h;
/* priv_onw filled in by onw transport */
lwsl_sspc_info(h, "txp path %s -> %s", h->txp_path.ops_in->name,
h->txp_path.ops_onw->name);
memcpy(&h->ssi, ssi, sizeof(*ssi));
ua = (uint8_t *)(h + 1);
memset(ua, 0, ssi->user_alloc);
p = (char *)ua + ssi->user_alloc;
memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1);
h->ssi.streamtype = (const char *)p;
h->context = context;
h->us_start_upstream = lws_now_usecs();
if (!ssi->manual_initial_tx_credit)
h->txc.peer_tx_cr_est = 500000000;
else
h->txc.peer_tx_cr_est = ssi->manual_initial_tx_credit;
#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_SYS_SMD)
if (!strcmp(ssi->streamtype, LWS_SMD_STREAMTYPENAME))
h->ignore_txc = 1;
#endif
lws_dll2_add_head(&h->client_list, &context->
#if !defined(STANDALONE)
pt[tsi].
#endif
ss_client_owner);
/* fill in the things the real api does for the caller */
*((void **)(ua + ssi->opaque_user_data_offset)) = opaque_user_data;
*((void **)(ua + ssi->handle_offset)) = h;
if (ppss)
*ppss = h;
/* try the actual connect */
lws_sspc_sul_retry_cb(&h->sul_retry);
return 0;
}
/* used on context destroy when iterating listed lws_ss on a pt */
int
lws_sspc_destroy_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_destroy(&h);
return 0;
}
void
lws_sspc_rxmetadata_destroy(lws_sspc_handle_t *h)
{
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
}
void
lws_sspc_destroy(lws_sspc_handle_t **ph)
{
lws_sspc_handle_t *h;
if (!*ph)
return;
h = *ph;
if (h == h->h_in_svc) {
lwsl_err("%s: illegal destroy, return LWSSSSRET_DESTROY_ME instead\n",
__func__);
assert(0);
return;
}
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (h->destroying)
return;
h->destroying = 1;
/* if this caliper is still dangling at destroy, we failed */
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
if (h->ss_dangling_connected && h->ssi.state) {
lws_sspc_event_helper(h, LWSSSCS_DISCONNECTED, 0);
h->ss_dangling_connected = 0;
}
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
lws_fi_destroy(&h->fic);
#endif
lws_sul_cancel(&h->sul_retry);
lws_dll2_remove(&h->client_list);
if (h->dsh)
lws_dsh_destroy(&h->dsh);
h->txp_path.ops_onw->_close(h->txp_path.priv_onw);
/* clean out any pending metadata changes that didn't make it */
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&(*ph)->metadata_owner)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
lws_sspc_rxmetadata_destroy(h);
lws_sspc_event_helper(h, LWSSSCS_DESTROYING, 0);
*ph = NULL;
lws_sul_cancel(&h->sul_retry);
#if !defined(STANDALONE)
/* confirm no sul left scheduled in handle or user allocation object */
lws_sul_debug_zombies(h->context, h, sizeof(*h) + h->ssi.user_alloc,
__func__);
#endif
#if !defined(STANDALONE)
__lws_lc_untag(h->context, &h->lc);
#endif
free(h);
}
lws_ss_state_return_t
lws_sspc_request_tx(lws_sspc_handle_t *h)
{
if (!h || !h->txp_path.priv_onw)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
lwsl_info("%s: state %u, conn_req_state %u\n", __func__,
(unsigned int)h->state,
(unsigned int)h->conn_req_state);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* Currently we fulfil the writeable part locally by just enabling POLLOUT on
* the UDS link, without serialization footprint, which is reasonable as far as
* it goes.
*
* But for the ..._len() variant, the expected payload length hint we are being
* told is something that must be serialized to the onward peer, since either
* that guy or someone upstream of him is the guy who will compose the framing
* with it that actually goes out.
*
* This information is needed at the upstream guy before we have sent any
* payload, eg, for http POST, he has to prepare the content-length in the
* headers, before any payload. So we have to issue a serialization of the
* length at this point.
*/
lws_ss_state_return_t
lws_sspc_request_tx_len(lws_sspc_handle_t *h, unsigned long len)
{
/*
* for client conns, they cannot even complete creation of the handle
* without the onwared connection to the proxy, it's not legal to start
* using it until it's operation and has the onward connection (and the
* link has called CREATED state)
*/
if (!h)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "setting writeable_len %u", (unsigned int)len);
h->writeable_len = len;
h->pending_writeable_len = 1;
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
/*
* We're going to use this up with serializing h->writeable_len... that
* will request again.
*/
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
lws_ss_state_return_t
lws_sspc_client_connect(struct lws_sspc_handle *h)
{
if (!h || h->state == LPCSCLI_OPERATIONAL)
return 0;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
assert(h->state == LPCSCLI_LOCAL_CONNECTED);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
struct lws_context *
lws_sspc_get_context(struct lws_sspc_handle *h)
{
return h->context;
}
const char *
lws_sspc_rideshare(struct lws_sspc_handle *h)
{
/*
* ...the serialized RX rideshare name if any...
*/
if (h->parser.rideshare[0]) {
lwsl_sspc_info(h, "parser %s", h->parser.rideshare);
return h->parser.rideshare;
}
/*
* The tx rideshare index
*/
if (h->rideshare_list[0]) {
lwsl_sspc_info(h, "tx list %s",
&h->rideshare_list[h->rideshare_ofs[h->rsidx]]);
return &h->rideshare_list[h->rideshare_ofs[h->rsidx]];
}
/*
* ... otherwise default to our stream type name
*/
lwsl_sspc_info(h, "def %s\n", h->ssi.streamtype);
return h->ssi.streamtype;
}
static int
_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len, int tx_cr_adjust)
{
lws_sspc_metadata_t *md;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
/*
* Are we replacing a pending metadata of the same name? It's not
* efficient to do this but user code can do what it likes... let's
* optimize away the old one.
*
* Tx credit adjust always has name ""
*/
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner)) {
md = lws_container_of(d, lws_sspc_metadata_t, list);
if (!strcmp(name, md->name)) {
lws_dll2_remove(&md->list);
lws_free(md);
break;
}
} lws_end_foreach_dll_safe(d, d1);
/*
* We have to stash the metadata and pass it to the proxy
*/
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fail_metadata_set"))
md = NULL;
else
#endif
md = lws_malloc(sizeof(*md) + len, "set metadata");
if (!md) {
lwsl_sspc_err(h, "OOM");
return 1;
}
memset(md, 0, sizeof(*md));
md->tx_cr_adjust = tx_cr_adjust;
h->txc.peer_tx_cr_est += tx_cr_adjust;
lws_strncpy(md->name, name, sizeof(md->name));
md->len = len;
if (len)
memcpy(&md[1], value, len);
lws_dll2_add_tail(&md->list, &h->metadata_owner);
if (len) {
#if !defined(STANDALONE)
lwsl_sspc_info(h, "set metadata %s", name);
lwsl_hexdump_sspc_info(h, value, len);
#endif
} else
lwsl_sspc_info(h, "serializing tx cr adj %d",
(int)tx_cr_adjust);
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
void
lws_sspc_server_ack(struct lws_sspc_handle *h, int nack)
{
//h->txn_resp = nack;
//h->txn_resp_set = 1;
}
int
lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len)
{
return _lws_sspc_set_metadata(h, name, value, len, 0);
}
int
lws_sspc_get_metadata(struct lws_sspc_handle *h, const char *name,
const void **value, size_t *len)
{
lws_sspc_metadata_t *md;
/*
* client side does not have access to policy
* and any metadata are new to it each time,
* we allocate them, removing any existing with
* the same name first
*/
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
md = lws_container_of(d,
lws_sspc_metadata_t, list);
if (!strcmp(md->name, name)) {
*len = md->len;
*value = &md[1];
return 0;
}
} lws_end_foreach_dll_safe(d, d1);
return 1;
}
int
lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "%d\n", (int)bump);
return _lws_sspc_set_metadata(h, "", NULL, 0, (int)bump);
}
int
lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
return h->txc.peer_tx_cr_est;
}
void
lws_sspc_start_timeout(struct lws_sspc_handle *h, unsigned int timeout_ms)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->txp_path.priv_onw)
/* we can't fulfil it */
return;
h->timeout_ms = (uint32_t)timeout_ms;
h->pending_timeout_update = 1;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
}
void
lws_sspc_cancel_timeout(struct lws_sspc_handle *h)
{
lws_sspc_start_timeout(h, (unsigned int)-1);
}
void *
lws_sspc_to_user_object(struct lws_sspc_handle *h)
{
return (void *)(h + 1);
}
struct lws_log_cx *
lwsl_sspc_get_cx(struct lws_sspc_handle *sspc)
{
if (!sspc)
return NULL;
return sspc->lc.log_cx;
}
void
lws_log_prepend_sspc(struct lws_log_cx *cx, void *obj, char **p, char *e)
{
struct lws_sspc_handle *h = (struct lws_sspc_handle *)obj;
#if defined(STANDALONE)
snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ", h->lc.gutag);
#else
*p += lws_snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ",
lws_sspc_tag(h));
#endif
}
void
lws_sspc_change_handlers(struct lws_sspc_handle *h, lws_sscb_rx rx,
lws_sscb_tx tx, lws_sscb_state state)
{
if (rx)
h->ssi.rx = rx;
if (tx)
h->ssi.tx = tx;
if (state)
h->ssi.state = state;
}
const char *
lws_sspc_tag(struct lws_sspc_handle *h)
{
if (!h)
return "[null sspc]";
#if defined(STANDALONE)
return h->lc.gutag;
#else
return lws_lc_tag(&h->lc);
#endif
}
int
lws_sspc_cancel_notify_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_event_helper(h, LWSSSCS_EVENT_WAIT_CANCELLED, 0);
return 0;
}

View File

@ -0,0 +1,853 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* Serialized Secure Streams deserializer for Proxy side
*/
#include <private-lib-core.h>
/*
* event loop is consuming dsh-buffered, already-serialized tx from the
* foreign side
*/
int
lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,
lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
uint8_t *p;
size_t si;
if (lws_dsh_get_head(dsh, KIND_C_TO_P, (void **)&p, &si)) {
*len = 0;
return 0;
}
/*
* The packet in the dsh has a proxying serialization header, process
* and strip it so we just forward the payload
*/
if (*len <= si - 23 || si < 23) {
/*
* What comes out of the dsh needs to fit in the tx buffer...
* we have arrangements at the proxy rx of the client UDS to
* chop chunks larger than 1380 into seuqential lumps of 1380
*/
lwsl_err("%s: *len = %d, si = %d\n", __func__, (int)*len, (int)si);
assert(0);
return 1;
}
if (p[0] != LWSSS_SER_TXPRE_TX_PAYLOAD) {
assert(0);
return 1;
}
*len = (size_t)(lws_ser_ru16be(&p[1]) - (23 - 3));
if (*len != si - 23) {
/*
* We cannot accept any length that doesn't reflect the actual
* length of what came in from the dsh, either something nasty
* happened with truncation or we are being attacked
*/
assert(0);
return 1;
}
memcpy(buf, p + 23, si - 23);
*flags = (int)lws_ser_ru32be(&p[3]);
lws_dsh_free((void **)&p);
return 0;
}
/*
* event loop side is consuming serialized data from the client via dsh, parse
* it using a bytewise parser for the serialization header(s)...
* it's possibly coalesced
*
* client: pss is pointing to the start of userdata. We can use
* pss_to_sspc_h(_pss, _ssi) to convert that to a pointer to the sspc
* handle
*
* proxy: pss is pointing to &conn->ss, a pointer to the ss handle
*
* Returns one of
*
* LWSSSSRET_OK
* LWSSSSRET_DISCONNECT_ME
* LWSSSSRET_DESTROY_ME
*/
/* convert userdata ptr _pss to handle pointer, allowing for any layout in
* userdata */
#define client_pss_to_sspc_h(_pss, _ssi) (*((lws_sspc_handle_t **) \
((uint8_t *)_pss) + _ssi->handle_offset))
/* client pss to sspc userdata */
#define client_pss_to_userdata(_pss) ((void *)_pss)
/* proxy convert pss to ss handle */
#define proxy_pss_to_ss_h(_pss) (*_pss)
/* convert userdata ptr _pss to handle pointer, allowing for any layout in
* userdata */
#define client_pss_to_sspc_h(_pss, _ssi) (*((lws_sspc_handle_t **) \
((uint8_t *)_pss) + _ssi->handle_offset))
/* client pss to sspc userdata */
#define client_pss_to_userdata(_pss) ((void *)_pss)
/* proxy convert pss to ss handle */
#define proxy_pss_to_ss_h(_pss) (*_pss)
int
lws_ss_proxy_deserialize_parse(struct lws_ss_serialization_parser *par,
struct lws_context *context,
struct lws_dsh *dsh, const uint8_t *cp,
size_t len, lws_ss_conn_states_t *state,
void *parconn, lws_ss_handle_t **pss,
lws_ss_info_t *ssi)
{
lws_ss_state_return_t r;
lws_ss_metadata_t *pm;
uint8_t pre[23];
uint32_t flags;
lws_usec_t us;
uint8_t *p;
int n;
while (len--) {
switch (par->ps) {
case RPAR_TYPE:
par->type = *cp++;
par->ps++;
break;
case RPAR_LEN_MSB: /* this is remaining frame length */
par->rem = (uint16_t)((*cp++) << 8);
par->ps++;
break;
case RPAR_LEN_LSB:
par->rem = (uint16_t)(par->rem | *cp++);
switch (par->type) {
/* event loop side */
case LWSSS_SER_TXPRE_TX_PAYLOAD:
if (*state != LPCSPROX_OPERATIONAL)
goto hangup;
par->ps = RPAR_FLAG_B3;
break;
case LWSSS_SER_TXPRE_DESTROYING:
par->ps = RPAR_TYPE;
lwsl_cx_notice(context, "DESTROYING");
goto hangup;
case LWSSS_SER_TXPRE_ONWARD_CONNECT:
if (*state != LPCSPROX_OPERATIONAL)
goto hangup;
par->ps = RPAR_TYPE;
lwsl_cx_notice(context, "ONWARD_CONNECT");
/*
* Shrug it off if we are already connecting or
* connected
*/
if (!proxy_pss_to_ss_h(pss) ||
proxy_pss_to_ss_h(pss)->wsi)
break;
/*
* We're going to try to do the onward connect
*/
if ((proxy_pss_to_ss_h(pss) &&
lws_fi(&proxy_pss_to_ss_h(pss)->fic,
"ssproxy_onward_conn_fail")) ||
_lws_ss_client_connect(proxy_pss_to_ss_h(pss),
0, parconn) ==
LWSSSSRET_DESTROY_ME)
goto hangup;
break;
case LWSSS_SER_TXPRE_STREAMTYPE:
if (*state != LPCSPROX_WAIT_INITIAL_TX)
goto hangup;
if (par->rem < 1 + 4 + 1)
goto hangup;
par->ps = RPAR_INIT_PROVERS;
break;
case LWSSS_SER_TXPRE_METADATA:
if (par->rem < 3)
goto hangup;
par->ctr = 0;
par->ps = RPAR_METADATA_NAMELEN;
break;
case LWSSS_SER_TXPRE_TXCR_UPDATE:
par->ps = RPAR_TXCR0;
par->ctr = 0;
break;
case LWSSS_SER_TXPRE_TIMEOUT_UPDATE:
if (par->rem != 4)
goto hangup;
par->ps = RPAR_TIMEOUT0;
par->ctr = 0;
break;
case LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT:
if (par->rem != 4)
goto hangup;
par->ps = RPAR_PAYLEN0;
par->ctr = 0;
break;
/* client side */
case LWSSS_SER_RXPRE_RX_PAYLOAD:
case LWSSS_SER_RXPRE_CREATE_RESULT:
case LWSSS_SER_RXPRE_CONNSTATE:
case LWSSS_SER_RXPRE_METADATA:
goto hangup;
case LWSSS_SER_RXPRE_TXCR_UPDATE:
par->ctr = 0;
par->ps = RPAR_RX_TXCR_UPDATE;
break;
case LWSSS_SER_RXPRE_PERF:
par->ctr = 0;
if (!par->rem)
goto hangup;
par->ps = RPAR_PERF;
break;
default:
lwsl_cx_notice(context, "bad type 0x%x",
par->type);
goto hangup;
}
break;
case RPAR_FLAG_B3:
case RPAR_FLAG_B2:
case RPAR_FLAG_B1:
case RPAR_FLAG_B0:
par->flags <<= 8;
par->flags |= *cp++;
par->ps++;
if (!par->rem--)
goto hangup;
break;
case RPAR_LATA3:
case RPAR_LATA2:
case RPAR_LATA1:
case RPAR_LATA0:
par->usd_phandling <<= 8;
par->usd_phandling |= *cp++;
par->ps++;
if (!par->rem--)
goto hangup;
break;
case RPAR_LATB7:
case RPAR_LATB6:
case RPAR_LATB5:
case RPAR_LATB4:
case RPAR_LATB3:
case RPAR_LATB2:
case RPAR_LATB1:
case RPAR_LATB0:
par->ust_pwait <<= 8;
par->ust_pwait |= *cp++;
par->ps++;
par->frag1 = 1;
if (!par->rem--)
goto hangup;
if (par->ps == RPAR_RIDESHARE_LEN &&
!(par->flags & LWSSS_FLAG_RIDESHARE))
par->ps = RPAR_PAYLOAD;
if (par->rem)
break;
/* fallthru - handle 0-length payload */
if (!(par->flags & LWSSS_FLAG_RIDESHARE))
goto payload_ff;
goto hangup;
/*
* Inbound rideshare info is provided on the RX packet
* itself
*/
case RPAR_RIDESHARE_LEN:
par->slen = *cp++;
par->ctr = 0;
par->ps++;
if (par->rem-- < par->slen)
goto hangup;
break;
case RPAR_PERF:
n = (int)len + 1;
if (n > par->rem)
n = par->rem;
if (n) {
cp += n;
par->rem = (uint16_t)(par->rem -
(uint16_t)(unsigned int)n);
len = (len + 1) - (unsigned int)n;
}
if (!par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_RIDESHARE:
par->rideshare[par->ctr++] = (char)*cp++;
if (!par->rem--)
goto hangup;
if (par->ctr != par->slen)
break;
par->ps = RPAR_PAYLOAD;
if (par->rem)
break;
/* fallthru - handle 0-length payload */
case RPAR_PAYLOAD:
payload_ff:
n = (int)len + 1;
if (n > par->rem)
n = par->rem;
/*
* We get called with a serialized buffer of a size
* chosen by the client. We can only create dsh entries
* with up to 1380 payload, to guarantee we can emit
* them on the onward connection atomically.
*
* If 1380 isn't enough to cover what was handed to us,
* we'll stop at 1380 and go around again and create
* more dsh entries for the rest, with their own
* headers.
*/
if (n > 1380)
n = 1380;
/*
* Since we're in the business of fragmenting client
* serialized payloads at 1380, we have to deal with
* refragmenting the SOM / EOM flags that covered the
* whole client serialized packet, so they apply to
* each dsh entry we split it into correctly
*/
flags = par->flags & LWSSS_FLAG_RELATED_START;
if (par->frag1)
/*
* Only set the first time we came to this
* state after deserialization of the header
*/
flags |= par->flags &
(LWSSS_FLAG_SOM | LWSSS_FLAG_POLL);
if (par->rem == n)
/*
* We are going to complete the advertised
* payload length from the client on this dsh,
* so give him the EOM type flags if any
*/
flags |= par->flags & (LWSSS_FLAG_EOM |
LWSSS_FLAG_RELATED_END);
par->frag1 = 0;
us = lws_now_usecs();
{
lws_ss_handle_t *hss;
/*
* Proxy - we received some serialized tx from
* the client.
*
* The header for buffering private to the
* proxy is 23 bytes vs 19, so we can hold the
* current time when it was buffered
* additionally
*/
hss = proxy_pss_to_ss_h(pss);
if (hss)
lwsl_ss_info(hss, "C2P RX: len %d", (int)n);
p = pre;
pre[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], (uint16_t)((unsigned int)n + 23 - 3));
lws_ser_wu32be(&p[3], flags);
/* us held at client before written */
lws_ser_wu32be(&p[7], par->usd_phandling);
/* us taken for transit to proxy */
lws_ser_wu32be(&p[11], (uint32_t)(us -
(lws_usec_t)par->ust_pwait));
/* time used later to find proxy hold time */
lws_ser_wu64be(&p[15], (uint64_t)us);
if ((hss &&
lws_fi(&hss->fic, "ssproxy_dsh_c2p_pay_oom")) ||
lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre,
23, cp, (unsigned int)n)) {
lwsl_ss_err(hss, "unable to alloc in dsh 3");
return LWSSSSRET_DISCONNECT_ME;
}
lwsl_notice("%s: dsh c2p %d, p2c %d\n", __func__,
(int)lws_dsh_get_size(dsh, KIND_C_TO_P),
(int)lws_dsh_get_size(dsh, 1));
if (hss)
_lws_ss_request_tx(hss);
}
if (n) {
cp += n;
par->rem = (uint16_t)(par->rem -
(uint16_t)(unsigned int)n);
len = (len + 1) - (unsigned int)n;
/*
* if we didn't consume it all, we'll come
* around again and produce more dsh entries up
* to 1380 each until it is gone
*/
}
if (!par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_RX_TXCR_UPDATE:
goto hangup;
case RPAR_INIT_PROVERS:
/* Protocol version byte for this connection */
par->protocol_version = *cp++;
/*
* So we have to know what versions of the serialization
* protocol we can support at the proxy side, and
* reject anythng we don't know how to deal with
* noisily in the logs.
*/
if (par->protocol_version != 1) {
lwsl_err("%s: Rejecting client with "
"unsupported SSv%d protocol\n",
__func__, par->protocol_version);
goto hangup;
}
if (!--par->rem)
goto hangup;
par->ctr = 0;
par->ps = RPAR_INIT_PID;
break;
case RPAR_INIT_PID:
if (!--par->rem)
goto hangup;
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4)
break;
par->client_pid = (uint32_t)par->temp32;
par->ctr = 0;
par->ps = RPAR_INITTXC0;
break;
case RPAR_INITTXC0:
if (!--par->rem)
goto hangup;
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4)
break;
par->txcr_out = par->temp32;
par->ctr = 0;
par->ps = RPAR_STREAMTYPE;
break;
/*
* These are the client adjusting our / the remote peer ability
* to send back to him. He's sending a signed u32 BE
*/
case RPAR_TXCR0:
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
/*
* We're the proxy, being told by the client
* that it wants to allow more tx from the peer
* on the onward connection towards it.
*/
#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)
if (proxy_pss_to_ss_h(pss) &&
proxy_pss_to_ss_h(pss)->wsi) {
lws_wsi_tx_credit(
proxy_pss_to_ss_h(pss)->wsi,
LWSTXCR_PEER_TO_US,
par->temp32);
lwsl_notice("%s: proxy RX_PEER_TXCR: +%d (est %d)\n",
__func__, par->temp32,
proxy_pss_to_ss_h(pss)->wsi->
txc.peer_tx_cr_est);
_lws_ss_request_tx(proxy_pss_to_ss_h(pss));
} else
#endif
lwsl_info("%s: dropping TXCR\n", __func__);
par->ps = RPAR_TYPE;
break;
case RPAR_TIMEOUT0:
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
/*
* Proxy...
*
* *pss may have gone away asynchronously inbetweentimes
*/
if (proxy_pss_to_ss_h(pss)) {
if ((unsigned int)par->temp32 == 0xffffffff) {
lwsl_notice("%s: cancel ss timeout\n",
__func__);
lws_ss_cancel_timeout(
proxy_pss_to_ss_h(pss));
} else {
if (!par->temp32)
par->temp32 = (int)
proxy_pss_to_ss_h(pss)->
policy->timeout_ms;
lwsl_notice("%s: set ss timeout for +%ums\n",
__func__, par->temp32);
lws_ss_start_timeout(
proxy_pss_to_ss_h(pss),
(unsigned int)par->temp32);
}
}
par->ps = RPAR_TYPE;
break;
case RPAR_PAYLEN0:
/*
* It's the length from lws_ss_request_tx_len() being
* passed up to the proxy
*/
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
lwsl_notice("%s: set payload len %u\n", __func__,
par->temp32);
par->ps = RPAR_TYPE;
if (proxy_pss_to_ss_h(pss)) {
r = lws_ss_request_tx_len(proxy_pss_to_ss_h(pss),
(unsigned long)par->temp32);
if (r == LWSSSSRET_DESTROY_ME)
goto hangup;
}
break;
case RPAR_METADATA_NAMELEN:
/* both client and proxy */
if (!--par->rem)
goto hangup;
par->slen = *cp++;
if (par->slen >= sizeof(par->metadata_name) - 1)
goto hangup;
par->ctr = 0;
par->ps++;
break;
case RPAR_METADATA_NAME:
/* both client and proxy */
if (!--par->rem)
goto hangup;
par->metadata_name[par->ctr++] = (char)*cp++;
if (par->ctr != par->slen)
break;
par->metadata_name[par->ctr] = '\0';
par->ps = RPAR_METADATA_VALUE;
/* proxy side is receiving it */
if (!proxy_pss_to_ss_h(pss))
goto hangup;
if (!proxy_pss_to_ss_h(pss)->policy) {
lwsl_err("%s: null policy\n", __func__);
goto hangup;
}
/*
* This is the policy's metadata list for the given
* name
*/
pm = lws_ss_policy_metadata(
proxy_pss_to_ss_h(pss)->policy,
par->metadata_name);
if (!pm) {
lwsl_err("%s: metadata %s not in proxy policy\n",
__func__, par->metadata_name);
goto hangup;
}
par->ssmd = lws_ss_get_handle_metadata(
proxy_pss_to_ss_h(pss),
par->metadata_name);
if (par->ssmd) {
if (par->ssmd->value_on_lws_heap)
lws_free_set_NULL(par->ssmd->value__may_own_heap);
par->ssmd->value_on_lws_heap = 0;
if (proxy_pss_to_ss_h(pss) &&
lws_fi(&proxy_pss_to_ss_h(pss)->fic, "ssproxy_rx_metadata_oom"))
par->ssmd->value__may_own_heap = NULL;
else
par->ssmd->value__may_own_heap =
lws_malloc((unsigned int)par->rem + 1, "metadata");
if (!par->ssmd->value__may_own_heap) {
lwsl_err("%s: OOM mdv\n", __func__);
goto hangup;
}
par->ssmd->length = par->rem;
((uint8_t *)par->ssmd->value__may_own_heap)[par->rem] = '\0';
/* mark it as needing cleanup */
par->ssmd->value_on_lws_heap = 1;
}
par->ctr = 0;
break;
case RPAR_METADATA_VALUE:
/* both client and proxy */
if (!par->ssmd) {
/* we don't recognize the name */
cp++;
if (--par->rem)
break;
par->ps = RPAR_TYPE;
break;
}
((uint8_t *)(par->ssmd->value__may_own_heap))[par->ctr++] = *cp++;
if (--par->rem)
break;
/* we think we got all the value */
lwsl_ss_info(proxy_pss_to_ss_h(pss),
"RPAR_METADATA_VALUE for %s (len %d)",
par->ssmd->name,
(int)par->ssmd->length);
lwsl_hexdump_ss_info(proxy_pss_to_ss_h(pss),
par->ssmd->value__may_own_heap,
par->ssmd->length);
par->ps = RPAR_TYPE;
break;
case RPAR_STREAMTYPE:
/* only the proxy can get these */
if (par->ctr == sizeof(par->streamtype) - 1)
goto hangup;
/*
* We can only expect to get this if we ourselves are
* in the state that we're waiting for it. If it comes
* later it's a protocol error.
*/
if (*state != LPCSPROX_WAIT_INITIAL_TX)
goto hangup;
/*
* We're the proxy, creating an SS on behalf of a
* client
*/
par->streamtype[par->ctr++] = (char)*cp++;
if (--par->rem)
break;
par->ps = RPAR_TYPE;
par->streamtype[par->ctr] = '\0';
lwsl_info("%s: proxy ss '%s', sssv%d, txcr %d\n",
__func__, par->streamtype,
par->protocol_version, par->txcr_out);
ssi->streamtype = par->streamtype;
if (par->txcr_out) // !!!
ssi->manual_initial_tx_credit = par->txcr_out;
/*
* Even for a synthetic SS proxing action like _lws_smd,
* we create an actual SS in the proxy representing the
* connection
*/
ssi->flags |= LWSSSINFLAGS_PROXIED;
ssi->sss_protocol_version = par->protocol_version;
ssi->client_pid = par->client_pid;
if (lws_ss_create(context, 0, ssi, parconn, pss,
NULL, NULL)) {
/*
* We're unable to create the onward secure
* stream he asked for... schedule a chance to
* inform him
*/
lwsl_err("%s: create '%s' fail\n", __func__,
par->streamtype);
*state = LPCSPROX_REPORTING_FAIL;
break;
} else {
lwsl_debug("%s: create '%s' OK\n",
__func__, par->streamtype);
*state = LPCSPROX_REPORTING_OK;
}
if (*pss) {
(*pss)->being_serialized = 1;
#if defined(LWS_WITH_SYS_SMD)
if ((*pss)->policy != &pol_smd)
/*
* In SMD case we overloaded the
* initial credit to be the class mask
*/
#endif
{
lwsl_info("%s: Created SS initial credit %d\n",
__func__, par->txcr_out);
(*pss)->info.manual_initial_tx_credit = par->txcr_out;
}
}
/* parent needs to schedule write on client conn */
break;
/* clientside states */
case RPAR_RESULT_CREATION:
case RPAR_RESULT_CREATION_RIDESHARE:
case RPAR_RESULT_CREATION_DSH:
case RPAR_STATEINDEX:
case RPAR_ORD3:
case RPAR_ORD2:
case RPAR_ORD1:
case RPAR_ORD0:
goto hangup;
default:
goto hangup;
}
}
return LWSSSSRET_OK;
hangup:
lwsl_cx_notice(context, "hangup");
return LWSSSSRET_DISCONNECT_ME;
}

View File

@ -0,0 +1,291 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* Proxy side of Client <-> Proxy wsi connection, usually on Unix Domain Socket
*/
#include <private-lib-core.h>
struct raw_pss {
struct lws_sss_proxy_conn *conn;
};
static int
lws_sss_proxy_transport_wsi_cb(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct raw_pss *pss = (struct raw_pss *)user;
struct lws_sss_proxy_conn *conn = NULL;
if (pss)
conn = pss->conn;
switch (reason) {
/* callbacks related to raw socket descriptor "accepted side" */
case LWS_CALLBACK_RAW_ADOPT:
lwsl_user("LWS_CALLBACK_RAW_ADOPT %s\n", lws_txp_inside_proxy.name);
if (lws_txp_inside_proxy.event_new_conn(
wsi->a.context,
&lws_txp_inside_proxy,
(lws_transport_priv_t)conn,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
&wsi->fic,
#endif
&pss->conn,
(lws_transport_priv_t)wsi)) {
lwsl_err("%s: hangup from new_conn\n", __func__);
return -1;
}
/* dsh is allocated when the onward ss is done */
wsi->bound_ss_proxy_conn = 1; /* opaque is conn */
lws_set_opaque_user_data(wsi, pss->conn);
pss->conn->state = LPCSPROX_WAIT_INITIAL_TX;
/*
* Client is expected to follow the unix domain socket
* acceptance up rapidly with an initial tx containing the
* streamtype name. We can't create the stream until then.
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
lwsl_user("%s: ADOPT: accepted\n", __func__);
break;
case LWS_CALLBACK_RAW_CLOSE:
lwsl_info("LWS_CALLBACK_RAW_CLOSE:\n");
if (!conn)
break;
/*
* the client unix domain socket connection (wsi / conn->wsi)
* has closed... eg, client has exited or otherwise has
* definitively finished with the proxying and onward connection
*
* But right now, the SS and possibly the SS onward wsi are
* still live...
*/
assert(conn->txp_path.priv_onw == wsi);
// if (conn->ss)
// conn->ss = NULL;
/* sever relationship with conn */
lws_set_opaque_user_data(wsi, NULL);
lws_txp_inside_proxy.event_close_conn(conn);
/* pss is about to be deleted */
if (pss)
pss->conn = NULL;
lwsl_notice("%s: close finished ok\n", __func__);
break;
case LWS_CALLBACK_RAW_RX:
/*
* ie, the proxy is receiving something from a client
*/
lwsl_info("%s: RX: rx %d\n", __func__, (int)len);
if (!conn) {
lwsl_err("%s: rx with conn %p / priv_in %p\n", __func__,
conn, conn->txp_path.priv_in);
return -1;
}
if (conn->txp_path.ops_in->proxy_read(
conn, in, len))
return -1;
break;
case LWS_CALLBACK_RAW_WRITEABLE:
lwsl_debug("%s: %s: LWS_CALLBACK_RAW_WRITEABLE, state 0x%x\n",
__func__, lws_wsi_tag(wsi), lwsi_state(wsi));
/*
* We can transmit something back to the client from the dsh
* of stuff we received on its behalf from the ss
*/
if (!conn)
break;
assert_is_conn(conn);
if (lws_txp_inside_proxy.event_proxy_can_write(conn
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
, &wsi->fic
#endif
))
return -1;
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static const struct lws_protocols protocols[] = {
{
"ssproxy-protocol",
lws_sss_proxy_transport_wsi_cb,
sizeof(struct raw_pss),
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
static void
lws_sss_proxy_wsi_onward_bind(lws_transport_priv_t priv, lws_ss_handle_t *h)
{
struct lws *wsi = (struct lws *)priv;
__lws_lc_tag_append(&wsi->lc, lws_ss_tag(h));
}
static void
lws_sss_proxy_wsi_req_write(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (wsi)
lws_callback_on_writable(wsi);
}
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
static const lws_fi_ctx_t *
lws_sss_proxy_wsi_fault_context(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (!wsi)
return NULL;
return &wsi->fic;
}
#endif
static int
lws_sss_proxy_wsi_write(lws_transport_priv_t priv, uint8_t *buf, size_t *len)
{
struct lws *wsi = (struct lws *)priv;
if (lws_write(wsi, buf, *len, LWS_WRITE_RAW) != (ssize_t)*len) {
lwsl_wsi_notice(wsi, "failed");
return -1;
}
/* leave *len alone */
return 0;
}
int
lws_sss_proxy_wsi_init_proxy_server(struct lws_context *context,
const struct lws_transport_proxy_ops *txp_ops_inward,
lws_transport_priv_t txp_priv_inward,
lws_txp_path_proxy_t *txp_ppath,
const void *txp_info,
const char *bind, int port)
{
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.vhost_name = "ssproxy";
info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG |
LWS_SERVER_OPTION_SS_PROXY;
info.port = port;
if (!port) {
if (!bind)
#if defined(__linux__)
bind = "@proxy.ss.lws";
#else
bind = "/tmp/proxy.ss.lws";
#endif
info.options |= LWS_SERVER_OPTION_UNIX_SOCK;
}
info.iface = bind;
#if defined(__linux__)
info.unix_socket_perms = "root:root";
#else
#endif
info.listen_accept_role = "raw-skt";
info.listen_accept_protocol = "ssproxy-protocol";
info.protocols = protocols;
if (!lws_create_vhost(context, &info)) {
lwsl_err("%s: Failed to create ss proxy vhost\n", __func__);
return 1;
}
return 0;
}
static void
lws_sss_proxy_wsi_client_up(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
lws_set_timeout(wsi, 0, 0);
}
static int
lws_sss_proxy_check_write_more(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (lws_send_pipe_choked(wsi))
return 0;
return 1;
}
const lws_transport_proxy_ops_t txp_ops_ssproxy_wsi = {
.name = "txp_proxy_wsi",
.init_proxy_server = lws_sss_proxy_wsi_init_proxy_server,
.proxy_req_write = lws_sss_proxy_wsi_req_write,
.proxy_write = lws_sss_proxy_wsi_write,
.event_onward_bind = lws_sss_proxy_wsi_onward_bind,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
.fault_context = lws_sss_proxy_wsi_fault_context,
#endif
.event_client_up = lws_sss_proxy_wsi_client_up,
.proxy_check_write_more = lws_sss_proxy_check_write_more,
};

View File

@ -0,0 +1,423 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
/*
* Proxy has received a new connection from a client
*/
static lws_ss_state_return_t
lws_ssproxy_txp_new_conn(struct lws_context *cx,
const struct lws_transport_proxy_ops *txp_ops_inward,
lws_transport_priv_t txp_priv_inward,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
const lws_fi_ctx_t *fic,
#endif
struct lws_sss_proxy_conn **conn,
lws_transport_priv_t txp_priv)
{
if (
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
fic &&
#endif
lws_fi(fic, "ssproxy_client_adopt_oom"))
*conn = NULL;
else
*conn = lws_zalloc(sizeof(**conn), __func__);
if (!*conn)
return 1;
/* dsh is allocated when the onward ss is done */
#if defined(_DEBUG)
(*conn)->magic = LWS_PROXY_CONN_MAGIC;
#endif
(*conn)->state = LPCSPROX_WAIT_INITIAL_TX;
(*conn)->txp_path = cx->txp_ppath;
(*conn)->txp_path.priv_onw = txp_priv;
(*conn)->txp_path.ops_in = txp_ops_inward;
(*conn)->txp_path.priv_in = txp_priv_inward;
(*conn)->cx = cx;
return LWSSSSRET_OK;
}
/*
* Proxy has received a close indication from a client
*/
static lws_ss_state_return_t
lws_ssproxy_txp_close_conn(struct lws_sss_proxy_conn *conn)
{
lws_transport_priv_t epriv;
conn->txp_path.priv_onw = NULL;
epriv = conn->txp_path.priv_onw;
/*
* If there's an outgoing, proxied SS conn on our behalf, we
* have to destroy it
*
* Wsi related stuff in here is talking about the onward wsi / ss
* connection, it doesn't introduce any dependency on the proxy -
* client link transport
*/
if (conn->ss) {
struct lws *cw;
cw = conn->ss->wsi;
/*
* conn->ss is the onward connection SS
*/
lwsl_info("%s: destroying %s, wsi %s\n",
__func__, lws_ss_tag(conn->ss),
lws_wsi_tag(conn->ss->wsi));
/* sever conn relationship with onward ss about to be deleted */
conn->ss->wsi = NULL;
if (cw && epriv != (lws_transport_priv_t)cw) {
/* disconnect onward SS from its wsi */
lws_set_opaque_user_data(cw, NULL);
/*
* The wsi doing the onward connection can no
* longer relate to the conn... otherwise when
* he gets callbacks he wants to bind to
* the ss we are about to delete
*/
lws_wsi_close(cw, LWS_TO_KILL_ASYNC);
}
/* destroy the onward ss (setting conn->ss NULL) */
lws_ss_destroy(&conn->ss);
/*
* Conn may have gone, at ss destroy handler in
* ssi.state for proxied ss
*/
return LWSSSSRET_OK;
}
if (conn->state == LPCSPROX_DESTROYED || !conn->ss) {
/*
* There's no onward secure stream and our client
* connection is closing. Destroy the conn.
*/
lws_dsh_destroy(&conn->dsh);
lws_free(conn);
} else
lwsl_debug("%s: CLOSE; %s\n", __func__, lws_ss_tag(conn->ss));
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
lws_ssproxy_txp_rx(lws_transport_priv_t txp_priv, const uint8_t *in, size_t len)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)txp_priv;
lws_ss_state_return_t r;
lws_ss_info_t ssi;
assert_is_conn(conn);
// lwsl_hexdump_info(in, len);
if (conn->state == LPCSPROX_WAIT_INITIAL_TX) {
memset(&ssi, 0, sizeof(ssi));
ssi.user_alloc = sizeof(ss_proxy_t);
ssi.handle_offset = offsetof(ss_proxy_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_proxy_t, conn);
ssi.rx = lws_sss_proxy_onward_rx;
ssi.tx = lws_sss_proxy_onward_tx;
}
ssi.state = lws_sss_proxy_onward_state;
ssi.flags = 0;
// coverity[uninit_use_in_call]
r = lws_ss_proxy_deserialize_parse(&conn->parser, conn->cx, conn->dsh,
in, len, &conn->state, conn,
&conn->ss, &ssi);
switch (r) {
default:
break;
case LWSSSSRET_DISCONNECT_ME:
return r;
case LWSSSSRET_DESTROY_ME:
if (conn->ss)
lws_ss_destroy(&conn->ss);
return r;
}
if ((conn->state == LPCSPROX_REPORTING_FAIL ||
conn->state == LPCSPROX_REPORTING_OK) &&
conn->txp_path.priv_onw)
conn->txp_path.ops_onw->proxy_req_write(conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
lws_ssproxy_txp_proxy_can_write(lws_transport_priv_t priv
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
, const lws_fi_ctx_t *fic
#endif
)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)priv;
const lws_ss_policy_t *rsp;
lws_ss_metadata_t *md;
const uint8_t *cp;
char _s[1580 + LWS_PRE], *s = _s + LWS_PRE;
size_t si, csi;
uint8_t *p;
char pay;
int n;
assert_is_conn(conn);
n = 0;
pay = 0;
*(s + 3) = 0;
cp = (const uint8_t *)s;
switch (conn->state) {
case LPCSPROX_REPORTING_FAIL:
*(s + 3) = 1;
/* fallthru */
case LPCSPROX_REPORTING_OK:
*s = LWSSS_SER_RXPRE_CREATE_RESULT;
*(s + 1) = 0;
*(s + 2) = 1;
n = 8;
lws_ser_wu32be((uint8_t *)s + 4, conn->ss &&
conn->ss->policy ?
conn->ss->policy->client_buflen : 0);
/*
* If there's rideshare sequencing, it's added after the
* first 4 bytes or the create result, comma-separated
*/
if (conn->ss) {
rsp = conn->ss->policy;
while (rsp) {
if (n != 4 && n < (int)sizeof(_s) - LWS_PRE - 2)
*(s + (n++)) = ',';
n += lws_snprintf(s + n, sizeof(_s) - LWS_PRE - (unsigned int)n,
"%s", rsp->streamtype);
rsp = lws_ss_policy_lookup(conn->cx,
rsp->rideshare_streamtype);
}
}
*(s + 2) = (char)(n - 3);
conn->state = LPCSPROX_OPERATIONAL;
conn->txp_path.ops_onw->event_client_up(conn->txp_path.priv_onw);
break;
case LPCSPROX_OPERATIONAL:
/*
* returning [onward -> ] proxy]-> client
* rx metadata has priority 1
*/
md = conn->ss->metadata;
while (md) {
// lwsl_notice("%s: check %s: %d\n", __func__,
// md->name, md->pending_onward);
if (md->pending_onward) {
size_t naml = strlen(md->name);
// lwsl_notice("%s: proxy issuing rxmd\n", __func__);
if (4 + naml + md->length > sizeof(_s) - LWS_PRE) {
lwsl_err("%s: rxmdata too big\n",
__func__);
goto hangup;
}
md->pending_onward = 0;
p = (uint8_t *)s;
p[0] = LWSSS_SER_RXPRE_METADATA;
lws_ser_wu16be(&p[1], (uint16_t)(1 + naml +
md->length));
p[3] = (uint8_t)naml;
memcpy(&p[4], md->name, naml);
p += 4 + naml;
memcpy(p, md->value__may_own_heap,
md->length);
p += md->length;
n = lws_ptr_diff(p, cp);
goto do_write_nz;
}
md = md->next;
}
/*
* If we have performance data, render it in JSON
* and send that in LWSSS_SER_RXPRE_PERF has
* priority 2
*/
#if defined(LWS_WITH_CONMON)
if (conn->ss->conmon_json) {
unsigned int xlen = conn->ss->conmon_len;
if (xlen > sizeof(s) - 3)
xlen = sizeof(s) - 3;
cp = (uint8_t *)s;
p = (uint8_t *)s;
p[0] = LWSSS_SER_RXPRE_PERF;
lws_ser_wu16be(&p[1], (uint16_t)xlen);
memcpy(&p[3], conn->ss->conmon_json, xlen);
lws_free_set_NULL(conn->ss->conmon_json);
n = (int)(xlen + 3);
pay = 0;
goto do_write_nz;
}
#endif
/*
* if no fresh rx metadata, just pass through incoming
* dsh
*/
if (lws_dsh_get_head(conn->dsh, KIND_SS_TO_P, (void **)&p, &si))
break;
cp = p;
pay = 1;
n = (int)si;
break;
default:
break;
}
do_write_nz:
if (!n)
return LWSSSSRET_OK;
if (
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
fic &&
#endif
lws_fi(fic, "ssproxy_client_write_fail"))
n = -1;
else {
si = csi = (size_t)n;
n = conn->txp_path.ops_onw->proxy_write(conn->txp_path.priv_onw,
(uint8_t *)cp, &csi);
}
if (n < 0) {
lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
goto hangup;
}
switch (conn->state) {
case LPCSPROX_REPORTING_FAIL:
goto hangup;
case LPCSPROX_OPERATIONAL:
if (!conn)
break;
if (pay) {
if (si == csi)
lws_dsh_free((void **)&p);
else
lws_dsh_consume(conn->dsh, KIND_SS_TO_P, csi);
/*
* Did we go below the rx flow threshold for
* this dsh?
*/
if (conn->onward_in_flow_control &&
conn->ss->policy->proxy_buflen_rxflow_on_above &&
conn->ss->wsi &&
lws_dsh_get_size(conn->dsh, KIND_SS_TO_P) <
conn->ss->policy->proxy_buflen_rxflow_off_below) {
lwsl_user("%s: %s: rxflow enabling rx (%lu / %lu, lwm %lu)\n", __func__,
lws_wsi_tag(conn->ss->wsi),
(unsigned long)lws_dsh_get_size(conn->dsh, KIND_SS_TO_P),
(unsigned long)conn->ss->policy->proxy_buflen,
(unsigned long)conn->ss->policy->proxy_buflen_rxflow_off_below);
/*
* Resume receiving taking in rx once
* below the low threshold
*/
lws_rx_flow_control(conn->ss->wsi,
LWS_RXFLOW_ALLOW);
conn->onward_in_flow_control = 0;
}
}
if (!lws_dsh_get_head(conn->dsh, KIND_SS_TO_P,
(void **)&p, &si)) {
if (conn && conn->txp_path.ops_onw->proxy_check_write_more &&
conn->txp_path.ops_onw->proxy_check_write_more(
conn->txp_path.priv_onw)) {
cp = p;
pay = 1;
n = (int)si;
goto do_write_nz;
}
conn->txp_path.ops_onw->proxy_req_write(
conn->txp_path.priv_onw);
}
default:
break;
}
return LWSSSSRET_OK;
hangup:
return LWSSSSRET_DISCONNECT_ME;
}
const lws_transport_proxy_ops_t lws_txp_inside_proxy = {
.name = "txp_inside_proxy",
.event_new_conn = lws_ssproxy_txp_new_conn,
.proxy_read = lws_ssproxy_txp_rx,
.event_close_conn = lws_ssproxy_txp_close_conn,
.event_proxy_can_write = lws_ssproxy_txp_proxy_can_write,
};

View File

@ -0,0 +1,492 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
*
* When the user code is in a different process, a non-tls unix domain socket
* proxy is used to asynchronusly transfer buffers in each direction via the
* network stack, without explicit IPC
*
* user_process{ [user code] | shim | socket-}------ lws_process{ lws }
*
* Lws exposes a listening unix domain socket in this case, the user processes
* connect to it and pass just info.streamtype in an initial tx packet. All
* packets are prepended by a 1-byte type field when used in this mode. See
* lws-secure-streams.h for documentation and definitions.
*
* Proxying in either direction can face the situation it cannot send the onward
* packet immediately and is subject to separating the write request from the
* write action. To make the best use of memory, a single preallocated buffer
* stashes pending packets in all four directions (c->p, p->c, p->ss, ss->p).
* This allows it to adapt to different traffic patterns without wasted areas
* dedicated to traffic that isn't coming in a particular application.
*
* A shim is provided to monitor the process' unix domain socket and regenerate
* the secure sockets api there with callbacks happening in the process thread
* context.
*
* This file implements the listening unix domain socket proxy... this code is
* only going to run on a Linux-class device with its implications about memory
* availability.
*/
#include <private-lib-core.h>
/*
* Proxy - onward secure-stream handler
*/
void
lws_proxy_clean_conn_ss(struct lws *wsi)
{
#if 0
lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
struct lws_sss_proxy_conn *conn = h->conn_if_sspc_onw;
if (!wsi)
return;
if (conn && conn->ss)
conn->ss->wsi = NULL;
#endif
}
void
ss_proxy_onward_link_proxy_req_writeable(lws_ss_handle_t *h_onward)
{
ss_proxy_t *m = (ss_proxy_t *)&h_onward[1];
if (m->conn->txp_path.priv_onw)
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
}
int
__lws_ss_proxy_bind_ss_to_conn_wsi(void *parconn, size_t dsh_size)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)parconn;
struct lws_context_per_thread *pt;
if (!conn || !conn->txp_path.priv_onw || !conn->ss)
return -1;
pt = &conn->ss->context->pt[(int)conn->ss->tsi];
if (lws_fi(&conn->ss->fic, "ssproxy_dsh_create_oom"))
return -1;
conn->dsh = lws_dsh_create(&pt->ss_dsh_owner, dsh_size,
(int)(conn->txp_path.ops_onw->flags | 2));
if (!conn->dsh)
return -1;
conn->dsh->splitat = 1300;
conn->txp_path.ops_onw->event_onward_bind(conn->txp_path.priv_onw,
conn->ss);
return 0;
}
/*
* event loop received something and is queueing it for the foreign side of
* the dsh to consume later as serialized rx
*/
static int
lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf,
size_t len, int flags, const char *rsp)
{
lws_usec_t us = lws_now_usecs();
uint8_t pre[128];
int est = 19, l = 0;
if (flags & LWSSS_FLAG_RIDESHARE) {
/*
* We should have the rideshare name if we have been told it's
* on a non-default rideshare
*/
assert(rsp);
if (!rsp)
return 1;
l = (int)strlen(rsp);
est += 1 + l;
} else
assert(!rsp);
// lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
// lwsl_hexdump_info(buf, len);
pre[0] = LWSSS_SER_RXPRE_RX_PAYLOAD;
lws_ser_wu16be(&pre[1], (uint16_t)(len + (size_t)est - 3));
lws_ser_wu32be(&pre[3], (uint32_t)flags);
lws_ser_wu32be(&pre[7], 0); /* write will compute latency here... */
lws_ser_wu64be(&pre[11], (uint64_t)us); /* ... and set this to the write time */
/*
* If we are on a non-default rideshare, append the non-default name to
* the headers of the payload part, 1-byte length first
*/
if (flags & LWSSS_FLAG_RIDESHARE) {
pre[19] = (uint8_t)l;
memcpy(&pre[20], rsp, (unsigned int)l);
}
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)est, buf, len)) {
#if defined(_DEBUG)
lws_dsh_describe(dsh, __func__);
#endif
lwsl_err("%s: unable to alloc in dsh 1\n", __func__);
return 1;
}
lwsl_notice("%s: dsh c2p %d, p2c %d\n", __func__,
(int)lws_dsh_get_size(dsh, KIND_C_TO_P),
(int)lws_dsh_get_size(dsh, KIND_SS_TO_P));
return 0;
}
/* Onward secure streams payload interface */
lws_ss_state_return_t
lws_sss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
const char *rsp = NULL;
int n;
// lwsl_notice("%s: len %d\n", __func__, (int)len);
/*
* The onward secure stream connection has received something.
*/
if (m->ss->rideshare != m->ss->policy && m->ss->rideshare) {
rsp = m->ss->rideshare->streamtype;
flags |= LWSSS_FLAG_RIDESHARE;
}
/*
* Apply SSS framing around this chunk of RX and stash it in the dsh
* in ss -> proxy [ -> client] direction. This can fail...
*/
n = 1;
if (m->conn->dsh && !lws_fi(&m->ss->fic, "ssproxy_dsh_rx_queue_oom"))
n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len,
flags, rsp);
if (n) {
if (m->conn->dsh) {
#if defined(_DEBUG)
lws_dsh_describe(m->conn->dsh, __func__);
#endif
/*
* We couldn't buffer this rx, eg due to OOM, let's
* escalate it to be a "loss of connection", which it
* basically is... as part of that, drop the dshes.
*
* This just affects the one stream that owns the
* dsh, caller should enter stream close flow and not
* send any further payload.
*/
lwsl_warn("%s: dropping SS dsh due to OOM\n", __func__);
lws_dsh_empty(m->conn->dsh);
}
return LWSSSSRET_DISCONNECT_ME;
}
/*
* Manage rx flow on the SS (onward) side according to our situation
* in the dsh holding proxy->client serialized forwarding rx
*/
if (!m->conn->onward_in_flow_control && m->ss->wsi &&
m->ss->policy->proxy_buflen_rxflow_on_above &&
lws_dsh_get_size(m->conn->dsh, KIND_SS_TO_P) >=
m->ss->policy->proxy_buflen_rxflow_on_above) {
lwsl_ss_user(m->ss, "rxflow disabling rx (%lu / %lu, hwm %lu)",
(unsigned long)lws_dsh_get_size(m->conn->dsh,
KIND_SS_TO_P),
(unsigned long)m->ss->policy->proxy_buflen,
(unsigned long)m->ss->policy->proxy_buflen_rxflow_on_above);
/*
* stop taking in rx once the onward wsi rx is above the
* high water mark
*/
lws_rx_flow_control(m->ss->wsi, 0);
m->conn->onward_in_flow_control = 1;
}
if (m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* we are transmitting buffered payload originally from the client on to the ss
*/
lws_ss_state_return_t
lws_sss_proxy_onward_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
void *p;
size_t si;
if (!m->conn->ss || m->conn->state != LPCSPROX_OPERATIONAL) {
lwsl_notice("%s: ss not ready\n", __func__);
*len = 0;
return LWSSSSRET_TX_DONT_SEND;
}
/*
* The onward secure stream says that we could send something to it
* (by putting it in buf, and setting *len and *flags)... dredge the
* next thing out of the dsh
*/
if (lws_ss_deserialize_tx_payload(m->conn->dsh, m->ss->wsi,
ord, buf, len, flags))
return LWSSSSRET_TX_DONT_SEND;
/* ... there's more we want to send? */
if (!lws_dsh_get_head(m->conn->dsh, KIND_C_TO_P, (void **)&p, &si))
_lws_ss_request_tx(m->conn->ss);
if (!*len && !*flags)
/* we don't actually want to send anything */
return LWSSSSRET_TX_DONT_SEND;
lwsl_info("%s: onward tx %d fl 0x%x\n", __func__, (int)*len, *flags);
return LWSSSSRET_OK;
}
/*
* event loop side is issuing state, serialize and put it in the dbuf for
* the foreign side to consume later
*/
static int
lws_ss_serialize_state(struct lws_sss_proxy_conn *conn, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
const lws_fi_ctx_t *fic = conn->txp_path.ops_onw->fault_context(
conn->txp_path.priv_onw);
#endif
struct lws_dsh *dsh = conn->dsh;
uint8_t pre[12];
int n = 4;
if (state == LWSSSCS_EVENT_WAIT_CANCELLED)
return 0;
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state),
(unsigned int)ack);
if (!dsh) {
/* he can't store anything further on the link */
lwsl_notice("%s: dsh for conn was destroyed\n", __func__);
return 0;
}
pre[0] = LWSSS_SER_RXPRE_CONNSTATE;
pre[1] = 0;
if (state > 255) {
pre[2] = 8;
lws_ser_wu32be(&pre[3], state);
n = 7;
} else {
pre[2] = 5;
pre[3] = (uint8_t)state;
}
lws_ser_wu32be(&pre[n], ack);
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)n + 4, NULL, 0)
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
|| (fic && lws_fi(fic, "sspc_dsh_ss2p_oom"))
#endif
) {
lwsl_err("%s: unable to alloc in dsh 2\n", __func__);
return 1;
}
return 0;
}
lws_ss_state_return_t
lws_sss_proxy_onward_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
size_t dsh_size;
switch (state) {
case LWSSSCS_CREATING:
/*
* conn is private to -process.c, call thru to a) adjust
* the accepted incoming proxy link wsi tag name to be
* appended with the onward ss tag information now we
* have it, and b) allocate the dsh buffer now we
* can find out the policy about it for the streamtype.
*/
dsh_size = m->ss->policy->proxy_buflen ?
m->ss->policy->proxy_buflen : 32768;
lwsl_notice("%s: %s: initializing dsh max len %lu\n",
__func__, lws_ss_tag(m->ss),
(unsigned long)dsh_size);
/* this includes ssproxy_dsh_create_oom fault generation */
if (__lws_ss_proxy_bind_ss_to_conn_wsi(m->conn, dsh_size)) {
/* failed to allocate the dsh */
lwsl_notice("%s: dsh init failed\n", __func__);
return LWSSSSRET_DESTROY_ME;
}
break;
case LWSSSCS_DESTROYING:
if (!m->conn)
break;
if (!m->conn->txp_path.priv_onw) {
/*
* Our onward secure stream is closing and our client
* connection has already gone away... destroy the conn.
*/
lwsl_notice("%s: Destroying conn\n", __func__);
lws_dsh_empty(m->conn->dsh);
if (!m->conn->ss) {
lws_dsh_destroy(&m->conn->dsh);
free(m->conn);
m->conn = NULL;
}
return 0;
} else
lwsl_info("%s: ss DESTROYING, wsi up\n", __func__);
break;
default:
break;
}
if (!m->conn) {
lwsl_warn("%s: dropping state due to conn not up\n", __func__);
return LWSSSSRET_OK;
}
if (lws_ss_serialize_state(m->conn, state, ack))
/*
* Failed to alloc state packet that we want to send in dsh,
* we will lose coherence and have to disconnect the link
*/
return LWSSSSRET_DISCONNECT_ME;
if (state != LWSSSCS_DESTROYING &&
m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* event loop side was told about remote peer tx credit window update, serialize
* and put it in the dbuf for the foreign side to consume later
*/
static int
lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr)
{
uint8_t pre[7];
lwsl_info("%s: %d\n", __func__, txcr);
pre[0] = LWSSS_SER_RXPRE_TXCR_UPDATE;
pre[1] = 0;
pre[2] = 4;
lws_ser_wu32be(&pre[3], (uint32_t)txcr);
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 7, NULL, 0)) {
lwsl_err("%s: unable to alloc in dsh 2\n", __func__);
return 1;
}
return 0;
}
void
ss_proxy_onward_txcr(void *userobj, int bump)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
if (!m->conn)
return;
lws_ss_serialize_txcr(m->conn->dsh, bump);
if (m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
}
/*
* called from create_context()
*/
int
lws_ss_proxy_create(struct lws_context *cx, const char *bind, int port)
{
assert(cx->txp_ppath.ops_onw);
return cx->txp_ppath.ops_onw->init_proxy_server(cx,
&lws_txp_inside_proxy,
NULL,
&cx->txp_ppath,
cx->txp_ssproxy_info,
bind, port);
}
lws_ss_state_return_t
lws_ss_proxy_destroy(struct lws_context *cx)
{
if (!cx->txp_ppath.ops_onw)
return 0;
if (!cx->txp_ppath.ops_onw->destroy_proxy_server)
return 0;
return cx->txp_ppath.ops_onw->destroy_proxy_server(cx);
}

View File

@ -0,0 +1,289 @@
/*
* LWA auth support for Secure Streams
*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
typedef struct ss_api_amazon_auth {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
struct lejp_ctx jctx;
size_t pos;
int expires_secs;
} ss_api_amazon_auth_t;
static const char * const lejp_tokens_lwa[] = {
"access_token",
"expires_in",
};
typedef enum {
LSSPPT_ACCESS_TOKEN,
LSSPPT_EXPIRES_IN,
} lejp_tokens_t;
enum {
AUTH_IDX_LWA,
AUTH_IDX_ROOT,
};
static void
lws_ss_sys_auth_api_amazon_com_kick(lws_sorted_usec_list_t *sul)
{
struct lws_context *context = lws_container_of(sul, struct lws_context,
sul_api_amazon_com_kick);
lws_state_transition_steps(&context->mgr_system,
LWS_SYSTATE_OPERATIONAL);
}
static void
lws_ss_sys_auth_api_amazon_com_renew(lws_sorted_usec_list_t *sul)
{
struct lws_context *context = lws_container_of(sul, struct lws_context,
sul_api_amazon_com);
lws_ss_sys_auth_api_amazon_com(context);
}
static signed char
auth_api_amazon_com_parser_cb(struct lejp_ctx *ctx, char reason)
{
ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)ctx->user;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lws_system_blob_t *blob;
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
return 0;
switch (ctx->path_match - 1) {
case LSSPPT_ACCESS_TOKEN:
if (!ctx->npos)
break;
blob = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_LWA);
if (!blob)
return -1;
if (lws_system_blob_heap_append(blob,
(const uint8_t *)ctx->buf,
ctx->npos)) {
lwsl_err("%s: unable to store auth token\n", __func__);
return -1;
}
break;
case LSSPPT_EXPIRES_IN:
m->expires_secs = atoi(ctx->buf);
lws_sul_schedule(context, 0, &context->sul_api_amazon_com,
lws_ss_sys_auth_api_amazon_com_renew,
(lws_usec_t)m->expires_secs * LWS_US_PER_SEC);
break;
}
return 0;
}
/* secure streams payload interface */
static lws_ss_state_return_t
ss_api_amazon_auth_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lws_system_blob_t *ab;
#if !defined(LWS_WITH_NO_LOGS)
size_t total;
#endif
int n;
ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA);
/* coverity */
if (!ab)
return LWSSSSRET_DISCONNECT_ME;
if (buf) {
if (flags & LWSSS_FLAG_SOM) {
lejp_construct(&m->jctx, auth_api_amazon_com_parser_cb,
m, lejp_tokens_lwa,
LWS_ARRAY_SIZE(lejp_tokens_lwa));
lws_system_blob_heap_empty(ab);
}
n = lejp_parse(&m->jctx, buf, (int)len);
if (n < 0) {
lejp_destruct(&m->jctx);
lws_system_blob_destroy(
lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_LWA));
return LWSSSSRET_DISCONNECT_ME;
}
}
if (!(flags & LWSSS_FLAG_EOM))
return LWSSSSRET_OK;
/* we should have the auth token now */
#if !defined(LWS_WITH_NO_LOGS)
total = lws_system_blob_get_size(ab);
lwsl_notice("%s: acquired %u-byte api.amazon.com auth token, exp %ds\n",
__func__, (unsigned int)total, m->expires_secs);
#endif
lejp_destruct(&m->jctx);
/* we move the system state at auth connection close */
return LWSSSSRET_DISCONNECT_ME;
}
static lws_ss_state_return_t
ss_api_amazon_auth_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lws_system_blob_t *ab;
size_t total;
int n;
/*
* We send out auth slot AUTH_IDX_ROOT, it's the LWA user / device
* identity token
*/
ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);
if (!ab)
return LWSSSSRET_DESTROY_ME;
total = lws_system_blob_get_size(ab);
n = lws_system_blob_get(ab, buf, len, m->pos);
if (n < 0)
return LWSSSSRET_TX_DONT_SEND;
if (!m->pos)
*flags |= LWSSS_FLAG_SOM;
m->pos += *len;
if (m->pos == total) {
*flags |= LWSSS_FLAG_EOM;
m->pos = 0; /* for next time */
}
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
ss_api_amazon_auth_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
ss_api_amazon_auth_t *m = (ss_api_amazon_auth_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lws_system_blob_t *ab;
size_t s;
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state),
(unsigned int)ack);
ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_ROOT);
/* coverity */
if (!ab)
return LWSSSSRET_DESTROY_ME;
switch (state) {
case LWSSSCS_CREATING:
//if (lws_ss_set_metadata(m->ss, "ctype", "application/json", 16))
// return LWSSSSRET_DESTROY_ME;
/* fallthru */
case LWSSSCS_CONNECTING:
s = lws_system_blob_get_size(ab);
if (!s)
lwsl_debug("%s: no auth blob\n", __func__);
m->pos = 0;
return lws_ss_request_tx_len(m->ss, (unsigned long)s);
case LWSSSCS_DISCONNECTED:
/*
* We defer moving the system state forward until we have
* closed our connection + tls for the auth action... this is
* because on small systems, we need that memory recovered
* before we can make another connection subsequently.
*
* At this point, we're ultimately being called from within
* the wsi close process, the tls tunnel is not freed yet.
* Use a sul to actually do it next time around the event loop
* when the close process for the auth wsi has completed and
* the related tls is already freed.
*/
s = lws_system_blob_get_size(ab);
if (s && context->mgr_system.state != LWS_SYSTATE_OPERATIONAL)
lws_sul_schedule(context, 0,
&context->sul_api_amazon_com_kick,
lws_ss_sys_auth_api_amazon_com_kick, 1);
context->hss_auth = NULL;
return LWSSSSRET_DESTROY_ME;
default:
break;
}
return LWSSSSRET_OK;
}
int
lws_ss_sys_auth_api_amazon_com(struct lws_context *context)
{
lws_ss_info_t ssi;
if (context->hss_auth) /* already exists */
return 0;
/* We're making an outgoing secure stream ourselves */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_api_amazon_auth_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_api_amazon_auth_t, opaque_data);
ssi.rx = ss_api_amazon_auth_rx;
ssi.tx = ss_api_amazon_auth_tx;
ssi.state = ss_api_amazon_auth_state;
ssi.user_alloc = sizeof(ss_api_amazon_auth_t);
ssi.streamtype = "api_amazon_com_auth";
if (lws_ss_create(context, 0, &ssi, context, &context->hss_auth,
NULL, NULL)) {
lwsl_info("%s: Create LWA auth ss failed (policy?)\n", __func__);
return 1;
}
return 0;
}

View File

@ -0,0 +1,574 @@
/*
* Sigv4 support for Secure Streams
*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2020 Andy Green <andy@warmcat.com>
* securestreams-dev@amazon.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
struct sigv4_header {
const char * name;
const char * value;
};
#define MAX_HEADER_NUM 8
struct sigv4 {
struct sigv4_header headers[MAX_HEADER_NUM];
uint8_t hnum;
char ymd[10]; /*YYYYMMDD*/
const char *timestamp;
const char *payload_hash;
const char *region;
const char *service;
};
static const uint8_t blob_idx[] = {
LWS_SYSBLOB_TYPE_EXT_AUTH1,
LWS_SYSBLOB_TYPE_EXT_AUTH2,
LWS_SYSBLOB_TYPE_EXT_AUTH3,
LWS_SYSBLOB_TYPE_EXT_AUTH4,
};
enum {
LWS_SS_SIGV4_KEYID,
LWS_SS_SIGV4_KEY,
LWS_SS_SIGV4_BLOB_SLOTS
};
static inline int add_header(struct sigv4 *s, const char *name, const char *value)
{
if (s->hnum >= MAX_HEADER_NUM) {
lwsl_err("%s too many sigv4 headers\n", __func__);
return -1;
}
s->headers[s->hnum].name = name;
s->headers[s->hnum].value = value;
s->hnum++;
if (!strncmp(name, "x-amz-content-sha256", strlen("x-amz-content-sha256")))
s->payload_hash = value;
if (!strncmp(name, "x-amz-date", strlen("x-amz-date"))) {
s->timestamp = value;
strncpy(s->ymd, value, 8);
}
return 0;
}
static int
cmp_header(const void * a, const void * b)
{
return strcmp(((struct sigv4_header *)a)->name,
((struct sigv4_header *)b)->name);
}
static int
init_sigv4(struct lws *wsi, struct lws_ss_handle *h, struct sigv4 *s)
{
lws_ss_metadata_t *polmd = h->policy->metadata;
int m = 0;
add_header(s, "host:", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST));
while (polmd) {
if (polmd->value__may_own_heap &&
((uint8_t *)polmd->value__may_own_heap)[0] &&
h->metadata[m].value__may_own_heap) {
/* consider all headers start with "x-amz-" need to be signed */
if (!strncmp(polmd->value__may_own_heap, "x-amz-",
strlen("x-amz-"))) {
if (add_header(s, polmd->value__may_own_heap,
h->metadata[m].value__may_own_heap))
return -1;
}
}
if (!strcmp(h->metadata[m].name, h->policy->aws_region) &&
h->metadata[m].value__may_own_heap)
s->region = h->metadata[m].value__may_own_heap;
if (!strcmp(h->metadata[m].name, h->policy->aws_service) &&
h->metadata[m].value__may_own_heap)
s->service = h->metadata[m].value__may_own_heap;
m++;
polmd = polmd->next;
}
qsort(s->headers, s->hnum, sizeof(struct sigv4_header), cmp_header);
#if 0
do {
int i;
for (i= 0; i<s->hnum; i++)
lwsl_debug("%s hdr %s %s\n", __func__,
s->headers[i].name, s->headers[i].value);
lwsl_debug("%s service: %s region: %s\n", __func__,
s->service, s->region);
} while(0);
#endif
return 0;
}
static void
bin2hex(uint8_t *in, size_t len, char *out)
{
static const char *hex = "0123456789abcdef";
size_t n;
for (n = 0; n < len; n++) {
*out++ = hex[(in[n] >> 4) & 0xf];
*out++ = hex[in[n] & 15];
}
*out = '\0';
}
static int
hmacsha256(const uint8_t *key, size_t keylen, const uint8_t *txt,
size_t txtlen, uint8_t *digest)
{
struct lws_genhmac_ctx hmacctx;
if (lws_genhmac_init(&hmacctx, LWS_GENHMAC_TYPE_SHA256,
key, keylen))
return -1;
if (lws_genhmac_update(&hmacctx, txt, txtlen)) {
lwsl_err("%s: hmac computation failed\n", __func__);
lws_genhmac_destroy(&hmacctx, NULL);
return -1;
}
if (lws_genhmac_destroy(&hmacctx, digest)) {
lwsl_err("%s: problem destroying hmac\n", __func__);
return -1;
}
return 0;
}
/* cut the last byte of the str */
static inline int hash_update_bite_str(struct lws_genhash_ctx *ctx, const char * str)
{
int ret = 0;
if ((ret = lws_genhash_update(ctx, (void *)str, strlen(str)-1))) {
lws_genhash_destroy(ctx, NULL);
lwsl_err("%s err %d line \n", __func__, ret);
}
return ret;
}
static inline int hash_update_str(struct lws_genhash_ctx *ctx, const char * str)
{
int ret = 0;
if ((ret = lws_genhash_update(ctx, (void *)str, strlen(str)))) {
lws_genhash_destroy(ctx, NULL);
lwsl_err("%s err %d \n", __func__, ret);
}
return ret;
}
static int
build_sign_string(struct lws *wsi, char *buf, size_t bufsz,
struct lws_ss_handle *h, struct sigv4 *s)
{
char hash[65], *end = &buf[bufsz - 1], *start;
struct lws_genhash_ctx hash_ctx;
uint8_t hash_bin[32];
int i, ret = 0;
start = buf;
if ((ret = lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256))) {
lws_genhash_destroy(&hash_ctx, NULL);
lwsl_err("%s genhash init err %d \n", __func__, ret);
return -1;
}
/*
* hash canonical_request
*/
if (hash_update_str(&hash_ctx, h->policy->u.http.method) ||
hash_update_str(&hash_ctx, "\n"))
return -1;
if (hash_update_str(&hash_ctx, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)) ||
hash_update_str(&hash_ctx, "\n"))
return -1;
/* TODO, append query string */
if (hash_update_str(&hash_ctx, "\n"))
return -1;
for (i = 0; i < s->hnum; i++) {
if (hash_update_str(&hash_ctx, s->headers[i].name) ||
hash_update_str(&hash_ctx, s->headers[i].value) ||
hash_update_str(&hash_ctx, "\n"))
return -1;
}
if (hash_update_str(&hash_ctx, "\n"))
return -1;
for (i = 0; i < s->hnum-1; i++) {
if (hash_update_bite_str(&hash_ctx, s->headers[i].name) ||
hash_update_str(&hash_ctx, ";"))
return -1;
}
if (hash_update_bite_str(&hash_ctx, s->headers[i].name) ||
hash_update_str(&hash_ctx, "\n") ||
hash_update_str(&hash_ctx, s->payload_hash))
return -1;
if ((ret = lws_genhash_destroy(&hash_ctx, hash_bin))) {
lws_genhash_destroy(&hash_ctx, NULL);
lwsl_err("%s lws_genhash error \n", __func__);
return -1;
}
bin2hex(hash_bin, sizeof(hash_bin), hash);
/*
* build sign string like the following
*
* "AWS4-HMAC-SHA256" + "\n" +
* timeStampISO8601Format + "\n" +
* date.Format(<YYYYMMDD>) + "/" + <region> + "/" + <service> + "/aws4_request" + "\n" +
* Hex(SHA256Hash(<CanonicalRequest>))
*/
buf = start;
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n",
"AWS4-HMAC-SHA256");
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s\n",
s->timestamp);
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s/%s/%s/%s\n",
s->ymd, s->region, s->service, "aws4_request");
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s", hash);
*buf++ = '\0';
assert(buf <= start + bufsz);
return 0;
}
/*
* DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>")
* DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>")
* DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>")
* SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
*/
static int
calc_signing_key(struct lws *wsi, struct lws_ss_handle *h,
struct sigv4 *s, uint8_t *sign_key)
{
uint8_t key[128], date_key[32], and_region_key[32],
and_service_key[32], *kb;
lws_system_blob_t *ab;
size_t keylen;
int n;
ab = lws_system_get_blob(wsi->a.context,
blob_idx[h->policy->auth->blob_index],
LWS_SS_SIGV4_KEY);
if (!ab)
return -1;
kb = key;
*kb++ = 'A';
*kb++ = 'W';
*kb++ = 'S';
*kb++ = '4';
keylen = sizeof(key) - 4;
if (lws_system_blob_get_size(ab) > keylen - 1)
return -1;
n = lws_system_blob_get(ab, kb, &keylen, 0);
if (n < 0)
return -1;
kb[keylen] = '\0';
hmacsha256((const uint8_t *)key, strlen((const char *)key),
(const uint8_t *)s->ymd, strlen(s->ymd), date_key);
hmacsha256(date_key, sizeof(date_key), (const uint8_t *)s->region,
strlen(s->region), and_region_key);
hmacsha256(and_region_key, sizeof(and_region_key),
(const uint8_t *)s->service,
strlen(s->service), and_service_key);
hmacsha256(and_service_key, sizeof(and_service_key),
(uint8_t *)"aws4_request",
strlen("aws4_request"), sign_key);
return 0;
}
/* Sample auth string:
*
* 'Authorization: AWS4-HMAC-SHA256 Credential=AKIAVHWASOFE7TJ7ZUQY/20200731/us-west-2/s3/aws4_request,
* SignedHeaders=host;x-amz-content-sha256;x-amz-date, \
* Signature=ad9fb75ff3b46c7990e3e8f090abfdd6c01fd67761a517111694377e20698377'
*/
static int
build_auth_string(struct lws *wsi, char * buf, size_t bufsz,
struct lws_ss_handle *h, struct sigv4 *s,
uint8_t *signature_bin)
{
#if defined(_DEBUG)
char *start = buf;
#endif
char *end = &buf[bufsz - 1];
char *c;
lws_system_blob_t *ab;
size_t keyidlen = 128; // max keyid len is 128
int n;
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
"AWS4-HMAC-SHA256 ");
ab = lws_system_get_blob(wsi->a.context,
blob_idx[h->policy->auth->blob_index],
LWS_SS_SIGV4_KEYID);
if (!ab)
return -1;
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
"Credential=");
n = lws_system_blob_get(ab,(uint8_t *)buf, &keyidlen, 0);
if (n < 0)
return -1;
buf += keyidlen;
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "/%s/%s/%s/%s, ",
s->ymd, s->region, s->service, "aws4_request");
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf), "%s",
"SignedHeaders=");
for (n = 0; n < s->hnum; n++) {
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
"%s",s->headers[n].name);
buf--; /* remove ':' */
*buf++ = ';';
}
c = buf - 1;
*c = ','; /* overwrite ';' back to ',' */
buf += lws_snprintf(buf, lws_ptr_diff_size_t(end, buf),
"%s", " Signature=");
bin2hex(signature_bin, 32, buf);
#if defined(_DEBUG)
assert(buf + 65 <= start + bufsz);
lwsl_debug("%s %s\n", __func__, start);
#endif
return 0;
}
int
lws_ss_apply_sigv4(struct lws *wsi, struct lws_ss_handle *h,
unsigned char **p, unsigned char *end)
{
uint8_t buf[512], sign_key[32], signature_bin[32], *bp;
struct sigv4 s;
memset(&s, 0, sizeof(s));
bp = buf;
init_sigv4(wsi, h, &s);
if (!s.timestamp || !s.payload_hash) {
lwsl_err("%s missing headers\n", __func__);
return -1;
}
if (build_sign_string(wsi, (char *)bp, sizeof(buf), h, &s))
return -1;
if (calc_signing_key(wsi, h, &s, sign_key))
return -1;
hmacsha256(sign_key, sizeof(sign_key), (const uint8_t *)buf,
strlen((const char *)buf), signature_bin);
bp = buf; /* reuse for auth_str */
if (build_auth_string(wsi, (char *)bp, sizeof(buf), h, &s,
signature_bin))
return -1;
if (lws_add_http_header_by_name(wsi,
(const uint8_t *)"Authorization:", buf,
(int)strlen((const char*)buf), p, end))
return -1;
return 0;
}
int
lws_ss_sigv4_set_aws_key(struct lws_context* context, uint8_t idx,
const char * keyid, const char * key)
{
const char * s[] = { keyid, key };
lws_system_blob_t *ab;
int i;
if (idx > LWS_ARRAY_SIZE(blob_idx))
return -1;
for (i = 0; i < LWS_SS_SIGV4_BLOB_SLOTS; i++) {
ab = lws_system_get_blob(context, blob_idx[idx], i);
if (!ab)
return -1;
lws_system_blob_heap_empty(ab);
if (lws_system_blob_heap_append(ab, (const uint8_t *)s[i],
strlen(s[i]))) {
lwsl_err("%s: can't store %d \n", __func__, i);
return -1;
}
}
return 0;
}
#if defined(__linux__) || defined(__APPLE__) || defined(WIN32) || \
defined(__FreeBSD__) || defined(__NetBSD__) || defined(__ANDROID__) || \
defined(__sun) || defined(__OpenBSD__) || defined(__NuttX__)
/* ie, if we have filesystem ops */
int
lws_aws_filesystem_credentials_helper(const char *path, const char *kid,
const char *ak, char **aws_keyid,
char **aws_key)
{
char *str = NULL, *val = NULL, *line = NULL, sth[128];
size_t len = sizeof(sth);
const char *home = "";
int i, poff = 0;
ssize_t rd;
FILE *fp;
*aws_keyid = *aws_key = NULL;
if (path[0] == '~') {
home = getenv("HOME");
if (home && strlen(home) > sizeof(sth) - 1) /* coverity */
return -1;
else {
if (!home)
home = "";
poff = 1;
}
}
lws_snprintf(sth, sizeof(sth), "%s%s", home, path + poff);
fp = fopen(sth, "r");
if (!fp) {
lwsl_err("%s can't open '%s'\n", __func__, sth);
return -1;
}
while ((rd = getline(&line, &len, fp)) != -1) {
for (i = 0; i < 2; i++) {
size_t slen;
if (strncmp(line, i ? kid : ak, strlen(i ? kid : ak)))
continue;
str = strchr(line, '=');
if (!str)
continue;
str++;
/* only read the first key for each */
if (*(i ? aws_keyid : aws_key))
continue;
/*
* Trim whitespace from the start and end
*/
slen = (size_t)(rd - lws_ptr_diff(str, line));
while (slen && *str == ' ') {
str++;
slen--;
}
while (slen && (str[slen - 1] == '\r' ||
str[slen - 1] == '\n' ||
str[slen - 1] == ' '))
slen--;
val = malloc(slen + 1);
if (!val)
goto bail;
strncpy(val, str, slen);
val[slen] = '\0';
*(i ? aws_keyid : aws_key) = val;
}
}
bail:
fclose(fp);
if (line)
free(line);
if (!*aws_keyid || !*aws_key) {
if (*aws_keyid) {
free(*aws_keyid);
*aws_keyid = NULL;
}
if (*aws_key) {
free(*aws_key);
*aws_key = NULL;
}
lwsl_err("%s can't find aws credentials! \
please check %s\n", __func__, path);
return -1;
}
lwsl_info("%s: '%s' '%s'\n", __func__, *aws_keyid, *aws_key);
return 0;
}
#endif

View File

@ -0,0 +1,98 @@
/*
* Captive portal detect for Secure Streams
*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
typedef struct ss_cpd {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
lws_sorted_usec_list_t sul;
} ss_cpd_t;
static lws_ss_state_return_t
ss_cpd_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
ss_cpd_t *m = (ss_cpd_t *)userobj;
struct lws_context *cx = (struct lws_context *)m->opaque_data;
lwsl_ss_info(m->ss, "%s, ord 0x%x\n", lws_ss_state_name((int)state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_start_timeout(m->ss, 3 * LWS_US_PER_SEC);
return lws_ss_request_tx(m->ss);
case LWSSSCS_QOS_ACK_REMOTE:
lws_system_cpd_set(cx, LWS_CPD_INTERNET_OK);
cx->ss_cpd = NULL;
return LWSSSSRET_DESTROY_ME;
case LWSSSCS_TIMEOUT:
case LWSSSCS_ALL_RETRIES_FAILED:
case LWSSSCS_DISCONNECTED:
/*
* First result reported sticks... if nothing else, this will
* cover the situation we didn't connect to anything
*/
lws_system_cpd_set(cx, LWS_CPD_NO_INTERNET);
cx->ss_cpd = NULL;
return LWSSSSRET_DESTROY_ME;
default:
break;
}
return LWSSSSRET_OK;
}
static const lws_ss_info_t ssi_cpd = {
.handle_offset = offsetof(ss_cpd_t, ss),
.opaque_user_data_offset = offsetof(ss_cpd_t, opaque_data),
.state = ss_cpd_state,
.user_alloc = sizeof(ss_cpd_t),
.streamtype = "captive_portal_detect",
};
int
lws_ss_sys_cpd(struct lws_context *cx)
{
if (cx->ss_cpd) {
lwsl_cx_notice(cx, "CPD already ongoing");
return 0;
}
if (lws_ss_create(cx, 0, &ssi_cpd, cx, &cx->ss_cpd, NULL, NULL)) {
lwsl_cx_info(cx, "Create stream failed (policy?)");
return 1;
}
return 0;
}

View File

@ -0,0 +1,170 @@
/*
* Policy fetching for Secure Streams
*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include <private-lib-core.h>
typedef struct ss_fetch_policy {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
lws_sorted_usec_list_t sul;
uint8_t partway;
} ss_fetch_policy_t;
/* secure streams payload interface */
static lws_ss_state_return_t
ss_fetch_policy_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_fetch_policy_t *m = (ss_fetch_policy_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
if (flags & LWSSS_FLAG_SOM) {
if (lws_ss_policy_parse_begin(context, 0))
return LWSSSSRET_OK;
m->partway = 1;
}
if (len && lws_ss_policy_parse(context, buf, len) < 0)
return LWSSSSRET_OK;
if (flags & LWSSS_FLAG_EOM)
m->partway = 2;
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
ss_fetch_policy_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
return LWSSSSRET_TX_DONT_SEND;
}
static void
policy_set(lws_sorted_usec_list_t *sul)
{
ss_fetch_policy_t *m = lws_container_of(sul, ss_fetch_policy_t, sul);
struct lws_context *context = (struct lws_context *)m->opaque_data;
/*
* We get called if the policy parse was successful, just after the
* ss connection close that was using the vhost from the old policy
*/
lws_ss_destroy(&m->ss);
if (lws_ss_policy_set(context, "updated"))
lwsl_err("%s: policy set failed\n", __func__);
else {
context->policy_updated = 1;
#if defined(LWS_WITH_SYS_STATE)
lws_state_transition_steps(&context->mgr_system,
LWS_SYSTATE_OPERATIONAL);
#endif
}
}
static lws_ss_state_return_t
ss_fetch_policy_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
ss_fetch_policy_t *m = (ss_fetch_policy_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
return lws_ss_request_tx(m->ss);
case LWSSSCS_CONNECTING:
break;
case LWSSSCS_QOS_ACK_REMOTE:
switch (m->partway) {
case 2:
lws_sul_schedule(context, 0, &m->sul, policy_set, 1);
m->partway = 0;
break;
}
break;
case LWSSSCS_DISCONNECTED:
if (m->partway == 1) {
lws_ss_policy_parse_abandon(context);
break;
}
m->partway = 0;
break;
default:
break;
}
return LWSSSSRET_OK;
}
int
lws_ss_sys_fetch_policy(struct lws_context *context)
{
lws_ss_info_t ssi;
if (context->hss_fetch_policy) /* already exists */
return 0;
/* We're making an outgoing secure stream ourselves */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_fetch_policy_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_fetch_policy_t, opaque_data);
ssi.rx = ss_fetch_policy_rx;
ssi.tx = ss_fetch_policy_tx;
ssi.state = ss_fetch_policy_state;
ssi.user_alloc = sizeof(ss_fetch_policy_t);
ssi.streamtype = "fetch_policy";
if (lws_ss_create(context, 0, &ssi, context, &context->hss_fetch_policy,
NULL, NULL)) {
/*
* If there's no fetch_policy streamtype, it can just be we're
* running on a proxied client with no policy of its own,
* it's OK.
*/
lwsl_info("%s: Policy fetch ss failed (stub policy?)\n", __func__);
return 0;
}
lwsl_info("%s: policy fetching ongoing\n", __func__);
/* fetching it is ongoing */
return 1;
}