client: secure streams

Secure Streams is an optional layer on top of lws that separates policy
like endpoint selection and tls cert validation into a device JSON
policy document.

Code that wants to open a client connection just specifies a streamtype name,
and no longer deals with details like the endpoint, the protocol (!) or anything
else other than payloads and optionally generic metadata; the JSON policy
contains all the details for each streamtype.  h1, h2, ws and mqtt client
connections are supported.

Logical secure streams outlive any particular connection and supports "nailed-up"
connectivity regardless of underlying connection stability.
This commit is contained in:
Andy Green 2020-02-29 12:37:24 +00:00
parent 9d099ba7be
commit 28ce32af64
79 changed files with 12428 additions and 44 deletions

View File

@ -11,6 +11,8 @@ env:
- LWS_METHOD=lwsws2 CMAKE_ARGS="-DLWS_WITH_LWSWS=ON -DLWS_WITHOUT_EXTENSIONS=0 -DLWS_WITH_HTTP2=1 -DLWS_WITH_ACME=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DCMAKE_BUILD_TYPE=DEBUG -DLWS_WITH_LWS_DSH=1"
- LWS_METHOD=default CMAKE_ARGS="-DLWS_WITH_MINIMAL_EXAMPLES=1"
- LWS_METHOD=mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_HTTP2=1 -DLWS_WITH_LWSWS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_JOSE=1 -DCMAKE_BUILD_TYPE=DEBUG"
- LWS_METHOD=ss CMAKE_ARGS="-DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1"
- LWS_METHOD=ss+mbedtls CMAKE_ARGS="-DLWS_WITH_MBEDTLS=1 -DLWS_WITH_SECURE_STREAMS=1 -DLWS_WITH_MINIMAL_EXAMPLES=1"
- LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
- LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"
- LWS_METHOD=noext CMAKE_ARGS="-DLWS_WITHOUT_EXTENSIONS=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"

View File

@ -60,6 +60,13 @@ option(LWS_WITH_SYS_DHCP_CLIENT "Build in tiny DHCP client" OFF)
option(LWS_WITH_HTTP_BASIC_AUTH "Support Basic Auth" ON)
option(LWS_WITH_HTTP_UNCOMMON_HEADERS "Include less common http header support" ON)
#
# Secure Streams
#
option(LWS_WITH_SECURE_STREAMS "Secure Streams protocol-agnostic API" OFF)
option(LWS_WITH_SECURE_STREAMS_PROXY_API "Secure Streams support to work across processes" OFF)
option(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM "Auth support for api.amazon.com" OFF)
#
# TLS library options... all except mbedTLS are basically OpenSSL variants.
#
@ -245,6 +252,11 @@ if(LWS_WITH_DISTRO_RECOMMENDED)
set(LWS_ROLE_MQTT 1)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
set(LWS_WITH_LWS_DSH 1)
set(LWS_WITH_UNIX_SOCK 1)
endif()
if (NOT LWS_WITH_NETWORK)
set(LWS_ROLE_MQTT 0)
set(LWS_ROLE_H1 0)
@ -394,6 +406,10 @@ endif()
include_directories(include plugins lib/core lib/core-net lib/event-libs include/abstract lib/tls lib/roles lib/event-libs/libuv lib/event-libs/poll lib/event-libs/libevent lib/event-libs/glib lib/event-libs/libev lib/jose/jwe lib/jose/jws lib/jose lib/misc lib/roles/http lib/roles/http/compression lib/roles/h1 lib/roles/h2 lib/roles/ws lib/roles/cgi lib/roles/dbus lib/roles/raw-proxy lib/abstract lib/system/async-dns lib/roles/mqtt)
if (LWS_WITH_SECURE_STREAMS)
set(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM 1)
endif()
if (LWS_PLAT_FREERTOS)
include_directories(lib/plat/freertos lib/plat/freertos/esp32)
else()
@ -752,14 +768,6 @@ if (LWS_WITHOUT_DAEMONIZE OR WIN32)
set(LWS_NO_DAEMONIZE 1)
endif()
if (LWS_WITHOUT_SERVER)
set(LWS_WITHOUT_SERVER 1)
endif()
if (LWS_WITHOUT_CLIENT)
set(LWS_WITHOUT_CLIENT 1)
endif()
if (LWS_WITH_LIBEV)
set(LWS_WITH_LIBEV 1)
endif()
@ -790,11 +798,11 @@ endif()
set(LWS_WITH_CLIENT 1)
if (LWS_WITHOUT_CLIENT)
set(LWS_WITH_CLIENT 0)
set(LWS_WITH_CLIENT)
endif()
set(LWS_WITH_SERVER 1)
if (LWS_WITHOUT_SERVER)
set(LWS_WITH_SERVER 0)
set(LWS_WITH_SERVER)
endif()
# using any abstract protocol enables LWS_WITH_ABSTRACT
@ -1165,6 +1173,53 @@ if (LWS_WITH_NETWORK)
endif()
endif()
if (LWS_WITH_SECURE_STREAMS)
list(APPEND SOURCES
lib/secure-streams/secure-streams.c
lib/secure-streams/policy.c
lib/secure-streams/system/fetch-policy/fetch-policy.c
)
if (LWS_ROLE_H1)
list(APPEND SOURCES
lib/secure-streams/protocols/ss-h1.c
)
endif()
if (LWS_ROLE_H2)
list(APPEND SOURCES
lib/secure-streams/protocols/ss-h2.c
)
endif()
if (LWS_ROLE_WS)
list(APPEND SOURCES
lib/secure-streams/protocols/ss-ws.c
)
endif()
if (LWS_ROLE_MQTT)
list(APPEND SOURCES
lib/secure-streams/protocols/ss-mqtt.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
list(APPEND SOURCES
lib/secure-streams/secure-streams-serialize.c
lib/secure-streams/secure-streams-client.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
list(APPEND SOURCES
lib/secure-streams/secure-streams-process.c
)
endif()
if (LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)
list(APPEND SOURCES
lib/secure-streams/system/auth-api.amazon.com/auth.c
)
endif()
endif()
if (LWS_WITH_STATS)
list(APPEND SOURCES
lib/core-net/stats.c
@ -1300,7 +1355,11 @@ endif()
if (NOT LWS_WITHOUT_SERVER)
list(APPEND SOURCES
lib/core-net/server.c
lib/core-net/server.c)
endif()
if (NOT LWS_WITHOUT_SERVER OR LWS_WITH_SECURE_STREAMS_PROCESS_API)
list(APPEND SOURCES
lib/roles/listen/ops-listen.c)
endif()
@ -1684,7 +1743,7 @@ if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG)
endif()
if (UNIX AND NOT LWS_PLAT_FREERTOS)
set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" )
set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wstrict-aliasing -Wuninitialized -Werror ${VISIBILITY_FLAG} -Wundef ${GCOV_FLAGS} ${CMAKE_C_FLAGS} ${ASAN_FLAGS}" )
else()
set(CMAKE_C_FLAGS "-Wall -Wsign-compare -Wuninitialized -Werror ${VISIBILITY_FLAG} ${GCOV_FLAGS} ${CMAKE_C_FLAGS}" )
endif()
@ -2666,6 +2725,59 @@ if (LWS_WITH_LWSWS)
)
endif (LWS_WITH_LWSWS)
# secure streams plugins
if (LWS_WITH_SECURE_STREAMS)
#
# 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
lib/secure-streams/plugins/${NAME}/${S2})
endif()
if ("${S3}" STREQUAL "")
else()
list(APPEND SSP_SRCS
lib/secure-streams/plugins/${NAME}/${S3})
endif()
if ("${S4}" STREQUAL "")
else()
list(APPEND SSP_SRCS
lib/secure-streams/plugins/${NAME}/${S4})
endif()
if ("${S5}" STREQUAL "")
else()
list(APPEND SSP_SRCS
lib/secure-streams/plugins/${NAME}/${S5})
endif()
if ("${S6}" STREQUAL "")
else()
list(APPEND SSP_SRCS
lib/secure-streams/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} )
add_dependencies(${NAME} websockets_shared)
list(APPEND SS_PLUGINS_LIST ${NAME})
endmacro()
include_directories(lib/secure-streams)
create_ss_plugin(ssp-h1url "h1url.c" "" "" "" "")
endif()
if (UNIX)
# Generate and install pkgconfig.

View File

@ -16,6 +16,26 @@ various scenarios, CC0-licensed (public domain) for cut-and-paste, allow you to
News
----
## Introducing Secure Streams client support
Secure Streams is an optional layer above lws (`-DLWS_WITH_SECURE_STREAMS=1`) that
separates connectivity policy into a JSON document, which can be part of the
firmware or fetched at boot time.
Code no longer deals with details like endpoint specification or tls cert stack used
to validate the remote server, it's all specified in JSON, eg, see
[this example](https://warmcat.com/policy/minimal-proxy.json). Even the protocol to use to talk to the
server, between h1, h2, ws or MQTT, is specified in the policy JSON and the code
itself just deals with payloads and optionally metadata, making it possible to
switch endpoints, update certs and even switch communication protocols by just
editing the JSON policy and leaving the code alone.
Logical Secure Stream connections outlive any underlying lws connection, and support
"nailed-up" connection reacquisition and exponential backoff management.
See [./lib/secure-streams/README.md](https://libwebsockets.org/git/libwebsockets/tree/lib/secure-streams/README.md) and the related minimal examples
for more details.
## mqtt client support
If you enable `-DLWS_ROLE_MQTT=1`, lws can now support QoS0 and QoS1 MQTT client

View File

@ -147,6 +147,9 @@
#cmakedefine LWS_WITH_POLARSSL
#cmakedefine LWS_WITH_POLL
#cmakedefine LWS_WITH_RANGES
#cmakedefine LWS_WITH_SECURE_STREAMS
#cmakedefine LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM
#cmakedefine LWS_WITH_SECURE_STREAMS_PROXY_API
#cmakedefine LWS_WITH_SELFTESTS
#cmakedefine LWS_WITH_SEQUENCER
#cmakedefine LWS_WITH_SERVER_STATUS

BIN
doc-assets/ss-explain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

View File

@ -576,6 +576,9 @@ struct lws;
#include <libwebsockets/lws-fts.h>
#include <libwebsockets/lws-diskcache.h>
#include <libwebsockets/lws-sequencer.h>
#include <libwebsockets/lws-secure-streams.h>
#include <libwebsockets/lws-secure-streams-policy.h>
#include <libwebsockets/lws-secure-streams-client.h>
#if !defined(LWS_PLAT_FREERTOS)
#include <libwebsockets/abstract/abstract.h>

View File

@ -227,6 +227,8 @@
#define lws_check_opt(c, f) ((((uint64_t)c) & ((uint64_t)f)) == ((uint64_t)f))
struct lws_plat_file_ops;
struct lws_ss_policy;
struct lws_ss_plugin;
typedef int (*lws_context_ready_cb_t)(struct lws_context *context);
@ -699,7 +701,7 @@ struct lws_context_creation_info {
/**< VHOST: optional retry and idle policy to apply to this vhost.
* Currently only the idle parts are applied to the connections.
*/
lws_state_notify_link_t **register_notifier_list;
lws_state_notify_link_t * const *register_notifier_list;
/**< CONTEXT: NULL, or pointer to an array of notifiers that should
* be registered during context creation, so they can see state change
* events from very early on. The array should end with a NULL. */
@ -709,6 +711,26 @@ struct lws_context_creation_info {
uint8_t udp_loss_sim_rx_pc;
/**< CONTEXT: percentage of udp reads we actually received
* to make disappear, in order to simulate and test udp retry flow */
#if defined(LWS_WITH_SECURE_STREAMS)
const char *pss_policies_json; /**< CONTEXT: point to a string
* containing a JSON description of the secure streams policies. Set
* to NULL if not using Secure Streams. */
const struct lws_ss_plugin **pss_plugins; /**< CONTEXT: point to an array
* of pointers to plugin structs here, terminated with a NULL ptr.
* Set to NULL if not using Secure Streams. */
const char *ss_proxy_bind; /**< CONTEXT: NULL, or: ss_proxy_port == 0:
* point to a string giving the Unix Domain Socket address to use (start
* with @ for abstract namespace), ss_proxy_port nonzero: set the
* network interface address (not name, it's ambiguous for ipv4/6) to
* bind the tcp connection to the proxy to */
const char *ss_proxy_address; /**< CONTEXT: NULL, or if ss_proxy_port
* nonzero: the tcp address of the ss proxy to connect to */
uint16_t ss_proxy_port; /* 0 = if connecting to ss proxy, do it via a
* Unix Domain Socket, "+@proxy.ss.lws" if ss_proxy_bind is NULL else
* the socket path given in ss_proxy_bind (start it with a + or +@);
* nonzero means connect via a tcp socket to the tcp address in
* ss_proxy_bind and the given port */
#endif
/* Add new things just above here ---^
* This is part of the ABI, don't needlessly break compatibility
@ -718,7 +740,7 @@ struct lws_context_creation_info {
* was not built against the newer headers.
*/
void *_unused[4]; /**< dummy */
void *_unused[2]; /**< dummy */
};
/**

View File

@ -0,0 +1,172 @@
/*
* 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 headers for secure stream api variants that deal with clients in
* different threads or even different processes.
*
* lws_ss_ when client is directly using the event loop
* lws_sstc_ when client is in a different thread to the event loop
* lws_sspc_ when client is in a different process to the event loop
*
* The client api is almost the same except the slightly diffent names.
*/
/*
* lws_sspc_ apis... different process
*/
/*
* Helper translation so user code written to lws_ss_ can be built for
* lws_sspc_ in one step by #define LWS_SS_USE_SSPC before including
*/
#if defined(LWS_SS_USE_SSPC)
#define lws_ss_handle lws_sspc_handle
#define lws_ss_create lws_sspc_create
#define lws_ss_destroy lws_sspc_destroy
#define lws_ss_request_tx lws_sspc_request_tx
#define lws_ss_client_connect lws_sspc_client_connect
#define lws_ss_get_sequencer lws_sspc_get_sequencer
#define lws_ss_proxy_create lws_sspc_proxy_create
#define lws_ss_get_context lws_sspc_get_context
#define lws_ss_rideshare lws_sspc_rideshare
#define lws_ss_set_metadata lws_sspc_set_metadata
#define lws_ss_add_peer_tx_credit lws_sspc_add_peer_tx_credit
#define lws_ss_get_est_peer_tx_credit lws_sspc_get_est_peer_tx_credit
#endif
struct lws_sspc_handle;
LWS_VISIBLE LWS_EXTERN int
lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, struct lws_sspc_handle **ppss,
struct lws_sequencer *seq_owner, const char **ppayload_fmt);
/**
* lws_sspc_destroy() - Destroy secure stream
*
* \param ppss: pointer to lws_ss_t pointer to be destroyed
*
* Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL.
*/
LWS_VISIBLE LWS_EXTERN void
lws_sspc_destroy(struct lws_sspc_handle **ppss);
/**
* lws_sspc_request_tx() - Schedule stream for tx
*
* \param pss: pointer to lws_ss_t representing stream that wants to transmit
*
* Schedules a write on the stream represented by \p pss. When it's possible to
* write on this stream, the *tx callback will occur with an empty buffer for
* the stream owner to fill in.
*/
LWS_VISIBLE LWS_EXTERN void
lws_sspc_request_tx(struct lws_sspc_handle *pss);
/**
* lws_sspc_client_connect() - Attempt the client connect
*
* \param h: secure streams handle
*
* Starts the connection process for the secure stream. Returns 0 if OK or
* nonzero if we have already failed.
*/
LWS_VISIBLE LWS_EXTERN int
lws_sspc_client_connect(struct lws_sspc_handle *h);
/**
* lws_sspc_get_sequencer() - Return parent sequencer pointer if any
*
* \param h: secure streams handle
*
* Returns NULL if the secure stream is not associated with a sequencer.
* Otherwise returns a pointer to the owning sequencer. You can use this to
* identify which sequencer to direct messages to, from the secure stream
* callback.
*/
LWS_VISIBLE LWS_EXTERN struct lws_sequencer *
lws_sspc_get_sequencer(struct lws_sspc_handle *h);
/**
* lws_sspc_proxy_create() - Start a unix domain socket proxy for Secure Streams
*
* \param context: lws_context
*
* Creates a vhost that listens on an abstract namespace unix domain socket at
* address "proxy.ss.lws". Client connections to this proxy to Secure Streams
*/
LWS_VISIBLE LWS_EXTERN int
lws_sspc_proxy_create(struct lws_context *context);
/**
* lws_ss_get_context() - convenience helper to recover the lws context
*
* \h: secure streams handle
*
* Returns the lws context. Dispenses with the need to pass a copy of it into
* your secure streams handler.
*/
LWS_VISIBLE LWS_EXTERN struct lws_context *
lws_sspc_get_context(struct lws_sspc_handle *h);
LWS_VISIBLE LWS_EXTERN const struct lws_protocols lws_sspc_protocols[];
LWS_VISIBLE LWS_EXTERN const char *
lws_sspc_rideshare(struct lws_sspc_handle *h);
/**
* lws_sspc_set_metadata() - allow user to bind external data to defined ss metadata
*
* \h: secure streams handle
* \name: metadata name from the policy
* \value: pointer to user-managed data to bind to name
* \len: length of the user-managed data in value
*
* Binds user-managed data to the named metadata item from the ss policy.
* If present, the metadata item is handled in a protocol-specific way using
* the associated policy information. For example, in the policy
*
* "\"metadata\":" "["
* "{\"uptag\":" "\"X-Upload-Tag:\"},"
* "{\"ctype\":" "\"Content-Type:\"},"
* "{\"xctype\":" "\"X-Content-Type:\"}"
* "],"
*
* when the policy is using h1 is interpreted to add h1 headers of the given
* name with the value of the metadata on the left.
*
* Return 0 if OK.
*/
LWS_VISIBLE LWS_EXTERN int
lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
void *value, size_t len);
LWS_VISIBLE LWS_EXTERN int
lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t add);
LWS_VISIBLE LWS_EXTERN int
lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h);

View File

@ -0,0 +1,237 @@
/*
* 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.
*
* included from libwebsockets.h
*/
typedef int (*plugin_auth_status_cb)(struct lws_ss_handle *ss, int status);
/**
* lws_ss_plugin_auth_t - api for an auth plugin
*
* Auth plugins create and sequence authenticated connections that can carry one
* or more streams to an endpoint. That may involve other connections to other
* places to eg, gather authenticated tokens and then make the real connection
* using the tokens.
*
* The secure stream object contains members to record which auth plugin the
* stream is bound to and an over-allocation of the secure stream object to
* contain the plugin auth private data.
*
* The auth plugin controls the state of the stream connection via the status
* callback, and handles retries.
*
* Network connections may require one kind of auth sequencing, and streams
* inside those connections another kind of auth sequencing depending on their
* role. So the secure stream object allows defining plugins for both kinds.
*
* Streams may disappear at any time and require reauth to bring a new one up.
* The auth plugin sequencer will connect / reconnect either on demand, or from
* the start and after any connectivity loss if any stream using the connection
* has the LWSSSPOLF_NAILED_UP flag.
*/
typedef struct lws_ss_plugin {
struct lws_ss_plugin *next;
const char *name; /**< auth plugin name */
size_t alloc; /**< size of private allocation */
int (*create)(struct lws_ss_handle *ss, void *info,
plugin_auth_status_cb status);
/**< called when the auth plugin is instantiated
and bound to the secure stream. status is
called back with advisory information about
the authenticated stream state as it
proceeds */
int (*destroy)(struct lws_ss_handle *ss);
/**< called when the related secure stream is
being destroyed, and anything the auth
plugin is doing should also be destroyed */
int (*munge)(struct lws_ss_handle *ss, char *path,
size_t path_len);
/**< if the plugin needs to munge transactions
that have metadata outside the payload (eg,
add http headers) this callback will give
it the opportunity to do so */
} lws_ss_plugin_t;
typedef struct lws_ss_x509 {
struct lws_ss_x509 *next;
const char *vhost_name; /**< vhost name using cert ctx */
const uint8_t *ca_der; /**< DER x.509 cert */
size_t ca_der_len; /**< length of DER cert */
} lws_ss_x509_t;
enum {
LWSSSPOLF_OPPORTUNISTIC = (1 << 0),
/**< the connection doesn't exist unless client asks to write */
LWSSSPOLF_NAILED_UP = (1 << 1),
/**< the connection tries to be connected the whole life of the ss */
LWSSSPOLF_URGENT_TX = (1 << 2),
/**< this connection carries critical tx data */
LWSSSPOLF_URGENT_RX = (1 << 3),
/**< this connection carries critical rx data */
LWSSSPOLF_TLS = (1 << 4),
/**< stream must be connected via a tls tunnel */
LWSSSPOLF_LONG_POLL = (1 << 5),
/**< stream used to receive async rx at arbitrary intervals */
LWSSSPOLF_AUTH_BEARER = (1 << 6),
/**< for http, use lws_system auth token 0 in authentication: bearer */
LWSSSPOLF_HTTP_NO_CONTENT_LENGTH = (1 << 7),
/**< don't add any content length even if we have it */
LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM = (1 << 8),
/**< set the client flag LCCSCF_H2_QUIRK_NGHTTP2_END_STREAM */
LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR = (1 << 9),
/**< set the client flag LCCSCF_H2_QUIRK_OVERFLOWS_TXCR */
LWSSSPOLF_H2_QUIRK_UNCLEAN_HPACK_STATE = (1 << 10),
/**< HPACK decoder state does not end cleanly */
LWSSSPOLF_HTTP_MULTIPART = (1 << 11),
/**< indicates stream goes out as specifically a multipart mime POST
* section... if the tx has LWSSS_FLAG_COALESCE_CONTINUES flag then more
* multipart sections are expected. Without it, the multipart wrapper
* is closed and the http transaction issue completed when this message
* finishes. */
LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED = (1 << 12),
/**< set up lws_system client cert */
LWSSSPOLF_LOCAL_SINK = (1 << 13),
/**< expected to bind to a local sink only */
};
typedef struct lws_ss_trust_store {
struct lws_ss_trust_store *next;
const char *name;
lws_ss_x509_t *ssx509[8];
int count;
} lws_ss_trust_store_t;
enum {
LWSSSP_H1,
LWSSSP_H2,
LWSSSP_WS,
LWSSS_HBI_AUTH = 0,
LWSSS_HBI_DSN,
LWSSS_HBI_FWV,
LWSSS_HBI_TYPE,
_LWSSS_HBI_COUNT /* always last */
};
typedef struct lws_ss_metadata {
struct lws_ss_metadata *next;
const char *name;
void *value;
size_t length;
uint8_t value_on_lws_heap; /* proxy does this */
} lws_ss_metadata_t;
/**
* lws_ss_policy_t: policy database entry for a stream type
*
* Decides the system policy for how to implement connections of name
* .streamtype.
*
* Streams may need one kind of auth sequencing for the network connection and
* another kind of auth sequencing for the streams that are carried inside it,
* this is the purpose of .nauth and .sauth. Both are optional and may be NULL.
*
* An array of these is set at context creation time, ending with one with a
* NULL streamtype.
*/
typedef struct lws_ss_policy {
struct lws_ss_policy *next;
const char *streamtype; /**< stream type lhs to match on */
const char *endpoint; /**< DNS address to connect to */
const char *rideshare_streamtype; /**< optional transport
* on another, preexisting stream of this
* streamtype name */
const char *payload_fmt;
const char *socks5_proxy;
lws_ss_metadata_t *metadata; /* linked-list of metadata */
/* protocol-specific connection policy details */
union {
/* details for http-related protocols... */
struct {
/* common to all http-related protocols */
const char *method;
const char *url;
const char *multipart_name;
const char *multipart_filename;
const char *multipart_content_type;
const char *blob_header[_LWSSS_HBI_COUNT];
const char *auth_preamble;
union {
// struct { /* LWSSSP_H1 */
// } h1;
// struct { /* LWSSSP_H2 */
// } h2;
struct { /* LWSSSP_WS */
const char *subprotocol;
uint8_t binary;
/* false = TEXT, true = BINARY */
} ws;
} u;
} http;
struct {
const char *topic; /* stream sends on this topic */
const char *subscribe; /* stream subscribes to this topic */
uint8_t qos;
} mqtt;
/* details for non-http related protocols... */
} u;
const
struct lws_ss_plugin *plugins[2]; /**< NULL or auth plugin */
const void *plugins_info[2]; /**< plugin-specific data */
const lws_ss_trust_store_t *trust_store; /**< CA certs needed for conn
validation, only set between policy parsing and vhost creation */
const lws_retry_bo_t *retry_bo; /**< retry policy to use */
uint32_t flags; /**< stream attribute flags */
uint16_t port; /**< endpoint port */
uint8_t metadata_count; /**< metadata count */
uint8_t protocol; /**< protocol index */
uint8_t client_cert; /**< which client cert to apply
0 = none, 1+ = cc 0+ */
} lws_ss_policy_t;

View File

@ -0,0 +1,492 @@
/*
* 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.
*
* included from libwebsockets.h
*
*
* Secure Streams is a *payload-only* client communication channel where all the
* details about the connection are held in a systemwide policy database and
* are keyed by the streamtype field... the user of the communication channel
* does not know or manage the choice of endpoint, tls CA, or even wire
* protocol. The advantage is he then does not have any dependency on any of
* those and they can be changed just by changing the policy database without
* touching the code using the stream.
*
* There are two ways secure streams interfaces to user code:
*
* 1) [Linux / RTOS] the natural, smallest interface is to call back to user
* code that only operates directly from the lws event loop thread context
* (direct callbacks from lws_ss_t)
*
* lws_thread( [user code] ---- lws )
*
* 2) [Linux] where the user code is in a different process and communicates
* asynchronously via a proxy socket
*
* user_process{ [user code] | shim | socket-}------ lws_process{ lws }
*
* In the second, IPC, case, all packets are prepended by one or more bytes
* indicating the packet type and serializing any associated data, known as
* Serialized Secure Streams or SSS.
*
* Serialized Secure Streams
* -------------------------
*
* On the transport, adjacent packets may be coalesced, that is, the original
* packet sizes are lost and two or more packets are combined. For that reason
* the serialization format always contains a 1-byte type and then a 2-byte
* frame length.
*
* Client to proxy
*
* - Proxied connection setup
*
* - 0: LWSSS_SER_TXPRE_STREAMTYPE
* - 1: 2-byte MSB-first rest-of-frame length
* - 3: 4 byte MSB-first initial tx credit
* - 7: the streamtype name with no NUL
*
* - Proxied tx
*
* - 0: LWSSS_SER_TXPRE_TX_PAYLOAD
* - 1: 2 byte MSB-first rest-of-frame length
* - 3: 4-byte MSB-first flags
* - 7: 4-byte MSB-first us between client requested write and wrote to proxy
* - 11: 8-byte MSB-first us resolution unix time client wrote to proxy
* - 17: payload
*
* - Proxied secure stream destroy
*
* - 0: LWSSS_SER_TXPRE_DESTROYING
* - 1: 00, 00
*
* - Proxied metadata - sent when one metadata item set clientside
*
* - 0: LWSSS_SER_TXPRE_METADATA
* - 1: 2-byte MSB-first rest-of-frame length
* - 2: 1-byte metadata name length
* - 3: metadata name
* - ...: metadata value (for rest of packet)
*
* Proxy to client
*
* - Proxied connection setup result
*
* - 0: LWSSS_SER_RXPRE_CREATE_RESULT
* - 1: 2 byte MSB-first rest-of-frame length (usually 00, 03)
* - 3: 1 byte result, 0 = success. On failure, proxy will close connection.
* - 4: 2 byte MSB-first initial tx credit
* - 6: if present, comma-sep list of rideshare types from policy
*
* - Proxied rx
*
* - 0: LWSSS_SER_RXPRE_RX_PAYLOAD
* - 1: 2 byte MSB-first rest-of-frame length
* - 3: 4-byte MSB-first flags
* - 7: 4-byte MSB-first us between inbound read and wrote to client
* - 11: 8-byte MSB-first us resolution unix time proxy wrote to client
* - 17: (rideshare name len + rideshare name if flags & LWSSS_FLAG_RIDESHARE)
* payload
*
* - Proxied tx credit
*
* - 0: LWSSS_SER_RXPRE_TXCR_UPDATE
* - 1: 00, 04
* - 3: 4-byte MSB-first addition tx credit bytes
*
* - Proxied state
*
* - 0: LWSSS_SER_RXPRE_CONNSTATE
* - 1: 00, 05
* - 3: 1 byte state index
* - 7: 4-byte MSB-first ordinal
*
*
* Proxied tx may be read by the proxy but rejected due to lack of buffer space
* at the proxy. For that reason, tx must be held at the sender until it has
* been acknowledged or denied.
*
* Sinks
* -----
*
* Sinks are logical "servers", you can register as a sink for a particular
* streamtype by using the lws_ss_create() api with ssi->register_sink set to 1.
*
* For directly fulfilled Secure Streams, new streams of that streamtype bind
* to the rx, tx and state handlers given when it was registered.
*
* - When new streams are created the registered sink handler for (*state) is
* called with event LWSSSCS_SINK_JOIN and the new client stream handle in
* the h_src parameter.
*
* - When the client stream sends something to the sink, it calls the sink's
* (*rx) with the client stream's
*/
#define LWS_SS_MTU 1540
struct lws_ss_handle;
typedef uint32_t lws_ss_tx_ordinal_t;
/*
* connection state events
*/
typedef enum {
LWSSSCS_CREATING,
LWSSSCS_DISCONNECTED,
LWSSSCS_UNREACHABLE,
LWSSSCS_AUTH_FAILED,
LWSSSCS_CONNECTED,
LWSSSCS_CONNECTING,
LWSSSCS_DESTROYING,
LWSSSCS_POLL,
LWSSSCS_ALL_RETRIES_FAILED, /* all retries in bo policy failed */
LWSSSCS_QOS_ACK_REMOTE, /* remote peer received and acked tx */
LWSSSCS_QOS_NACK_REMOTE,
LWSSSCS_QOS_ACK_LOCAL, /* local proxy accepted our tx */
LWSSSCS_QOS_NACK_LOCAL, /* local proxy refused our tx */
LWSSSCS_SINK_JOIN, /* sinks get this when a new source
* stream joins the sink */
LWSSSCS_SINK_PART, /* sinks get this when a new source
* stream leaves the sink */
} lws_ss_constate_t;
enum {
LWSSS_FLAG_SOM = (1 << 0),
/* payload contains the start of new message */
LWSSS_FLAG_EOM = (1 << 1),
/* payload contains the end of message */
LWSSS_FLAG_POLL = (1 << 2),
/* Not a real transmit... poll for rx if protocol needs it */
LWSSS_FLAG_RELATED_START = (1 << 3),
/* Appears in a zero-length message indicating a message group of zero
* or more messages is now starting. */
LWSSS_FLAG_RELATED_END = (1 << 4),
/* Appears in a zero-length message indicating a message group of zero
* or more messages has now finished. */
LWSSS_FLAG_RIDESHARE = (1 << 5),
/* Serialized payload starts with non-default rideshare name length and
* name string without NUL, then payload */
/*
* In the case the secure stream is proxied across a process or thread
* boundary, eg by proxying through a socket for IPC, metadata must be
* carried in-band. A byte is prepended to each rx payload to
* differentiate what it is.
*
* Secure streams where the user is called back directly does not need
* any of this and only pure payloads are passed.
*
* rx (received by client) prepends for proxied connections
*/
LWSSS_SER_RXPRE_RX_PAYLOAD = 0x55,
LWSSS_SER_RXPRE_CREATE_RESULT,
LWSSS_SER_RXPRE_CONNSTATE,
LWSSS_SER_RXPRE_TXCR_UPDATE,
LWSSS_SER_RXPRE_TLSNEG_ENCLAVE_SIGN,
/* tx (send by client) prepends for proxied connections */
LWSSS_SER_TXPRE_STREAMTYPE = 0xaa,
LWSSS_SER_TXPRE_ONWARD_CONNECT,
LWSSS_SER_TXPRE_DESTROYING,
LWSSS_SER_TXPRE_TX_PAYLOAD,
LWSSS_SER_TXPRE_METADATA,
LWSSS_SER_TXPRE_TXCR_UPDATE,
LWSSS_SER_TXPRE_TLSNEG_ENCLAVE_SIGNED,
};
typedef enum {
LPCS_WAIT_INITIAL_TX = 1, /* after connect, must send streamtype */
LPCS_REPORTING_FAIL, /* stream creation failed, wait to to tell */
LPCS_REPORTING_OK, /* stream creation succeeded, wait to to tell */
LPCS_OPERATIONAL, /* ready for payloads */
LPCS_DESTROYED,
LPCS_SENDING_INITIAL_TX = 1, /* after connect, must send streamtype */
LPCS_WAITING_CREATE_RESULT, /* wait to hear if proxy ss create OK */
LPCS_LOCAL_CONNECTED, /* we are in touch with the proxy */
LPCS_ONWARD_CONNECT, /* request onward ss connection */
} lws_ss_conn_states_t;
/**
* lws_ss_info_t: information about stream to be created
*
* Prepare this struct with information about what the stream type is and how
* the stream should interface with your code, and pass it to lws_ss_create()
* to create the requested stream.
*/
typedef struct lws_ss_info {
const char *streamtype; /**< type of stream we want to create */
size_t user_alloc; /**< size of user allocation */
size_t handle_offset; /**< offset of handle stg in user_alloc type,
set to offsetof(mytype, my_handle_member) */
size_t opaque_user_data_offset;
/**< offset of opaque user data ptr in user_alloc type, set to
offsetof(mytype, opaque_ud_member) */
int (*rx)(void *userobj, const uint8_t *buf, size_t len,
int flags);
/**< callback with rx payload for this stream */
int (*tx)(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags);
/**< callback to send payload on this stream... 0 = send as set in
* len and flags, 1 = do not send anything (ie, not even 0 len frame) */
int (*state)(void *userobj, void *h_src /* ss handle type */,
lws_ss_constate_t state, lws_ss_tx_ordinal_t ack);
/**< advisory cb about state of stream and QoS status if applicable...
* h_src is only used with sinks and LWSSSCS_SINK_JOIN/_PART events.
* Return nonzero to indicate you want to destroy the stream. */
int manual_initial_tx_credit;
/**< 0 = manage any tx credit automatically, nonzero explicitly sets the
* peer stream to have the given amount of tx credit, if the protocol
* can support it. */
char register_sink;
/**< If set, we're not creating a specific stream, but registering
* ourselves as the "sink" for .streamtype. It's analogous to saying
* we want to be the many-to-one "server" for .streamtype; when other
* streams are created with that streamtype, they should be forwarded
* to this stream owner, where they join and part from the sink via
* (*state) LWSSSCS_SINK_JOIN / _PART events, the new client handle
* being provided in the h_src parameter.
*/
} lws_ss_info_t;
/**
* lws_ss_create() - Create secure stream
*
* \param context: the lws context to create this inside
* \param tsi: service thread index to create on (normally 0)
* \param ssi: pointer to lws_ss_info_t filled in with info about desired stream
* \param opaque_user_data: opaque data to set in the stream's user object
* \param ppss: pointer to secure stream handle pointer set on exit
* \param ppayload_fmt: NULL or pointer to a string ptr to take payload format
* name from the policy
*
* Requests a new secure stream described by \p ssi be created. If successful,
* the stream is created, its state callback called with LWSSSCS_CREATING, *ppss
* is set to point to the handle, and it returns 0. If it failed, it returns
* nonzero.
*
* Along with the opaque stream object, streams overallocate
*
* 1) a user data struct whose size is set in ssi
* 2) nauth plugin instantiation data (size set in the plugin struct)
* 3) sauth plugin instantiation data (size set in the plugin struct)
* 4) space for a copy of the stream type name
*
* The user data struct is initialized to all zeros, then the .handle_offset and
* .opaque_user_data_offset fields of the ssi are used to prepare the user data
* struct with the ss handle that was created, and a copy of the
* opaque_user_data pointer given as an argument.
*
* If you want to set up the stream with specific information, point to it in
* opaque_user_data and use the copy of that pointer in your user data member
* for it starting from the LWSSSCS_CREATING state call.
*
* Since different endpoints chosen by the policy may require different payload
* formats, \p ppayload_fmt is set to point to the name of the needed payload
* format from the policy database if non-NULL.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, struct lws_ss_handle **ppss,
struct lws_sequencer *seq_owner, const char **ppayload_fmt);
/**
* lws_ss_destroy() - Destroy secure stream
*
* \param ppss: pointer to lws_ss_t pointer to be destroyed
*
* Destroys the lws_ss_t pointed to by *ppss, and sets *ppss to NULL.
*/
LWS_VISIBLE LWS_EXTERN void
lws_ss_destroy(struct lws_ss_handle **ppss);
/**
* lws_ss_request_tx() - Schedule stream for tx
*
* \param pss: pointer to lws_ss_t representing stream that wants to transmit
*
* Schedules a write on the stream represented by \p pss. When it's possible to
* write on this stream, the *tx callback will occur with an empty buffer for
* the stream owner to fill in.
*/
LWS_VISIBLE LWS_EXTERN void
lws_ss_request_tx(struct lws_ss_handle *pss);
/**
* lws_ss_request_tx() - Schedule stream for tx
*
* \param pss: pointer to lws_ss_t representing stream that wants to transmit
* \param len: the length of the write in bytes
*
* Schedules a write on the stream represented by \p pss. When it's possible to
* write on this stream, the *tx callback will occur with an empty buffer for
* the stream owner to fill in.
*
* This api variant should be used when it's possible the payload will go out
* over h1 with x-web-form-urlencoded or similar Content-Type.
*/
LWS_VISIBLE LWS_EXTERN void
lws_ss_request_tx_len(struct lws_ss_handle *pss, unsigned long len);
/**
* lws_ss_client_connect() - Attempt the client connect
*
* \param h: secure streams handle
*
* Starts the connection process for the secure stream. Returns 0 if OK or
* nonzero if we have already failed.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_client_connect(struct lws_ss_handle *h);
/**
* lws_ss_get_sequencer() - Return parent sequencer pointer if any
*
* \param h: secure streams handle
*
* Returns NULL if the secure stream is not associated with a sequencer.
* Otherwise returns a pointer to the owning sequencer. You can use this to
* identify which sequencer to direct messages to, from the secure stream
* callback.
*/
LWS_VISIBLE LWS_EXTERN struct lws_sequencer *
lws_ss_get_sequencer(struct lws_ss_handle *h);
/**
* lws_ss_proxy_create() - Start a unix domain socket proxy for Secure Streams
*
* \param context: lws_context
* \param bind: if port is 0, unix domain path with leading @ for abstract.
* if port nonzero, NULL, or network interface to bind listen to
* \param port: tcp port to listen on
*
* Creates a vhost that listens either on an abstract namespace unix domain
* socket (port = 0) or a tcp listen socket (port nonzero). If bind is NULL
* and port is 0, the abstract unix domain socket defaults to "proxy.ss.lws".
*
* Client connections to this proxy to Secure Streams are fulfilled using the
* policy local to the proxy and the data passed between the client and the
* proxy using serialized Secure Streams protocol.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_proxy_create(struct lws_context *context, const char *bind, int port);
/**
* lws_ss_state_name() - convenience helper to get a printable conn state name
*
* \param state: the connection state index
*
* Returns a printable name for the connection state index passed in.
*/
LWS_VISIBLE LWS_EXTERN const char *
lws_ss_state_name(int state);
/**
* lws_ss_get_context() - convenience helper to recover the lws context
*
* \param h: secure streams handle
*
* Returns the lws context. Dispenses with the need to pass a copy of it into
* your secure streams handler.
*/
LWS_VISIBLE LWS_EXTERN struct lws_context *
lws_ss_get_context(struct lws_ss_handle *h);
/**
* lws_ss_rideshare() - find the current streamtype when types rideshare
*
* \param h: the stream handle
*
* Under some conditions, the payloads may be structured using protocol-
* specific formatting, eg, http multipart mime. It's possible to map the
* logical partitions in the payload to different stream types using
* the policy "rideshare" feature.
*
* This api lets the callback code find out which rideshare stream type the
* current payload chunk belongs to.
*/
LWS_VISIBLE LWS_EXTERN const char *
lws_ss_rideshare(struct lws_ss_handle *h);
/**
* lws_ss_set_metadata() - allow user to bind external data to defined ss metadata
*
* \param h: secure streams handle
* \param name: metadata name from the policy
* \param value: pointer to user-managed data to bind to name
* \param len: length of the user-managed data in value
*
* Binds user-managed data to the named metadata item from the ss policy.
* If present, the metadata item is handled in a protocol-specific way using
* the associated policy information. For example, in the policy
*
* "\"metadata\":" "["
* "{\"uptag\":" "\"X-Upload-Tag:\"},"
* "{\"ctype\":" "\"Content-Type:\"},"
* "{\"xctype\":" "\"\"}"
* "],"
*
* when the policy is using h1 is interpreted to add h1 headers of the given
* name with the value of the metadata on the left.
*
* Return 0 if OK or nonzero if, eg, metadata name does not exist on the
* streamtype.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_set_metadata(struct lws_ss_handle *h, const char *name,
void *value, size_t len);
/**
* lws_ss_add_peer_tx_credit() - allow peer to transmit more to us
*
* \param h: secure streams handle
* \param add: additional tx credit (signed)
*
* Indicate to remote peer that we can accept \p add bytes more payload being
* sent to us.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t add);
/**
* lws_ss_get_est_peer_tx_credit() - get our current estimate of peer's tx credit
*
* \param h: secure streams handle
*
* Based on what credit we gave it, and what we have received, report our
* estimate of peer's tx credit usable to transmit to us. This may be outdated
* in that some or all of its credit may already have been expended by sending
* stuff to us that is in flight already.
*/
LWS_VISIBLE LWS_EXTERN int
lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h);

View File

@ -49,6 +49,14 @@ typedef enum {
LWSSEQ_WSI_CONN_FAIL, /* wsi we bound to us has failed to connect */
LWSSEQ_WSI_CONN_CLOSE, /* wsi we bound to us has closed */
LWSSEQ_SS_STATE_BASE, /* secure streams owned by a sequencer provide
* automatic messages about state changes on
* the sequencer, passing the oridinal in the
* event argument field. The message index is
* LWSSEQ_SS_STATE_BASE + the enum from
* lws_ss_constate_t */
LWSSEQ_USER_BASE = 100 /* define your events from here */
} lws_seq_events_t;

View File

@ -234,8 +234,9 @@ __lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason,
const char *caller)
{
struct lws_context_per_thread *pt;
struct lws *wsi1, *wsi2;
const struct lws_protocols *pro;
struct lws_context *context;
struct lws *wsi1, *wsi2;
int n, ccb;
lwsl_info("%s: %p: caller: %s\n", __func__, wsi, caller);
@ -585,8 +586,15 @@ just_kill_connection:
*/
ccb = 1;
pro = wsi->protocol;
#if defined(LWS_WITH_CLIENT)
if (!ccb && (lwsi_state_PRE_CLOSE(wsi) & LWSIFS_NOT_EST) &&
lwsi_role_client(wsi)) {
lws_inform_client_conn_fail(wsi, "Closed before conn", 18);
}
#endif
if (ccb) {
const struct lws_protocols *pro = wsi->protocol;
if (!wsi->protocol && wsi->vhost && wsi->vhost->protocols)
pro = &wsi->vhost->protocols[0];

View File

@ -313,8 +313,14 @@ lws_client_connect_via_info(const struct lws_client_connect_info *i)
i->uri_replace_to);
#endif
if (i->method && (!strcmp(i->method, "RAW") ||
!strcmp(i->method, "MQTT"))) {
if (i->method && (!strcmp(i->method, "RAW") // ||
// !strcmp(i->method, "MQTT")
)) {
/*
* Not for MQTT here, since we don't know if we will
* pipeline it or not...
*/
#if defined(LWS_WITH_TLS)

View File

@ -213,7 +213,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
memset(&sin, 0, sizeof(sin));
#if defined(LWS_WITH_UNIX_SOCK)
if (LWS_UNIX_SOCK_ENABLED(vhost)) {
if (!port && LWS_UNIX_SOCK_ENABLED(vhost)) {
v = (struct sockaddr *)&serv_unix;
n = sizeof(struct sockaddr_un);
memset(&serv_unix, 0, sizeof(serv_unix));
@ -313,7 +313,7 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port,
}
#if defined(LWS_WITH_UNIX_SOCK)
if (LWS_UNIX_SOCK_ENABLED(vhost)) {
if (!port && LWS_UNIX_SOCK_ENABLED(vhost)) {
uid_t uid = vhost->context->uid;
gid_t gid = vhost->context->gid;

View File

@ -264,10 +264,10 @@ lws_write(struct lws *wsi, unsigned char *buf, size_t len,
wsi->detlat.acc_size = m;
wsi->detlat.type = LDLT_WRITE;
if (wsi->detlat.earliest_write_req_pre_write)
wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] =
wsi->detlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] =
us - wsi->detlat.earliest_write_req_pre_write;
else
wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] = 0;
wsi->detlat.latencies[LAT_DUR_PROXY_PROXY_REQ_TO_WRITE] = 0;
wsi->detlat.latencies[LAT_DUR_USERCB] = lws_now_usecs() - us;
lws_det_lat_cb(wsi->context, &wsi->detlat);

View File

@ -359,6 +359,15 @@ struct lws_context_per_thread {
struct lws_dll2_owner seq_owner; /* list of lws_sequencer-s */
lws_dll2_owner_t attach_owner; /* pending lws_attach */
#if defined(LWS_WITH_SECURE_STREAMS)
lws_dll2_owner_t ss_owner;
#endif
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API) || \
defined(LWS_WITH_SECURE_STREAMS_THREAD_API)
lws_dll2_owner_t ss_dsh_owner;
lws_dll2_owner_t ss_client_owner;
#endif
struct lws_dll2_owner pt_sul_owner;
#if defined (LWS_WITH_SEQUENCER)
@ -588,9 +597,10 @@ struct lws_vhost {
int log_fd;
#endif
unsigned int allocated_vhost_protocols:1;
unsigned int created_vhost_protocols:1;
unsigned int being_destroyed:1;
uint8_t allocated_vhost_protocols:1;
uint8_t created_vhost_protocols:1;
uint8_t being_destroyed:1;
uint8_t from_ss_policy:1;
unsigned char default_protocol_index;
unsigned char raw_protocol_index;
@ -1150,6 +1160,9 @@ lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len);
lws_usec_t
__lws_seq_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow);
lws_usec_t
__lws_ss_timeout_check(struct lws_context_per_thread *pt, lws_usec_t usnow);
struct lws * LWS_WARN_UNUSED_RESULT
lws_client_connect_2_dnsreq(struct lws *wsi);

View File

@ -86,7 +86,7 @@ lws_set_socks(struct lws_vhost *vhost, const char *socks)
}
}
lwsl_notice("%s: Connections via Socks5 %s:%u\n", __func__,
lwsl_debug("%s: Connections via Socks5 %s:%u\n", __func__,
vhost->socks_proxy_address, vhost->socks_proxy_port);
return 0;

View File

@ -71,6 +71,24 @@ const struct lws_protocols *available_abstract_protocols[] = {
};
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
const struct lws_protocols *available_secstream_protocols[] = {
#if defined(LWS_ROLE_H1)
&protocol_secstream_h1,
#endif
#if defined(LWS_ROLE_H2)
&protocol_secstream_h2,
#endif
#if defined(LWS_ROLE_WS)
&protocol_secstream_ws,
#endif
#if defined(LWS_ROLE_MQTT)
&protocol_secstream_mqtt,
#endif
NULL
};
#endif
static const char * const mount_protocols[] = {
"http://",
"https://",
@ -456,7 +474,7 @@ lws_create_vhost(struct lws_context *context,
struct lws_plugin *plugin = context->plugin_list;
#endif
struct lws_protocols *lwsp;
int m, f = !info->pvo, fx = 0, abs_pcol_count = 0;
int m, f = !info->pvo, fx = 0, abs_pcol_count = 0, sec_pcol_count = 0;
char buf[96];
#if ((defined(LWS_CLIENT_HTTP_PROXYING) && defined(LWS_WITH_CLIENT)) \
|| defined(LWS_WITH_SOCKS5)) && defined(LWS_HAVE_GETENV)
@ -581,6 +599,9 @@ lws_create_vhost(struct lws_context *context,
#if defined(LWS_WITH_ABSTRACT)
abs_pcol_count = (int)LWS_ARRAY_SIZE(available_abstract_protocols) - 1;
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
sec_pcol_count = (int)LWS_ARRAY_SIZE(available_secstream_protocols) - 1;
#endif
/*
* give the vhost a unified list of protocols including:
@ -592,7 +613,7 @@ lws_create_vhost(struct lws_context *context,
*/
lwsp = lws_zalloc(sizeof(struct lws_protocols) *
(vh->count_protocols +
abs_pcol_count +
abs_pcol_count + sec_pcol_count +
context->plugin_protocol_count +
fx + 1),
"vhost-specific plugin table");
@ -633,6 +654,14 @@ lws_create_vhost(struct lws_context *context,
}
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
for (n = 0; n < sec_pcol_count; n++) {
memcpy(&lwsp[m++], available_secstream_protocols[n],
sizeof(*lwsp));
vh->count_protocols++;
}
#endif
/*
* 3: For compatibility, all protocols enabled on vhost if only
* the default vhost exists. Otherwise only vhosts who ask
@ -1287,6 +1316,8 @@ lws_vhost_destroy(struct lws_vhost *vh)
lws_vhost_destroy1(vh);
lwsl_debug("%s: count_bound_wsi %d\n", __func__, vh->count_bound_wsi);
if (!vh->count_bound_wsi) {
/*
* After listen handoff, there are already no wsi bound to this
@ -1406,6 +1437,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)
return ACTIVE_CONNS_QUEUED;
}
#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)
if (wsi->mux.parent_wsi) {
/*
* We already decided...
@ -1415,6 +1447,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)
return ACTIVE_CONNS_MUXED;
}
#endif
lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */
@ -1448,7 +1481,7 @@ lws_vhost_active_conns(struct lws *wsi, struct lws **nwsi, const char *adsin)
* connection that it doesn't support pipelining...
*/
if (w->keepalive_rejected) {
lwsl_info("defeating pipelining due to no "
lwsl_notice("defeating pipelining due to no "
"keepalive on server\n");
goto solo;
}

View File

@ -294,7 +294,7 @@ lws_validity_cb(lws_sorted_usec_list_t *sul)
/* schedule a protocol-dependent ping */
lwsl_info("%s: wsi %p: scheduling validity check\n", __func__, wsi);
lwsl_notice("%s: wsi %p: scheduling validity check\n", __func__, wsi);
if (wsi->role_ops && wsi->role_ops->issue_keepalive)
wsi->role_ops->issue_keepalive(wsi, 0);

View File

@ -114,7 +114,7 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr,
lws_system_do_attach(&context->pt[n]);
#if defined(LWS_WITH_SYS_DHCP_CLIENT)
if (current == LWS_SYSTATE_DHCP) {
if (target == LWS_SYSTATE_DHCP) {
/*
* Don't let it past here until at least one iface has been
* configured for operation with DHCP
@ -125,6 +125,40 @@ lws_state_notify_protocol_init(struct lws_state_manager *mgr,
}
#endif
#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)
/*
* Skip this if we are running something without the policy for it
*/
if (target == LWS_SYSTATE_AUTH1 &&
context->pss_policies &&
!lws_system_blob_get_size(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
0))) {
lwsl_info("%s: AUTH1 state triggering api.amazon.com auth\n", __func__);
/*
* Start trying to acquire it if it's not already in progress
* returns nonzero if we determine it's not needed
*/
if (!lws_ss_sys_auth_api_amazon_com(context))
return 1;
}
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
/*
* Skip this if we are running something without the policy for it
*/
if (target == LWS_SYSTATE_POLICY_VALID &&
context->pss_policies && !context->policy_updated) {
/*
* Start trying to acquire it if it's not already in progress
* returns nonzero if we determine it's not needed
*/
if (!lws_ss_sys_fetch_policy(context))
return 1;
}
#endif
/* protocol part */
if (context->protocol_init_done)
@ -245,6 +279,15 @@ lws_create_context(const struct lws_context_creation_info *info)
__func__, context->udp_loss_sim_tx_pc,
context->udp_loss_sim_rx_pc);
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
context->ss_proxy_bind = info->ss_proxy_bind;
context->ss_proxy_port = info->ss_proxy_port;
context->ss_proxy_address = info->ss_proxy_address;
lwsl_notice("%s: using ss proxy bind '%s', port %d, ads '%s'\n",
__func__, context->ss_proxy_bind, context->ss_proxy_port,
context->ss_proxy_address);
#endif
#if defined(LWS_WITH_NETWORK)
context->count_threads = count_threads;
#if defined(LWS_WITH_DETAILED_LATENCY)
@ -258,6 +301,11 @@ lws_create_context(const struct lws_context_creation_info *info)
#endif
#endif
#if defined(LWS_WITH_SECURE_STREAMS)
context->pss_policies_json = info->pss_policies_json;
context->pss_plugins = info->pss_plugins;
#endif
/* if he gave us names, set the uid / gid */
if (lws_plat_drop_app_privileges(context, 0))
goto bail;
@ -634,7 +682,6 @@ lws_create_context(const struct lws_context_creation_info *info)
context->user_space = info->user;
#if defined(LWS_WITH_SERVER)
strcpy(context->canonical_hostname, "unknown");
#if defined(LWS_WITH_NETWORK)
@ -755,6 +802,33 @@ lws_create_context(const struct lws_context_creation_info *info)
goto fail_clean_pipes;
}
#if defined(LWS_WITH_SECURE_STREAMS)
if (context->pss_policies_json) {
/*
* You must create your context with the explicit vhosts flag
* in order to use secure streams
*/
assert(lws_check_opt(info->options,
LWS_SERVER_OPTION_EXPLICIT_VHOSTS));
if (lws_ss_policy_parse_begin(context))
goto bail;
n = lws_ss_policy_parse(context,
(uint8_t *)context->pss_policies_json,
strlen(context->pss_policies_json));
if (n != LEJP_CONTINUE && n < 0)
goto bail;
if (lws_ss_policy_set(context, "hardcoded")) {
lwsl_err("%s: policy set failed\n", __func__);
goto bail;
}
} else
lws_create_vhost(context, info);
#endif
lws_context_init_extensions(info, context);
lwsl_info(" mem: per-conn: %5lu bytes + protocol rx buf\n",
@ -859,9 +933,10 @@ static void
lws_context_destroy3(struct lws_context *context)
{
struct lws_context **pcontext_finalize = context->pcontext_finalize;
#if defined(LWS_WITH_NETWORK)
int n;
#if defined(LWS_WITH_NETWORK)
lwsl_debug("%s\n", __func__);
for (n = 0; n < context->count_threads; n++) {
@ -905,6 +980,10 @@ lws_context_destroy3(struct lws_context *context)
compatible_close(context->latencies_fd);
#endif
for (n = 0; n < LWS_SYSBLOB_TYPE_COUNT; n++)
lws_system_blob_destroy(
lws_system_get_blob(context, n, 0));
lws_free(context);
lwsl_info("%s: ctx %p freed\n", __func__, context);
@ -921,6 +1000,7 @@ lws_context_destroy2(struct lws_context *context)
{
#if defined(LWS_WITH_NETWORK)
struct lws_vhost *vh = NULL, *vh1;
int n;
#endif
#if defined(LWS_WITH_PEER_LIMITS)
uint32_t nu;
@ -932,6 +1012,48 @@ lws_context_destroy2(struct lws_context *context)
context->being_destroyed2 = 1;
#if defined(LWS_WITH_NETWORK)
/*
* We're going to trash things like vhost-protocols
* So we need to finish dealing with wsi close that
* might make callbacks first
*/
for (n = 0; n < context->count_threads; n++) {
struct lws_context_per_thread *pt = &context->pt[n];
(void)pt;
#if defined(LWS_WITH_SECURE_STREAMS)
lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll);
if (context->ac_policy)
lwsac_free(&context->ac_policy);
#endif
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
lws_dll2_foreach_safe(&pt->ss_client_owner, NULL, lws_sspc_destroy_dll);
#endif
#if defined(LWS_WITH_SEQUENCER)
lws_seq_destroy_all_on_pt(pt);
#endif
LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) {
if (ar->pt_init_destroy)
ar->pt_init_destroy(context, NULL, pt, 1);
} LWS_FOR_EVERY_AVAILABLE_ROLE_END;
#if defined(LWS_WITH_CGI)
role_ops_cgi.pt_init_destroy(context, NULL, pt, 1);
#endif
if (context->event_loop_ops->destroy_pt)
context->event_loop_ops->destroy_pt(context, n);
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
while (pt->http.ah_list)
_lws_destroy_ah(pt, pt->http.ah_list);
#endif
}
/*
* free all the per-vhost allocations
*/
@ -982,6 +1104,7 @@ lws_context_destroy2(struct lws_context *context)
lws_check_deferred_free(context, 0, 1);
#endif
#if LWS_MAX_SMP > 1
lws_mutex_refcount_destroy(&context->mr);
#endif
@ -1185,3 +1308,13 @@ out:
context->inside_context_destroy = 0;
#endif
}
struct lws_context *
lws_system_context_from_system_mgr(lws_state_manager_t *mgr)
{
#if defined(LWS_WITH_NETWORK)
return lws_container_of(mgr, struct lws_context, mgr_system);
#else
return NULL;
#endif
}

View File

@ -197,6 +197,9 @@ struct lws;
#if defined(LWS_WITH_NETWORK)
#include "private-lib-event-libs.h"
#if defined(LWS_WITH_SECURE_STREAMS)
#include "private-lib-secure-streams.h"
#endif
struct lws_io_watcher {
#ifdef LWS_WITH_LIBEV
@ -357,6 +360,14 @@ struct lws_context {
lws_async_dns_t async_dns;
#endif
#if defined(LWS_WITH_SECURE_STREAMS_SYS_AUTH_API_AMAZON_COM)
void *pol_args;
struct lws_ss_handle *hss_auth;
struct lws_ss_handle *hss_fetch_policy;
lws_sorted_usec_list_t sul_api_amazon_com;
lws_sorted_usec_list_t sul_api_amazon_com_kick;
#endif
lws_state_manager_t mgr_system;
lws_state_notify_link_t protocols_notify;
#if defined (LWS_WITH_SYS_DHCP_CLIENT)
@ -397,6 +408,11 @@ struct lws_context {
#endif
#endif /* NETWORK */
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
const char *ss_proxy_bind;
const char *ss_proxy_address;
#endif
#if defined(LWS_WITH_FILE_OPS)
const struct lws_plat_file_ops *fops;
#endif
@ -425,6 +441,14 @@ struct lws_context {
#endif
const lws_system_ops_t *system_ops;
#if defined(LWS_WITH_SECURE_STREAMS)
const char *pss_policies_json;
const lws_ss_policy_t *pss_policies;
const lws_ss_plugin_t **pss_plugins;
struct lwsac *ac_policy;
#endif
void *external_baggage_free_on_destroy;
const struct lws_token_limits *token_limits;
void *user_space;
@ -487,6 +511,7 @@ struct lws_context {
unsigned int done_protocol_destroy_cb:1;
unsigned int finalize_destroy_after_internal_loops_stopped:1;
unsigned int max_fds_unrelated_to_ulimit:1;
unsigned int policy_updated:1;
short count_threads;
short plugin_protocol_count;
@ -494,6 +519,9 @@ struct lws_context {
short server_string_len;
unsigned short ws_ping_pong_interval;
unsigned short deprecation_pending_listen_close_count;
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
uint16_t ss_proxy_port;
#endif
uint8_t max_fi;
uint8_t udp_loss_sim_tx_pc;

View File

@ -146,6 +146,7 @@ _lwsac_use(struct lwsac **head, size_t ensure, size_t chunk_size, char backfill)
if (al >= alloc - hp)
alloc = al + hp;
lwsl_debug("%s: alloc %d for %d\n", __func__, (int)alloc, (int)ensure);
bf = malloc(alloc);
if (!bf) {
lwsl_err("%s: OOM trying to alloc %llud\n", __func__,

View File

@ -2611,7 +2611,7 @@ lws_h2_client_stream_long_poll_rxonly(struct lws *wsi)
wsi->h2.long_poll = 1;
wsi->h2.send_END_STREAM = 1;
lws_header_table_detach(wsi, 0);
// lws_header_table_detach(wsi, 0);
lws_callback_on_writable(wsi);

View File

@ -200,6 +200,31 @@ send_hs:
else {
/* for a method = "RAW" connection, this makes us
* established */
#if defined(LWS_WITH_TLS)
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
/* we can retry this... just cook the SSL BIO the first time */
if (lws_ssl_client_bio_create(wsi) < 0) {
lwsl_err("%s: bio_create failed\n", __func__);
goto failed;
}
//#if !defined(LWS_WITH_SYS_ASYNC_DNS)
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
n = lws_ssl_client_connect1(wsi);
if (!n)
return wsi;
if (n < 0) {
lwsl_err("%s: lws_ssl_client_connect1 failed\n", __func__);
goto failed;
}
}
//#endif
}
#endif
#if 0
#if defined(LWS_WITH_SYS_ASYNC_DNS)
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {

View File

@ -286,10 +286,10 @@ reset:
lws_pt_unlock(pt);
#if defined(LWS_WITH_CLIENT)
#if defined(LWS_ROLE_MQTT)
connect_via_info2:
#endif
#if defined(LWS_WITH_CLIENT)
if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED)
if (!lws_http_client_connect_via_info2(wsi))
/* our client connect has failed, the wsi
@ -349,6 +349,7 @@ int __lws_header_table_detach(struct lws *wsi, int autoservice)
lws_peer_track_ah_detach(context, wsi->peer);
#endif
ah->wsi = NULL; /* no owner */
wsi->http.ah = NULL;
pwsi = &pt->http.ah_wait_list;

View File

@ -1900,8 +1900,8 @@ lws_http_to_fallback(struct lws *wsi, unsigned char *obuf, size_t olen)
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
if (wsi->role_ops->adoption_cb[lwsi_role_server(wsi)])
n = wsi->role_ops->adoption_cb[lwsi_role_server(wsi)];
if (wsi->role_ops->adoption_cb[0])
n = wsi->role_ops->adoption_cb[0];
ipbuf[0] = '\0';
#if !defined(LWS_PLAT_OPTEE)

View File

@ -1705,6 +1705,8 @@ lws_mqtt_client_send_publish(struct lws *wsi, lws_mqtt_publish_param_t *pub,
do_write:
// lwsl_hexdump_err(start, lws_ptr_diff(p, start));
if (lws_write(nwsi, start, lws_ptr_diff(p, start), LWS_WRITE_BINARY) !=
lws_ptr_diff(p, start)) {
lwsl_err("%s: write failed\n", __func__);

View File

@ -0,0 +1,398 @@
# Secure Streams
Secure Streams is a client api that strictly separates payload from any metadata.
That includes the endpoint address for the connection, the tls CA 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.
![overview](../doc-assets/ss-explain.png)
# 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
### `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"
### `s`
These are an array of policies for the supported stream type names.
### `endpoint`
The DNS address the secure stream should connect to
### `port`
The port number as an integer on the endpoint to connect to
### `protocol`
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|
### `plugins`
Array of plugin names to apply to the stream, if any
### `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
### `tls_trust_store`
The name of the trust store described in the `trust_stores` section to apply
to validate the remote server cert.
## http transport
### `http_method`
HTTP method to use with http-related protocols, like GET or POST.
Not required for ws.
### `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_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_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
### `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`
Name of the ws subprotocol to use.
### `ws_binary`
Use if the ws messages are binary
## 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
## 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.
## 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).
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_SOCKS=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.

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
};

942
lib/secure-streams/policy.c Normal file
View File

@ -0,0 +1,942 @@
/*
* 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 backoffs {
struct backoffs *next;
const char *name;
lws_retry_bo_t r;
} backoff_t;
static const char * const lejp_tokens_policy[] = {
"release",
"product",
"schema-version",
"via-socks5",
"retry[].*.backoff",
"retry[].*.conceal",
"retry[].*.jitterpc",
"retry[].*.svalidping",
"retry[].*.svalidhup",
"retry[].*",
"certs[].*",
"trust_stores[].name",
"trust_stores[].stack",
"s[].*.endpoint",
"s[].*.via-socks5",
"s[].*.protocol",
"s[].*.port",
"s[].*.plugins",
"s[].*.tls",
"s[].*.client_cert",
"s[].*.opportunistic",
"s[].*.nailed_up",
"s[].*.urgent_tx",
"s[].*.urgent_rx",
"s[].*.long_poll",
"s[].*.retry",
"s[].*.tls_trust_store",
"s[].*.metadata",
"s[].*.metadata[].*",
"s[].*.http_auth_header",
"s[].*.http_dsn_header",
"s[].*.http_fwv_header",
"s[].*.http_devtype_header",
"s[].*.http_auth_preamble",
"s[].*.http_no_content_length",
"s[].*.rideshare", /* streamtype name this rides shotgun with */
"s[].*.payload_fmt",
"s[].*.http_method",
"s[].*.http_url",
"s[].*.nghttp2_quirk_end_stream",
"s[].*.h2q_oflow_txcr",
"s[].*.http_multipart_name",
"s[].*.http_multipart_filename",
"s[].*.http_mime_content_type",
"s[].*.http_www_form_urlencoded",
"s[].*.ws_subprotocol",
"s[].*.ws_binary",
"s[].*.local_sink",
"s[].*.mqtt_topic",
"s[].*.mqtt_subscribe",
"s[].*.mqtt_qos",
"s[].*",
};
typedef enum {
LSSPPT_RELEASE,
LSSPPT_PRODUCT,
LSSPPT_SCHEMA_VERSION,
LSSPPT_VIA_SOCKS5,
LSSPPT_BACKOFF,
LSSPPT_CONCEAL,
LSSPPT_JITTERPC,
LSSPPT_VALIDPING_S,
LSSPPT_VALIDHUP_S,
LSSPPT_RETRY,
LSSPPT_CERTS,
LSSPPT_TRUST_STORES_NAME,
LSSPPT_TRUST_STORES_STACK,
LSSPPT_ENDPOINT,
LSSPPT_VH_VIA_SOCKS5,
LSSPPT_PROTOCOL,
LSSPPT_PORT,
LSSPPT_PLUGINS,
LSSPPT_TLS,
LSSPPT_TLS_CLIENT_CERT,
LSSPPT_OPPORTUNISTIC,
LSSPPT_NAILED_UP,
LSSPPT_URGENT_TX,
LSSPPT_URGENT_RX,
LSSPPT_LONG_POLL,
LSSPPT_RETRYPTR,
LSSPPT_TRUST,
LSSPPT_METADATA,
LSSPPT_METADATA_ITEM,
LSSPPT_HTTP_AUTH_HEADER,
LSSPPT_HTTP_DSN_HEADER,
LSSPPT_HTTP_FWV_HEADER,
LSSPPT_HTTP_TYPE_HEADER,
LSSPPT_HTTP_AUTH_PREAMBLE,
LSSPPT_HTTP_NO_CONTENT_LENGTH,
LSSPPT_RIDESHARE,
LSSPPT_PAYLOAD_FORMAT,
LSSPPT_HTTP_METHOD,
LSSPPT_HTTP_URL,
LSSPPT_NGHTTP2_QUIRK_END_STREAM,
LSSPPT_H2_QUIRK_OVERFLOWS_TXCR,
LSSPPT_HTTP_MULTIPART_NAME,
LSSPPT_HTTP_MULTIPART_FILENAME,
LSSPPT_HTTP_MULTIPART_CONTENT_TYPE,
LSSPPT_HTTP_WWW_FORM_URLENCODED,
LSSPPT_WS_SUBPROTOCOL,
LSSPPT_WS_BINARY,
LSSPPT_LOCAL_SINK,
LSSPPT_MQTT_TOPIC,
LSSPPT_MQTT_SUBSCRIBE,
LSSPPT_MQTT_QOS,
LSSPPT_STREAMTYPES
} policy_token_t;
union u {
backoff_t *b;
lws_ss_x509_t *x;
lws_ss_trust_store_t *t;
lws_ss_policy_t *p;
};
enum {
LTY_BACKOFF,
LTY_X509,
LTY_TRUSTSTORE,
LTY_POLICY,
_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;
union u heads[_LTY_COUNT];
union u curr[_LTY_COUNT];
uint8_t *p;
int count;
};
#define POL_AC_INITIAL 2048
#define POL_AC_GRAIN 800
#define MAX_CERT_TEMP 2048 /* used to discover actual cert size for realloc */
static uint8_t sizes[] = {
sizeof(backoff_t),
sizeof(lws_ss_x509_t),
sizeof(lws_ss_trust_store_t),
sizeof(lws_ss_policy_t),
};
static const char *protonames[] = {
"h1", /* LWSSSP_H1 */
"h2", /* LWSSSP_H2 */
"ws", /* LWSSSP_WS */
"mqtt", /* LWSSSP_MQTT */
};
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;
}
int
lws_ss_set_metadata(struct lws_ss_handle *h, const char *name,
void *value, size_t len)
{
lws_ss_metadata_t *omd = lws_ss_policy_metadata(h->policy, name);
if (!omd) {
lwsl_err("%s: unknown metadata %s\n", __func__, name);
return 1;
}
h->metadata[omd->length].name = name;
h->metadata[omd->length].value = value;
h->metadata[omd->length].length = len;
return 0;
}
lws_ss_metadata_t *
lws_ss_get_handle_metadata(struct lws_ss_handle *h, const char *name)
{
lws_ss_metadata_t *omd = lws_ss_policy_metadata(h->policy, name);
if (!omd)
return NULL;
return &h->metadata[omd->length];
}
static signed char
lws_ss_policy_parser_cb(struct lejp_ctx *ctx, char reason)
{
struct policy_cb_args *a = (struct policy_cb_args *)ctx->user;
const lws_ss_plugin_t **pin;
char **pp, dotstar[32], *q;
lws_ss_trust_store_t *ts;
lws_ss_metadata_t *pmd;
lws_retry_bo_t *b;
size_t inl, outl;
lws_ss_x509_t *x;
uint8_t *extant;
backoff_t *bot;
int n = -1;
lwsl_debug("%s: %d %d %s\n", __func__, reason, ctx->path_match - 1,
ctx->path);
switch (ctx->path_match - 1) {
case LSSPPT_RETRY:
n = LTY_BACKOFF;
break;
case LSSPPT_CERTS:
n = LTY_X509;
break;
case LSSPPT_TRUST_STORES_NAME:
case LSSPPT_TRUST_STORES_STACK:
n = LTY_TRUSTSTORE;
break;
case LSSPPT_STREAMTYPES:
n = LTY_POLICY;
break;
}
if (reason == LEJPCB_ARRAY_START &&
(ctx->path_match - 1 == LSSPPT_PLUGINS ||
ctx->path_match - 1 == LSSPPT_METADATA))
a->count = 0;
if (reason == LEJPCB_ARRAY_END &&
ctx->path_match - 1 == LSSPPT_TRUST_STORES_STACK && !a->count) {
lwsl_err("%s: at least one cert required in trust store\n",
__func__);
goto oom;
}
if (reason == LEJPCB_OBJECT_END && a->p) {
/*
* Allocate a just-the-right-size buf for the cert DER now
* we decoded it into the a->p temp buffer and know the exact
* size
*/
a->curr[LTY_X509].x->ca_der = lws_malloc(a->count, "ssx509");
if (!a->curr[LTY_X509].x->ca_der)
goto oom;
memcpy((uint8_t *)a->curr[LTY_X509].x->ca_der, a->p, a->count);
a->curr[LTY_X509].x->ca_der_len = a->count;
/*
* ... and then we can free the temp buffer
*/
lws_free_set_NULL(a->p);
return 0;
}
if (reason == LEJPCB_PAIR_NAME && n != -1 && n != LTY_TRUSTSTORE) {
/*
* We do the pointers always as .b, all of the participating
* structs begin with .next and .name
*/
a->curr[n].b = lwsac_use_zero(&a->ac, sizes[n], POL_AC_GRAIN);
if (!a->curr[n].b)
goto oom;
if (n == LTY_X509) {
a->p = lws_malloc(MAX_CERT_TEMP, "cert temp");
if (!a->p)
goto oom;
memset(&a->b64, 0, sizeof(a->b64));
}
a->count = 0;
a->curr[n].b->next = a->heads[n].b;
a->heads[n].b = a->curr[n].b;
pp = (char **)&a->curr[n].b->name;
goto string1;
}
if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
return 0;
switch (ctx->path_match - 1) {
/* strings */
case LSSPPT_RELEASE:
break;
case LSSPPT_PRODUCT:
break;
case LSSPPT_SCHEMA_VERSION:
break;
case LSSPPT_VIA_SOCKS5:
/* the global / default proxy */
pp = (char **)&a->socks5_proxy;
goto string2;
case LSSPPT_BACKOFF:
b = &a->curr[LTY_BACKOFF].b->r;
if (b->retry_ms_table_count == 8) {
lwsl_err("%s: > 8 backoff levels\n", __func__);
return 1;
}
if (!b->retry_ms_table_count) {
b->retry_ms_table = (uint32_t *)lwsac_use_zero(&a->ac,
sizeof(uint32_t) * 8, POL_AC_GRAIN);
if (!b->retry_ms_table)
goto oom;
}
((uint32_t *)b->retry_ms_table)
[b->retry_ms_table_count++] = atoi(ctx->buf);
break;
case LSSPPT_CONCEAL:
a->curr[LTY_BACKOFF].b->r.conceal_count = atoi(ctx->buf);
break;
case LSSPPT_JITTERPC:
a->curr[LTY_BACKOFF].b->r.jitter_percent = atoi(ctx->buf);
break;
case LSSPPT_VALIDPING_S:
a->curr[LTY_BACKOFF].b->r.secs_since_valid_ping = atoi(ctx->buf);
break;
case LSSPPT_VALIDHUP_S:
a->curr[LTY_BACKOFF].b->r.secs_since_valid_hangup = atoi(ctx->buf);
break;
case LSSPPT_CERTS:
if (a->count + ctx->npos >= MAX_CERT_TEMP) {
lwsl_err("%s: cert too big\n", __func__);
goto oom;
}
inl = ctx->npos;
outl = MAX_CERT_TEMP - a->count;
lws_b64_decode_stateful(&a->b64, ctx->buf, &inl,
a->p + a->count, &outl,
reason == LEJPCB_VAL_STR_END);
a->count += outl;
if (inl != ctx->npos) {
lwsl_err("%s: b64 decode fail\n", __func__);
goto oom;
}
break;
case LSSPPT_TRUST_STORES_NAME:
/*
* We do the pointers always as .b, all of the participating
* structs begin with .next and .name
*/
a->curr[LTY_TRUSTSTORE].b = lwsac_use_zero(&a->ac,
sizes[LTY_TRUSTSTORE], POL_AC_GRAIN);
if (!a->curr[LTY_TRUSTSTORE].b)
goto oom;
a->count = 0;
a->curr[LTY_TRUSTSTORE].b->next = a->heads[LTY_TRUSTSTORE].b;
a->heads[LTY_TRUSTSTORE].b = a->curr[LTY_TRUSTSTORE].b;
pp = (char **)&a->curr[LTY_TRUSTSTORE].b->name;
goto string2;
case LSSPPT_TRUST_STORES_STACK:
if (a->count >= (int)LWS_ARRAY_SIZE(
a->curr[LTY_TRUSTSTORE].t->ssx509)) {
lwsl_err("%s: trust store too big\n", __func__);
goto oom;
}
lwsl_debug("%s: trust stores stack %.*s\n", __func__,
ctx->npos, ctx->buf);
x = a->heads[LTY_X509].x;
while (x) {
if (!strncmp(x->vhost_name, ctx->buf, ctx->npos)) {
a->curr[LTY_TRUSTSTORE].t->ssx509[a->count++] = x;
a->curr[LTY_TRUSTSTORE].t->count++;
return 0;
}
x = x->next;
}
lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar));
lwsl_err("%s: unknown trust store entry %s\n", __func__,
dotstar);
goto oom;
case LSSPPT_ENDPOINT:
pp = (char **)&a->curr[LTY_POLICY].p->endpoint;
goto string2;
case LSSPPT_VH_VIA_SOCKS5:
pp = (char **)&a->curr[LTY_POLICY].p->socks5_proxy;
goto string2;
case LSSPPT_PORT:
a->curr[LTY_POLICY].p->port = atoi(ctx->buf);
break;
case LSSPPT_HTTP_METHOD:
pp = (char **)&a->curr[LTY_POLICY].p->u.http.method;
goto string2;
case LSSPPT_HTTP_URL:
pp = (char **)&a->curr[LTY_POLICY].p->u.http.url;
goto string2;
case LSSPPT_RIDESHARE:
pp = (char **)&a->curr[LTY_POLICY].p->rideshare_streamtype;
goto string2;
case LSSPPT_PAYLOAD_FORMAT:
pp = (char **)&a->curr[LTY_POLICY].p->payload_fmt;
goto string2;
case LSSPPT_PLUGINS:
pin = a->context->pss_plugins;
if (a->count ==
(int)LWS_ARRAY_SIZE(a->curr[LTY_POLICY].p->plugins)) {
lwsl_err("%s: too many plugins\n", __func__);
goto oom;
}
if (!pin)
break;
while (*pin) {
if (!strncmp((*pin)->name, ctx->buf, ctx->npos)) {
a->curr[LTY_POLICY].p->plugins[a->count++] = *pin;
return 0;
}
pin++;
}
lwsl_err("%s: unknown plugin\n", __func__);
goto oom;
case LSSPPT_TLS:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_TLS;
break;
case LSSPPT_TLS_CLIENT_CERT:
a->curr[LTY_POLICY].p->client_cert = atoi(ctx->buf) + 1;
break;
case LSSPPT_OPPORTUNISTIC:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_OPPORTUNISTIC;
break;
case LSSPPT_NAILED_UP:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_NAILED_UP;
break;
case LSSPPT_URGENT_TX:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_URGENT_TX;
break;
case LSSPPT_URGENT_RX:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_URGENT_RX;
break;
case LSSPPT_LONG_POLL:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_LONG_POLL;
break;
case LSSPPT_HTTP_WWW_FORM_URLENCODED:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_HTTP_X_WWW_FORM_URLENCODED;
break;
case LSSPPT_RETRYPTR:
bot = a->heads[LTY_BACKOFF].b;
while (bot) {
if (!strncmp(ctx->buf, bot->name, ctx->npos)) {
a->curr[LTY_POLICY].p->retry_bo = &bot->r;
return 0;
}
bot = bot->next;
}
lwsl_err("%s: unknown backoff scheme\n", __func__);
return -1;
case LSSPPT_TRUST:
ts = a->heads[LTY_TRUSTSTORE].t;
while (ts) {
if (!strncmp(ctx->buf, ts->name, ctx->npos)) {
a->curr[LTY_POLICY].p->trust_store = ts;
return 0;
}
ts = ts->next;
}
lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar));
lwsl_err("%s: unknown trust store name %s\n", __func__,
dotstar);
return -1;
case LSSPPT_METADATA:
break;
case LSSPPT_METADATA_ITEM:
pmd = a->curr[LTY_POLICY].p->metadata;
a->curr[LTY_POLICY].p->metadata = lwsac_use_zero(&a->ac,
sizeof(lws_ss_metadata_t) + ctx->npos +
(ctx->path_match_len - ctx->st[ctx->sp - 2].p + 1) + 2,
POL_AC_GRAIN);
a->curr[LTY_POLICY].p->metadata->next = pmd;
q = (char *)a->curr[LTY_POLICY].p->metadata +
sizeof(lws_ss_metadata_t);
a->curr[LTY_POLICY].p->metadata->name = q;
memcpy(q, ctx->path + ctx->st[ctx->sp - 2].p + 1,
ctx->path_match_len - ctx->st[ctx->sp - 2].p);
q += ctx->path_match_len - ctx->st[ctx->sp - 2].p;
a->curr[LTY_POLICY].p->metadata->value = q;
memcpy(q, ctx->buf, ctx->npos);
a->curr[LTY_POLICY].p->metadata->length = /* the index in handle->metadata */
a->curr[LTY_POLICY].p->metadata_count++;
break;
case LSSPPT_HTTP_AUTH_HEADER:
case LSSPPT_HTTP_DSN_HEADER:
case LSSPPT_HTTP_FWV_HEADER:
case LSSPPT_HTTP_TYPE_HEADER:
pp = (char **)&a->curr[LTY_POLICY].p->u.http.blob_header[
(ctx->path_match - 1) - LSSPPT_HTTP_AUTH_HEADER];
goto string2;
case LSSPPT_HTTP_AUTH_PREAMBLE:
pp = (char **)&a->curr[LTY_POLICY].p->u.http.auth_preamble;
goto string2;
case LSSPPT_HTTP_NO_CONTENT_LENGTH:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_HTTP_NO_CONTENT_LENGTH;
break;
case LSSPPT_NGHTTP2_QUIRK_END_STREAM:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_QUIRK_NGHTTP2_END_STREAM;
break;
case LSSPPT_H2_QUIRK_OVERFLOWS_TXCR:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |=
LWSSSPOLF_H2_QUIRK_OVERFLOWS_TXCR;
break;
case LSSPPT_HTTP_MULTIPART_NAME:
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART;
pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_name;
goto string2;
case LSSPPT_HTTP_MULTIPART_FILENAME:
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART;
pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_filename;
goto string2;
case LSSPPT_HTTP_MULTIPART_CONTENT_TYPE:
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_HTTP_MULTIPART;
pp = (char **)&a->curr[LTY_POLICY].p->u.http.multipart_content_type;
goto string2;
case LSSPPT_WS_SUBPROTOCOL:
pp = (char **)&a->curr[LTY_POLICY].p->u.http.u.ws.subprotocol;
goto string2;
case LSSPPT_WS_BINARY:
a->curr[LTY_POLICY].p->u.http.u.ws.binary =
reason == LEJPCB_VAL_TRUE;
break;
case LSSPPT_LOCAL_SINK:
if (reason == LEJPCB_VAL_TRUE)
a->curr[LTY_POLICY].p->flags |= LWSSSPOLF_LOCAL_SINK;
break;
case LSSPPT_MQTT_TOPIC:
pp = (char **)&a->curr[LTY_POLICY].p->u.mqtt.topic;
goto string2;
case LSSPPT_MQTT_SUBSCRIBE:
pp = (char **)&a->curr[LTY_POLICY].p->u.mqtt.subscribe;
goto string2;
case LSSPPT_MQTT_QOS:
a->curr[LTY_POLICY].p->u.mqtt.qos = atoi(ctx->buf);
break;
case LSSPPT_PROTOCOL:
a->curr[LTY_POLICY].p->protocol = 0xff;
for (n = 0; n < (int)LWS_ARRAY_SIZE(protonames); n++)
if (strlen(protonames[n]) == ctx->npos &&
!strncmp(ctx->buf, protonames[n], ctx->npos))
a->curr[LTY_POLICY].p->protocol = (uint8_t)n;
if (a->curr[LTY_POLICY].p->protocol != 0xff)
break;
lws_strnncpy(dotstar, ctx->buf, ctx->npos, sizeof(dotstar));
lwsl_err("%s: unknown protocol name %s\n", __func__, dotstar);
return -1;
}
return 0;
string2:
/*
* If we can do const string folding, reuse the existing string rather
* than make a new entry
*/
extant = lwsac_scan_extant(a->ac, (uint8_t *)ctx->buf, ctx->npos, 1);
if (extant) {
*pp = (char *)extant;
return 0;
}
*pp = lwsac_use_backfill(&a->ac, ctx->npos + 1, POL_AC_GRAIN);
if (!*pp)
goto oom;
memcpy(*pp, ctx->buf, ctx->npos);
(*pp)[ctx->npos] = '\0';
return 0;
string1:
n = ctx->st[ctx->sp].p;
*pp = lwsac_use_backfill(&a->ac, ctx->path_match_len + 1 - n,
POL_AC_GRAIN);
if (!*pp)
goto oom;
memcpy(*pp, ctx->path + n, ctx->path_match_len - n);
(*pp)[ctx->path_match_len - n] = '\0';
return 0;
oom:
lwsl_err("%s: OOM\n", __func__);
lws_free_set_NULL(a->p);
lwsac_free(&a->ac);
return -1;
}
int
lws_ss_policy_parse_begin(struct lws_context *context)
{
struct policy_cb_args *args;
char *p;
args = lws_zalloc(sizeof(struct policy_cb_args), __func__);
if (!args) {
lwsl_err("%s: OOM\n", __func__);
return 1;
}
context->pol_args = args;
args->context = context;
p = lwsac_use(&args->ac, 1, POL_AC_INITIAL);
if (!p) {
lwsl_err("%s: OOM\n", __func__);
lws_free_set_NULL(context->pol_args);
return -1;
}
*p = 0;
lejp_construct(&args->jctx, lws_ss_policy_parser_cb, args,
lejp_tokens_policy, LWS_ARRAY_SIZE(lejp_tokens_policy));
return 0;
}
int
lws_ss_policy_parse_abandon(struct lws_context *context)
{
struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args;
lejp_destruct(&args->jctx);
lws_free_set_NULL(context->pol_args);
return 0;
}
int
lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len)
{
struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args;
int m;
m = (int)(signed char)lejp_parse(&args->jctx, buf, len);
if (m == LEJP_CONTINUE || m >= 0)
return m;
lwsl_err("%s: parse failed: %d: %s\n", __func__, m,
lejp_error_to_string(m));
lws_ss_policy_parse_abandon(context);
return m;
}
int
lws_ss_policy_set(struct lws_context *context, const char *name)
{
struct policy_cb_args *args = (struct policy_cb_args *)context->pol_args;
lws_ss_trust_store_t *ts;
struct lws_vhost *v;
lws_ss_x509_t *x;
char buf[16];
int m, ret = 0;
/*
* Parsing seems to have succeeded, and we're going to use the new
* policy that's laid out in args->ac
*/
lejp_destruct(&args->jctx);
if (context->ac_policy) {
/*
* 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.
*/
v = context->vhost_list;
while (v) {
if (v->from_ss_policy) {
struct lws_vhost *vh = v->vhost_next;
lwsl_debug("%s: destroying vh %p\n", __func__, v);
lws_vhost_destroy(v);
v = vh;
continue;
}
v = v->vhost_next;
}
lws_check_deferred_free(context, 0, 1);
}
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;
lwsl_notice("%s: %s, pad %d%c: %s\n", __func__, buf, m, '%', name);
/* Create vhosts for each type of trust store */
ts = args->heads[LTY_TRUSTSTORE].t;
while (ts) {
struct lws_context_creation_info i;
memset(&i, 0, sizeof(i));
/*
* We get called from context creation... instantiates
* vhosts with client tls contexts set up for each unique CA.
*
* Create the vhost with the first (mandatory) entry in the
* trust store...
*/
v = lws_get_vhost_by_name(context, ts->name);
if (!v) {
int n;
i.options = context->options;
i.vhost_name = ts->name;
lwsl_debug("%s: %s\n", __func__, i.vhost_name);
i.client_ssl_ca_mem = ts->ssx509[0]->ca_der;
i.client_ssl_ca_mem_len = ts->ssx509[0]->ca_der_len;
i.port = CONTEXT_PORT_NO_LISTEN;
lwsl_info("%s: %s trust store initial '%s'\n", __func__,
ts->name, ts->ssx509[0]->vhost_name);
v = lws_create_vhost(context, &i);
if (!v) {
lwsl_err("%s: failed to create vhost %s\n",
__func__, ts->name);
ret = 1;
} else
v->from_ss_policy = 1;
for (n = 1; v && n < ts->count; n++) {
lwsl_info("%s: add '%s' to trust store\n",
__func__, ts->ssx509[n]->vhost_name);
if (lws_tls_client_vhost_extra_cert_mem(v,
ts->ssx509[n]->ca_der,
ts->ssx509[n]->ca_der_len)) {
lwsl_err("%s: add extra cert failed\n",
__func__);
ret = 1;
}
}
}
ts = ts->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
/* now we processed the x.509 CAs, we can free all of our originals */
x = args->heads[LTY_X509].x;
while (x) {
/*
* Free all the DER buffers now they have been parsed into
* tls library X.509 objects
*/
lws_free((void *)x->ca_der);
x->ca_der = NULL;
x = x->next;
}
/* and we can discard the parsing args object now, invalidating args */
lws_free_set_NULL(context->pol_args);
return ret;
}
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;
while (p) {
if (!strcmp(p->streamtype, streamtype))
return p;
p = p->next;
}
return NULL;
}

View File

@ -0,0 +1,347 @@
/*
* 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.
*/
/*
* 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;
/**
* 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 */
struct lws_dll2 list; /**< pt lists active ss */
struct lws_dll2 to_list; /**< pt lists ss with pending to-s */
struct lws_dll2_owner src_list; /**< sink'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_sequencer *seq; /**< owning sequencer if any */
struct lws *wsi; /**< the stream wsi if any */
void *nauthi; /**< the nauth plugin instance data */
void *sauthi; /**< the sauth plugin instance data */
lws_ss_metadata_t *metadata;
const lws_ss_policy_t *rideshare;
struct lws_ss_handle *h_sink; /**< sink we are bound to, or NULL */
void *sink_obj;/**< sink's private object representing us */
lws_sorted_usec_list_t sul;
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 any:1; /* any content has been sent */
uint8_t good_respcode:1; /* 200 type response code */
union {
struct { /* LWSSSP_H1 */
} h1;
struct { /* LWSSSP_H2 */
} h2;
struct { /* LWSSSP_WS */
} 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;
} mqtt;
#endif
} u;
unsigned long writeable_len;
lws_ss_constate_t connstate;/**< public connection state */
lws_ss_seq_state_t seqstate; /**< private connection state */
uint16_t retry; /**< retry / backoff tracking */
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 hanging_som:1;
uint8_t inside_msg:1;
uint8_t being_serialized:1; /* we are not the consumer */
} 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;
#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,
};
struct lws_ss_serialization_parser {
char streamtype[32];
char rideshare[32];
char metadata_name[32];
uint64_t ust_pwait;
lws_ss_metadata_t *ssmd;
int ps;
int ctr;
uint32_t usd_phandling;
uint32_t flags;
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;
};
/*
* 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;
typedef struct lws_sspc_handle {
char rideshare_list[128];
lws_ss_info_t ssi;
lws_sorted_usec_list_t sul_retry;
struct lws_ss_serialization_parser parser;
lws_dll2_owner_t metadata_owner;
struct lws_dll2 client_list;
struct lws_tx_credit txc;
struct lws *cwsi;
struct lws_dsh *dsh;
struct lws_context *context;
lws_usec_t us_earliest_write_req;
lws_ss_conn_states_t state;
int16_t temp16;
uint32_t ord;
uint8_t rideshare_ofs[4];
uint8_t conn_req;
uint8_t rsidx;
uint8_t destroying:1;
} lws_sspc_handle_t;
int
lws_ss_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, char client);
int
lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf,
size_t len, int flags, const char *rsp);
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);
int
lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack);
void
lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state);
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);
int
lws_ss_policy_parse_begin(struct lws_context *context);
int
lws_ss_policy_parse(struct lws_context *context, const uint8_t *buf, size_t len);
int
lws_ss_policy_set(struct lws_context *context, const char *name);
int
lws_ss_policy_parse_abandon(struct lws_context *context);
int
lws_ss_sys_fetch_policy(struct lws_context *context);
int
lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs);
int
lws_ss_backoff(lws_ss_handle_t *h);
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_serialize_txcr(struct lws_dsh *dsh, int txcr);
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);
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);
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);
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);
struct ss_pcols {
const char *name;
const char *alpn;
const char *protocol_name;
const secstream_protocol_connect_munge_t munge;
const secstream_protocol_add_txcr_t tx_cr_add;
const secstream_protocol_get_txcr_t tx_cr_est;
};
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 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;

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.

View File

@ -0,0 +1,571 @@
/*
* 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 h1 to Secure Streams.
*/
#include <private-lib-core.h>
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
static int
ss_http_multipart_parser(lws_ss_handle_t *h, void *in, size_t len)
{
uint8_t *q = (uint8_t *)in;
int pending_issue = 0, n = 0;
/* let's stick it in the boundary state machine first */
while (n < (int)len) {
if (h->u.http.boundary_seq != h->u.http.boundary_len) {
if (q[n] == h->u.http.boundary[h->u.http.boundary_seq])
h->u.http.boundary_seq++;
else {
h->u.http.boundary_seq = 0;
h->u.http.boundary_dashes = 0;
h->u.http.boundary_post = 0;
}
goto around;
}
/*
* We already matched the boundary string, now we're
* looking if there's a -- afterwards
*/
if (h->u.http.boundary_dashes < 2) {
if (q[n] == '-') {
h->u.http.boundary_dashes++;
goto around;
}
/* there was no final -- ... */
}
if (h->u.http.boundary_dashes == 2) {
/*
* It's an EOM boundary: issue pending + multipart EOP
*/
lwsl_debug("%s: seen EOP, n %d pi %d\n",
__func__, n, pending_issue);
/*
* It's possible we already started the decode before
* the end of the last packet. Then there is no
* remainder to send.
*/
if (n >= pending_issue + h->u.http.boundary_len +
(h->u.http.any ? 2 : 0) + 1)
h->info.rx(ss_to_userobj(h),
&q[pending_issue],
n - pending_issue -
h->u.http.boundary_len - 1 -
(h->u.http.any ? 2 : 0) /* crlf */,
(!h->u.http.som ? LWSSS_FLAG_SOM : 0) |
LWSSS_FLAG_EOM | LWSSS_FLAG_RELATED_END);
/*
* Peer may not END_STREAM us
*/
return 0;
//return -1;
}
/* how about --boundaryCRLF */
if (h->u.http.boundary_post < 2) {
if ((!h->u.http.boundary_post && q[n] == '\x0d') ||
(h->u.http.boundary_post && q[n] == '\x0a')) {
h->u.http.boundary_post++;
goto around;
}
/* there was no final CRLF ... it's wrong */
return -1;
}
if (h->u.http.boundary_post != 2)
goto around;
/*
* We have a starting "--boundaryCRLF" or intermediate
* "CRLF--boundaryCRLF" boundary
*/
lwsl_debug("%s: b_post = 2 (pi %d)\n", __func__, pending_issue);
h->u.http.boundary_seq = 0;
h->u.http.boundary_post = 0;
if (n >= pending_issue && (h->u.http.any || !h->u.http.som)) {
/* Intermediate... do the EOM */
lwsl_debug("%s: seen interm EOP n %d pi %d\n", __func__,
n, pending_issue);
/*
* It's possible we already started the decode before
* the end of the last packet. Then there is no
* remainder to send.
*/
if (n >= pending_issue + h->u.http.boundary_len +
(h->u.http.any ? 2 : 0))
h->info.rx(ss_to_userobj(h), &q[pending_issue],
n - pending_issue -
h->u.http.boundary_len -
(h->u.http.any ? 2 /* crlf */ : 0),
(!h->u.http.som ? LWSSS_FLAG_SOM : 0) |
LWSSS_FLAG_EOM);
}
/* Next message starts after this boundary */
pending_issue = n;
h->u.http.som = 0;
around:
n++;
}
if (pending_issue != n) {
h->info.rx(ss_to_userobj(h), &q[pending_issue], n - pending_issue,
(!h->u.http.som ? LWSSS_FLAG_SOM : 0));
h->u.http.any = 1;
h->u.http.som = 1;
}
return 0;
}
#endif
static const uint8_t blob_idx[] = {
LWS_SYSBLOB_TYPE_AUTH,
LWS_SYSBLOB_TYPE_DEVICE_SERIAL,
LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION,
LWS_SYSBLOB_TYPE_DEVICE_TYPE,
};
int
secstream_h1(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);
uint8_t buf[LWS_PRE + 1520], *p = &buf[LWS_PRE],
*end = &buf[sizeof(buf) - 1];
int f = 0, m, status;
size_t buflen;
switch (reason) {
/* because we are protocols[0] ... */
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
assert(h);
assert(h->policy);
lwsl_info("%s: h: %p, %s CLIENT_CONNECTION_ERROR: %s\n", __func__,
h, h->policy->streamtype, in ? (char *)in : "(null)");
lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
h->wsi = NULL;
lws_ss_backoff(h);
break;
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
if (!h)
break;
lwsl_info("%s: h: %p, %s LWS_CALLBACK_CLOSED_CLIENT_HTTP\n",
__func__, h,
h->policy ? h->policy->streamtype : "no policy");
h->wsi = NULL;
//bad = status != 200;
//lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
!h->txn_ok && !wsi->context->being_destroyed)
lws_ss_backoff(h);
if (lws_ss_event_helper(h, LWSSSCS_DISCONNECTED))
lws_ss_destroy(&h);
break;
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
status = lws_http_client_http_response(wsi);
lwsl_info("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: %d\n", __func__, status);
// if (!status)
/* it's just telling use we connected / joined the nwsi */
// break;
h->u.http.good_respcode = (status >= 200 && status < 300);
// lwsl_err("%s: good resp %d %d\n", __func__, status, h->u.http.good_respcode);
if (h->u.http.good_respcode)
lwsl_info("%s: Connected streamtype %s, %d\n", __func__,
h->policy->streamtype, status);
else
lwsl_warn("%s: Connected streamtype %s, BAD %d\n", __func__,
h->policy->streamtype, status);
h->hanging_som = 0;
h->retry = 0;
h->seqstate = SSSEQ_CONNECTED;
lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);
lws_ss_event_helper(h, LWSSSCS_CONNECTED);
/*
* Since it's an http transaction we initiated... this is
* proof of connection validity
*/
lws_validity_confirmed(wsi);
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
if (lws_hdr_copy(wsi, (char *)buf, sizeof(buf),
WSI_TOKEN_HTTP_CONTENT_TYPE) > 0 &&
/* multipart/form-data;
* boundary=----WebKitFormBoundarycc7YgAPEIHvgE9Bf */
(!strncmp((char *)buf, "multipart/form-data", 19) ||
!strncmp((char *)buf, "multipart/related", 17))) {
struct lws_tokenize ts;
lws_tokenize_elem e;
// puts((const char *)buf);
memset(&ts, 0, sizeof(ts));
ts.start = (char *)buf;
ts.len = strlen(ts.start);
ts.flags = LWS_TOKENIZE_F_RFC7230_DELIMS |
LWS_TOKENIZE_F_SLASH_NONTERM |
LWS_TOKENIZE_F_MINUS_NONTERM;
h->u.http.boundary[0] = '\0';
do {
e = lws_tokenize(&ts);
if (e == LWS_TOKZE_TOKEN_NAME_EQUALS &&
!strncmp(ts.token, "boundary", 8) &&
ts.token_len == 8) {
e = lws_tokenize(&ts);
if (e != LWS_TOKZE_TOKEN)
goto malformed;
h->u.http.boundary[0] = '\x0d';
h->u.http.boundary[1] = '\x0a';
h->u.http.boundary[2] = '-';
h->u.http.boundary[3] = '-';
lws_strnncpy(h->u.http.boundary + 4,
ts.token, ts.token_len,
sizeof(h->u.http.boundary) - 4);
h->u.http.boundary_len = ts.token_len + 4;
h->u.http.boundary_seq = 2;
h->u.http.boundary_dashes = 0;
}
} while (e > 0);
lwsl_info("%s: multipart boundary '%s' len %d\n", __func__,
h->u.http.boundary, h->u.http.boundary_len);
/* inform the ss that a related message group begins */
if (h->u.http.boundary[0])
h->info.rx(ss_to_userobj(h), NULL, 0,
LWSSS_FLAG_RELATED_START);
// lws_header_table_detach(wsi, 0);
}
break;
malformed:
lwsl_notice("%s: malformed multipart header\n", __func__);
return -1;
#else
break;
#endif
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
if (h->writeable_len)
wsi->http.writeable_len = h->writeable_len;
{
uint8_t **p = (uint8_t **)in, *end = (*p) + len,
*oin = *(uint8_t **)in;
/*
* blob-based headers
*/
for (m = 0; m < _LWSSS_HBI_COUNT; m++) {
int o = 0, n;
if (!h->policy->u.http.blob_header[m])
continue;
if (m == LWSSS_HBI_AUTH &&
h->policy->u.http.auth_preamble)
o = lws_snprintf((char *)buf, sizeof(buf), "%s",
h->policy->u.http.auth_preamble);
if (o > (int)sizeof(buf) - 2)
return -1;
buflen = sizeof(buf) - o - 2;
n = lws_system_blob_get(
lws_system_get_blob(wsi->context, blob_idx[m], 0),
buf + o, &buflen, 0);
if (n < 0)
return -1;
buf[o + buflen] = '\0';
lwsl_debug("%s: adding blob %d: %s\n", __func__, m, buf);
if (lws_add_http_header_by_name(wsi,
(uint8_t *)h->policy->u.http.blob_header[m],
buf, buflen + o, p, end))
return -1;
}
/*
* metadata-based headers
*/
for (m = 0; m < h->policy->metadata_count; m++) {
lws_ss_metadata_t *polmd;
/* has to have a header string listed */
if (!h->metadata[m].value)
continue;
polmd = lws_ss_policy_metadata_index(h->policy, m);
assert(polmd);
/* has to have a value */
if (polmd->value && ((uint8_t *)polmd->value)[0]) {
if (lws_add_http_header_by_name(wsi,
polmd->value,
h->metadata[m].value,
h->metadata[m].length, p, end))
return -1;
}
}
/*
* Content-length on POST if we have the length information
*/
if (!strcmp(h->policy->u.http.method, "POST") &&
wsi->http.writeable_len) {
if (!(h->policy->flags &
LWSSSPOLF_HTTP_NO_CONTENT_LENGTH)) {
int n = lws_snprintf((char *)buf, 20, "%u",
(unsigned int)wsi->http.writeable_len);
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_CONTENT_LENGTH,
buf, n, p, end))
return -1;
}
lws_client_http_body_pending(wsi, 1);
}
(void)oin;
// if (*p != oin)
// lwsl_hexdump_notice(oin, lws_ptr_diff(*p, oin));
}
break;
/* chunks of chunked content, with header removed */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ: read %d\n",
__func__, (int)len);
if (!h)
return 0;
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
if (h->u.http.boundary[0])
return ss_http_multipart_parser(h, in, len);
#endif
if (!h->subseq) {
f |= LWSSS_FLAG_SOM;
h->hanging_som = 1;
h->subseq = 1;
}
// lwsl_notice("%s: HTTP_READ: client side sent len %d fl 0x%x\n",
// __func__, (int)len, (int)f);
h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);
return 0; /* don't passthru */
/* uninterpreted http content */
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
{
char *px = (char *)buf + LWS_PRE; /* guarantees LWS_PRE */
int lenx = sizeof(buf) - LWS_PRE;
if (lws_http_client_read(wsi, &px, &lenx) < 0)
return -1;
}
lws_set_timeout(wsi, 99, 30);
return 0; /* don't passthru */
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
lwsl_debug("%s: LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n", __func__);
if (h->hanging_som)
h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM);
wsi->http.writeable_len = h->writeable_len = 0;
if (h->u.http.good_respcode)
lws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE);
else
lws_ss_event_helper(h, LWSSSCS_QOS_NACK_REMOTE);
h->wsi = NULL;
h->txn_ok = 1;
//bad = status != 200;
lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
break;
case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
lwsl_info("%s: LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n", __func__);
if (!h)
return 0;
if (!h->rideshare)
h->rideshare = h->policy;
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
if (!h->inside_msg && h->rideshare->u.http.multipart_name)
lws_client_http_multipart(wsi,
h->rideshare->u.http.multipart_name,
h->rideshare->u.http.multipart_filename,
h->rideshare->u.http.multipart_content_type,
(char **)&p, (char *)end);
buflen = lws_ptr_diff(end, p);
if (h->policy->u.http.multipart_name)
buflen -= 24; /* allow space for end of multipart */
#endif
if (h->info.tx(ss_to_userobj(h), h->txord++, p, &buflen, &f)) {
/* don't want to send anything */
lwsl_debug("%s: dont want to write\n", __func__);
return 0;
}
lwsl_info("%s: WRITEABLE: user tx says len %d fl 0x%x\n",
__func__, (int)buflen, (int)f);
p += buflen;
if (f & LWSSS_FLAG_EOM) {
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
/* end of rideshares */
if (!h->rideshare->rideshare_streamtype) {
lws_client_http_body_pending(wsi, 0);
if (h->rideshare->u.http.multipart_name)
lws_client_http_multipart(wsi, NULL, NULL, NULL,
(char **)&p, (char *)end);
} else {
#endif
h->rideshare = lws_ss_policy_lookup(wsi->context,
h->rideshare->rideshare_streamtype);
lws_callback_on_writable(wsi);
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
}
#endif
h->inside_msg = 0;
} else {
/* otherwise we can spin with zero length writes */
if (!f && !lws_ptr_diff(p, buf + LWS_PRE))
break;
h->inside_msg = 1;
lws_callback_on_writable(wsi);
}
lwsl_info("%s: lws_write %d %d\n", __func__,
lws_ptr_diff(p, buf + LWS_PRE), f);
if (lws_write(wsi, buf + LWS_PRE, lws_ptr_diff(p, buf + LWS_PRE),
LWS_WRITE_HTTP) != (int)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 lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols protocol_secstream_h1 = {
"lws-secstream-h1",
secstream_h1,
0,
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.
*/
static int
secstream_connect_munge_h1(lws_ss_handle_t *h, char *buf, size_t len,
struct lws_client_connect_info *i,
union lws_ss_contemp *ct)
{
size_t used_in, used_out;
lws_strexp_t exp;
if (!h->policy->u.http.url)
return 0;
#if !defined(LWS_PLAT_FREERTOS) || defined(LWS_ROLE_H2)
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;
#endif
/* 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, h->policy->u.http.url,
strlen(h->policy->u.http.url),
&used_in, &used_out) != LSTRX_DONE)
return 1;
return 0;
}
const struct ss_pcols ss_pcol_h1 = {
"h1",
"http/1.1",
"lws-secstream-h1",
secstream_connect_munge_h1,
NULL
};

View File

@ -0,0 +1,183 @@
/*
* 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);
int n;
switch (reason) {
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
#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 %p entering LONG_POLL\n",
__func__, wsi);
lws_h2_client_stream_long_poll_rxonly(wsi);
}
return n;
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
// lwsl_err("%s: h2 COMPLETED_CLIENT_HTTP\n", __func__);
h->info.rx(ss_to_userobj(h), NULL, 0, LWSSS_FLAG_EOM);
h->wsi = NULL;
h->txn_ok = 1;
//bad = status != 200;
lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
break;
case LWS_CALLBACK_WSI_TX_CREDIT_GET:
/*
* The peer has sent us additional tx credit...
*/
lwsl_info("%s: LWS_CALLBACK_WSI_TX_CREDIT_GET: %d\n",
__func__, (int32_t)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,
};
/*
* 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)
{
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;
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 (!h->policy->u.http.url)
return 0;
/* protocol aux is the path part */
i->path = buf;
lws_snprintf(buf, len, "/%s", h->policy->u.http.url);
return 0;
}
static int
secstream_tx_credit_add_h2(lws_ss_handle_t *h, int add)
{
lwsl_info("%s: h %p: add %d\n", __func__, h, add);
if (h->wsi)
return lws_h2_update_peer_txcredit(h->wsi, 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: h %p: est %d\n", __func__, h,
lws_h2_get_peer_txcredit_estimate(h->wsi));
return lws_h2_get_peer_txcredit_estimate(h->wsi);
}
lwsl_info("%s: h %p: Unknown (0)\n", __func__, h);
return 0;
}
const struct ss_pcols ss_pcol_h2 = {
"h2",
NULL,
"lws-secstream-h2",
secstream_connect_munge_h2,
secstream_tx_credit_add_h2,
secstream_tx_credit_est_h2
};

View File

@ -0,0 +1,248 @@
/*
* 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_mqtt(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_mqtt_publish_param_t mqpp, *pmqpp;
uint8_t buf[LWS_PRE + 1400];
size_t buflen;
int f = 0;
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;
lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
h->wsi = NULL;
lws_ss_backoff(h);
break;
case LWS_CALLBACK_MQTT_CLIENT_CLOSED:
if (!h)
break;
f = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);
if (h->wsi)
lws_set_opaque_user_data(h->wsi, NULL);
h->wsi = NULL;
if (f) {
lws_ss_destroy(&h);
break;
}
if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
!h->txn_ok && !wsi->context->being_destroyed)
lws_ss_backoff(h);
break;
case LWS_CALLBACK_MQTT_CLIENT_ESTABLISHED:
/*
* Make sure the handle wsi points to the stream wsi not the
* original nwsi, in the case it was migrated
*/
h->wsi = wsi;
h->retry = 0;
h->seqstate = SSSEQ_CONNECTED;
lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);
lws_ss_event_helper(h, LWSSSCS_CONNECTED);
if (h->policy->u.mqtt.topic)
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_MQTT_CLIENT_RX:
// lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len);
if (!h)
return 0;
pmqpp = (lws_mqtt_publish_param_t *)in;
f = 0;
if (!pmqpp->payload_pos)
f |= LWSSS_FLAG_SOM;
if (pmqpp->payload_pos + len == pmqpp->payload_len)
f |= LWSSS_FLAG_EOM;
h->subseq = 1;
h->info.rx(ss_to_userobj(h), (const uint8_t *)pmqpp->payload,
len, f);
return 0; /* don't passthru */
case LWS_CALLBACK_MQTT_SUBSCRIBED:
wsi->mqtt->done_subscribe = 1;
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_MQTT_ACK:
lws_ss_event_helper(h, LWSSSCS_QOS_ACK_REMOTE);
break;
case LWS_CALLBACK_MQTT_CLIENT_WRITEABLE:
if (!h)
return 0;
lwsl_notice("%s: ss %p: WRITEABLE\n", __func__, h);
if (h->seqstate != SSSEQ_CONNECTED) {
lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate);
break;
}
if (h->policy->u.mqtt.subscribe && !wsi->mqtt->done_subscribe) {
/*
* The policy says to subscribe to something, and we
* haven't done it yet
*/
lwsl_warn("%s: subscribing %s\n", __func__, h->policy->u.mqtt.subscribe);
memset(&h->u.mqtt.sub_top, 0, sizeof(h->u.mqtt.sub_top));
h->u.mqtt.sub_top.name = h->policy->u.mqtt.subscribe;
h->u.mqtt.sub_top.qos = h->policy->u.mqtt.qos;
memset(&h->u.mqtt.sub_info, 0, sizeof(h->u.mqtt.sub_info));
h->u.mqtt.sub_info.num_topics = 1;
h->u.mqtt.sub_info.topic = &h->u.mqtt.sub_top;
if (lws_mqtt_client_send_subcribe(wsi, &h->u.mqtt.sub_info)) {
lwsl_notice("%s: unable to subscribe", __func__);
return -1;
}
return 0;
}
buflen = sizeof(buf) - LWS_PRE;
if (h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE,
&buflen, &f))
/* don't want to send anything */
return 0;
memset(&mqpp, 0, sizeof(mqpp));
mqpp.topic = (char *)h->policy->u.mqtt.topic;
mqpp.topic_len = strlen(mqpp.topic);
mqpp.packet_id = h->txord - 1;
mqpp.payload = buf + LWS_PRE;
if (h->writeable_len)
mqpp.payload_len = h->writeable_len;
else
mqpp.payload_len = buflen;
lwsl_notice("%s: payload len %d\n", __func__, (int)mqpp.payload_len);
mqpp.qos = h->policy->u.mqtt.qos;
if (lws_mqtt_client_send_publish(wsi, &mqpp,
(const char *)buf + LWS_PRE, buflen,
f & LWSSS_FLAG_EOM)) {
lwsl_notice("%s: failed to publish\n", __func__);
return -1;
}
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols protocol_secstream_mqtt = {
"lws-secstream-mqtt",
secstream_mqtt,
0,
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_mqtt(lws_ss_handle_t *h, char *buf, size_t len,
struct lws_client_connect_info *i,
union lws_ss_contemp *ct)
{
memset(&ct->ccp, 0, sizeof(ct->ccp));
ct->ccp.client_id = "lwsMqttClient";
ct->ccp.keep_alive = 60;
ct->ccp.clean_start = 1;
ct->ccp.will_param.topic = "good/bye";
ct->ccp.will_param.message = "sign-off";
ct->ccp.will_param.qos = 0;
ct->ccp.will_param.retain = 0;
lwsl_notice("%s\n", __func__);
h->u.mqtt.topic_qos.name = h->policy->u.mqtt.subscribe;
h->u.mqtt.topic_qos.qos = h->policy->u.mqtt.qos;
i->method = "MQTT";
i->mqtt_cp = &ct->ccp;
i->alpn = "x-amzn-mqtt-ca";
/* share connections where possible */
i->ssl_connection |= LCCSCF_PIPELINE;
/*
if (!h->policy->u.http.url)
return 0;
// protocol aux is the path part ; ws subprotocol name
i->path = NULL;
lws_snprintf(buf, len, "/%s", h->policy->u.mqtt.topic);
// i->protocol = h->policy->u.mqtt.u.ws.subprotocol;
lwsl_notice("%s: url %s, ws subprotocol %s\n", __func__, buf, i->protocol);
*/
return 0;
}
const struct ss_pcols ss_pcol_mqtt = {
"MQTT",
"x-amzn-mqtt-ca", //"mqtt/3.1.1",
"lws-secstream-mqtt",
secstream_connect_munge_mqtt
};

View File

@ -0,0 +1,165 @@
/*
* 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)
{
lws_ss_handle_t *h = (lws_ss_handle_t *)lws_get_opaque_user_data(wsi);
uint8_t buf[LWS_PRE + 1400];
size_t buflen;
int f = 0, f1;
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;
lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
h->wsi = NULL;
lws_ss_backoff(h);
break;
case LWS_CALLBACK_CLIENT_CLOSED:
if (!h)
break;
f = lws_ss_event_helper(h, LWSSSCS_DISCONNECTED);
if (h->wsi)
lws_set_opaque_user_data(h->wsi, NULL);
h->wsi = NULL;
if (f) {
lws_ss_destroy(&h);
break;
}
if (h->policy && !(h->policy->flags & LWSSSPOLF_OPPORTUNISTIC) &&
!h->txn_ok && !wsi->context->being_destroyed)
lws_ss_backoff(h);
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
h->retry = 0;
h->seqstate = SSSEQ_CONNECTED;
lws_ss_set_timeout_us(h, LWS_SET_TIMER_USEC_CANCEL);
lws_ss_event_helper(h, LWSSSCS_CONNECTED);
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
// lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: read %d\n", (int)len);
if (!h)
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;
h->info.rx(ss_to_userobj(h), (const uint8_t *)in, len, f);
return 0; /* don't passthru */
case LWS_CALLBACK_CLIENT_WRITEABLE:
if (!h)
return 0;
// lwsl_notice("%s: ss %p: WRITEABLE\n", __func__, h);
if (h->seqstate != SSSEQ_CONNECTED) {
lwsl_warn("%s: seqstate %d\n", __func__, h->seqstate);
break;
}
buflen = sizeof(buf) - LWS_PRE;
if (h->info.tx(ss_to_userobj(h), h->txord++, buf + LWS_PRE,
&buflen, &f))
/* don't want to send anything */
return 0;
f1 = lws_write_ws_flags(LWS_WRITE_BINARY,
!!(f & LWSSS_FLAG_SOM),
!!(f & LWSSS_FLAG_EOM));
if (lws_write(wsi, buf + LWS_PRE, buflen, f1) != (int)buflen) {
lwsl_err("%s: write failed\n", __func__);
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,
};
/*
* 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)
{
lwsl_notice("%s\n", __func__);
if (!h->policy->u.http.url)
return 0;
/* protocol aux is the path part ; ws subprotocol name */
i->path = h->policy->u.http.url;
lws_snprintf(buf, len, "/%s", h->policy->u.http.url);
i->protocol = h->policy->u.http.u.ws.subprotocol;
lwsl_notice("%s: url %s, ws subprotocol %s\n", __func__, buf, i->protocol);
return 0;
}
const struct ss_pcols ss_pcol_ws = {
"ws", "http/1.1", "lws-secstream-ws", secstream_connect_munge_ws
};

View File

@ -0,0 +1,574 @@
/*
* lws-minimal-secure-streams-client
*
* 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.
*
*
* This client does not perform any INET networking... instead it opens a unix
* domain socket on a proxy that is listening for it, and that creates the
* actual secure stream connection.
*
* We are able to use the usual secure streams api in the client process, with
* payloads and connection state information proxied over the unix domain
* socket and fulfilled in the proxy process.
*
* The public client helper pieces are built as part of lws
*/
#include <private-lib-core.h>
static 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);
static 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
i.address = "+@proxy.ss.lws";
}
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 = &h->cwsi;
i.opaque_user_data = (void *)h;
if (!lws_client_connect_via_info(&i)) {
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
return;
}
}
static int
lws_sspc_serialize_metadata(lws_sspc_metadata_t *md, uint8_t *p)
{
int n, txc;
if (md->name[0] == '\0') {
lwsl_info("%s: sending tx credit update %d\n", __func__,
md->tx_cr_adjust);
p[0] = LWSSS_SER_TXPRE_TXCR_UPDATE;
lws_ser_wu16be(&p[1], 4);
lws_ser_wu32be(&p[3], md->tx_cr_adjust);
n = 7;
} else {
lwsl_info("%s: sending metadata\n", __func__);
p[0] = LWSSS_SER_TXPRE_METADATA;
txc = strlen(md->name);
n = txc + 1 + md->len;
lws_ser_wu16be(&p[1], n);
p[3] = txc;
memcpy(&p[4], md->name, txc);
memcpy(&p[4 + txc], &md[1], md->len);
n = 4 + txc + md->len;
}
lws_dll2_remove(&md->list);
lws_free(md);
return n;
}
static int
callback_sspc_client(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);
uint8_t s[32], pkt[LWS_PRE + 1400], *p = pkt + LWS_PRE;
void *m = (void *)((uint8_t *)&h[1]);
const uint8_t *cp;
lws_usec_t us;
int flags, n;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_warn("%s: CONNECTION_ERROR\n", __func__);
lws_set_opaque_user_data(wsi, NULL);
h->cwsi = NULL;
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
break;
case LWS_CALLBACK_RAW_CONNECTED:
if (!h)
return -1;
lwsl_info("%s: CONNECTED (%s)\n", __func__, h->ssi.streamtype);
h->state = LPCS_SENDING_INITIAL_TX;
h->dsh = lws_dsh_create(NULL, (LWS_PRE + LWS_SS_MTU) * 160, 1);
if (!h->dsh)
return -1;
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_RAW_CLOSE:
/*
* our ss proxy Unix Domain socket has closed...
*/
lwsl_notice("%s: LWS_CALLBACK_RAW_CLOSE: proxy conn down\n", __func__);
h->cwsi = NULL;
//lws_sspc_destroy(&h);
break;
case LWS_CALLBACK_RAW_RX:
lwsl_info("%s: RAW_RX: rx %d\n", __func__, (int)len);
if (!h || !h->cwsi) {
lwsl_err("%s: rx with bad conn state\n", __func__);
return -1;
}
if (lws_ss_deserialize_parse(&h->parser, lws_get_context(wsi),
h->dsh, in, len, &h->state, h,
(lws_ss_handle_t **)m, &h->ssi, 1))
return -1;
if (wsi && h->state == LPCS_LOCAL_CONNECTED)
lws_set_timeout(wsi, 0, 0);
break;
case LWS_CALLBACK_RAW_WRITEABLE:
/*
* We can transmit something to the proxy...
*/
if (!h)
break;
lwsl_info("%s: WRITEABLE %p: (%s) state %d\n", __func__, wsi,
h->ssi.streamtype, h->state);
n = 0;
cp = s;
s[1] = 0;
switch (h->state) {
case LPCS_SENDING_INITIAL_TX:
n = strlen(h->ssi.streamtype) + 4;
s[0] = LWSSS_SER_TXPRE_STREAMTYPE;
lws_ser_wu16be(&s[1], n);
lws_ser_wu32be(&s[3], h->txc.peer_tx_cr_est);
//h->txcr_out = txc;
lws_strncpy((char *)&s[7], h->ssi.streamtype, sizeof(s) - 7);
n += 3;
h->state = LPCS_WAITING_CREATE_RESULT;
break;
case LPCS_LOCAL_CONNECTED:
if (!h->conn_req)
break;
/*
* 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);
cp = p;
n = lws_sspc_serialize_metadata(md, p);
/* in case anything else to write */
lws_callback_on_writable(h->cwsi);
break;
}
h->conn_req = 0;
s[0] = LWSSS_SER_TXPRE_ONWARD_CONNECT;
s[1] = 0;
s[2] = 0;
n = 3;
break;
case LPCS_OPERATIONAL:
/*
* Do we want to adjust the peer's ability to write
* to us?
*/
/*
* 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);
cp = p;
n = lws_sspc_serialize_metadata(md, p);
/* in case anything else to write */
lws_callback_on_writable(h->cwsi);
break;
}
/* we can't write anything if we don't have credit */
if (h->txc.tx_cr <= 0) {
lwsl_notice("%s: WRITEABLE / OPERATIONAL:"
" lack credit (%d)\n", __func__,
h->txc.tx_cr);
break;
}
len = sizeof(pkt) - LWS_PRE - 19;
flags = 0;
if (h->ssi.tx(m, h->ord++, pkt + LWS_PRE + 19, &len, &flags))
break;
h->txc.tx_cr -= len;
cp = p;
n = len + 19;
us = lws_now_usecs();
p[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], len + 19 - 3);
lws_ser_wu32be(&p[3], flags);
/* time spent here waiting to send this */
lws_ser_wu32be(&p[7], us - h->us_earliest_write_req);
/* ust that the client write happened */
lws_ser_wu64be(&p[11], 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++;
break;
default:
break;
}
if (!n)
break;
// lwsl_hexdump_notice(cp, n);
n = lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW);
if (n < 0) {
lwsl_notice("%s: WRITEABLE: %d\n", __func__, n);
goto hangup;
}
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
hangup:
lwsl_warn("hangup\n");
/* hang up on him */
return -1;
}
const struct lws_protocols lws_sspc_protocols[] = {
{
"ssproxy-protocol",
callback_sspc_client,
0,
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
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,
struct lws_sequencer *seq_owner, const char **ppayload_fmt)
{
lws_sspc_handle_t *h;
uint8_t *ua;
char *p;
lwsl_notice("%s: streamtype %s\n", __func__, ssi->streamtype);
/* 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);
memset(h, 0, sizeof(*h));
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;
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;
lws_dll2_add_head(&h->client_list, &context->pt[tsi].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_destroy(lws_sspc_handle_t **ph)
{
lws_sspc_handle_t *h;
void *m;
lwsl_debug("%s\n", __func__);
if (!*ph)
return;
h = *ph;
m = (void *)((uint8_t *)&h[1]);
if (h->destroying)
return;
h->destroying = 1;
lws_sul_schedule(h->context, 0, &h->sul_retry, NULL,
LWS_SET_TIMER_USEC_CANCEL);
lws_dll2_remove(&h->client_list);
if (h->dsh)
lws_dsh_destroy(&h->dsh);
if (h->cwsi) {
struct lws *wsi = h->cwsi;
h->cwsi = NULL;
lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC);
}
/* 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);
h->ssi.state(m, NULL, LWSSSCS_DESTROYING, 0);
*ph = NULL;
free(h);
}
void
lws_sspc_request_tx(lws_sspc_handle_t *h)
{
if (!h || !h->cwsi)
return;
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
lws_callback_on_writable(h->cwsi);
}
int
lws_sspc_client_connect(lws_sspc_handle_t *h)
{
if (!h || h->state == LPCS_OPERATIONAL)
return 0;
assert(h->state == LPCS_LOCAL_CONNECTED);
h->conn_req = 1;
if (h->cwsi)
lws_callback_on_writable(h->cwsi);
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_info("%s: parser %s\n", __func__, h->parser.rideshare);
return h->parser.rideshare;
}
/*
* The tx rideshare index
*/
if (h->rideshare_list[0]) {
lwsl_info("%s: tx list %s\n", __func__,
&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_info("%s: def %s\n", __func__, h->ssi.streamtype);
return h->ssi.streamtype;
}
static int
_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
void *value, size_t len, int tx_cr_adjust)
{
lws_sspc_metadata_t *md;
/*
* 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
*/
md = lws_malloc(sizeof(*md) + len, "set metadata");
if (!md) {
lwsl_err("%s: OOM\n", __func__);
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) {
lwsl_info("%s: set metadata %s\n", __func__, name);
lwsl_hexdump_info(value, len);
} else
lwsl_info("%s: serializing tx cr adj %d\n", __func__,
(int)tx_cr_adjust);
if (h->cwsi)
lws_callback_on_writable(h->cwsi);
return 0;
}
int
lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
void *value, size_t len)
{
return _lws_sspc_set_metadata(h, name, value, len, 0);
}
int
lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump)
{
lwsl_notice("%s: %d\n", __func__, bump);
return _lws_sspc_set_metadata(h, "", NULL, 0, (int)bump);
}
int
lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h)
{
return h->txc.peer_tx_cr_est;
}

View File

@ -0,0 +1,533 @@
/*
* 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.
*
*
* 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>
/*
* Because both sides of the connection share the conn, we allocate it
* during accepted adoption, and both sides point to it.
*
* The last one of the accepted side and the onward side to close frees it.
*/
struct conn {
struct lws_ss_serialization_parser parser;
lws_dsh_t *dsh; /* unified buffer for both sides */
struct lws *wsi; /* the client side */
lws_ss_handle_t *ss; /* the onward, ss side */
lws_ss_conn_states_t state;
};
struct raw_pss {
struct conn *conn;
};
/*
* Proxy - onward secure-stream handler
*/
typedef struct ss_proxy_onward {
lws_ss_handle_t *ss;
struct conn *conn;
} ss_proxy_t;
/* secure streams payload interface */
static int
ss_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;
/*
* 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;
}
n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len, flags, rsp);
if (n)
return n;
if (m->conn->wsi) /* if possible, request client conn write */
lws_callback_on_writable(m->conn->wsi);
return 0;
}
/*
* we are transmitting buffered payload originally from the client on to the ss
*/
static int
ss_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 != LPCS_OPERATIONAL) {
lwsl_notice("%s: ss not ready\n", __func__);
*len = 0;
return 1;
}
/*
* The onward secure stream says that we could send something to it
* (by putting it in buf, and setting *len and *flags)
*/
if (lws_ss_deserialize_tx_payload(m->conn->dsh, m->ss->wsi,
ord, buf, len, flags))
return 1;
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)
return 1; /* we don't actually want to send anything */
lwsl_info("%s: onward tx %d fl 0x%x\n", __func__, (int)*len, *flags);
#if 0
{
int ff = open("/tmp/z", O_RDWR | O_CREAT | O_APPEND, 0666);
if (ff == -1)
lwsl_err("%s: errno %d\n", __func__, errno);
write(ff, buf, *len);
close(ff);
}
#endif
return 0;
}
static int
ss_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;
switch (state) {
case LWSSSCS_CREATING:
break;
case LWSSSCS_DESTROYING:
if (!m->conn)
break;
if (!m->conn->wsi) {
/*
* Our onward secure stream is closing and our client
* connection has already gone away... destroy the conn.
*/
lwsl_info("%s: Destroying conn\n", __func__);
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 0;
}
lws_ss_serialize_state(m->conn->dsh, state, ack);
if (m->conn->wsi) /* if possible, request client conn write */
lws_callback_on_writable(m->conn->wsi);
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->wsi) /* if possible, request client conn write */
lws_callback_on_writable(m->conn->wsi);
}
/*
* Client - Proxy connection on unix domain socket
*/
static int
callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
struct raw_pss *pss = (struct raw_pss *)user;
const lws_ss_policy_t *rsp;
struct conn *conn = NULL;
lws_ss_info_t ssi;
const uint8_t *cp;
#if defined(LWS_WITH_DETAILED_LATENCY)
lws_usec_t us;
#endif
char s[128];
uint8_t *p;
size_t si;
char pay;
int n;
if (pss)
conn = pss->conn;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
break;
/* callbacks related to raw socket descriptor "accepted side" */
case LWS_CALLBACK_RAW_ADOPT:
lwsl_info("LWS_CALLBACK_RAW_ADOPT\n");
if (!pss)
return -1;
pss->conn = malloc(sizeof(struct conn));
if (!pss->conn)
return -1;
memset(pss->conn, 0, sizeof(*pss->conn));
pss->conn->dsh = lws_dsh_create(&pt->ss_dsh_owner,
LWS_SS_MTU * 160, 2);
if (!pss->conn->dsh) {
free(pss->conn);
return -1;
}
pss->conn->wsi = wsi;
pss->conn->state = LPCS_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);
break;
case LWS_CALLBACK_RAW_CLOSE:
lwsl_info("LWS_CALLBACK_RAW_CLOSE:\n");
/*
* the client unix domain socket connection has closed...
* eg, client has exited or otherwise has definitively finished
* with the proxying and onward connection
*/
if (!conn)
break;
if (conn->ss) {
lwsl_info("%s: destroying ss\n", __func__);
/* sever relationship with ss about to be deleted */
lws_set_opaque_user_data(wsi, NULL);
conn->wsi = NULL;
lws_ss_destroy(&conn->ss);
/* conn may have gone */
break;
}
if (conn->state == LPCS_DESTROYED || !conn->ss) {
/*
* There's no onward secure stream and our client
* connection is closing. Destroy the conn.
*/
lws_dsh_destroy(&conn->dsh);
free(conn);
pss->conn = NULL;
} else
lwsl_debug("%s: CLOSE; ss=%p\n", __func__, conn->ss);
break;
case LWS_CALLBACK_RAW_RX:
lwsl_info("%s: RX: rx %d\n", __func__, (int)len);
if (!conn || !conn->wsi) {
lwsl_err("%s: rx with bad conn state\n", __func__);
return -1;
}
// lwsl_hexdump_info(in, len);
if (conn->state == LPCS_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 = ss_proxy_onward_rx;
ssi.tx = ss_proxy_onward_tx;
ssi.state = ss_proxy_onward_state;
}
if (lws_ss_deserialize_parse(&conn->parser,
lws_get_context(wsi), conn->dsh, in, len,
&conn->state, conn, &conn->ss, &ssi, 0)) {
lwsl_err("%s: RAW_RX: deserialize_parse fail\n", __func__);
return -1;
}
if (conn->state == LPCS_REPORTING_FAIL ||
conn->state == LPCS_REPORTING_OK)
lws_callback_on_writable(conn->wsi);
break;
case LWS_CALLBACK_RAW_WRITEABLE:
// lwsl_notice("LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE\n");
/*
* We can transmit something back to the client from the dsh
* of stuff we received on its behalf from the ss
*/
if (!conn || !conn->wsi)
break;
n = 0;
pay = 0;
s[3] = 0;
cp = (const uint8_t *)s;
switch (conn->state) {
case LPCS_REPORTING_FAIL:
s[3] = 1;
/* fallthru */
case LPCS_REPORTING_OK:
s[0] = LWSSS_SER_RXPRE_CREATE_RESULT;
s[1] = 0;
s[2] = 1;
n = 4;
/*
* If there's rideshare sequencing, it's added after the
* first 4 bytes or the create result, comma-separated
*/
rsp = conn->ss->policy;
while (rsp) {
if (n != 4 && n < (int)sizeof(s) - 2)
s[n++] = ',';
n += lws_snprintf(&s[n], sizeof(s) - n,
"%s", rsp->streamtype);
rsp = lws_ss_policy_lookup(wsi->context,
rsp->rideshare_streamtype);
}
s[2] = n - 3;
conn->state = LPCS_OPERATIONAL;
lws_set_timeout(wsi, 0, 0);
break;
case LPCS_OPERATIONAL:
if (lws_dsh_get_head(conn->dsh, KIND_SS_TO_P,
(void **)&p, &si))
break;
cp = p;
#if defined(LWS_WITH_DETAILED_LATENCY)
if (cp[0] == LWSSS_SER_RXPRE_RX_PAYLOAD &&
wsi->context->detailed_latency_cb) {
/*
* we're fulfilling rx that came in on ss
* by sending it back out to the client on
* the Unix Domain Socket
*
* + 7 u32 write will compute latency here...
* + 11 u32 ust we received from ss
*
* lws_write will report it and fill in
* LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE
*/
us = lws_now_usecs();
lws_ser_wu32be(&p[7], us -
lws_ser_ru64be(&p[11]));
lws_ser_wu64be(&p[11], us);
wsi->detlat.acc_size =
wsi->detlat.req_size = si - 19;
/* time proxy held it */
wsi->detlat.latencies[
LAT_DUR_PROXY_RX_TO_ONWARD_TX] =
lws_ser_ru32be(&p[7]);
}
#endif
pay = 1;
n = (int)si;
break;
default:
break;
}
again:
if (!n)
break;
n = lws_write(wsi, (uint8_t *)cp, n, LWS_WRITE_RAW);
if (n < 0) {
lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
goto hangup;
}
switch (conn->state) {
case LPCS_REPORTING_FAIL:
goto hangup;
case LPCS_OPERATIONAL:
if (pay)
lws_dsh_free((void **)&p);
if (!lws_dsh_get_head(conn->dsh, KIND_SS_TO_P,
(void **)&p, &si)) {
if (!lws_send_pipe_choked(wsi)) {
cp = p;
pay = 1;
n = (int)si;
goto again;
}
lws_callback_on_writable(wsi);
}
break;
default:
break;
}
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
hangup:
//lws_ss_destroy(&conn->ss);
//conn->state = LPCS_DESTROYED;
/* hang up on him */
return -1;
}
static const struct lws_protocols protocols[] = {
{
"ssproxy-protocol",
callback_ss_proxy,
sizeof(struct raw_pss),
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
/*
* called from create_context()
*/
int
lws_ss_proxy_create(struct lws_context *context, 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;
info.port = port;
if (!port) {
if (!bind)
bind = "@proxy.ss.lws";
info.options |= LWS_SERVER_OPTION_UNIX_SOCK;
}
info.iface = bind;
info.unix_socket_perms = "root:root";
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;
}

View File

@ -0,0 +1,926 @@
/*
* 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.
*
*
* In the case Secure Streams protocol needs to pass through a buffer,
* or a streamed connection, the protocol metadata must be serialized. This
* file provides internal apis to perform the serialization and deserialization
* in and out of an lws_dsh fifo-type buffer.
*/
#include <private-lib-core.h>
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_RESULT_CREATION_RIDESHARE,
RPAR_METADATA_NAMELEN,
RPAR_METADATA_NAME,
RPAR_METADATA_VALUE,
RPAR_PAYLOAD,
RPAR_RX_TXCR_UPDATE,
RPAR_STREAMTYPE,
RPAR_INITTXC0,
RPAR_TXCR0,
RPAR_RESULT_CREATION,
RPAR_STATEINDEX,
RPAR_ORD3,
RPAR_ORD2,
RPAR_ORD1,
RPAR_ORD0,
} rx_parser_t;
#if defined(_DEBUG)
static const char *sn[] = {
"unset",
"LPCS_WAIT_INITIAL_TX",
"LPCS_REPORTING_FAIL",
"LPCS_REPORTING_OK",
"LPCS_OPERATIONAL",
"LPCS_DESTROYED",
"LPCS_SENDING_INITIAL_TX",
"LPCS_WAITING_CREATE_RESULT",
"LPCS_LOCAL_CONNECTED",
"LPCS_ONWARD_CONNECT",
};
#endif
void
lws_ss_serialize_state_transition(lws_ss_conn_states_t *state, int new_state)
{
#if defined(_DEBUG)
lwsl_info("%s: %s -> %s\n", __func__, sn[*state], sn[new_state]);
#endif
*state = new_state;
}
/*
* event loop received something and is queueing it for the foreign side of
* the dsh to consume later as serialized rx
*/
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);
l = 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], len + est - 3);
lws_ser_wu32be(&pre[3], flags);
lws_ser_wu32be(&pre[7], 0); /* write will compute latency here... */
lws_ser_wu64be(&pre[11], 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, l);
}
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, est, buf, len)) {
lwsl_err("%s: unable to alloc in dsh 1\n", __func__);
return 1;
}
return 0;
}
/*
* 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
*/
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 = lws_ser_ru16be(&p[1]) - (23 - 3);
assert(*len == si - 23);
memcpy(buf, p + 23, si - 23);
*flags = lws_ser_ru32be(&p[3]);
#if defined(LWS_WITH_DETAILED_LATENCY)
if (wsi && wsi->context->detailed_latency_cb) {
/*
* use the proxied latency information to compute the client
* and our delays, and apply to wsi.
*
* + 7 u32 us held at client before written
* +11 u32 us taken for transit to proxy
* +15 u64 ustime when proxy got packet from client
*/
lws_usec_t us = lws_now_usecs();
wsi->detlat.acc_size = wsi->detlat.req_size = si - 23;
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
lws_ser_ru32be(&p[7]);
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] =
lws_ser_ru32be(&p[11]);
wsi->detlat.latencies[LAT_DUR_PROXY_RX_TO_ONWARD_TX] =
us - lws_ser_ru64be(&p[15]);
wsi->detlat.latencies[LAT_DUR_USERCB] = 0;
}
#endif
// lwsl_user("%s: len %d, flags: %d\n", __func__, (int)*len, *flags);
// lwsl_hexdump_info(buf, *len);
lws_dsh_free((void **)&p);
return 0;
}
/*
* event loop side is issuing state, serialize and put it in the dbuf for
* the foreign side to consume later
*/
int
lws_ss_serialize_state(struct lws_dsh *dsh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
uint8_t pre[8];
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
pre[0] = LWSSS_SER_RXPRE_CONNSTATE;
pre[1] = 0;
pre[2] = 5;
pre[3] = (uint8_t)state;
lws_ser_wu32be(&pre[4], ack);
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 8, NULL, 0)) {
lwsl_err("%s: unable to alloc in dsh 2\n", __func__);
return 1;
}
return 0;
}
/*
* 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
*/
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], 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;
}
/*
* 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
*/
int
lws_ss_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, char client)
{
lws_ss_metadata_t *pm;
lws_sspc_handle_t *h;
uint8_t pre[23];
lws_usec_t us;
uint32_t flags;
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 = (*cp++) << 8;
par->ps++;
break;
case RPAR_LEN_LSB:
par->rem |= *cp++;
switch (par->type) {
/* event loop side */
case LWSSS_SER_TXPRE_TX_PAYLOAD:
if (client)
goto hangup;
if (*state != LPCS_OPERATIONAL)
goto hangup;
par->ps = RPAR_FLAG_B3;
break;
case LWSSS_SER_TXPRE_DESTROYING:
if (client)
goto hangup;
par->ps = RPAR_TYPE;
lwsl_notice("%s: DESTROYING\n", __func__);
goto hangup;
case LWSSS_SER_TXPRE_ONWARD_CONNECT:
if (client)
goto hangup;
if (*state != LPCS_OPERATIONAL)
goto hangup;
par->ps = RPAR_TYPE;
if (*pss)
lws_ss_client_connect(*pss);
break;
case LWSSS_SER_TXPRE_STREAMTYPE:
if (client)
goto hangup;
if (*state != LPCS_WAIT_INITIAL_TX)
goto hangup;
if (par->rem < 4)
goto hangup;
par->ctr = 0;
par->ps = RPAR_INITTXC0;
break;
case LWSSS_SER_TXPRE_METADATA:
if (client)
goto hangup;
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;
/* client side */
case LWSSS_SER_RXPRE_RX_PAYLOAD:
if (!client)
goto hangup;
if (*state != LPCS_OPERATIONAL &&
*state != LPCS_LOCAL_CONNECTED) {
lwsl_err("rx in state %d\n", *state);
goto hangup;
}
par->rideshare[0] = '\0';
par->ps = RPAR_FLAG_B3;
break;
case LWSSS_SER_RXPRE_CREATE_RESULT:
if (!client)
goto hangup;
if (*state != LPCS_WAITING_CREATE_RESULT) {
lwsl_err("a2\n");
goto hangup;
}
if (par->rem < 1) {
lwsl_err("a3\n");
goto hangup;
}
par->ps = RPAR_RESULT_CREATION;
break;
case LWSSS_SER_RXPRE_CONNSTATE:
if (!client)
goto hangup;
if (*state != LPCS_LOCAL_CONNECTED &&
*state != LPCS_OPERATIONAL) {
lwsl_err("a4\n");
goto hangup;
}
if (par->rem < 4) {
lwsl_err("a5\n");
goto hangup;
}
par->ps = RPAR_STATEINDEX;
break;
case LWSSS_SER_RXPRE_TXCR_UPDATE:
par->ctr = 0;
par->ps = RPAR_RX_TXCR_UPDATE;
break;
default:
lwsl_notice("%s: bad type 0x%x\n", __func__,
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_RIDESHARE:
par->rideshare[par->ctr++] = *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;
if (n > 1380)
n = 1380;
/* deal with refragmented SOM / EOM flags */
flags = par->flags & LWSSS_FLAG_RELATED_START;
if (par->frag1)
flags |= par->flags &
(LWSSS_FLAG_SOM | LWSSS_FLAG_POLL);
if (par->rem == n)
flags |= par->flags & (LWSSS_FLAG_EOM |
LWSSS_FLAG_RELATED_END);
par->frag1 = 0;
us = lws_now_usecs();
if (!client) {
/*
* Proxy - we received some serialized tx from
* the client.
*
* The header for buffering private to the
* proxy is 23 bytes vs 19 to hold the
* current time when it was buffered
*/
lwsl_info("%s: C2P RX: len %d\n", __func__, (int)n);
p = pre;
pre[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], n + 23 - 3);
lws_ser_wu32be(&p[3], par->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], us - par->ust_pwait);
/* time used later to find proxy hold time */
lws_ser_wu64be(&p[15], us);
if (lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre,
23, cp, n)) {
lwsl_err("%s: unable to alloc in dsh 3\n",
__func__);
return 1;
}
lws_ss_request_tx(*pss);
} else {
/*
* Client receives some RX from proxy
*
* Pass whatever payload we have to ss user
*/
lwsl_info("%s: P2C RX: len %d\n", __func__, (int)n);
h = lws_container_of(par, lws_sspc_handle_t, parser);
h->txc.peer_tx_cr_est -= n;
ssi->rx((void *)pss, (uint8_t *)cp, n, flags);
#if defined(LWS_WITH_DETAILED_LATENCY)
if (lws_det_lat_active(context)) {
lws_detlat_t d;
d.type = LDLT_READ;
d.acc_size = d.req_size = n;
d.latencies[LAT_DUR_USERCB] =
lws_now_usecs() - us;
d.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
par->usd_phandling;
d.latencies[LAT_DUR_PROXY_CLIENT_WRITE_TO_PROXY_RX] =
us - par->ust_pwait;
lws_det_lat_cb(context, &d);
}
#endif
}
if (n) {
cp += n;
par->rem -= n;
len = (len + 1) - n;
}
if (!par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_RX_TXCR_UPDATE:
if (!--par->rem && par->ctr != 3)
goto hangup;
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4)
break;
/*
* Proxy is telling us remote endpoint is allowing us
* par->temp32 more bytes tx credit to write to it
*/
h = lws_container_of(par, lws_sspc_handle_t, parser);
h->txc.tx_cr += par->temp32;
lwsl_info("%s: RX_PEER_TXCR: %d\n", __func__, par->temp32);
lws_sspc_request_tx(h); /* in case something waiting */
par->ctr = 0;
par->ps = RPAR_TYPE;
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;
if (!client) {
/*
* 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 ((*pss)->wsi) {
lws_wsi_tx_credit((*pss)->wsi,
LWSTXCR_PEER_TO_US,
par->temp32);
lwsl_notice("%s: proxy RX_PEER_TXCR: +%d (est %d)\n",
__func__, par->temp32,
(*pss)->wsi->txc.peer_tx_cr_est);
lws_ss_request_tx(*pss);
} else
#endif
lwsl_info("%s: dropping TXCR\n", __func__);
} else {
/*
* We're the client, being told by the proxy
* about tx credit being given to us from the
* remote peer, allowing the client to write to
* it.
*/
h = lws_container_of(par, lws_sspc_handle_t, parser);
h->txc.tx_cr += par->temp32;
lwsl_info("%s: client RX_PEER_TXCR: %d\n",
__func__, par->temp32);
lws_sspc_request_tx(h); /* in case something waiting */
}
par->ps = RPAR_TYPE;
break;
case RPAR_METADATA_NAMELEN:
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:
if (!--par->rem)
goto hangup;
par->metadata_name[par->ctr++] = *cp++;
if (par->ctr != par->slen)
break;
par->ps = RPAR_METADATA_VALUE;
/* only non-client side can receive these */
/*
* This is the policy's metadata list for the given
* name
*/
pm = lws_ss_policy_metadata((*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 = &(*pss)->metadata[pm->length];
if (par->ssmd->value_on_lws_heap)
lws_free_set_NULL(par->ssmd->value);
par->ssmd->value_on_lws_heap = 0;
par->ssmd->value = lws_malloc(par->rem + 1, "metadata");
if (!par->ssmd->value) {
lwsl_err("%s: OOM mdv\n", __func__);
goto hangup;
}
par->ssmd->length = par->rem;
/* mark it as needing cleanup */
par->ssmd->value_on_lws_heap = 1;
par->ctr = 0;
break;
case RPAR_METADATA_VALUE:
((uint8_t *)(par->ssmd->value))[par->ctr++] = *cp++;
if (--par->rem)
break;
/* we think we got all the value */
lwsl_info("%s: RPAR_METADATA_VALUE for %s (len %d)\n",
__func__, par->ssmd->name,
(int)par->ssmd->length);
lwsl_hexdump_info(par->ssmd->value, par->ssmd->length);
par->ps = RPAR_TYPE;
break;
case RPAR_STREAMTYPE:
if (client)
goto hangup;
if (par->ctr == sizeof(par->streamtype) - 1)
goto hangup;
/*
* We're the proxy, creating an SS on behalf of a
* client
*/
par->streamtype[par->ctr++] = *cp++;
if (--par->rem)
break;
par->ps = RPAR_TYPE;
par->streamtype[par->ctr] = '\0';
lwsl_notice("%s: creating proxied ss '%s', txcr %d\n",
__func__, par->streamtype, par->txcr_out);
ssi->streamtype = par->streamtype;
if (par->txcr_out)
ssi->manual_initial_tx_credit = par->txcr_out;
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 = LPCS_REPORTING_FAIL;
} else {
lwsl_debug("%s: create '%s' OK\n",
__func__, par->streamtype);
*state = LPCS_REPORTING_OK;
}
if (*pss) {
(*pss)->being_serialized = 1;
lwsl_notice("%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:
if (*cp++) {
lwsl_err("%s: stream creation failed\n",
__func__);
goto hangup;
}
lws_ss_serialize_state_transition(state,
LPCS_LOCAL_CONNECTED);
h = lws_container_of(par, lws_sspc_handle_t, parser);
if (h->cwsi)
lws_callback_on_writable(h->cwsi);
/*
* This is telling us that the streamtype could be (and
* was) created at the proxy. It's not telling us that
* the onward peer connection could be connected.
*
* We'll get a proxied state() coming later that informs
* us about the situation with that.
*/
par->rsl_pos = 0;
par->rsl_idx = 0;
h = lws_container_of(par, lws_sspc_handle_t, parser);
memset(&h->rideshare_ofs[0], 0, sizeof(h->rideshare_ofs[0]));
h->rideshare_list[0] = '\0';
h->rsidx = 0;
if (!--par->rem)
par->ps = RPAR_TYPE;
else {
par->ps = RPAR_RESULT_CREATION_RIDESHARE;
if (par->rem >= sizeof(h->rideshare_list))
goto hangup;
}
break;
case RPAR_RESULT_CREATION_RIDESHARE:
h = lws_container_of(par, lws_sspc_handle_t, parser);
if (*cp == ',') {
cp++;
h->rideshare_list[par->rsl_pos++] = '\0';
if (par->rsl_idx == LWS_ARRAY_SIZE(h->rideshare_ofs))
goto hangup;
h->rideshare_ofs[++par->rsl_idx] = par->rsl_pos;
} else
h->rideshare_list[par->rsl_pos++] = *cp++;
if (!--par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_STATEINDEX:
par->ctr = *cp++;
par->ps = RPAR_ORD3;
break;
case RPAR_ORD3:
par->flags = (*cp++) << 24;
par->ps++;
break;
case RPAR_ORD2:
par->flags |= (*cp++) << 16;
par->ps++;
break;
case RPAR_ORD1:
par->flags |= (*cp++) << 8;
par->ps++;
break;
case RPAR_ORD0:
par->flags |= *cp++;
par->ps++;
par->ps = RPAR_TYPE;
/*
* we received a proxied state change
*/
switch (par->ctr) {
case LWSSSCS_DISCONNECTED:
case LWSSSCS_UNREACHABLE:
case LWSSSCS_AUTH_FAILED:
lws_ss_serialize_state_transition(state,
LPCS_LOCAL_CONNECTED);
break;
case LWSSSCS_CONNECTED:
lwsl_info("%s: CONNECTED %s\n", __func__,
ssi->streamtype);
lws_ss_serialize_state_transition(state,
LPCS_OPERATIONAL);
break;
default:
break;
}
if (par->ctr < 0 || par->ctr > 9)
goto hangup;
#if defined(_DEBUG)
lwsl_info("%s: forwarding proxied state %s\n",
__func__, sn[par->ctr]);
#endif
if (ssi->state((void *)pss, NULL, par->ctr, par->flags))
goto hangup;
break;
default:
goto hangup;
}
}
return 0;
hangup:
return -1;
}

View File

@ -0,0 +1,569 @@
/*
* 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 const struct ss_pcols *ss_pcols[] = {
#if defined(LWS_ROLE_H1)
&ss_pcol_h1, /* LWSSSP_H1 */
#else
NULL,
#endif
#if defined(LWS_ROLE_H2)
&ss_pcol_h2, /* LWSSSP_H2 */
#else
NULL,
#endif
#if defined(LWS_ROLE_WS)
&ss_pcol_ws, /* LWSSSP_WS */
#else
NULL,
#endif
#if defined(LWS_ROLE_MQTT)
&ss_pcol_mqtt, /* LWSSSP_MQTT */
#else
NULL,
#endif
};
static const char *state_names[] = {
"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",
};
const char *
lws_ss_state_name(int state)
{
if (state >= (int)LWS_ARRAY_SIZE(state_names))
return "unknown";
return state_names[state];
}
int
lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs)
{
if (!h)
return 0;
#if defined(LWS_WITH_SEQUENCER)
/*
* A parent sequencer for the ss is optional, if we have one, keep it
* informed of state changes on the ss connection
*/
if (h->seq && cs != LWSSSCS_DESTROYING)
lws_seq_queue_event(h->seq, LWSSEQ_SS_STATE_BASE + cs,
(void *)h, NULL);
#endif
if (h->h_sink &&h->h_sink->info.state(h->sink_obj, h->h_sink, cs, 0))
return 1;
return h->info.state(ss_to_userobj(h), NULL, cs, 0);
}
static void
lws_ss_timeout_sul_check_cb(lws_sorted_usec_list_t *sul)
{
lws_ss_handle_t *h = lws_container_of(sul, lws_ss_handle_t, sul);
lwsl_err("%s: retrying ss h %p after backoff\n", __func__, h);
/* we want to retry... */
h->seqstate = SSSEQ_DO_RETRY;
lws_ss_request_tx(h);
}
int
lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,
size_t olen, size_t *exp_ofs)
{
lws_ss_handle_t *h = (lws_ss_handle_t *)priv;
const char *replace = NULL;
size_t total, budget;
lws_ss_metadata_t *md = lws_ss_policy_metadata(h->policy, name);
if (!md) {
lwsl_err("%s: Unknown metadata %s\n", __func__, name);
return LSTRX_FATAL_NAME_UNKNOWN;
}
lwsl_info("%s %s %d\n", __func__, name, (int)md->length);
replace = h->metadata[md->length].value;
total = h->metadata[md->length].length;
// lwsl_hexdump_err(replace, total);
budget = olen - *pos;
total -= *exp_ofs;
if (total < budget)
budget = total;
memcpy(out + *pos, replace + (*exp_ofs), budget);
*exp_ofs += budget;
*pos += budget;
if (budget == total)
return LSTRX_DONE;
return LSTRX_FILLED_OUT;
}
int
lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us)
{
struct lws_context_per_thread *pt = &h->context->pt[h->tsi];
h->sul.cb = lws_ss_timeout_sul_check_cb;
__lws_sul_insert(&pt->pt_sul_owner, &h->sul, us);
return 0;
}
int
lws_ss_backoff(lws_ss_handle_t *h)
{
uint64_t ms;
char conceal;
if (h->seqstate == SSSEQ_RECONNECT_WAIT)
return 0;
/* figure out what we should do about another retry */
lwsl_info("%s: ss %p: retry backoff after failure\n", __func__, h);
ms = lws_retry_get_delay_ms(h->context, h->policy->retry_bo,
&h->retry, &conceal);
if (!conceal) {
lwsl_info("%s: ss %p: abandon conn attempt \n",__func__, h);
h->seqstate = SSSEQ_IDLE;
lws_ss_event_helper(h, LWSSSCS_ALL_RETRIES_FAILED);
return 1;
}
h->seqstate = SSSEQ_RECONNECT_WAIT;
lws_ss_set_timeout_us(h, ms * LWS_US_PER_MS);
lwsl_info("%s: ss %p: retry wait %"PRIu64"ms\n", __func__, h, ms);
return 0;
}
int
lws_ss_client_connect(lws_ss_handle_t *h)
{
struct lws_client_connect_info i;
const struct ss_pcols *ssp;
union lws_ss_contemp ct;
char path[128];
if (!h->policy) {
lwsl_err("%s: ss with no policy\n", __func__);
return -1;
}
/*
* We are already bound to a sink?
*/
if (h->h_sink)
return 0;
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = h->context;
if (h->policy->flags & LWSSSPOLF_TLS) {
lwsl_info("%s: using tls\n", __func__);
i.ssl_connection = LCCSCF_USE_SSL;
if (!h->policy->trust_store) {
lwsl_err("%s: tls required but no policy trust store\n",
__func__);
return -1;
}
i.vhost = lws_get_vhost_by_name(h->context,
h->policy->trust_store->name);
if (!i.vhost) {
lwsl_err("%s: missing vh for policy ca\n", __func__);
return -1;
}
}
i.address = h->policy->endpoint;
i.port = h->policy->port;
i.host = i.address;
i.origin = i.address;
i.opaque_user_data = h;
i.seq = h->seq;
i.retry_and_idle_policy = h->policy->retry_bo;
i.sys_tls_client_cert = h->policy->client_cert;
i.path = "";
ssp = ss_pcols[(int)h->policy->protocol];
if (!ssp) {
lwsl_err("%s: unsupported protocol\n", __func__);
return -1;
}
i.alpn = ssp->alpn;
/*
* For http, we can get the method from the http object, override in
* the protocol-specific munge callback below if not http
*/
i.method = h->policy->u.http.method;
i.protocol = ssp->protocol_name; /* lws protocol name */
i.local_protocol_name = i.protocol;
ssp->munge(h, path, sizeof(path), &i, &ct);
i.pwsi = &h->wsi;
if (h->policy->plugins[0] && h->policy->plugins[0]->munge)
h->policy->plugins[0]->munge(h, path, sizeof(path));
lwsl_info("%s: connecting %s, '%s' '%s' %s\n", __func__, i.method,
i.alpn, i.address, i.path);
h->txn_ok = 0;
if (lws_ss_event_helper(h, LWSSSCS_CONNECTING))
return -1;
if (!lws_client_connect_via_info(&i)) {
lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
lws_ss_backoff(h);
return 1;
}
return 0;
}
/*
* Public API
*/
/*
* Create either a stream or a sink
*/
int
lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, lws_ss_handle_t **ppss,
struct lws_sequencer *seq_owner, const char **ppayload_fmt)
{
struct lws_context_per_thread *pt = &context->pt[tsi];
const lws_ss_policy_t *pol;
lws_ss_metadata_t *smd;
lws_ss_handle_t *h;
size_t size;
void **v;
char *p;
int n;
pol = lws_ss_policy_lookup(context, ssi->streamtype);
if (!pol) {
lwsl_err("%s: unknown stream type %s\n", __func__,
ssi->streamtype);
return 1;
}
if (ssi->register_sink) {
/*
* This can register a secure streams sink as well as normal
* secure streams connections. If that's what's happening,
* confirm the policy agrees that this streamtype should be
* directed to a sink.
*/
if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) {
/*
* Caller wanted to create a sink for this streamtype,
* but the policy does not agree the streamtype should
* be routed to a local sink.
*/
lwsl_err("%s: %s policy does not allow local sink\n",
__func__, ssi->streamtype);
return 1;
}
} else {
if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) {
}
// lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll);
}
/*
* We overallocate and point to things in the overallocation...
*
* 1) the user_alloc from the stream info
* 2) network auth plugin instantiation data
* 3) stream auth plugin instantiation data
* 4) as many metadata pointer structs as the policy tells
* 5) the streamtype name (length is not aligned)
*
* ... when we come to destroy it, just one free to do.
*/
size = sizeof(*h) + ssi->user_alloc + strlen(ssi->streamtype) + 1;
if (pol->plugins[0])
size += pol->plugins[0]->alloc;
if (pol->plugins[1])
size += pol->plugins[1]->alloc;
size += pol->metadata_count * sizeof(lws_ss_metadata_t);
h = lws_zalloc(size, __func__);
if (!h)
return 2;
h->info = *ssi;
h->policy = pol;
h->context = context;
h->tsi = tsi;
h->seq = seq_owner;
/* start of overallocated area */
p = (char *)&h[1];
/* set the handle pointer in the user data struct */
v = (void **)(p + ssi->handle_offset);
*v = h;
/* set the opaque user data in the user data struct */
v = (void **)(p + ssi->opaque_user_data_offset);
*v = opaque_user_data;
p += ssi->user_alloc;
if (pol->plugins[0]) {
h->nauthi = p;
p += pol->plugins[0]->alloc;
}
if (pol->plugins[1]) {
h->sauthi = p;
p += pol->plugins[1]->alloc;
}
if (pol->metadata_count) {
h->metadata = (lws_ss_metadata_t *)p;
p += pol->metadata_count * sizeof(lws_ss_metadata_t);
lwsl_info("%s: %s metadata count %d\n", __func__,
pol->streamtype, pol->metadata_count);
}
smd = pol->metadata;
for (n = 0; n < pol->metadata_count; n++) {
h->metadata[n].name = smd->name;
if (n + 1 == pol->metadata_count)
h->metadata[n].next = NULL;
else
h->metadata[n].next = &h->metadata[n + 1];
smd = smd->next;
}
memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1);
h->info.streamtype = p;
lws_pt_lock(pt, __func__);
lws_dll2_add_head(&h->list, &pt->ss_owner);
lws_pt_unlock(pt);
if (ppss)
*ppss = h;
if (ppayload_fmt)
*ppayload_fmt = pol->payload_fmt;
if (ssi->register_sink) {
/*
*
*/
}
lws_ss_event_helper(h, LWSSSCS_CREATING);
if (!ssi->register_sink && (h->policy->flags & LWSSSPOLF_NAILED_UP))
if (lws_ss_client_connect(h))
lws_ss_backoff(h);
return 0;
}
void
lws_ss_destroy(lws_ss_handle_t **ppss)
{
struct lws_context_per_thread *pt;
lws_ss_handle_t *h = *ppss;
lws_ss_metadata_t *pmd;
if (!h)
return;
if (h->wsi) {
/*
* Don't let the wsi point to us any more,
* we (the ss object bound to the wsi) are going away now
*/
// lws_set_opaque_user_data(h->wsi, NULL);
lws_set_timeout(h->wsi, 1, LWS_TO_KILL_SYNC);
}
pt = &h->context->pt[h->tsi];
lws_pt_lock(pt, __func__);
*ppss = NULL;
lws_dll2_remove(&h->list);
lws_dll2_remove(&h->to_list);
lws_ss_event_helper(h, LWSSSCS_DESTROYING);
lws_pt_unlock(pt);
/* in proxy case, metadata value on heap may need cleaning up */
pmd = h->metadata;
while (pmd) {
lwsl_info("%s: pmd %p\n", __func__, pmd);
if (pmd->value_on_lws_heap)
lws_free_set_NULL(pmd->value);
pmd = pmd->next;
}
lws_sul_schedule(h->context, 0, &h->sul, NULL, LWS_SET_TIMER_USEC_CANCEL);
lws_free_set_NULL(h);
}
void
lws_ss_request_tx(lws_ss_handle_t *h)
{
lwsl_info("%s: wsi %p\n", __func__, h->wsi);
if (h->wsi) {
lws_callback_on_writable(h->wsi);
return;
}
if (h->seqstate != SSSEQ_IDLE &&
h->seqstate != SSSEQ_DO_RETRY)
return;
h->seqstate = SSSEQ_TRY_CONNECT;
lws_ss_event_helper(h, LWSSSCS_POLL);
if (lws_ss_client_connect(h))
lws_ss_backoff(h);
}
void
lws_ss_request_tx_len(lws_ss_handle_t *h, unsigned long len)
{
if (h->wsi)
h->wsi->http.writeable_len = len;
else
h->writeable_len = len;
lws_ss_request_tx(h);
}
/*
* private helpers
*/
/* used on context destroy when iterating listed lws_ss on a pt */
int
lws_ss_destroy_dll(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;
}
struct lws_sequencer *
lws_ss_get_sequencer(lws_ss_handle_t *h)
{
return h->seq;
}
struct lws_context *
lws_ss_get_context(struct lws_ss_handle *h)
{
return h->context;
}
const char *
lws_ss_rideshare(struct lws_ss_handle *h)
{
if (!h->rideshare)
return h->policy->streamtype;
return h->rideshare->streamtype;
}
int
lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t bump)
{
const struct ss_pcols *ssp;
ssp = ss_pcols[(int)h->policy->protocol];
if (h->wsi && ssp && ssp->tx_cr_add)
return ssp->tx_cr_add(h, bump);
return 0;
}
int
lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h)
{
const struct ss_pcols *ssp;
ssp = ss_pcols[(int)h->policy->protocol];
if (h->wsi && ssp && ssp->tx_cr_add)
return ssp->tx_cr_est(h);
return 0;
}

View File

@ -0,0 +1,276 @@
/*
* 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;
lws_sorted_usec_list_t sul;
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);
/* if nothing is there to intercept anything, go all the way */
lws_state_transition_steps(&context->mgr_system,
LWS_SYSTATE_OPERATIONAL);
}
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;
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;
if (lws_system_blob_heap_append(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_LWA),
(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,
(uint64_t)m->expires_secs * LWS_US_PER_SEC);
break;
}
return 0;
}
/* secure streams payload interface */
static int
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;
size_t total;
int n;
ab = lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH, AUTH_IDX_LWA);
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 = (int)(signed char)lejp_parse(&m->jctx, buf, 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 -1;
}
}
if (!(flags & LWSSS_FLAG_EOM))
return 0;
/* we should have the auth token now */
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);
lejp_destruct(&m->jctx);
/* we move the system state at auth connection close */
return 0;
}
static int
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);
total = lws_system_blob_get_size(ab);
n = lws_system_blob_get(ab, buf, len, m->pos);
if (n < 0)
return 1;
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 0;
}
static int
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;
size_t s;
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
case LWSSSCS_CONNECTING:
s = lws_system_blob_get_size(
lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_ROOT));
lws_ss_request_tx_len(m->ss, s);
m->pos = 0;
break;
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(
lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_LWA));
if (s)
lws_sul_schedule(context, 0,
&context->sul_api_amazon_com_kick,
lws_ss_sys_auth_api_amazon_com_kick, 1);
break;
case LWSSSCS_DESTROYING:
lws_sul_schedule(context, 0, &context->sul_api_amazon_com,
NULL, LWS_SET_TIMER_USEC_CANCEL);
lws_system_blob_destroy(
lws_system_get_blob(context, LWS_SYSBLOB_TYPE_AUTH,
AUTH_IDX_LWA));
break;
default:
break;
}
return 0;
}
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,159 @@
/*
* 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 int
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))
return 1;
m->partway = 1;
}
if (len && lws_ss_policy_parse(context, buf, len) < 0)
return 1;
if (flags & LWSSS_FLAG_EOM)
m->partway = 2;
return 0;
}
static int
ss_fetch_policy_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
return 1;
}
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
*/
if (lws_ss_policy_set(context, "updated"))
lwsl_err("%s: policy set failed\n", __func__);
else {
context->policy_updated = 1;
lws_state_transition_steps(&context->mgr_system,
LWS_SYSTATE_OPERATIONAL);
}
}
static int
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(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_request_tx(m->ss);
break;
case LWSSSCS_CONNECTING:
break;
case LWSSSCS_DISCONNECTED:
lwsl_info("%s: DISCONNECTED\n", __func__);
switch (m->partway) {
case 1:
lws_ss_policy_parse_abandon(context);
break;
case 2:
lws_sul_schedule(context, 0, &m->sul, policy_set, 1);
break;
}
m->partway = 0;
break;
default:
break;
}
return 0;
}
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: Create LWA auth ss failed (policy?)\n", __func__);
return 1;
}
return 0;
}

View File

@ -340,14 +340,17 @@ do_cb:
if (p + 14 > e)
return -1;
/* it should have exactly reached rrpaylen */
#if 0
/* it should have exactly reached rrpaylen if only one
* CNAME, else somewhere in the middle */
if (p != pay + rrpaylen) {
lwsl_err("%s: cname name bad len %d\n", __func__, rrpaylen);
return -1;
}
#endif
lwsl_notice("%s: recursing looking for %s\n", __func__, stack[stp].name);
lwsl_info("%s: recursing looking for %s\n", __func__,
stack[stp].name);

View File

@ -134,7 +134,7 @@ callback_ntpc(struct lws *wsi, enum lws_callback_reasons reason, void *user,
if (v)
break;
lwsl_debug("%s: LWS_CALLBACK_PROTOCOL_INIT\n", __func__);
lwsl_debug("%s: LWS_CALLBACK_PROTOCOL_INIT:\n", __func__);
lws_protocol_vh_priv_zalloc(wsi->vhost, wsi->protocol,
sizeof(*v));
v = (struct vhd_ntpc *)lws_protocol_vh_priv_get(wsi->vhost,

View File

@ -56,6 +56,8 @@ lws_system_blob_heap_append(lws_system_blob_t *b, const uint8_t *buf, size_t len
{
assert(!b->is_direct);
lwsl_debug("%s: blob %p\n", __func__, b);
if (lws_buflist_append_segment(&b->u.bl, buf, len) < 0)
return -1;
@ -126,6 +128,7 @@ lws_system_blob_destroy(lws_system_blob_t *b)
{
if (!b)
return;
lwsl_info("%s: blob %p\n", __func__, b);
if (!b->is_direct)
lws_buflist_destroy_all_segments(&b->u.bl);
}

View File

@ -60,6 +60,8 @@ int lws_ssl_get_error(struct lws *wsi, int n)
lwsl_debug("%s: %p %d -> %d (errno %d)\n", __func__, wsi->tls.ssl, n, m,
errno);
assert (errno != 9);
return m;
}

View File

@ -6,6 +6,7 @@ dbus-server|Minimal examples showing how to integrate DBUS into lws event loop
http-client|Minimal examples providing an http client
http-server|Minimal examples providing an http server
raw|Minimal examples related to adopting raw file or socket descriptors into the event loop
secure-streams|Minimal examples related to the Secure Streams client api
ws-client|Minimal examples providing a ws client
ws-server|Minimal examples providing a ws server (and an http server)

View File

@ -39,7 +39,7 @@ drain_cb(lws_sorted_usec_list_t *sul)
{
struct pss *pss = lws_container_of(sul, struct pss, sul);
lws_h2_update_peer_txcredit(pss->wsi, LWS_H2_STREAM_SID, each);
lws_wsi_tx_credit(pss->wsi, LWSTXCR_PEER_TO_US, each);
lws_sul_schedule(lws_get_context(pss->wsi), 0, &pss->sul, drain_cb,
250 * LWS_US_PER_MS);

View File

@ -0,0 +1,14 @@
# Secure Streams
Secure Streams is a client API that strictly decouples the policy for connections
from the payloads. The user code only deals with the stream type name and payloads,
a policy database set at `lws_context` creation time decides all policy about the
connection, including the endpoint, tls CA, and even the wire protocol.
|name|demonstrates|
---|---
minimal-secure-streams|Minimal secure streams client / proxy example
minimal-secure-streams-tx|Proxy used for client-tx test below
minimal-secure-streams-client-tx|Secure streams client showing tx and rx

View File

@ -0,0 +1,93 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-alexa)
set(SRCS main.c alexa.c audio.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
require_lws_config(LWS_WITH_ALSA 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared asound pv_porcupine mpg123)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets asound pv_porcupine mpg123)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
add_compile_options(-DLWS_SS_USE_SSPC)
add_executable(${SAMP}-client ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP}-client websockets_shared asound pv_porcupine mpg123)
add_dependencies(${SAMP}-client websockets_shared)
else()
target_link_libraries(${SAMP}-client websockets asound pv_porcupine mpg123)
endif()
endif()
endif()

View File

@ -0,0 +1,77 @@
# lws secure streams alexa
This demonstrates AVS Alexa usage using secure streams. It connects to AVS,
uses your linux computer's microphone to wait for the 'alexa' wakeword, sends
the utterance to AVS and plays back the result.
## build
There are some special build considerations:
1) Build lws with cmake options `-DLWS_WITH_ALSA=1 -DLWS_WITH_SECURE_STREAMS=1`
2) Install distro build dependency packages:
|Dependency|Ubuntu package|Fedora Package|
|---|---|---|
|libasound|libasound2-dev|alsa-lib-devel|
|mpg123|libmpg123-dev|mpg123-devel|
3) Clone Picovoice Porcupine Apache-licensed demo version from here
https://github.com/Picovoice/porcupine
It provides binary libs for wakeword detection on various platforms. Copy
the headers and binary lib to your build context, eg, for native x86_64
```
$ sudo cp ./include/* /usr/include
$ sudo cp ./lib/linux/x86_64/libpv_porcupine.* /usr/lib
$ sudo ldconfig
```
Enter the minimal example dir for secure-streams-alexa and make the sample
```
$ cd ./minimal-examples/secure-streams/minimal-secure-streams-alexa
$ cmake .
$ make
```
## usage
```
$ ./lws-minimal-secure-streams-alexa
[2019/10/16 16:22:01:1097] U: LWS secure streams - Alex voice test [-d<verb>]
[2019/10/16 16:22:01:1115] N: lws_create_context: creating Secure Streams policy
[2019/10/16 16:22:01:1115] N: lwsac_use: alloc 1532 for 1
[2019/10/16 16:22:01:1119] N: lwsac_use: alloc 288 for 168
[2019/10/16 16:22:01:1119] N: lws_ss_policy_set: policy lwsac size: 1.796KiB, pad 11%
[2019/10/16 16:22:02:4114] N: lws_ss_client_connect: connecting 0 api.amazon.com /auth/o2/token
[2019/10/16 16:22:02:8686] N: auth_api_amazon_com_parser_cb: expires in 3600
[2019/10/16 16:22:02:8686] N: ss_api_amazon_auth_rx: acquired 656-byte api.amazon.com auth token
[2019/10/16 16:22:02:8754] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/directives
[2019/10/16 16:22:02:3182] N: secstream_h2: h2 client entering LONG_POLL
[2019/10/16 16:22:02:3183] U: Connected to Alexa... speak "Alexa, ..."
[2019/10/16 16:22:06:9380] W: ************* Wakeword
[2019/10/16 16:22:06:9380] N: avs_query_start:
[2019/10/16 16:22:06:9381] N: lws_ss_client_connect: connecting 1 alexa.na.gateway.devices.a2z.com /v20160207/events
[2019/10/16 16:22:06:9381] N: lws_vhost_active_conns: just join h2 directly
[2019/10/16 16:22:06:9384] N: metadata done
[2019/10/16 16:22:06:1524] N: est: 42 1
[2019/10/16 16:22:06:3723] N: est: 108 1
[2019/10/16 16:22:07:5914] N: est: 352 1
[2019/10/16 16:22:07:8112] N: est: 4284 1
[2019/10/16 16:22:07:0300] N: est: 3369 1
[2019/10/16 16:22:07:2325] N: est: 577 1
[2019/10/16 16:22:08:4519] N: est: 9 1
[2019/10/16 16:22:08:6716] N: est: 3 1
[2019/10/16 16:22:08:6718] N: est: 11 1
[2019/10/16 16:22:08:8915] N: est: 10 1
[2019/10/16 16:22:08:8915] W: callback_audio: ended capture
[2019/10/16 16:22:09:0993] N: identified reply...
^C[2019/10/16 16:22:14:3067] U: Disconnected from Alexa
[2019/10/16 16:22:14:3123] U: Completed
$
```

View File

@ -0,0 +1,678 @@
/*
* lws-minimal-secure-streams-alexa
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <mpg123.h>
#include "private.h"
struct lws_ss_handle *hss_avs_event, *hss_avs_sync;
/* this is the type for the long poll event channel */
typedef struct ss_avs_event {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
struct lejp_ctx jctx;
} ss_avs_event_t;
enum {
LAMP3STATE_IDLE,
LAMP3STATE_SPOOLING,
LAMP3STATE_DRAINING,
};
/* this is the type for the utterance metadata (and audio rideshares) */
typedef struct ss_avs_metadata {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
struct lws_buflist *dribble; /* next mp3 data while draining last */
struct lejp_ctx jctx;
size_t pos;
size_t mp3_in;
mpg123_handle *mh;
lws_sorted_usec_list_t sul;
uint8_t stash_eom[16];
uint8_t se_head;
uint8_t se_tail;
char mp3_state;
char first_mp3;
uint8_t mp3_mime_match;
uint8_t seen;
uint8_t inside_mp3;
} ss_avs_metadata_t;
/*
* The remote server only seems to give us a budget of 10s to consume the
* results, after that it doesn't drop the stream, but doesn't send us anything
* further on it.
*
* This makes it impossible to optimize buffering for incoming mp3 since we
* have to go ahead and take it before the 10s is up.
*/
#define MAX_MP3_IN_BUFFERING_BYTES 32768
/*
* Structure of JSON metadata for utterance handling
*/
static const char *metadata = "{"
"\"event\": {"
"\"header\": {"
"\"namespace\": \"SpeechRecognizer\","
"\"name\": \"Recognize\","
"\"messageId\": \"message-123\","
"\"dialogRequestId\": \"dialog-request-321\""
"},"
"\"payload\": {"
"\"profile\":" "\"CLOSE_TALK\","
"\"format\":" "\"AUDIO_L16_RATE_16000_CHANNELS_1\""
"}"
"}"
"}";
/*
* avs metadata
*/
static void
use_buffer_250ms(lws_sorted_usec_list_t *sul)
{
ss_avs_metadata_t *m = lws_container_of(sul, ss_avs_metadata_t, sul);
struct lws_context *context = (struct lws_context *)m->opaque_data;
int est = lws_ss_get_est_peer_tx_credit(m->ss);
lwsl_notice("%s: est txcr %d\n", __func__, est);
if (est < MAX_MP3_IN_BUFFERING_BYTES - (MAX_MP3_IN_BUFFERING_BYTES / 4)) {
lwsl_notice(" adding %d\n", MAX_MP3_IN_BUFFERING_BYTES / 4);
lws_ss_add_peer_tx_credit(m->ss, MAX_MP3_IN_BUFFERING_BYTES / 4);
}
lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms,
250 * LWS_US_PER_MS);
}
static const char *mp3_mimetype = "application/octet-stream",
*match2 = "\x0d\x0a\x0d\x0a";
static int
ss_avs_mp3_open(ss_avs_metadata_t *m)
{
int r;
lwsl_notice("%s\n", __func__);
m->first_mp3 = 1;
m->mh = mpg123_new(NULL, NULL);
if (!m->mh) {
lwsl_err("%s: unable to make new mp3\n",
__func__);
goto bail;
}
mpg123_format_none(m->mh);
r = mpg123_format(m->mh, 16000, MPG123_M_MONO,
MPG123_ENC_SIGNED_16);
if (r) {
lwsl_err("%s: mpg123 format failed %d\n",
__func__, r);
goto bail1;
}
r = mpg123_open_feed(m->mh);
if (r) {
lwsl_err("%s: mpg123 open feed failed %d\n",
__func__, r);
goto bail1;
}
return 0;
bail1:
mpg123_delete(m->mh);
m->mh = NULL;
bail:
return 1;
}
static int
ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags);
/*
* This is called when the mp3 has drained it's input buffer and destroyed
* itself.
*/
static int
drain_end_cb(void *v)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)v;
struct lws_context *context = (struct lws_context *)m->opaque_data;
int tot = 0;
lwsl_err("%s\n", __func__);
/*
* We have drained and destroyed the existing mp3 session. Is there
* a new one pending?
*/
m->first_mp3 = 1;
m->mp3_state = LAMP3STATE_IDLE;
if (lws_buflist_total_len(&m->dribble)) {
/* we started another one */
/* resume tx credit top up */
lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1);
if (ss_avs_mp3_open(m))
return 1;
m->mp3_state = LAMP3STATE_SPOOLING;
/*
* Dump what we stashed from draining into the new mp3
*/
while (lws_buflist_total_len(&m->dribble)) {
size_t s;
uint8_t *u, t;
s = lws_buflist_next_segment_len(&m->dribble, &u);
t = m->stash_eom[m->se_tail];
lwsl_notice("%s: preload %d: %d\n", __func__, (int)s, t);
mpg123_feed(m->mh, u, s);
lws_buflist_use_segment(&m->dribble, s);
if (m->first_mp3) {
play_mp3(m->mh, NULL, NULL);
m->first_mp3 = 0;
}
tot += s;
m->se_tail = (m->se_tail + 1) % sizeof(m->stash_eom);
if (t) {
lwsl_notice("%s: preloaded EOM\n", __func__);
/*
* We stashed the whole of the message, we need
* to also do the EOM processing. We will come
* back here if there's another message in the
* stash.
*/
m->mp3_state = LAMP3STATE_DRAINING;
if (m->mh)
play_mp3(NULL, drain_end_cb, m);
lws_ss_add_peer_tx_credit(m->ss, tot);
#if 0
/*
* Put a hold on bringing in any more data
*/
lws_sul_schedule(context, 0, &m->sul, NULL,
LWS_SET_TIMER_USEC_CANCEL);
#endif
/* destroy our copy of the handle */
m->mh = NULL;
break;
}
}
lws_ss_add_peer_tx_credit(m->ss, tot);
}
return 0;
}
static int
ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
int n = 0, hit = 0;
lwsl_notice("%s: len %d, flags %d (est peer txcr %d)\n", __func__,
(int)len, flags, lws_ss_get_est_peer_tx_credit(m->ss));
// lwsl_hexdump_warn(buf, len);
if ((flags & LWSSS_FLAG_SOM) && !m->mh && !m->seen) {
m->mp3_mime_match = 0;
m->seen = 0;
m->inside_mp3 = 0;
}
if (!m->inside_mp3) {
/*
* Identify the part with the mp3 in, if any
*/
while (n < (int)len - 24) {
if (!m->seen) {
if (buf[n] == mp3_mimetype[m->mp3_mime_match]) {
m->mp3_mime_match++;
if (m->mp3_mime_match == 24) {
m->mp3_mime_match = 0;
m->seen = 1;
n++;
continue;
}
} else
m->mp3_mime_match = 0;
} else {
if (buf[n] == match2[m->mp3_mime_match]) {
m->mp3_mime_match++;
if (m->mp3_mime_match == 4) {
m->seen = 0;
m->mp3_mime_match = 0;
hit = 1;
n++;
buf += n;
len -= n;
lwsl_notice("identified reply...\n");
m->inside_mp3 = 1;
break;
}
} else
m->mp3_mime_match = 0;
}
n++;
}
if (!hit) {
lws_ss_add_peer_tx_credit(m->ss, len);
return 0;
}
}
// lwsl_notice("%s: state %d\n", __func__, m->mp3_state);
switch (m->mp3_state) {
case LAMP3STATE_IDLE:
if (hit) {
lws_ss_add_peer_tx_credit(m->ss, n);
if (ss_avs_mp3_open(m))
goto bail;
lws_sul_schedule(context, 0, &m->sul, use_buffer_250ms, 1);
m->mp3_state = LAMP3STATE_SPOOLING;
break;
}
lws_ss_add_peer_tx_credit(m->ss, len);
if (!m->inside_mp3)
break;
/* fallthru */
case LAMP3STATE_SPOOLING:
if (m->dribble)
goto draining;
if (len) {
/*
* We are shoving encoded mp3 into mpg123-allocated heap
* buffers... unfortunately mpg123 doesn't seem to
* expose where it is in its allocated input so we can
* track how much is stashed. Instead while in playback
* mode, we assume 64kbps mp3 encoding, ie, 8KB/s, and
* run a sul that allows an additional 2KB tx credit
* every 250ms, with 4KB initial credit.
*/
lwsl_notice("%s: SPOOL %d\n", __func__, (int)len);
mpg123_feed(m->mh, buf, len);
if (m->first_mp3) {
lws_sul_schedule(context, 0, &m->sul,
use_buffer_250ms, 1);
// lws_ss_add_peer_tx_credit(m->ss,
// len + (MAX_MP3_IN_BUFFERING_BYTES / 2));
play_mp3(m->mh, NULL, NULL);
} //else
// lws_ss_add_peer_tx_credit(m->ss, len);
m->first_mp3 = 0;
}
if (flags & LWSSS_FLAG_EOM) {
/*
* This means one "message" / mime part with mp3 data
* has finished coming in. But there may be whole other
* parts with other mp3s following, with potentially
* different mp3 parameters. So we want to tell this
* one to drain and finish and destroy the current mp3
* object before we go on.
*
* But not knowing the length of the current one, there
* will already be outstanding tx credit at the server,
* so it's going to spam us with the next part before we
* have the new mp3 sink for it.
*/
lwsl_notice("%s: EOM\n", __func__);
m->mp3_mime_match = 0;
m->seen = 0;
m->mp3_state = LAMP3STATE_DRAINING;
/* from input POV, we're no longer inside an mp3 */
m->inside_mp3 = 0;
if (m->mh)
play_mp3(NULL, drain_end_cb, m);
#if 0
/*
* Put a hold on bringing in any more data
*/
lws_sul_schedule(context, 0, &m->sul, NULL,
LWS_SET_TIMER_USEC_CANCEL);
#endif
/* destroy our copy of the handle */
m->mh = NULL;
}
break;
case LAMP3STATE_DRAINING:
draining:
if (buf && len && m->inside_mp3) {
lwsl_notice("%s: DRAINING: stashing %d: %d %d %d\n",
__func__, (int)len, !!(flags & LWSSS_FLAG_EOM),
m->se_head, m->se_tail);
lwsl_hexdump_notice(buf, len);
if (lws_buflist_append_segment(&m->dribble, buf, len) < 0)
goto bail;
m->stash_eom[m->se_head] = !!(flags & LWSSS_FLAG_EOM);
m->se_head = (m->se_head + 1) % sizeof(m->stash_eom);
lwsl_notice("%s: next head %d\n", __func__, m->se_head);
lws_ss_add_peer_tx_credit(m->ss, len);
}
if (flags & LWSSS_FLAG_EOM) {
if (!len && m->se_head != m->se_tail) {
/* 0-len EOM... retrospectively mark last stash */
lwsl_notice("%s: retro EOM\n", __func__);
m->stash_eom[(m->se_head - 1) % sizeof(m->stash_eom)] = 1;
}
lwsl_notice("%s: Draining EOM\n", __func__);
m->inside_mp3 = 0;
}
/*
* Don't provide any additional tx credit... we're just
* mopping up the overspill from the previous mp3 credit
*/
break;
}
return 0;
bail:
return -1;
}
/*
* Because this is multipart mime in h2 currently, use a "rideshare" to handle
* first the native metadata on this secure stream, then the "rideshare" audio
* stream mentioned in the policy.
*
* Lws takes care of interleaving the multipart mime pieces since the policy
* calls for it.
*/
static int
ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
size_t tot;
int n;
// lwsl_notice("%s %d\n", __func__, (int)m->pos);
if ((long)m->pos < 0) {
*len = 0;
lwsl_info("%s: skip\n", __func__);
return 1;
}
if (!strcmp(lws_ss_rideshare(m->ss), "avs_audio")) {
/* audio rideshare part */
if (!m->pos)
*flags |= LWSSS_FLAG_SOM;
n = spool_capture(buf, *len);
if (n > 0)
*len = n;
else
*len = 0;
if (!n) {
lwsl_info("%s: trying to skip tx\n", __func__);
return 1;
}
m->pos += *len;
if (n < 0) {
*flags |= LWSSS_FLAG_EOM;
m->pos = (long)-1l; /* ban subsequent until new stream */
}
lwsl_notice("%s: tx audio %d\n", __func__, (int)*len);
#if 0
{
int ff = open("/tmp/z1", O_RDWR | O_CREAT | O_APPEND, 0666);
if (ff == -1)
lwsl_err("%s: errno %d\n", __func__, errno);
write(ff, buf, *len);
close(ff);
}
#endif
return 0;
}
/* metadata part */
tot = strlen(metadata);
if (!m->pos)
*flags |= LWSSS_FLAG_SOM;
if (*len > tot - m->pos)
*len = tot - m->pos;
memcpy(buf, metadata + m->pos, *len);
m->pos += *len;
if (m->pos == tot) {
lwsl_notice("metadata done\n");
*flags |= LWSSS_FLAG_EOM;
m->pos = 0; /* for next time */
}
return 0;
}
static int
ss_avs_metadata_state(void *userobj, void *sh,
lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lwsl_notice("%s: %p: %s, ord 0x%x\n", __func__, m->ss,
lws_ss_state_name(state), (unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_client_connect(m->ss);
break;
case LWSSSCS_CONNECTING:
m->pos = 0;
break;
case LWSSSCS_CONNECTED:
lwsl_info("%s: CONNECTED\n", __func__);
lws_ss_request_tx(m->ss);
break;
case LWSSSCS_DISCONNECTED:
lws_sul_schedule(context, 0, &m->sul, NULL,
LWS_SET_TIMER_USEC_CANCEL);
//if (m->mh) {
play_mp3(NULL, NULL, NULL);
m->mh = NULL;
//}
/*
* For this stream encapsulating an alexa exchange, dropping
* is the end of its life
*/
return 1;
case LWSSSCS_DESTROYING:
lws_buflist_destroy_all_segments(&m->dribble);
break;
default:
break;
}
return 0;
}
/*
* avs event
*/
static int
ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
return 0;
}
static int
ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
return 1; /* don't transmit anything */
}
static int
ss_avs_event_state(void *userobj, void *sh,
lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)
{
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
mpg123_init();
break;
case LWSSSCS_CONNECTING:
break;
case LWSSSCS_CONNECTED:
lwsl_user("Connected to Alexa... speak \"Alexa, ...\"\n");
break;
case LWSSSCS_DISCONNECTED:
lwsl_user("Disconnected from Alexa\n");
break;
case LWSSSCS_DESTROYING:
mpg123_exit();
break;
default:
break;
}
return 0;
}
int
avs_query_start(struct lws_context *context)
{
lws_ss_info_t ssi;
lwsl_notice("%s:\n", __func__);
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_avs_metadata_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_avs_metadata_t, opaque_data);
ssi.rx = ss_avs_metadata_rx;
ssi.tx = ss_avs_metadata_tx;
ssi.state = ss_avs_metadata_state;
ssi.user_alloc = sizeof(ss_avs_metadata_t);
ssi.streamtype = "avs_metadata";
ssi.manual_initial_tx_credit = 8192;
if (lws_ss_create(context, 0, &ssi, context, &hss_avs_sync, NULL, NULL)) {
lwsl_err("%s: failed to create avs metadata secstream\n",
__func__);
return 1;
}
lwsl_user("%s: created query stream %p\n", __func__, hss_avs_sync);
return 0;
}
int
avs_example_start(struct lws_context *context)
{
lws_ss_info_t ssi;
if (hss_avs_event)
return 0;
lwsl_info("%s: Starting AVS stream\n", __func__);
/* AVS wants us to establish the long poll event stream first */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_avs_event_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_avs_event_t, opaque_data);
ssi.rx = ss_avs_event_rx;
ssi.tx = ss_avs_event_tx;
ssi.state = ss_avs_event_state;
ssi.user_alloc = sizeof(ss_avs_event_t);
ssi.streamtype = "avs_event";
if (lws_ss_create(context, 0, &ssi, context, &hss_avs_event, NULL, NULL)) {
lwsl_err("%s: failed to create avs event secure stream\n",
__func__);
return 1;
}
return 0;
}

View File

@ -0,0 +1,469 @@
/*
* alsa audio handling
*
* 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.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <alsa/asoundlib.h>
#include <pv_porcupine.h>
#include <mpg123.h>
#include "private.h"
extern struct lws_ss_handle *hss_avs_event, *hss_avs_sync;
int
avs_query_start(struct lws_context *context);
enum {
MODE_IDLE,
MODE_CAPTURING,
MODE_PLAYING
};
struct raw_vhd {
int16_t p[8 * 1024]; /* 500ms at 16kHz 16-bit PCM */
pv_porcupine_object_t *porc;
snd_pcm_t *pcm_capture;
snd_pcm_t *pcm_playback;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
int16_t *porcbuf;
mpg123_handle *mh;
mp3_done_cb done_cb;
void *opaque;
int mode;
int rate;
int porc_spf;
int filefd;
int rpos;
int wpos;
int porcpos;
int npos;
int times;
int quietcount;
int anycount;
int wplay;
int rplay;
char last_wake_detect;
char destroy_mh_on_drain;
};
static struct raw_vhd *avhd;
/*
* called from alexa.c to grab the next chunk of audio capture buffer
* for upload
*/
int
spool_capture(uint8_t *buf, size_t len)
{
int16_t *sam = (int16_t *)buf;
size_t s, os;
if (avhd->mode != MODE_CAPTURING)
return -1;
os = s = len / 2;
while (s && avhd->wpos != avhd->npos) {
*sam++ = avhd->p[avhd->npos];
avhd->npos = (avhd->npos + 1) % LWS_ARRAY_SIZE(avhd->p);
s--;
}
lwsl_info("Copied %d samples (%d %d)\n", (int)(os - s),
avhd->wpos, avhd->npos);
return (os - s) * 2;
}
/*
* Called from alexa.c to control when the mp3 playback should begin and end
*/
int
play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque)
{
if (mh) {
avhd->mh = mh;
avhd->mode = MODE_PLAYING;
snd_pcm_prepare(avhd->pcm_playback);
return 0;
}
avhd->destroy_mh_on_drain = 1;
avhd->done_cb = cb;
avhd->opaque = opaque;
return 0;
}
/*
* Helper used to set alsa hwparams on both capture and playback channels
*/
static int
set_hw_params(struct lws_vhost *vh, snd_pcm_t **pcm, int type)
{
unsigned int rate = pv_sample_rate(); /* it's 16kHz */
snd_pcm_hw_params_t *params;
lws_sock_file_fd_type u;
struct pollfd pfd;
struct lws *wsi1;
int n;
n = snd_pcm_open(pcm, "default", type, SND_PCM_NONBLOCK);
if (n < 0) {
lwsl_err("%s: Can't open default for playback: %s\n",
__func__, snd_strerror(n));
return -1;
}
if (snd_pcm_poll_descriptors(*pcm, &pfd, 1) != 1) {
lwsl_err("%s: failed to get playback desc\n", __func__);
return -1;
}
u.filefd = (lws_filefd_type)(long long)pfd.fd;
wsi1 = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u,
"lws-audio-test", NULL);
if (!wsi1) {
lwsl_err("%s: Failed to adopt playback desc\n", __func__);
goto bail;
}
if (type == SND_PCM_STREAM_PLAYBACK)
lws_rx_flow_control(wsi1, 0); /* no POLLIN */
snd_pcm_hw_params_malloc(&params);
snd_pcm_hw_params_any(*pcm, params);
n = snd_pcm_hw_params_set_access(*pcm, params,
SND_PCM_ACCESS_RW_INTERLEAVED);
if (n < 0)
goto bail1;
n = snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE);
if (n < 0)
goto bail1;
n = snd_pcm_hw_params_set_channels(*pcm, params, 1);
if (n < 0)
goto bail1;
n = snd_pcm_hw_params_set_rate_near(*pcm, params, &rate, 0);
if (n < 0)
goto bail1;
lwsl_notice("%s: %s rate %d\n", __func__,
type == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", rate);
n = snd_pcm_hw_params(*pcm, params);
snd_pcm_hw_params_free(params);
if (n < 0)
goto bail;
return 0;
bail1:
snd_pcm_hw_params_free(params);
bail:
lwsl_err("%s: Set hw params failed: %s\n", __func__, snd_strerror(n));
return -1;
}
/*
* The lws RAW file protocol handler that wraps ALSA.
*
* The timing is coming from ALSA capture channel... since they are both set to
* 16kHz, it's enough just to have the one.
*/
static int
callback_audio(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
lws_get_vhost(wsi), lws_get_protocol(wsi));
uint16_t rands[50];
int16_t temp[256];
bool det;
long avg;
int n, s;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
if (avhd) /* just on one vhost */
return 0;
avhd = vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
lws_get_protocol(wsi), sizeof(struct raw_vhd));
/*
* Set up the wakeword library
*/
n = pv_porcupine_init("porcupine_params.pv", "alexa_linux.ppn",
1.0, &vhd->porc);
if (n) {
lwsl_err("%s: porcupine init fail %d\n", __func__, n);
return -1;
}
vhd->porc_spf = pv_porcupine_frame_length();
vhd->porcbuf = malloc(vhd->porc_spf * 2);
lwsl_info("%s: %s porc frame length is %d samples\n", __func__,
lws_get_vhost_name(lws_get_vhost(wsi)),
vhd->porc_spf);
vhd->rate = pv_sample_rate(); /* 16kHz */
/* set up alsa */
if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_playback,
SND_PCM_STREAM_PLAYBACK)) {
lwsl_err("%s: Can't open default for playback\n",
__func__);
return -1;
}
if (set_hw_params(lws_get_vhost(wsi), &vhd->pcm_capture,
SND_PCM_STREAM_CAPTURE)) {
lwsl_err("%s: Can't open default for capture\n",
__func__);
return -1;
}
snd_config_update_free_global();
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
lwsl_info("%s: LWS_CALLBACK_PROTOCOL_DESTROY\n", __func__);
if (!vhd)
break;
if (vhd->porcbuf) {
free(vhd->porcbuf);
vhd->porcbuf = NULL;
}
if (vhd->pcm_playback) {
snd_pcm_drop(vhd->pcm_playback);
snd_pcm_close(vhd->pcm_playback);
vhd->pcm_playback = NULL;
}
if (vhd->pcm_capture) {
snd_pcm_drop(vhd->pcm_capture);
snd_pcm_close(vhd->pcm_capture);
vhd->pcm_capture = NULL;
}
if (vhd->porc) {
pv_porcupine_delete(vhd->porc);
vhd->porc = NULL;
}
/* avoid most of the valgrind mess from alsa */
snd_config_update_free_global();
break;
case LWS_CALLBACK_RAW_CLOSE_FILE:
lwsl_info("%s: closed\n", __func__);
break;
case LWS_CALLBACK_RAW_RX_FILE:
/* we come here about every 250ms */
/*
* Playing back the mp3?
*/
if (vhd->mode == MODE_PLAYING && vhd->mh) {
size_t amt, try;
do {
try = snd_pcm_avail(vhd->pcm_playback);
if (try > LWS_ARRAY_SIZE(vhd->p))
try = LWS_ARRAY_SIZE(vhd->p);
n = mpg123_read(vhd->mh, (uint8_t *)vhd->p,
try * 2, &amt);
lwsl_info("%s: PLAYING: mpg123 read %d, n %d\n",
__func__, (int)amt, n);
if (n == MPG123_NEW_FORMAT) {
snd_pcm_start(vhd->pcm_playback);
memset(vhd->p, 0, try);
snd_pcm_writei(vhd->pcm_playback,
vhd->p, try / 2);
snd_pcm_prepare(vhd->pcm_playback);
}
} while (n == MPG123_NEW_FORMAT);
if (amt) {
n = snd_pcm_writei(vhd->pcm_playback,
vhd->p, amt / 2);
if (n < 0)
lwsl_notice("%s: snd_pcm_writei: %d %s\n",
__func__, n, snd_strerror(n));
if (n == -EPIPE) {
lwsl_err("%s: did EPIPE prep\n", __func__);
snd_pcm_prepare(vhd->pcm_playback);
}
} else
if (vhd->destroy_mh_on_drain &&
n != MPG123_NEW_FORMAT) {
snd_pcm_drain(vhd->pcm_playback);
vhd->destroy_mh_on_drain = 0;
lwsl_notice("%s: mp3 destroyed\n",
__func__);
mpg123_close(vhd->mh);
mpg123_delete(vhd->mh);
vhd->mh = NULL;
vhd->mode = MODE_IDLE;
if (vhd->done_cb)
vhd->done_cb(vhd->opaque);
}
}
/*
* Get the capture data
*/
n = snd_pcm_readi(vhd->pcm_capture, temp, LWS_ARRAY_SIZE(temp));
s = 0;
while (s < n) {
vhd->p[(vhd->wpos + s) % LWS_ARRAY_SIZE(vhd->p)] = temp[s];
s++;
}
if (vhd->mode == MODE_CAPTURING) {
/*
* We are recording an utterance.
*
* Estimate the sound density in the frame by picking 50
* samples at random and averaging the sampled
* [abs()^2] / 10000 to create a Figure of Merit.
*
* Speaking on my laptop gets us 1000 - 5000, silence
* is typ under 30. The wakeword tells us there was
* speech at the start, end the capture when there's
* ~750ms (12000 samples) under 125 FOM.
*/
#define SILENCE_THRESH 125
avg = 0;
lws_get_random(lws_get_context(wsi), rands, sizeof(rands));
for (s = 0; s < (int)LWS_ARRAY_SIZE(rands); s++) {
long q;
q = temp[rands[s] % n];
avg += (q * q);
}
avg = (avg / (int)LWS_ARRAY_SIZE(rands)) / 10000;
lwsl_notice("est audio energy: %ld %d\n", avg, vhd->mode);
/*
* Only start looking for "silence" after 1.5s, in case
* he does a long pause after the wakeword
*/
if (vhd->anycount < (3 *vhd->rate) / 2 &&
avg < SILENCE_THRESH) {
vhd->quietcount += n;
/* then 500ms of "silence" does it for us */
if (vhd->quietcount >= ((vhd->rate * 3) / 4)) {
lwsl_warn("%s: ended capture\n", __func__);
vhd->mode = MODE_IDLE;
vhd->quietcount = 0;
}
}
/* if we're not "silent", reset the count */
if (avg > SILENCE_THRESH * 2)
vhd->quietcount = 0;
/*
* Since we are in capturing mode, we have something
* new to send now.
*
* We must send an extra one at the end so we can finish
* the tx.
*/
lws_ss_request_tx(hss_avs_sync);
}
/*
* Just waiting for a wakeword
*/
while (vhd->mode == MODE_IDLE) {
int m = 0, ppold = vhd->porcpos;
s = (vhd->wpos - vhd->porcpos) % LWS_ARRAY_SIZE(vhd->p);
if (s < vhd->porc_spf)
goto eol;
while (m < vhd->porc_spf) {
vhd->porcbuf[m++] = avhd->p[vhd->porcpos];
vhd->porcpos = (vhd->porcpos + 1) %
LWS_ARRAY_SIZE(vhd->p);
}
if (pv_porcupine_process(vhd->porc, vhd->porcbuf, &det))
lwsl_err("%s: porc_process failed\n", __func__);
if (!det && vhd->last_wake_detect &&
vhd->mode == MODE_IDLE) {
lwsl_warn("************* Wakeword\n");
if (!avs_query_start(lws_get_context(wsi))) {
vhd->mode = MODE_CAPTURING;
vhd->quietcount = 0;
vhd->last_wake_detect = det;
vhd->npos = ppold;
break;
}
}
vhd->last_wake_detect = det;
}
eol:
vhd->wpos = (vhd->wpos + n) % LWS_ARRAY_SIZE(vhd->p);
break;
default:
break;
}
return 0;
}
struct lws_protocols protocol_audio_test =
{ "lws-audio-test", callback_audio, 0, 0 };

View File

@ -0,0 +1,417 @@
/*
* lws-minimal-secure-streams-alexa
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
extern int
avs_example_start(struct lws_context *context);
static int interrupted;
static lws_state_notify_link_t nl;
#if !defined(LWS_SS_USE_SSPC)
/*
* If not using the proxy, we need to bring our own policy
*/
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "60,"
"\"svalidhup\":" "64"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
"{\"digicert_global_root_g2\": \"" /* api.amazon.com 2038-01 */
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl"
"MrY="
"\"},"
"{\"digicert_global_ca_g2\": \"" /* api.amazon.com 2028-08 */
"MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH"
"MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh"
"bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd"
"W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X"
"au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5"
"IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR"
"ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6"
"OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4"
"8V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P"
"AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j"
"c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp"
"Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6"
"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD"
"VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj"
"ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud"
"IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL"
"OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2"
"dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ"
"8uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co"
"atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA"
"jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk"
"92hiHuwZ4STyhxGs6QiA"
"\"},"
"{\"amazon_root_ca_1\": \""
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF"
"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6"
"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL"
"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv"
"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj"
"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM"
"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw"
"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6"
"VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L"
"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm"
"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC"
"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA"
"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI"
"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs"
"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv"
"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU"
"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy"
"rqXRfboQnoZsG4q5WTP468SQvvG5"
"\"},"
"{\"starfield_services_root_ca\": \""
"MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx"
"EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT"
"HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs"
"ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5"
"MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD"
"VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy"
"ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy"
"dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI"
"hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p"
"OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2"
"8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K"
"Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe"
"hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk"
"6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw"
"DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q"
"AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI"
"bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB"
"ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z"
"qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd"
"iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn"
"0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN"
"sSi6"
"\"},"
"{\"starfield_class_2_ca\": \""
"MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl"
"MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp"
"U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw"
"NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE"
"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp"
"ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3"
"DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf"
"8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN"
"+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0"
"X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa"
"K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA"
"1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G"
"A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR"
"zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0"
"YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD"
"bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w"
"DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3"
"L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D"
"eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl"
"xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp"
"VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY"
"WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q="
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{" /* chain for alexa.na.gateway.devices.a2z.com */
"\"name\": \"avs_via_starfield\","
"\"stack\": ["
"\"starfield_class_2_ca\","
"\"starfield_services_root_ca\""
"]"
"},"
"{" /* chain for api.amazon.com */
"\"name\": \"api_amazon_com\","
"\"stack\": ["
"\"digicert_global_ca_g2\","
"\"digicert_global_root_g2\""
"]"
"}"
"],"
"\"s\": [" /* the supported stream types */
"{\"api_amazon_com_auth\": {"
"\"endpoint\":" "\"api.amazon.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"auth/o2/token\","
"\"plugins\":" "[],"
"\"opportunistic\":" "true,"
"\"tls\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_www_form_urlencoded\":" "true,"
"\"http_no_content_length\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"api_amazon_com\""
"}},"
/*
* long poll event listener
*/
"{\"avs_event\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"v20160207/directives\","
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"nailed_up\":" "true,"
"\"long_poll\":" "true,"
"\"retry\":" "\"default\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}},"
/*
* Utterance metadata and audio send and reply processing.
*
* "Rideshare" and http_multipart_mime means these both go out
* in one multipart http transaction.
*/
"{\"avs_metadata\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"v20160207/events\","
"\"opportunistic\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"http_multipart_name\":" "\"metadata\","
"\"http_mime_content_type\":" "\"application/json; charset=UTF-8\","
"\"http_no_content_length\":" "true,"
"\"rideshare\":" "\"avs_audio\","
"\"retry\":" "\"default\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}},"
"{\"avs_audio\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"v20160207/events\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"http_multipart_name\":" "\"audio\","
"\"http_mime_content_type\":" "\"application/octet-stream\","
"\"http_no_content_length\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}}"
"]"
"}"
;
#endif
static const char *canned_root_token_payload =
"grant_type=refresh_token"
"&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
"SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
"zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
"0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
"k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
"iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
"KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
"AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
"xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
"&client_id="
"amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
/*
* Register the root token, and make the sticky AVS connection at the
* appropriate times during system startup
*/
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = lws_system_context_from_system_mgr(mgr);
lws_system_blob_t *ab = lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
size_t size;
/*
* For the things we care about, let's notice if we are trying to get
* past them when we haven't solved them yet, and make the system
* state wait while we trigger the dependent action.
*/
switch (target) {
case LWS_SYSTATE_REGISTERED:
size = lws_system_blob_get_size(ab);
if (size)
break;
/* let's register our canned root token so auth can use it */
lws_system_blob_direct_set(ab,
(const uint8_t *)canned_root_token_payload,
strlen(canned_root_token_payload));
break;
case LWS_SYSTATE_OPERATIONAL:
if (current == target)
avs_example_start(context);
break;
case LWS_SYSTATE_POLICY_INVALID:
/*
* This is a NOP since we used direct set... but in a real
* system this could easily change to be done on the heap, then
* this would be important
*/
lws_system_blob_destroy(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
1 /* AUTH_IDX_ROOT */));
break;
}
return 0;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
extern struct lws_protocols protocol_audio_test;
static const struct lws_protocols *protocols[] = {
&protocol_audio_test,
#if defined(LWS_SS_USE_SSPC)
lws_sspc_protocols,
#endif
NULL
};
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
int n = 0;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS secure streams - Alexa voice test [-d<verb>]\n");
info.fd_limit_per_thread = 1 + 6 + 1;
#if !defined(LWS_SS_USE_SSPC)
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.pss_policies_json = default_ss_policy;
#else
{
const char *p;
/* connect to ssproxy via UDS by default, else via
* tcp connection to this port */
if ((p = lws_cmdline_option(argc, argv, "-p")))
info.ss_proxy_port = atoi(p);
/* UDS "proxy.ss.lws" in abstract namespace, else this socket
* path; when -p given this can specify the network interface
* to bind to */
if ((p = lws_cmdline_option(argc, argv, "-i")))
info.ss_proxy_bind = p;
/* if -p given, -a specifies the proxy address to connect to */
if ((p = lws_cmdline_option(argc, argv, "-a")))
info.ss_proxy_address = p;
}
#endif
info.port = CONTEXT_PORT_NO_LISTEN;
info.pprotocols = protocols;
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy";
#endif
/* integrate us with lws system state management when context created */
nl.name = "app";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/* create an explicit vhost so the sound protocol is initialized */
info.vhost_name = "asound";
if (!lws_create_vhost(context, &info)) {
lwsl_err("lws init failed\n");
goto bail;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
bail:
lws_context_destroy(context);
lwsl_user("Completed\n");
return 0;
}

View File

@ -0,0 +1,8 @@
typedef int (*mp3_done_cb)(void *opaque);
int
play_mp3(mpg123_handle *mh, mp3_done_cb cb, void *opaque);
int
spool_capture(uint8_t *buf, size_t len);

View File

@ -0,0 +1,91 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-avs)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
if (requirements)
add_executable(${SAMP} main.c avs.c)
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
add_compile_options(-DLWS_SS_USE_SSPC)
add_executable(${SAMP}-client main-client.c avs.c)
if (websockets_shared)
target_link_libraries(${SAMP}-client websockets_shared)
add_dependencies(${SAMP}-client websockets_shared)
else()
target_link_libraries(${SAMP}-client websockets)
endif()
endif()
endif()

View File

@ -0,0 +1,418 @@
/*
* lws-minimal-secure-streams-avs
*
* Written in 2019-2020 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This sends a canned WAV and received (and discards) the mp3 response.
* However it rate-limits the response reception to manage a small ringbuffer
* using ss / h2 flow control apis, reflecting consumption at 64kbps and only
* and 8KB buffer, indtended to model optimizing rx buffering on mp3 playback
* on a constrained device.
*/
#include <libwebsockets.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
extern int interrupted, bad;
static struct lws_ss_handle *hss_avs_event, *hss_avs_sync;
static uint8_t *wav;
static size_t wav_len;
typedef struct ss_avs_event {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
struct lejp_ctx jctx;
} ss_avs_event_t;
typedef struct ss_avs_metadata {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
struct lejp_ctx jctx;
size_t pos;
/*
* We simulate a ringbuffer that is used up by a sul at 64Kbit/sec
* rate, and managed at the same rate using tx credit
*/
lws_sorted_usec_list_t sul;
uint8_t buf[8192];
int head;
int tail;
char filled;
} ss_avs_metadata_t;
static const char *metadata = "{"
"\"event\": {"
"\"header\": {"
"\"namespace\": \"SpeechRecognizer\","
"\"name\": \"Recognize\","
"\"messageId\": \"message-123\","
"\"dialogRequestId\": \"dialog-request-321\""
"},"
"\"payload\": {"
"\"profile\":" "\"CLOSE_TALK\","
"\"format\":" "\"AUDIO_L16_RATE_16000_CHANNELS_1\""
"}"
"}"
"}";
/*
* avs metadata
*/
static void
use_buffer_50ms(lws_sorted_usec_list_t *sul)
{
ss_avs_metadata_t *m = lws_container_of(sul, ss_avs_metadata_t, sul);
struct lws_context *context = (struct lws_context *)m->opaque_data;
size_t n;
int e;
/*
* Use up 50ms-worth (8KB / 20) == 401 bytes of buffered data
*/
/* remaining data in buffer */
n = ((m->head - m->tail) % sizeof(m->buf));
lwsl_info("%s: avail %d\n", __func__, (int)n);
if (n < 401)
lwsl_err("%s: underrun\n", __func__);
m->tail = (m->tail + 401) % sizeof(m->buf);
n = ((m->head - m->tail) % sizeof(m->buf));
e = lws_ss_get_est_peer_tx_credit(m->ss);
lwsl_info("%s: avail after: %d, curr est %d\n", __func__, (int)n, e);
if (n < (sizeof(m->buf) * 2) / 3 && e < (int)(sizeof(m->buf) - 1 - n)) {
lwsl_info("%s: requesting additional %d\n", __func__,
(int)(sizeof(m->buf) - 1 - e - n));
lws_ss_add_peer_tx_credit(m->ss, (sizeof(m->buf) - 1 - e - n));
}
lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms,
50 * LWS_US_PER_MS);
}
static int
ss_avs_metadata_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
size_t n, n1;
lwsl_notice("%s: rideshare %s, len %d, flags 0x%x\n", __func__,
lws_ss_rideshare(m->ss), (int)len, flags);
// lwsl_hexdump_warn(buf, len);
n = sizeof(m->buf) - ((m->head - m->tail) % sizeof(m->buf));
lwsl_info("%s: len %d, buf h %d, t %d, space %d\n", __func__,
(int)len, (int)m->head, (int)m->tail, (int)n);
lws_ss_get_est_peer_tx_credit(m->ss);
assert (len <= n);
if (m->head < m->tail) /* |****h-------t**| */
memcpy(&m->buf[m->head], buf, len);
else { /* |---t*****h-----| */
n1 = sizeof(m->buf) - m->head;
if (len < n1)
n1 = len;
memcpy(&m->buf[m->head], buf, n1);
if (n1 != len)
memcpy(m->buf, buf, len - n1);
}
m->head = (m->head + len) % sizeof(m->buf);
lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms,
50 * LWS_US_PER_MS);
return 0;
}
static int
ss_avs_metadata_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
//struct lws_context *context = (struct lws_context *)m->opaque_data;
size_t tot;
if ((long)m->pos < 0) {
*len = 0;
lwsl_notice("%s: skip tx\n", __func__);
return 1;
}
lwsl_notice("%s: rideshare '%s'\n", __func__, lws_ss_rideshare(m->ss));
if (!strcmp(lws_ss_rideshare(m->ss), "avs_audio")) {
/* audio rideshare */
if (!m->pos)
*flags |= LWSSS_FLAG_SOM;
if (*len > wav_len - m->pos)
*len = wav_len - m->pos;
memcpy(buf, wav + m->pos, *len);
m->pos += *len;
if (m->pos == wav_len) {
*flags |= LWSSS_FLAG_EOM;
lwsl_info("%s: tx done\n", __func__);
m->pos = (long)-1l; /* ban subsequent until new stream */
} else
lws_ss_request_tx(m->ss);
lwsl_hexdump_info(buf, *len);
return 0;
}
/* metadata part */
tot = strlen(metadata);
if (!m->pos)
*flags |= LWSSS_FLAG_SOM;
if (*len > tot - m->pos)
*len = tot - m->pos;
memcpy(buf, metadata + m->pos, *len);
m->pos += *len;
if (m->pos == tot) {
*flags |= LWSSS_FLAG_EOM;
m->pos = 0; /* for next time */
lws_ss_request_tx(m->ss);
}
lwsl_hexdump_info(buf, *len);
return 0;
}
static int
ss_avs_metadata_state(void *userobj, void *sh,
lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)
{
ss_avs_metadata_t *m = (ss_avs_metadata_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lwsl_user("%s: CREATING\n", __func__);
lws_ss_client_connect(m->ss);
m->pos = 0;
break;
case LWSSSCS_CONNECTING:
break;
case LWSSSCS_CONNECTED:
lws_ss_request_tx(m->ss);
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* for this demo app, we want to exit on fail to connect */
case LWSSSCS_DISCONNECTED:
/* for this demo app, we want to exit after complete flow */
lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms,
LWS_SET_TIMER_USEC_CANCEL);
interrupted = 1;
break;
case LWSSSCS_DESTROYING:
lws_sul_schedule(context, 0, &m->sul, use_buffer_50ms,
LWS_SET_TIMER_USEC_CANCEL);
break;
default:
break;
}
return 0;
}
/*
* avs event
*/
static int
ss_avs_event_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_avs_event_t *m = (ss_avs_event_t *)userobj;
// struct lws_context *context = (struct lws_context *)m->opaque_data;
lwsl_notice("%s: rideshare %s, len %d, flags 0x%x\n", __func__,
lws_ss_rideshare(m->ss), (int)len, flags);
//lwsl_hexdump_warn(buf, len);
bad = 0; /* for this demo, receiving something here == success */
return 0;
}
static int
ss_avs_event_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_avs_event_t *m = (ss_avs_event_t *)userobj;
lwsl_notice("%s: rideshare %s\n", __func__, lws_ss_rideshare(m->ss));
return 1; /* don't transmit anything */
}
static int
ss_avs_event_state(void *userobj, void *sh,
lws_ss_constate_t state, lws_ss_tx_ordinal_t ack)
{
ss_avs_event_t *m = (ss_avs_event_t *)userobj;
struct lws_context *context = (struct lws_context *)m->opaque_data;
lws_ss_info_t ssi;
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
case LWSSSCS_CONNECTING:
break;
case LWSSSCS_CONNECTED:
if (hss_avs_sync)
break;
lwsl_notice("%s: starting the second avs stream\n", __func__);
/*
* When we have established the event stream, we must POST
* on another stream within 10s
*/
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_avs_metadata_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_avs_metadata_t,
opaque_data);
ssi.rx = ss_avs_metadata_rx;
ssi.tx = ss_avs_metadata_tx;
ssi.state = ss_avs_metadata_state;
ssi.user_alloc = sizeof(ss_avs_metadata_t);
ssi.streamtype = "avs_metadata";
/*
* We want to allow the other side to fill our buffer, but no
* more. But it's a bit tricky when the payload is inside
* framing like multipart MIME and contains other parts
*/
ssi.manual_initial_tx_credit =
sizeof(((ss_avs_metadata_t *)0)->buf) / 2;
if (lws_ss_create(context, 0, &ssi, context, &hss_avs_sync,
NULL, NULL)) {
lwsl_err("%s: failed to create avs metadata secstream\n",
__func__);
}
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* for this demo app, we want to exit on fail to connect */
interrupted = 1;
break;
case LWSSSCS_DISCONNECTED:
break;
case LWSSSCS_DESTROYING:
lwsl_notice("%s: DESTROYING\n", __func__);
if (wav) {
free(wav);
wav = NULL;
}
break;
default:
break;
}
return 0;
}
int
avs_example_start(struct lws_context *context)
{
lws_ss_info_t ssi;
struct stat stat;
int fd;
if (hss_avs_event)
return 0;
fd = open("./year.wav", O_RDONLY);
if (fd < 0) {
lwsl_err("%s: failed to open wav file\n", __func__);
return 1;
}
if (fstat(fd, &stat) < 0) {
lwsl_err("%s: failed to stat wav file\n", __func__);
goto bail;
}
wav_len = stat.st_size;
wav = malloc(wav_len);
if (!wav) {
lwsl_err("%s: failed to alloc wav buffer", __func__);
goto bail;
}
if (read(fd, wav, wav_len) != (int)wav_len) {
lwsl_err("%s: failed to read wav\n", __func__);
goto bail;
}
close(fd);
lwsl_user("%s: Starting AVS stream\n", __func__);
/* AVS wants us to establish the long poll event stream first */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(ss_avs_event_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_avs_event_t, opaque_data);
ssi.rx = ss_avs_event_rx;
ssi.tx = ss_avs_event_tx;
ssi.state = ss_avs_event_state;
ssi.user_alloc = sizeof(ss_avs_event_t);
ssi.streamtype = "avs_event";
if (lws_ss_create(context, 0, &ssi, context, &hss_avs_event, NULL, NULL)) {
lwsl_err("%s: failed to create avs event secure stream\n",
__func__);
free(wav);
wav = NULL;
return 1;
}
return 0;
bail:
close(fd);
return 1;
}

View File

@ -0,0 +1,128 @@
/*
* lws-minimal-secure-streams-avs
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
extern int
avs_example_start(struct lws_context *context);
int interrupted, bad = 1;
static lws_state_notify_link_t nl;
static const char *canned_root_token_payload =
"grant_type=refresh_token"
"&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
"SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
"zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
"0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
"k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
"iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
"KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
"AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
"xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
"&client_id="
"amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = lws_system_context_from_system_mgr(mgr);
lws_system_blob_t *ab = lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
size_t size;
/*
* For the things we care about, let's notice if we are trying to get
* past them when we haven't solved them yet, and make the system
* state wait while we trigger the dependent action.
*/
switch (target) {
case LWS_SYSTATE_REGISTERED:
size = lws_system_blob_get_size(ab);
if (size)
break;
/* let's register our canned root token so auth can use it */
lws_system_blob_direct_set(ab,
(const uint8_t *)canned_root_token_payload,
strlen(canned_root_token_payload));
break;
case LWS_SYSTATE_OPERATIONAL:
if (current == LWS_SYSTATE_OPERATIONAL)
avs_example_start(context);
break;
case LWS_SYSTATE_POLICY_INVALID:
/*
* This is a NOP since we used direct set... but in a real
* system this could easily change to be done on the heap, then
* this would be important
*/
lws_system_blob_destroy(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
1 /* AUTH_IDX_ROOT */));
break;
}
return 0;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
int n = 0;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS secure streams - AVS test client [-d<verb>]\n");
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 6 + 1;
info.protocols = lws_sspc_protocols;
info.port = CONTEXT_PORT_NO_LISTEN;
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy";
#endif
/* integrate us with lws system state management when context created */
nl.name = "app";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,357 @@
/*
* lws-minimal-secure-streams-avs
*
* Written in 2019-2020 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
extern int
avs_example_start(struct lws_context *context);
int interrupted, bad = 1;
static lws_state_notify_link_t nl;
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
// "\"via-socks5\":" "\"127.0.0.1:1080\","
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "60,"
"\"svalidhup\":" "64"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
"{\"digicert_global_root_g2\": \"" /* api.amazon.com 2038-01 */
"MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH"
"MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI"
"2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx"
"1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ"
"q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz"
"tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ"
"vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP"
"BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV"
"5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY"
"1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4"
"NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG"
"Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91"
"8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe"
"pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl"
"MrY="
"\"},"
"{\"digicert_global_ca_g2\": \"" /* api.amazon.com 2028-08 */
"MIIEizCCA3OgAwIBAgIQDI7gyQ1qiRWIBAYe4kH5rzANBgkqhkiG9w0BAQsFADBh"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH"
"MjAeFw0xMzA4MDExMjAwMDBaFw0yODA4MDExMjAwMDBaMEQxCzAJBgNVBAYTAlVT"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxHjAcBgNVBAMTFURpZ2lDZXJ0IEdsb2Jh"
"bCBDQSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANNIfL7zBYZd"
"W9UvhU5L4IatFaxhz1uvPmoKR/uadpFgC4przc/cV35gmAvkVNlW7SHMArZagV+X"
"au4CLyMnuG3UsOcGAngLH1ypmTb+u6wbBfpXzYEQQGfWMItYNdSWYb7QjHqXnxr5"
"IuYUL6nG6AEfq/gmD6yOTSwyOR2Bm40cZbIc22GoiS9g5+vCShjEbyrpEJIJ7RfR"
"ACvmfe8EiRROM6GyD5eHn7OgzS+8LOy4g2gxPR/VSpAQGQuBldYpdlH5NnbQtwl6"
"OErXb4y/E3w57bqukPyV93t4CTZedJMeJfD/1K2uaGvG/w/VNfFVbkhJ+Pi474j4"
"8V4Rd6rfArMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P"
"AQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0cDovL29j"
"c3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6Ly9jcmw0LmRp"
"Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwN6A1oDOGMWh0dHA6"
"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RHMi5jcmwwPQYD"
"VR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2lj"
"ZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFCRuKy3QapJRUSVpAaqaR6aJ50AgMB8GA1Ud"
"IwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA0GCSqGSIb3DQEBCwUAA4IBAQAL"
"OYSR+ZfrqoGvhOlaOJL84mxZvzbIRacxAxHhBsCsMsdaVSnaT0AC9aHesO3ewPj2"
"dZ12uYf+QYB6z13jAMZbAuabeGLJ3LhimnftiQjXS8X9Q9ViIyfEBFltcT8jW+rZ"
"8uckJ2/0lYDblizkVIvP6hnZf1WZUXoOLRg9eFhSvGNoVwvdRLNXSmDmyHBwW4co"
"atc7TlJFGa8kBpJIERqLrqwYElesA8u49L3KJg6nwd3jM+/AVTANlVlOnAM2BvjA"
"jxSZnE0qnsHhfTuvcqdFuhOWKU4Z0BqYBvQ3lBetoxi6PrABDJXWKTUgNX31EGDk"
"92hiHuwZ4STyhxGs6QiA"
"\"},"
"{\"starfield_services_root_ca\": \""
"MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx"
"EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT"
"HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs"
"ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5"
"MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD"
"VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy"
"ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy"
"dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI"
"hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p"
"OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2"
"8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K"
"Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe"
"hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk"
"6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw"
"DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q"
"AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI"
"bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB"
"ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z"
"qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd"
"iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn"
"0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN"
"sSi6"
"\"},"
"{\"starfield_class_2_ca\": \""
"MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl"
"MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp"
"U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw"
"NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE"
"ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp"
"ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3"
"DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf"
"8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN"
"+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0"
"X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa"
"K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA"
"1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G"
"A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR"
"zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0"
"YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD"
"bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w"
"DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3"
"L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D"
"eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl"
"xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp"
"VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY"
"WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q="
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{" /* chain for alexa.na.gateway.devices.a2z.com */
"\"name\": \"avs_via_starfield\","
"\"stack\": ["
"\"starfield_class_2_ca\","
"\"starfield_services_root_ca\""
"]"
"},"
"{" /* chain for api.amazon.com */
"\"name\": \"api_amazon_com\","
"\"stack\": ["
"\"digicert_global_ca_g2\","
"\"digicert_global_root_g2\""
"]"
"}"
"],"
"\"s\": [" /* the supported stream types */
"{\"api_amazon_com_auth\": {"
"\"endpoint\":" "\"api.amazon.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"auth/o2/token\","
"\"plugins\":" "[],"
"\"opportunistic\":" "true,"
"\"tls\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_www_form_urlencoded\":" "true,"
"\"http_no_content_length\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"api_amazon_com\""
"}},"
"{\"avs_event\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"v20160207/directives\","
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"nailed_up\":" "true,"
"\"long_poll\":" "true,"
"\"retry\":" "\"default\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}},"
"{\"avs_metadata\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"v20160207/events\","
"\"http_no_content_length\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"http_multipart_name\":" "\"metadata\","
"\"http_mime_content_type\":" "\"application/json; charset=UTF-8\","
"\"rideshare\":" "\"avs_audio\","
"\"retry\":" "\"default\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}},"
"{\"avs_audio\": {"
"\"endpoint\":" "\"alexa.na.gateway.devices.a2z.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"v20160207/events\","
"\"http_no_content_length\":" "true,"
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"h2q_oflow_txcr\":" "true,"
"\"http_auth_header\":" "\"authorization:\","
"\"http_auth_preamble\":" "\"Bearer \","
"\"http_multipart_name\":" "\"audio\","
"\"http_mime_content_type\":" "\"application/octet-stream\","
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"avs_via_starfield\""
"}}"
"]"
"}"
;
static const char *canned_root_token_payload =
"grant_type=refresh_token"
"&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
"SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
"zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
"0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
"k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
"iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
"KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
"AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
"xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
"&client_id="
"amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = lws_system_context_from_system_mgr(mgr);
lws_system_blob_t *ab = lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
size_t size;
/*
* For the things we care about, let's notice if we are trying to get
* past them when we haven't solved them yet, and make the system
* state wait while we trigger the dependent action.
*/
switch (target) {
case LWS_SYSTATE_REGISTERED:
size = lws_system_blob_get_size(ab);
if (size)
break;
/* let's register our canned root token so auth can use it */
lws_system_blob_direct_set(ab,
(const uint8_t *)canned_root_token_payload,
strlen(canned_root_token_payload));
break;
case LWS_SYSTATE_OPERATIONAL:
if (current == LWS_SYSTATE_OPERATIONAL)
avs_example_start(context);
break;
case LWS_SYSTATE_POLICY_INVALID:
/*
* This is a NOP since we used direct set... but in a real
* system this could easily change to be done on the heap, then
* this would be important
*/
lws_system_blob_destroy(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
1 /* AUTH_IDX_ROOT */));
break;
}
return 0;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
int n = 0;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS secure streams - AVS test [-d<verb>]\n");
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 6 + 1;
info.pss_policies_json = default_ss_policy;
info.port = CONTEXT_PORT_NO_LISTEN;
#if defined(LWS_SS_USE_SSPC)
{
const char *p;
/* connect to ssproxy via UDS by default, else via
* tcp connection to this port */
if ((p = lws_cmdline_option(argc, argv, "-p")))
info.ss_proxy_port = atoi(p);
/* UDS "proxy.ss.lws" in abstract namespace, else this socket
* path; when -p given this can specify the network interface
* to bind to */
if ((p = lws_cmdline_option(argc, argv, "-i")))
info.ss_proxy_bind = p;
/* if -p given, -a specifies the proxy address to connect to */
if ((p = lws_cmdline_option(argc, argv, "-a")))
info.ss_proxy_address = p;
}
#endif
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy";
#endif
/* integrate us with lws system state management when context created */
nl.name = "app";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,81 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-client-tx)
set(SRCS minimal-secure-streams-client-tx.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View File

@ -0,0 +1,64 @@
# lws minimal secure streams
The application goes to https://warmcat.com and reads index.html there.
It does it using Secure Streams... the main code in minimal-secure-streams.c
just sets up the context and opens a secure stream of type "mintest".
The handler for state changes and payloads for "mintest" is in ss-myss.c
The information about how a "mintest" stream should connect and the
protocol it uses is kept separated in policy-database.c
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-f| Force connecting to the wrong endpoint to check backoff retry flow
-p| Run as proxy server for clients to connect to over unix domain socket
```
[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d<verbosity>] [-f]
[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0
[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0
[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /
[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0
[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0
[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1
[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0
[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2
[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0
[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0
[2019/08/12 07:16:13:4781] USR: Completed: OK
```

View File

@ -0,0 +1,198 @@
/*
* lws-minimal-secure-streams-tx
*
* 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.
*
*
* This demonstrates tx from secure streams.
*
* It opens a stream and fires small 80-byte payloads on it at 50Hz (20ms)
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#define PKT_SIZE 80
#define RATE_US 50000
static int interrupted, bad = 1;
typedef struct myss {
struct lws_sspc_handle *ss;
void *opaque_data;
/* ... application specific state ... */
lws_sorted_usec_list_t sul;
int count;
char due;
} myss_t;
/* secure streams payload interface */
static int
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
// myss_t *m = (myss_t *)userobj;
//lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
//lwsl_hexdump_info(buf, len);
return 0;
}
static void
txcb(struct lws_sorted_usec_list *sul)
{
myss_t *m = lws_container_of(sul, myss_t, sul);
if (m->count == 1000) {
interrupted = 1;
return;
}
m->due = 1;
lws_sspc_request_tx(m->ss);
lws_sul_schedule(lws_sspc_get_context(m->ss), 0, &m->sul, txcb, RATE_US);
}
static int
myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
int *flags)
{
myss_t *m = (myss_t *)userobj;
if (!m->due)
return 0;
m->due = 0;
if (lws_get_random(lws_sspc_get_context(m->ss), buf, PKT_SIZE) != PKT_SIZE)
return 1;
*len = PKT_SIZE;
*flags = 0;
if (!m->count)
*flags |= LWSSS_FLAG_SOM;
if (m->count == 999) {
*flags |= LWSSS_FLAG_EOM;
lwsl_user("%s: sent final packet\n", __func__);
bad = 0;
}
m->count++;
lws_sul_schedule(lws_sspc_get_context(m->ss), 0, &m->sul, txcb, RATE_US);
// lwsl_user("%s: sending pkt %d\n", __func__, m->count);
return 0;
}
static int
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
myss_t *m = (myss_t *)userobj;
struct lws_context *context = lws_sspc_get_context(m->ss);
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
break;
case LWSSSCS_CONNECTED:
lws_sul_schedule(context, 0, &m->sul, txcb, RATE_US);
break;
case LWSSSCS_DISCONNECTED:
lws_sul_schedule(context, 0, &m->sul, txcb,
LWS_SET_TIMER_USEC_CANCEL);
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* if we're out of retries, we want to close the app and FAIL */
interrupted = 1;
break;
default:
break;
}
return 0;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
struct lws_context_creation_info info;
struct lws_context *context;
lws_ss_info_t ssi;
const char *p;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS secure streams client TX [-d<verb>]\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = //LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 6 + 1;
info.port = CONTEXT_PORT_NO_LISTEN;
info.protocols = lws_sspc_protocols;
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssclient";
#endif
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/*
* We're requesting a secure stream via proxy... where and how this
* connects are details managed by the proxy policy
*/
memset(&ssi, 0, sizeof ssi);
ssi.handle_offset = offsetof(myss_t, ss);
ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data);
ssi.rx = myss_rx;
ssi.tx = myss_tx;
ssi.state = myss_state;
ssi.user_alloc = sizeof(myss_t);
ssi.streamtype = "spam";
if (lws_sspc_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) {
lwsl_err("%s: create secure stream failed\n", __func__);
goto bail;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
bail:
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,80 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-proxy)
set(SRCS main.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS_PROXY_API 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View File

@ -0,0 +1,33 @@
# lws minimal secure streams proxy
Operates as a secure streams proxy, by default on a listening unix domain socket
"proxy.ss.lws" in the Linux abstract namespace.
Give -p <port> to have it listen on a specific tcp port instead.
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-f| Force connecting to the wrong endpoint to check backoff retry flow
-p <port>|If not given, proxy listens on a Unix Domain Socket, if given listen on specified tcp port
-i <iface>|Optionally specify the UDS path (no -p) or network interface to bind to (if -p also given)
```
[2020/02/26 15:41:27:5768] U: LWS secure streams Proxy [-d<verb>]
[2020/02/26 15:41:27:5770] N: lws_ss_policy_set: 2.064KiB, pad 70%: hardcoded
[2020/02/26 15:41:27:5771] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391
[2020/02/26 15:41:27:8681] N: lws_ss_policy_set: 4.512KiB, pad 15%: updated
[2020/02/26 15:41:27:8682] N: lws_tls_client_create_vhost_context: using mem client CA cert 837
[2020/02/26 15:41:27:8683] N: lws_tls_client_create_vhost_context: using mem client CA cert 1043
[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1167
[2020/02/26 15:41:27:8684] N: lws_tls_client_create_vhost_context: using mem client CA cert 1391
[2020/02/26 15:41:28:4226] N: ss_api_amazon_auth_rx: acquired 567-byte api.amazon.com auth token, exp 3600s
```

View File

@ -0,0 +1,285 @@
/*
* lws-minimal-secure-streams-proxy
*
* 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.
*
*
* This is the proxy part for examples built to use it to connect to... it has
* the policy and the core SS function, but it doesn't contain any of the user
* code "business logic"... that's in the clients.
*
* The proxy side has the policy and performs the onward connection proxying
* fulfilment. The clients state the streamtype name they want and ask for the
* client to do the connection part.
*
* Rideshare information is being parsed out at the proxy side; the SSS RX part
* also brings with it rideshare names.
*
* Metadata is passed back over SSS from the client in the TX messages for the
* proxy to use per the policy.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted, bad = 1, port = 0 /* unix domain socket */;
static const char *ibind = NULL; /* default to unix domain skt "proxy.ss.lws" */
static lws_state_notify_link_t nl;
/*
* We just define enough policy so it can fetch the latest one securely
*/
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "30,"
"\"svalidhup\":" "35"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
/*
* Let's Encrypt certs for warmcat.com / libwebsockets.org
*
* We fetch the real policy from there using SS and switch to
* using that.
*/
"{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
"\"},"
"{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */
"MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1"
"WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg"
"RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi"
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX"
"NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf"
"89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl"
"Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc"
"Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz"
"uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB"
"AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU"
"BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB"
"FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo"
"SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js"
"LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF"
"BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG"
"AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD"
"VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB"
"ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx"
"A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM"
"UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2"
"DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1"
"eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu"
"OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw"
"p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY"
"2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0"
"ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR"
"PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b"
"rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt"
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{"
"\"name\": \"le_via_isrg\","
"\"stack\": ["
"\"isrg_root_x1\","
"\"LEX3_isrg_root_x1\""
"]"
"}"
"],"
"\"s\": ["
"{\"fetch_policy\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"policy/minimal-proxy.json\","
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}}"
"}"
;
static const char *canned_root_token_payload =
"grant_type=refresh_token"
"&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
"SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
"zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
"0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
"k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
"iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
"KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
"AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
"xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
"&client_id="
"amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = lws_system_context_from_system_mgr(mgr);
lws_system_blob_t *ab = lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
size_t size;
/*
* For the things we care about, let's notice if we are trying to get
* past them when we haven't solved them yet, and make the system
* state wait while we trigger the dependent action.
*/
switch (target) {
case LWS_SYSTATE_REGISTERED:
size = lws_system_blob_get_size(ab);
if (size)
break;
/* let's register our canned root token so auth can use it */
lws_system_blob_direct_set(ab,
(const uint8_t *)canned_root_token_payload,
strlen(canned_root_token_payload));
break;
case LWS_SYSTATE_OPERATIONAL:
if (current == LWS_SYSTATE_OPERATIONAL)
/*
* At this point we have DHCP, ntp, system auth token
* and we can reasonably create the proxy
*/
if (lws_ss_proxy_create(context, ibind, port)) {
lwsl_err("%s: failed to create ss proxy\n",
__func__);
return -1;
}
break;
case LWS_SYSTATE_POLICY_INVALID:
/*
* This is a NOP since we used direct set... but in a real
* system this could easily change to be done on the heap, then
* this would be important
*/
lws_system_blob_destroy(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH,
1 /* AUTH_IDX_ROOT */));
break;
}
return 0;
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
struct lws_context_creation_info info;
struct lws_context *context;
const char *p;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
/* connect to ssproxy via UDS by default, else via tcp with this port */
if ((p = lws_cmdline_option(argc, argv, "-p")))
port = atoi(p);
/* UDS "proxy.ss.lws" in abstract namespace, else this socket path;
* when -p given this can specify the network interface to bind to */
if ((p = lws_cmdline_option(argc, argv, "-i")))
ibind = p;
lws_set_log_level(logs, NULL);
lwsl_user("LWS secure streams Proxy [-d<verb>]\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 6 + 1;
info.pss_policies_json = default_ss_policy;
info.port = CONTEXT_PORT_NO_LISTEN;
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy";
#endif
/* integrate us with lws system state management when context created */
nl.name = "app";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
bad = 0;
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-seq)
set(SRCS minimal-secure-streams.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View File

@ -0,0 +1,65 @@
# lws minimal sequre streams seq
The application goes to https://warmcat.com and reads index.html there.
It does it using Secure Streams... the main code in minimal-secure-streams.c
just sets up the context and opens a secure stream of type "mintest".
The handler for state changes and payloads for "mintest" is in ss-myss.c
The information about how a "mintest" stream should connect and the
protocol it uses is kept separated in policy-database.c
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-f| Force connecting to the wrong endpoint to check backoff retry flow
```
$ ./lws-minimal-secure-streams-seq
[2018/03/04 14:43:20:8562] USER: LWS minimal http client
[2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on
[2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default
[2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
[2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com
[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520
[2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up
[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0174] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:0179] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3010] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3015] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3020] USER: RECEIVE_CLIENT_HTTP_READ: read 1015
[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 1024
[2018/03/04 14:43:22:3022] USER: RECEIVE_CLIENT_HTTP_READ: read 974
[2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1
[2018/03/04 14:43:23:3042] USER: Completed
```

View File

@ -0,0 +1,443 @@
/*
* lws-minimal-secure-streams-seq
*
* 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.
*
*
* This demonstrates the a minimal http client using secure streams api.
*
* It visits https://warmcat.com/ and receives the html page there.
*
* This is the "secure streams" api equivalent of minimal-http-client...
* it shows how to use a sequencer to make it easy to build more complex
* schemes on top of this example.
*
* The layering looks like this
*
* lifetime
*
* ------ app ------ process
* ---- sequencer ---- process
* --- secure stream --- process
* ------- wsi ------- connection
*
* see minimal-secure-streams for a similar example without the sequencer.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted, bad = 1, flag_conn_fail, flag_h1post;
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "300,"
"\"svalidhup\":" "310"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
/*
* Need to be in order from root cert... notice sometimes as
* with Let's Encrypt there are multiple possible validation
* paths, all the pieces for one validation path must be
* given, excluding the server cert itself. Let's Encrypt
* intermediate is signed by their ISRG Root CA but also is
* cross-signed by an IdenTrust intermediate that's widely
* deployed in browsers. We use the ISRG path because that
* way we can skip the extra IdenTrust root cert.
*/
"{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
"\"},"
"{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */
"MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1"
"WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg"
"RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi"
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX"
"NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf"
"89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl"
"Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc"
"Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz"
"uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB"
"AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU"
"BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB"
"FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo"
"SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js"
"LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF"
"BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG"
"AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD"
"VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB"
"ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx"
"A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM"
"UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2"
"DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1"
"eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu"
"OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw"
"p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY"
"2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0"
"ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR"
"PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b"
"rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt"
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{"
"\"name\": \"le_via_isrg\","
"\"stack\": ["
"\"isrg_root_x1\","
"\"LEX3_isrg_root_x1\""
"]"
"}"
"],"
"\"s\": [" /* the supported stream types */
"{\"mintest\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"index.html\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}},"
"{\"mintest-fail\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "22,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"index.html\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}},"
"{\"minpost\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"POST\","
"\"http_url\":" "\"testserver/formtest\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}}"
"]"
"}"
;
typedef struct myss {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
} myss_t;
/* secure streams payload interface */
static int
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
// myss_t *m = (myss_t *)userobj;
lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
lwsl_hexdump_info(buf, len);
/*
* If we received the whole message, we let the sequencer know it
* was a success
*/
if (flags & LWSSS_FLAG_EOM) {
bad = 0;
interrupted = 1;
}
return 0;
}
static int
myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
int *flags)
{
// myss_t *m = (myss_t *)userobj;
/* in this example, we don't send any payload */
return 0;
}
static int
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
myss_t *m = (myss_t *)userobj;
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_request_tx(m->ss);
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* if we're out of retries, we want to close the app and FAIL */
interrupted = 1;
break;
default:
break;
}
return 0;
}
typedef enum {
SEQ_IDLE,
SEQ_TRY_CONNECT,
SEQ_RECONNECT_WAIT,
SEQ_CONNECTED,
} myseq_state_t;
typedef struct myseq {
struct lws_ss_handle *ss;
myseq_state_t state;
int http_resp;
uint16_t try;
} myseq_t;
/*
* This defines the sequence of things the test app does.
*/
static lws_seq_cb_return_t
min_sec_str_sequencer_cb(struct lws_sequencer *seq, void *user, int event,
void *v, void *a)
{
struct myseq *s = (struct myseq *)user;
lws_ss_info_t ssi;
switch ((int)event) {
/* these messages are created just by virtue of being a sequencer */
case LWSSEQ_CREATED: /* our sequencer just got started */
s->state = SEQ_IDLE;
lwsl_notice("%s: LWSSEQ_CREATED\n", __func__);
/* We're making an outgoing secure stream ourselves */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(myss_t, ss);
ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data);
ssi.rx = myss_rx;
ssi.tx = myss_tx;
ssi.state = myss_state;
ssi.user_alloc = sizeof(myss_t);
/* requested to fail (to check backoff)? */
if (flag_conn_fail)
ssi.streamtype = "mintest-fail";
else
/* request to check h1 POST */
if (flag_h1post)
ssi.streamtype = "minpost";
else
/* default to h1 GET */
ssi.streamtype = "mintest";
if (lws_ss_create(lws_seq_get_context(seq), 0, &ssi, NULL,
&s->ss, seq, NULL)) {
lwsl_err("%s: failed to create secure stream\n",
__func__);
return LWSSEQ_RET_DESTROY;
}
break;
case LWSSEQ_DESTROYED:
lwsl_notice("%s: LWSSEQ_DESTROYED\n", __func__);
break;
case LWSSEQ_TIMED_OUT: /* current step timed out */
if (s->state == SEQ_RECONNECT_WAIT)
lws_ss_request_tx(s->ss);
break;
/*
* These messages are created because we have a secure stream that was
* bound to this sequencer at creation time. It copies its state
* events to us as its sequencer parent. v is the lws_ss_handle_t *
*/
case LWSSEQ_SS_STATE_BASE + LWSSSCS_CREATING:
lwsl_info("%s: seq LWSSSCS_CREATING\n", __func__);
lws_ss_request_tx(s->ss);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_DISCONNECTED:
lwsl_info("%s: seq LWSSSCS_DISCONNECTED\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_UNREACHABLE:
lwsl_info("%s: seq LWSSSCS_UNREACHABLE\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_AUTH_FAILED:
lwsl_info("%s: seq LWSSSCS_AUTH_FAILED\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTED:
lwsl_info("%s: seq LWSSSCS_CONNECTED\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_CONNECTING:
lwsl_info("%s: seq LWSSSCS_CONNECTING\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_DESTROYING:
lwsl_info("%s: seq LWSSSCS_DESTROYING\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_POLL:
/* somebody called lws_ss_poll() on the stream */
lwsl_info("%s: seq LWSSSCS_POLL\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_ALL_RETRIES_FAILED:
lwsl_info("%s: seq LWSSSCS_ALL_RETRIES_FAILED\n", __func__);
interrupted = 1;
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_REMOTE:
lwsl_info("%s: seq LWSSSCS_QOS_ACK_REMOTE\n", __func__);
break;
case LWSSEQ_SS_STATE_BASE + LWSSSCS_QOS_ACK_LOCAL:
lwsl_info("%s: seq LWSSSCS_QOS_ACK_LOCAL\n", __func__);
break;
/*
* This is the message we send from the ss handler to inform the
* sequencer we had the payload properly
*/
case LWSSEQ_USER_BASE:
bad = 0;
interrupted = 1;
break;
default:
break;
}
return LWSSEQ_RET_CONTINUE;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
struct lws_context_creation_info info;
struct lws_context *context;
lws_seq_info_t i;
const char *p;
myseq_t *ms;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal secure streams [-d<verbosity>][-f][--h1post]\n");
flag_conn_fail = !!lws_cmdline_option(argc, argv, "-f");
flag_h1post = !!lws_cmdline_option(argc, argv, "--h1post");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 1 + 1;
info.pss_policies_json = default_ss_policy;
info.port = CONTEXT_PORT_NO_LISTEN;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/*
* Create the sequencer that performs the steps of the test action
* from inside the event loop.
*/
memset(&i, 0, sizeof(i));
i.context = context;
i.user_size = sizeof(myseq_t);
i.puser = (void **)&ms;
i.cb = min_sec_str_sequencer_cb;
i.name = "min-sec-stream-seq";
if (!lws_seq_create(&i)) {
lwsl_err("%s: failed to create sequencer\n", __func__);
goto bail;
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
bail:
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams-sink)
set(SRCS main.c)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
endif()

View File

@ -0,0 +1,64 @@
# lws minimal secure streams
The application goes to https://warmcat.com and reads index.html there.
It does it using Secure Streams... the main code in minimal-secure-streams.c
just sets up the context and opens a secure stream of type "mintest".
The handler for state changes and payloads for "mintest" is in ss-myss.c
The information about how a "mintest" stream should connect and the
protocol it uses is kept separated in policy-database.c
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-f| Force connecting to the wrong endpoint to check backoff retry flow
-p| Run as proxy server for clients to connect to over unix domain socket
```
[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d<verbosity>] [-f]
[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0
[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0
[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /
[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0
[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0
[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1
[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0
[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2
[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0
[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0
[2019/08/12 07:16:13:4781] USR: Completed: OK
```

View File

@ -0,0 +1,288 @@
/*
* lws-minimal-secure-streams-sink
*
* 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.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
static int interrupted, bad = 1;
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "300,"
"\"svalidhup\":" "310"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
/*
* Need to be in order from root cert... notice sometimes as
* with Let's Encrypt there are multiple possible validation
* paths, all the pieces for one validation path must be
* given, excluding the server cert itself. Let's Encrypt
* intermediate is signed by their ISRG Root CA but also is
* cross-signed by an IdenTrust intermediate that's widely
* deployed in browsers. We use the ISRG path because that
* way we can skip the extra IdenTrust root cert.
*/
"{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
"\"},"
"{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */
"MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1"
"WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg"
"RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi"
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX"
"NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf"
"89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl"
"Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc"
"Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz"
"uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB"
"AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU"
"BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB"
"FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo"
"SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js"
"LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF"
"BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG"
"AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD"
"VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB"
"ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx"
"A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM"
"UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2"
"DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1"
"eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu"
"OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw"
"p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY"
"2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0"
"ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR"
"PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b"
"rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt"
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{"
"\"name\": \"le_via_isrg\","
"\"stack\": ["
"\"isrg_root_x1\","
"\"LEX3_isrg_root_x1\""
"]"
"}"
"],"
"\"s\": [" /* the supported stream types */
"{\"\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h2\","
"\"http_method\":" "\"GET\","
"\"http_url\":" "\"index.html\","
"\"plugins\":" "[],"
"\"tls\":" "true,"
"\"nailed_up\":" "true,"
"\"long_poll\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}}"
"]"
"}"
;
typedef struct myss {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
lws_sorted_usec_list_t sul;
int count;
} myss_t;
/* secure streams payload interface */
static int
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
// myss_t *m = (myss_t *)userobj;
lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
lwsl_hexdump_info(buf, len);
/*
* If we received the whole message, for our example it means
* we are done.
*/
if (flags & LWSSS_FLAG_EOM) {
bad = 0;
interrupted = 1;
}
return 0;
}
static int
myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
int *flags)
{
//myss_t *m = (myss_t *)userobj;
return 0;
}
static int
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
myss_t *m = (myss_t *)userobj;
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_request_tx(m->ss);
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* if we're out of retries, we want to close the app and FAIL */
interrupted = 1;
break;
default:
break;
}
return 0;
}
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
int n = 0;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS secure streams [-d<verb>] [-f] [-p] [--h1post]\n");
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.fd_limit_per_thread = 1 + 6 + 1;
info.pss_policies_json = default_ss_policy;
info.port = CONTEXT_PORT_NO_LISTEN;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
// puts(default_ss_policy);
if (lws_cmdline_option(argc, argv, "-p")) {
/* we are being the proxy */
#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)
if (lws_ss_proxy_create(context, NULL, 0)) {
lwsl_err("%s: failed to create ss proxy\n", __func__);
goto bail;
}
lwsl_notice("%s: secure streams proxy mode\n", __func__);
#else
lwsl_err("%s: needs cmake LWS_WITH_SECURE_STREAMS_PROXY_API\n",
__func__);
#endif
} else {
lws_ss_info_t ssi;
/* We're making an outgoing secure stream ourselves */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(myss_t, ss);
ssi.opaque_user_data_offset = offsetof(myss_t, opaque_data);
ssi.rx = myss_rx;
ssi.tx = myss_tx;
ssi.state = myss_state;
ssi.user_alloc = sizeof(myss_t);
/* requested to fail (to check backoff)? */
if (lws_cmdline_option(argc, argv, "-f"))
ssi.streamtype = "mintest-fail";
else
/* request to check h1 POST */
if (lws_cmdline_option(argc, argv, "--h1post"))
ssi.streamtype = "minpost";
else
/* default to h1 GET */
ssi.streamtype = "mintest";
if (lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) {
lwsl_err("%s: failed to create secure stream\n",
__func__);
goto bail;
}
}
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
bail:
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -0,0 +1,91 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
set(SAMP lws-minimal-secure-streams)
# If we are being built as part of lws, confirm current build config supports
# reqconfig, else skip building ourselves.
#
# If we are being built externally, confirm installed lws was configured to
# support reqconfig, else error out with a helpful message about the problem.
#
MACRO(require_lws_config reqconfig _val result)
if (DEFINED ${reqconfig})
if (${reqconfig})
set (rq 1)
else()
set (rq 0)
endif()
else()
set(rq 0)
endif()
if (${_val} EQUAL ${rq})
set(SAME 1)
else()
set(SAME 0)
endif()
if (LWS_WITH_MINIMAL_EXAMPLES AND NOT ${SAME})
if (${_val})
message("${SAMP}: skipping as lws being built without ${reqconfig}")
else()
message("${SAMP}: skipping as lws built with ${reqconfig}")
endif()
set(${result} 0)
else()
if (LWS_WITH_MINIMAL_EXAMPLES)
set(MET ${SAME})
else()
CHECK_C_SOURCE_COMPILES("#include <libwebsockets.h>\nint main(void) {\n#if defined(${reqconfig})\n return 0;\n#else\n fail;\n#endif\n return 0;\n}\n" HAS_${reqconfig})
if (NOT DEFINED HAS_${reqconfig} OR NOT HAS_${reqconfig})
set(HAS_${reqconfig} 0)
else()
set(HAS_${reqconfig} 1)
endif()
if ((HAS_${reqconfig} AND ${_val}) OR (NOT HAS_${reqconfig} AND NOT ${_val}))
set(MET 1)
else()
set(MET 0)
endif()
endif()
if (NOT MET)
if (${_val})
message(FATAL_ERROR "This project requires lws must have been configured with ${reqconfig}")
else()
message(FATAL_ERROR "Lws configuration of ${reqconfig} is incompatible with this project")
endif()
endif()
endif()
ENDMACRO()
set(requirements 1)
require_lws_config(LWS_ROLE_H1 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
if (requirements)
add_executable(${SAMP} minimal-secure-streams.c)
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared)
else()
target_link_libraries(${SAMP} websockets)
endif()
if (LWS_WITH_SECURE_STREAMS_PROXY_API)
add_compile_options(-DLWS_SS_USE_SSPC)
add_executable(${SAMP}-client minimal-secure-streams.c)
if (websockets_shared)
target_link_libraries(${SAMP}-client websockets_shared)
add_dependencies(${SAMP}-client websockets_shared)
else()
target_link_libraries(${SAMP}-client websockets)
endif()
endif()
endif()

View File

@ -0,0 +1,64 @@
# lws minimal secure streams
The application goes to https://warmcat.com and reads index.html there.
It does it using Secure Streams... the main code in minimal-secure-streams.c
just sets up the context and opens a secure stream of type "mintest".
The handler for state changes and payloads for "mintest" is in ss-myss.c
The information about how a "mintest" stream should connect and the
protocol it uses is kept separated in policy-database.c
## build
```
$ cmake . && make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
-f| Force connecting to the wrong endpoint to check backoff retry flow
-p| Run as proxy server for clients to connect to over unix domain socket
```
[2019/08/12 07:16:11:0045] USR: LWS minimal secure streams [-d<verbosity>] [-f]
[2019/08/12 07:16:12:6102] USR: myss_state: LWSSSCS_CREATING, ord 0x0
[2019/08/12 07:16:12:6107] USR: myss_state: LWSSSCS_POLL, ord 0x0
[2019/08/12 07:16:12:6117] N: lws_ss_client_connect: connecting h1get warmcat.com /
[2019/08/12 07:16:12:6118] USR: myss_state: LWSSSCS_CONNECTING, ord 0x0
[2019/08/12 07:16:13:4171] USR: myss_state: LWSSSCS_CONNECTED, ord 0x0
[2019/08/12 07:16:13:4222] USR: myss_rx: len 1024, flags: 1
[2019/08/12 07:16:13:4243] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4244] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4245] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4246] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4247] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4252] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4264] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4265] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4266] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4267] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4268] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4269] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4270] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4278] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4279] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4280] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4281] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4282] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4283] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4284] USR: myss_rx: len 1015, flags: 0
[2019/08/12 07:16:13:4287] USR: myss_rx: len 1024, flags: 0
[2019/08/12 07:16:13:4288] USR: myss_rx: len 947, flags: 0
[2019/08/12 07:16:13:4293] USR: myss_rx: len 0, flags: 2
[2019/08/12 07:16:13:4399] USR: myss_state: LWSSSCS_DISCONNECTED, ord 0x0
[2019/08/12 07:16:13:4761] USR: myss_state: LWSSSCS_DESTROYING, ord 0x0
[2019/08/12 07:16:13:4781] USR: Completed: OK
```

View File

@ -0,0 +1,421 @@
/*
* lws-minimal-secure-streams
*
* 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.
*
*
* This demonstrates a minimal http client using secure streams api.
*
* It visits https://warmcat.com/ and receives the html page there.
*
* This example is built two different ways from the same source... one includes
* the policy everything needed to fulfil the stream directly. The other -client
* variant has no policy itself and some other minor init changes, and connects
* to the -proxy example to actually get the connection done.
*
* In the -client build case, the example does not even init the tls libraries
* since the proxy part will take care of all that.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
/*
* uncomment to force network traffic through 127.0.0.1:1080
*
* On your local machine, you can run a SOCKS5 proxy like this
*
* $ ssh -N -D 0.0.0.0:1080 localhost -v
*
* If enabled, this also fetches a remote policy that also
* specifies that all traffic should go through the remote
* proxy.
*/
// #define VIA_LOCALHOST_SOCKS
static int interrupted, bad = 1;
static lws_state_notify_link_t nl;
/*
* If the -proxy app is fulfilling our connection, then we don't need to have
* the policy in the client.
*
* When we build with LWS_SS_USE_SSPC, the apis hook up to a proxy process over
* a Unix Domain Socket. To test that, you need to separately run the
* ./lws-minimal-secure-streams-proxy test app on the same machine.
*/
#if !defined(LWS_SS_USE_SSPC)
static const char * const default_ss_policy =
"{"
"\"release\":" "\"01234567\","
"\"product\":" "\"myproduct\","
"\"schema-version\":" "1,"
#if defined(VIA_LOCALHOST_SOCKS)
"\"via-socks5\":" "\"127.0.0.1:1080\","
#endif
"\"retry\": [" /* named backoff / retry strategies */
"{\"default\": {"
"\"backoff\": [" "1000,"
"2000,"
"3000,"
"5000,"
"10000"
"],"
"\"conceal\":" "5,"
"\"jitterpc\":" "20,"
"\"svalidping\":" "30,"
"\"svalidhup\":" "35"
"}}"
"],"
"\"certs\": [" /* named individual certificates in BASE64 DER */
/*
* Let's Encrypt certs for warmcat.com / libwebsockets.org
*
* We fetch the real policy from there using SS and switch to
* using that.
*/
"{\"isrg_root_x1\": \"" /* ISRG ROOT X1 */
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4"
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu"
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY"
"MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc"
"h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+"
"0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U"
"A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW"
"T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH"
"B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC"
"B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv"
"KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn"
"OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn"
"jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw"
"qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI"
"rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV"
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq"
"hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL"
"ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ"
"3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK"
"NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5"
"ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur"
"TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC"
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc"
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq"
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA"
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d"
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc="
"\"},"
"{\"LEX3_isrg_root_x1\": \"" /* LE X3 signed by ISRG X1 root */
"MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw"
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh"
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1"
"WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg"
"RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi"
"MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX"
"NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf"
"89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl"
"Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc"
"Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz"
"uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB"
"AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU"
"BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB"
"FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo"
"SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js"
"LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF"
"BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG"
"AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD"
"VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB"
"ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx"
"A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM"
"UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2"
"DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1"
"eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu"
"OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw"
"p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY"
"2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0"
"ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR"
"PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b"
"rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt"
"\"}"
"],"
"\"trust_stores\": [" /* named cert chains */
"{"
"\"name\": \"le_via_isrg\","
"\"stack\": ["
"\"isrg_root_x1\","
"\"LEX3_isrg_root_x1\""
"]"
"}"
"],"
"\"s\": ["
"{\"fetch_policy\": {"
"\"endpoint\":" "\"warmcat.com\","
"\"port\":" "443,"
"\"protocol\":" "\"h1\","
"\"http_method\":" "\"GET\","
#if defined(VIA_LOCALHOST_SOCKS)
"\"http_url\":" "\"policy/minimal-proxy-socks.json\","
#else
"\"http_url\":" "\"policy/minimal-proxy.json\","
#endif
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"retry\":" "\"default\","
"\"tls_trust_store\":" "\"le_via_isrg\""
"}}"
"}"
;
#endif
typedef struct myss {
struct lws_ss_handle *ss;
void *opaque_data;
/* ... application specific state ... */
lws_sorted_usec_list_t sul;
} myss_t;
static const char *canned_root_token_payload =
"grant_type=refresh_token"
"&refresh_token=Atzr|IwEBIJedGXjDqsU_vMxykqOMg"
"SHfYe3CPcedueWEMWSDMaDnEmiW8RlR1Kns7Cb4B-TOSnqp7ifVsY4BMY2B8tpHfO39XP"
"zfu9HapGjTR458IyHX44FE71pWJkGZ79uVBpljP4sazJuk8XS3Oe_yLnm_DIO6fU1nU3Y"
"0flYmsOiOAQE_gRk_pdlmEtHnpMA-9rLw3mkY5L89Ty9kUygBsiFaYatouROhbsTn8-jW"
"k1zZLUDpT6ICtBXSnrCIg0pUbZevPFhTwdXd6eX-u4rq0W-XaDvPWFO7au-iPb4Zk5eZE"
"iX6sissYrtNmuEXc2uHu7MnQO1hHCaTdIO2CANVumf-PHSD8xseamyh04sLV5JgFzY45S"
"KvKMajiUZuLkMokOx86rjC2Hdkx5DO7G-dbG1ufBDG-N79pFMSs7Ck5pc283IdLoJkCQc"
"AGvTX8o8I29QqkcGou-9TKhOJmpX8As94T61ok0UqqEKPJ7RhfQHHYdCtsdwxgvfVr9qI"
"xL_hDCcTho8opCVX-6QhJHl6SQFlTw13"
"&client_id="
"amzn1.application-oa2-client.4823334c434b4190a2b5a42c07938a2d";
/* secure streams payload interface */
static int
myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
// myss_t *m = (myss_t *)userobj;
lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
lwsl_hexdump_info(buf, len);
/*
* If we received the whole message, for our example it means
* we are done.
*/
if (flags & LWSSS_FLAG_EOM) {
bad = 0;
interrupted = 1;
}
return 0;
}
static int
myss_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len,
int *flags)
{
//myss_t *m = (myss_t *)userobj;
return 0;
}
static int
myss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
myss_t *m = (myss_t *)userobj;
lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name(state),
(unsigned int)ack);
switch (state) {
case LWSSSCS_CREATING:
lws_ss_set_metadata(m->ss, "uptag", "myuptag123", 10);
lws_ss_set_metadata(m->ss, "ctype", "myctype", 7);
lws_ss_client_connect(m->ss);
break;
case LWSSSCS_ALL_RETRIES_FAILED:
/* if we're out of retries, we want to close the app and FAIL */
interrupted = 1;
break;
case LWSSSCS_QOS_ACK_REMOTE:
lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__);
break;
default:
break;
}
return 0;
}
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
struct lws_context *context = lws_system_context_from_system_mgr(mgr);
lws_system_blob_t *ab = lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_AUTH, 1 /* AUTH_IDX_ROOT */);
size_t size;
/*
* For the things we care about, let's notice if we are trying to get
* past them when we haven't solved them yet, and make the system
* state wait while we trigger the dependent action.
*/
switch (target) {
case LWS_SYSTATE_REGISTERED:
size = lws_system_blob_get_size(ab);
if (size)
break;
/* let's register our canned root token so auth can use it */
lws_system_blob_direct_set(ab,
(const uint8_t *)canned_root_token_payload,
strlen(canned_root_token_payload));
break;
case LWS_SYSTATE_OPERATIONAL:
if (current == LWS_SYSTATE_OPERATIONAL) {
lws_ss_info_t ssi;
/* We're making an outgoing secure stream ourselves */
memset(&ssi, 0, sizeof(ssi));
ssi.handle_offset = offsetof(myss_t, ss);
ssi.opaque_user_data_offset = offsetof(myss_t,
opaque_data);
ssi.rx = myss_rx;
ssi.tx = myss_tx;
ssi.state = myss_state;
ssi.user_alloc = sizeof(myss_t);
ssi.streamtype = "mintest";
if (lws_ss_create(context, 0, &ssi, NULL, NULL,
NULL, NULL)) {
lwsl_err("%s: failed to create secure stream\n",
__func__);
return -1;
}
}
break;
}
return 0;
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
static void
sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
struct lws_context_creation_info info;
struct lws_context *context;
int n = 0;
signal(SIGINT, sigint_handler);
memset(&info, 0, sizeof info);
lws_cmdline_option_handle_builtin(argc, argv, &info);
lwsl_user("LWS secure streams test client [-d<verb>]\n");
info.fd_limit_per_thread = 1 + 6 + 1;
info.port = CONTEXT_PORT_NO_LISTEN;
#if defined(LWS_SS_USE_SSPC)
info.protocols = lws_sspc_protocols;
{
const char *p;
/* connect to ssproxy via UDS by default, else via
* tcp connection to this port */
if ((p = lws_cmdline_option(argc, argv, "-p")))
info.ss_proxy_port = atoi(p);
/* UDS "proxy.ss.lws" in abstract namespace, else this socket
* path; when -p given this can specify the network interface
* to bind to */
if ((p = lws_cmdline_option(argc, argv, "-i")))
info.ss_proxy_bind = p;
/* if -p given, -a specifies the proxy address to connect to */
if ((p = lws_cmdline_option(argc, argv, "-a")))
info.ss_proxy_address = p;
}
#else
info.pss_policies_json = default_ss_policy;
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
#endif
#if defined(LWS_WITH_DETAILED_LATENCY)
info.detailed_latency_cb = lws_det_lat_plot_cb;
info.detailed_latency_filepath = "/tmp/lws-latency-ssproxy";
#endif
/* integrate us with lws system state management when context created */
nl.name = "app";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
/* create the context */
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/*
* Set the related lws_system blobs
*
* ...direct_set() sets a pointer, so the thing pointed to has to have
* a suitable lifetime, eg, something that already exists on the heap or
* a const string in .rodata like this
*/
lws_system_blob_direct_set(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_DEVICE_SERIAL, 0),
(const uint8_t *)"SN12345678", 10);
lws_system_blob_direct_set(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_DEVICE_FW_VERSION, 0),
(const uint8_t *)"v0.01", 5);
/*
* ..._heap_append() appends to a buflist kind of arrangement on heap,
* just one block is fine, otherwise it will concatenate the fragments
* in the order they were appended (and take care of freeing them at
* context destroy time). ..._heap_empty() is also available to remove
* everything that was already allocated.
*
* Here we use _heap_append() just so it's tested as well as direct set.
*/
lws_system_blob_heap_append(lws_system_get_blob(context,
LWS_SYSBLOB_TYPE_DEVICE_TYPE, 0),
(const uint8_t *)"spacerocket", 11);
/* the event loop */
while (n >= 0 && !interrupted)
n = lws_service(context, 0);
lws_context_destroy(context);
lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
return bad;
}

View File

@ -1,7 +1,7 @@
#!/bin/bash
if [ "$COVERITY_SCAN_BRANCH" != 1 -a "$TRAVIS_OS_NAME" = "osx" ]; then
if [ "$LWS_METHOD" != "mbedtls" ] ; then
if [ "$LWS_METHOD" != "mbedtls" -a "$LWS_METHOD" != "ss+mbedtls" ] ; then
mkdir build && cd build &&
cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl" $CMAKE_ARGS .. &&
cmake --build .
@ -34,7 +34,7 @@ else
cmake --build . &&
../scripts/h2load-smp.sh
else
if [ "$LWS_METHOD" = "mbedtls" ] ; then
if [ "$LWS_METHOD" = "mbedtls" -o "$LWS_METHOD" = "ss+mbedtls" ] ; then
cmake $CMAKE_ARGS .. &&
cmake --build . &&
sudo make install &&

View File

@ -21,7 +21,7 @@ then
sudo update-ca-certificates
fi
if [ "$LWS_METHOD" == "mbedtls" ];
if [ "$LWS_METHOD" == "mbedtls" -o "$LWS_METHOD" == "ss+mbedtls" ];
then
sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev valgrind
wget https://libwebsockets.org/openssl-1.1.0-trusty.tar.bz2 -O/tmp/openssl.tar.bz2