dbus-ws-proxy

This builds on the new dbus role support to provide a minimal example proxy
between ws and dbus.

A client app is provided that asks the proxy to connect to libwebsockets.org
and proxy the drawing data from the mirror example there back to the dbus
client using dbus messages.
This commit is contained in:
Andy Green 2018-10-03 14:14:29 +08:00
parent 31dfc4aa12
commit 47e10ab200
16 changed files with 1856 additions and 9 deletions

View File

@ -4,8 +4,8 @@ env:
global:
- secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI="
matrix:
- LWS_METHOD=lwsws 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"
- 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"
- LWS_METHOD=lwsws 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_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/"
- 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_ROLE_DBUS=1 -DLWS_DBUS_INCLUDE2=/usr/lib/x86_64-linux-gnu/dbus-1.0/include/"
- 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 -DCMAKE_BUILD_TYPE=DEBUG"
- LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON -DLWS_WITH_MINIMAL_EXAMPLES=1"

View File

@ -133,6 +133,12 @@ if(LWS_WITH_DISTRO_RECOMMENDED)
set(LWS_ROLE_DBUS 1)
endif()
# do you care about this? Then send me a patch where it disables it on travis
# but allows it on APPLE
if (APPLE)
set(LWS_ROLE_DBUS 0)
endif()
if(NOT DEFINED CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type")
endif()
@ -686,15 +692,15 @@ CHECK_INCLUDE_FILE(pthread.h LWS_HAVE_PTHREAD_H)
CHECK_LIBRARY_EXISTS(cap cap_set_flag "" LWS_HAVE_LIBCAP)
if (LWS_ROLE_DBUS)
if (NOT LWS_HAVE_LIBDBUS)
message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
endif()
if (NOT LWS_DBUS_LIB)
set(LWS_DBUS_LIB "dbus-1")
endif()
CHECK_LIBRARY_EXISTS(${LWS_DBUS_LIB} dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
if (NOT LWS_HAVE_LIBDBUS)
message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
endif()
if (NOT LWS_DBUS_INCLUDE1)
# look in fedora and debian / ubuntu place

View File

@ -1,4 +1,4 @@
|Example|Demonstrates|
---|---
minimal-dbus-client|Shows how to connect to a DBusServer dbus server like minimal-dbus-server
minimal-dbus-ws-proxy-testclient|A test client for use with minimal-dbus-ws-proxy

View File

@ -40,8 +40,9 @@ client_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
dbus_message_get_member(message),
dbus_message_get_path(message));
if (!dbus_message_get_args(message, NULL, DBUS_TYPE_STRING,
&str, DBUS_TYPE_INVALID))
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &str,
DBUS_TYPE_INVALID))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
lwsl_notice("%s: '%s'\n", __func__, str);

View File

@ -0,0 +1,120 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
include(CheckLibraryExists)
set(SAMP lws-minimal-dbus-ws-proxy-testclient)
set(SRCS minimal-dbus-ws-proxy-testclient.c)
if (NOT LWS_WITH_MINIMAL_EXAMPLES)
CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
if (NOT LWS_HAVE_LIBDBUS)
message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
endif()
if (NOT LWS_DBUS_LIB)
set(LWS_DBUS_LIB "dbus-1")
endif()
if (NOT LWS_DBUS_INCLUDE1)
# look in fedora and debian / ubuntu place
if (EXISTS "/usr/include/dbus-1.0")
set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
else()
message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
endif()
endif()
if (NOT LWS_DBUS_INCLUDE2)
# look in fedora... debian / ubuntu has the ARCH in the path...
if (EXISTS "/usr/lib64/dbus-1.0/include")
set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
else()
message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
endif()
endif()
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
endif()
endif()
# 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_DBUS 1 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
include_directories("${LWS_DBUS_INCLUDE1}")
include_directories("${LWS_DBUS_INCLUDE2}")
list(APPEND LIB_LIST dbus-1)
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
else()
target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
endif()
endif()

View File

@ -0,0 +1,52 @@
# lws minimal dbus ws proxy testclient
This is a test client used to test `./minimal-examples/dbus-server/minimal-dbus-ws-proxy`
It asks the minimal dbus ws proxy application to connect to libwebsockets.org
over the mirror protocol. And it proxies back the ASCII packets used to
communicate the mirror sample drawing vectors over dbus to this test client
if you draw on the mirror example app at https://libwebsockets.org/testserver/
in a browser.
## build
Using libdbus requires additional non-default include paths setting, same as
is necessary for lws build described in ./lib/roles/dbus/README.md
CMake can guess one path and the library name usually, see the README above
for details of how to override for custom libdbus and cross build.
Fedora example:
```
$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
$ make
```
Ubuntu example:
```
$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
$ make
```
## usage
Commandline option|Meaning
---|---
-d <loglevel>|Debug verbosity in decimal, eg, -d15
This connects to the minimal-dbus-ws-proxy example running in another terminal.
```
$ ./lws-minimal-dbus-ws-proxy-testclient
[2018/10/05 14:17:16:6286] USER: LWS minimal DBUS ws proxy testclient
[2018/10/05 14:17:16:6538] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
[2018/10/05 14:17:16:6617] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy'
[2018/10/05 14:17:16:7189] NOTICE: create_dbus_client_conn: created OK
[2018/10/05 14:17:16:7429] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol
[2018/10/05 14:17:17:0387] USER: pending_call_notify: received 'Connecting'
[2018/10/05 14:17:18:7475] NOTICE: client_message_handler: (type 7) 'ws client connection established'
[2018/10/05 14:17:21:2028] NOTICE: client_message_handler: (type 6) 'd #000000 323 63 323 67;'
[2018/10/05 14:17:21:2197] NOTICE: client_message_handler: (type 6) 'd #000000 323 67 327 73;'
...
```

View File

@ -0,0 +1,447 @@
/*
* lws-minimal-dbus-ws-proxy-testclient
*
* Copyright (C) 2018 Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This acts as a test client over DBUS, opening a session with
* minimal-dbus-ws-proxy and sending and receiving data on the libwebsockets
* mirror demo page.
*/
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>
#include <libwebsockets/lws-dbus.h>
/*
* These are the various states our connection can be in, both with regards
* to the direct connection to the proxy, and the state of the onward ws
* connection the proxy opens at our request.
*/
enum lws_dbus_client_state {
LDCS_NOTHING, /* no connection yet */
LDCS_CONN, /* conn to proxy */
LDCS_CONN_WAITING_ONWARD, /* conn to proxy, awaiting proxied conn */
LDCS_CONN_ONWARD, /* conn to proxy and onward conn OK */
LDCS_CONN_CLOSED, /* conn to proxy but onward conn closed */
LDCS_CLOSED, /* connection to proxy is closed */
};
/*
* our expanded dbus context
*/
struct lws_dbus_ctx_wsproxy_client {
struct lws_dbus_ctx ctx;
enum lws_dbus_client_state state;
};
static struct lws_dbus_ctx_wsproxy_client *dbus_ctx;
static struct lws_context *context;
static int interrupted, autoexit_budget = -1, count_rx, count_tx;
#define THIS_INTERFACE "org.libwebsockets.wsclientproxy"
#define THIS_OBJECT "/org/libwebsockets/wsclientproxy"
#define THIS_BUSNAME "org.libwebsockets.wsclientproxy"
#define THIS_LISTEN_PATH "unix:abstract=org.libwebsockets.wsclientproxy"
static void
state_transition(struct lws_dbus_ctx_wsproxy_client *dcwc,
enum lws_dbus_client_state state)
{
lwsl_notice("%s: %p: from state %d -> %d\n", __func__,
dcwc,dcwc->state, state);
dcwc->state = state;
}
static DBusHandlerResult
filter(DBusConnection *conn, DBusMessage *message, void *data)
{
struct lws_dbus_ctx_wsproxy_client *dcwc =
(struct lws_dbus_ctx_wsproxy_client *)data;
const char *str;
if (!dbus_message_get_args(message, NULL,
DBUS_TYPE_STRING, &str,
DBUS_TYPE_INVALID))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
/* received ws data */
if (dbus_message_is_signal(message, THIS_INTERFACE, "Receive")) {
lwsl_user("%s: Received '%s'\n", __func__, str);
count_rx++;
}
/* proxy ws connection failed */
if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
!strcmp(str, "ws client connection error"))
state_transition(dcwc, LDCS_CONN_CLOSED);
/* proxy ws connection succeeded */
if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
!strcmp(str, "ws client connection established"))
state_transition(dcwc, LDCS_CONN_ONWARD);
/* proxy ws connection has closed */
if (dbus_message_is_signal(message, THIS_INTERFACE, "Status") &&
!strcmp(str, "ws client connection closed"))
state_transition(dcwc, LDCS_CONN_CLOSED);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static void
destroy_dbus_client_conn(struct lws_dbus_ctx_wsproxy_client **pdcwc)
{
struct lws_dbus_ctx_wsproxy_client *dcwc = *pdcwc;
if (!dcwc || !dcwc->ctx.conn)
return;
lwsl_notice("%s\n", __func__);
dbus_connection_remove_filter(dcwc->ctx.conn, filter, &dcwc->ctx);
dbus_connection_close(dcwc->ctx.conn);
dbus_connection_unref(dcwc->ctx.conn);
free(dcwc);
*pdcwc = NULL;
}
/*
* This callback is coming when lws has noticed the fd took a POLLHUP. The
* ctx has effectively gone out of scope before this, and the connection can
* be cleaned up and the ctx freed.
*/
static void
cb_closing(struct lws_dbus_ctx *ctx)
{
struct lws_dbus_ctx_wsproxy_client *dcwc =
(struct lws_dbus_ctx_wsproxy_client *)ctx;
lwsl_err("%s: closing\n", __func__);
if (dcwc == dbus_ctx)
dbus_ctx = NULL;
destroy_dbus_client_conn(&dcwc);
interrupted = 1;
}
static struct lws_dbus_ctx_wsproxy_client *
create_dbus_client_conn(struct lws_vhost *vh, int tsi, const char *ads)
{
struct lws_dbus_ctx_wsproxy_client *dcwc;
DBusError e;
dcwc = malloc(sizeof(*dcwc));
if (!dcwc)
return NULL;
memset(dcwc, 0, sizeof(*dcwc));
dcwc->state = LDCS_NOTHING;
dcwc->ctx.vh = vh;
dcwc->ctx.tsi = tsi;
dbus_error_init(&e);
lwsl_user("%s: connecting to '%s'\n", __func__, ads);
#if 1
/* connect to our daemon bus */
dcwc->ctx.conn = dbus_connection_open_private(ads, &e);
if (!dcwc->ctx.conn) {
lwsl_err("%s: Failed to connect: %s\n",
__func__, e.message);
goto fail;
}
#else
/* connect to the SYSTEM bus */
dcwc->ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e);
if (!dcwc->ctx.conn) {
lwsl_err("%s: Failed to get a session DBus connection: %s\n",
__func__, e.message);
goto fail;
}
#endif
dbus_connection_set_exit_on_disconnect(dcwc->ctx.conn, 0);
if (!dbus_connection_add_filter(dcwc->ctx.conn, filter,
&dcwc->ctx, NULL)) {
lwsl_err("%s: Failed to add filter\n", __func__);
goto fail;
}
/*
* This is the part that binds the connection to lws watcher and
* timeout handling provided by lws
*/
if (lws_dbus_connection_setup(&dcwc->ctx, dcwc->ctx.conn, cb_closing)) {
lwsl_err("%s: connection bind to lws failed\n", __func__);
goto fail;
}
state_transition(dcwc, LDCS_CONN);
lwsl_notice("%s: created OK\n", __func__);
return dcwc;
fail:
dbus_error_free(&e);
free(dcwc);
return NULL;
}
void sigint_handler(int sig)
{
interrupted = 1;
}
/*
* This gets called if we timed out waiting for the dbus server reply, or the
* reply arrived.
*/
static void
pending_call_notify(DBusPendingCall *pending, void *data)
{
const char *payload;
DBusMessage *msg;
if (!dbus_pending_call_get_completed(pending)) {
lwsl_err("%s: timed out waiting for reply\n", __func__);
goto bail;
}
msg = dbus_pending_call_steal_reply(pending);
if (!msg)
goto bail;
if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &payload,
DBUS_TYPE_INVALID)) {
goto bail1;
}
lwsl_user("%s: received '%s'\n", __func__, payload);
bail1:
dbus_message_unref(msg);
bail:
dbus_pending_call_unref(pending);
}
static int
remote_method_call(struct lws_dbus_ctx_wsproxy_client *dcwc)
{
const char *uri = "wss://libwebsockets.org/";
const char *subprotocol = "lws-mirror-protocol";
DBusMessage *msg;
int ret = 1;
msg = dbus_message_new_method_call(
/* dest */ THIS_BUSNAME,
/* object-path */ THIS_OBJECT,
/* interface */ THIS_INTERFACE,
/* method */ "Connect");
if (!msg)
return 1;
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &uri,
DBUS_TYPE_STRING, &subprotocol,
DBUS_TYPE_INVALID))
goto bail;
lwsl_user("%s: requesting proxy connection %s %s\n", __func__,
uri, subprotocol);
if (!dbus_connection_send_with_reply(dcwc->ctx.conn, msg, &dcwc->ctx.pc,
DBUS_TIMEOUT_USE_DEFAULT)) {
lwsl_err("%s: unable to send\n", __func__);
goto bail;
}
dbus_pending_call_set_notify(dcwc->ctx.pc, pending_call_notify,
&dcwc->ctx, NULL);
state_transition(dcwc, LDCS_CONN_WAITING_ONWARD);
ret = 0;
bail:
dbus_message_unref(msg);
return ret;
}
/*
* Stub lws protocol, just so we can get synchronous timers conveniently.
*
* Set up a 1Hz timer and if our connection state is suitable, use that
* to write mirror protocol drawing packets to the proxied ws connection
*/
static int
callback_just_timer(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
char payload[64];
const char *ws_pkt = payload;
DBusMessage *msg;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
case LWS_CALLBACK_USER:
lwsl_info("%s: LWS_CALLBACK_USER\n", __func__);
if (!dbus_ctx || dbus_ctx->state != LDCS_CONN_ONWARD)
goto again;
if (autoexit_budget > 0) {
if (!--autoexit_budget) {
lwsl_notice("reached autoexit budget\n");
interrupted = 1;
break;
}
}
msg = dbus_message_new_method_call(THIS_BUSNAME, THIS_OBJECT,
THIS_INTERFACE, "Send");
if (!msg)
break;
lws_snprintf(payload, sizeof(payload), "d #%06X %d %d %d %d;",
rand() & 0xffffff, rand() % 480, rand() % 300,
rand() % 480, rand() % 300);
if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &ws_pkt,
DBUS_TYPE_INVALID)) {
dbus_message_unref(msg);
break;
}
if (!dbus_connection_send_with_reply(dbus_ctx->ctx.conn, msg,
&dbus_ctx->ctx.pc,
DBUS_TIMEOUT_USE_DEFAULT)) {
lwsl_err("%s: unable to send\n", __func__);
dbus_message_unref(msg);
break;
}
dbus_message_unref(msg);
dbus_pending_call_set_notify(dbus_ctx->ctx.pc,
pending_call_notify,
&dbus_ctx->ctx, NULL);
count_tx++;
again:
lws_timed_callback_vh_protocol(lws_get_vhost(wsi),
lws_get_protocol(wsi),
LWS_CALLBACK_USER, 2);
break;
default:
break;
}
return 0;
}
static struct lws_protocols protocols[] = {
{ "_just_timer", callback_just_timer, 0, 10, 0, NULL, 0 },
{ }
};
int main(int argc, const char **argv)
{
struct lws_vhost *vh;
struct lws_context_creation_info info;
const char *p;
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
/* for LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
/* | LLL_DEBUG */ /* | LLL_THREAD */;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
if ((p = lws_cmdline_option(argc, argv, "-x")))
autoexit_budget = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS minimal DBUS ws proxy testclient\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
info.protocols = protocols;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
vh = lws_create_vhost(context, &info);
if (!vh)
goto bail;
dbus_ctx = create_dbus_client_conn(vh, 0, THIS_LISTEN_PATH);
if (!dbus_ctx)
goto bail1;
if (remote_method_call(dbus_ctx))
goto bail2;
/* lws event loop (default poll one) */
while (n >= 0 && !interrupted)
n = lws_service(context, 1000);
bail2:
destroy_dbus_client_conn(&dbus_ctx);
bail1:
/* this is required for valgrind-cleanliness */
dbus_shutdown();
lws_context_destroy(context);
lwsl_notice("Exiting cleanly, rx: %d, tx: %d\n", count_rx, count_tx);
return 0;
bail:
lwsl_err("%s: failed to start\n", __func__);
lws_context_destroy(context);
return 1;
}

View File

@ -1,3 +1,4 @@
|Example|Demonstrates|
---|---
minimal-dbus-server|Shows how to run a DBUS session server using lws event loop
minimal-dbus-ws-proxy|Control ws client connections via DBUS

View File

@ -0,0 +1,122 @@
cmake_minimum_required(VERSION 2.8)
include(CheckCSourceCompiles)
include(CheckLibraryExists)
set(SAMP lws-minimal-dbus-ws-proxy)
set(SRCS main.c)
if (NOT LWS_WITH_MINIMAL_EXAMPLES)
CHECK_LIBRARY_EXISTS(dbus-1 dbus_connection_set_watch_functions "" LWS_HAVE_LIBDBUS)
if (NOT LWS_HAVE_LIBDBUS)
message(FATAL_ERROR "Install dbus-devel, or libdbus-1-dev etc")
endif()
if (NOT LWS_DBUS_LIB)
set(LWS_DBUS_LIB "dbus-1")
endif()
if (NOT LWS_DBUS_INCLUDE1)
# look in fedora and debian / ubuntu place
if (EXISTS "/usr/include/dbus-1.0")
set(LWS_DBUS_INCLUDE1 "/usr/include/dbus-1.0")
else()
message(FATAL_ERROR "Set LWS_DBUS_INCLUDE1 to /usr/include/dbus-1.0 or wherever the main dbus includes are")
endif()
endif()
if (NOT LWS_DBUS_INCLUDE2)
# look in fedora... debian / ubuntu has the ARCH in the path...
if (EXISTS "/usr/lib64/dbus-1.0/include")
set(LWS_DBUS_INCLUDE2 "/usr/lib64/dbus-1.0/include")
else()
message(FATAL_ERROR "Set LWS_DBUS_INCLUDE2 to /usr/lib/ARCH-linux-gnu/dbus-1.0/include or wherever dbus-arch-deps.h is on your system")
endif()
endif()
set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES};${LWS_DBUS_INCLUDE1};${LWS_DBUS_INCLUDE2})
if (NOT LWS_DBUS_INCLUDE1 OR NOT LWS_DBUS_INCLUDE2)
message(FATAL_ERROR "To build with libdbus, LWS_DBUS_INCLUDE1/2 must be given. See lib/roles/dbus/README.md")
endif()
endif()
# 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_DBUS 1 requirements)
require_lws_config(LWS_ROLE_WS 1 requirements)
require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
if (requirements)
add_executable(${SAMP} ${SRCS})
include_directories("${LWS_DBUS_INCLUDE1}")
include_directories("${LWS_DBUS_INCLUDE2}")
list(APPEND LIB_LIST dbus-1)
if (websockets_shared)
target_link_libraries(${SAMP} websockets_shared)
add_dependencies(${SAMP} websockets_shared ${LWS_DBUS_LIB})
else()
target_link_libraries(${SAMP} websockets ${LWS_DBUS_LIB})
endif()
endif()

View File

@ -0,0 +1,115 @@
# lws minimal dbus ws proxy
This is an application which presents a DBUS server on one side, and a
websocket client proxy on the other.
You connect to it over DBUS, send a Connect method on its interface giving
a URI and a ws subprotocol name.
It replies with a string "Connecting" if all is well.
Connection progress (including close) is then provided using type 7 messages
sent back to the dbus client.
Payload from the ws connection is provided using type 6 messages sent back to
the dbus client.
## build
Using libdbus requires additional non-default include paths setting, same as
is necessary for lws build described in ./lib/roles/dbus/README.md
CMake can guess one path and the library name usually, see the README above
for details of how to override for custom libdbus and cross build.
Fedora example:
```
$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib64/dbus-1.0/include"
$ make
```
Ubuntu example:
```
$ cmake .. -DLWS_DBUS_INCLUDE2="/usr/lib/x86_64-linux-gnu/dbus-1.0/include"
$ make
```
## Configuration
The dbus-ws-proxy server tries to register its actual bus name with the SYSTEM
bus in DBUS. If it fails, eg because of insufficient permissions on the user,
then it continues without that and starts its own daemon normally.
The main dbus daemon must be told how to accept these registrations if that's
what you want. A config file is provided that tells dbus to allow the
well-known busname for this daemon to be registered, but only by root.
```
$ sudo cp org.libwebsockets.wsclientproxy.conf /etc/dbus-1/system.d
$ sudo systemctl restart dbus
```
## usage
Run the dbus-ws-proxy server, then start lws-minimal-dbus-ws-proxy-testclient in
another terminal.
This test app sends a random line drawing message to the mirror example on
https://libwebsockets.org/testserver every couple of seconds, and displays
any received messages (such as its own sends mirrored back, or anything
drawn in the canvas in a browser).
```
$ sudo ./lws-minimal-dbus-ws-proxy-testclient
[2018/10/07 10:05:29:2084] USER: LWS minimal DBUS ws proxy testclient
[2018/10/07 10:05:29:2345] NOTICE: Creating Vhost 'default' port 0, 1 protocols, IPv6 off
[2018/10/07 10:05:29:2424] USER: create_dbus_client_conn: connecting to 'unix:abstract=org.libwebsockets.wsclientproxy'
[2018/10/07 10:05:29:2997] NOTICE: state_transition: 0x5679720: from state 0 -> 1
[2018/10/07 10:05:29:2999] NOTICE: create_dbus_client_conn: created OK
[2018/10/07 10:05:29:3232] USER: remote_method_call: requesting proxy connection wss://libwebsockets.org/ lws-mirror-protocol
[2018/10/07 10:05:29:3450] NOTICE: state_transition: 0x5679720: from state 1 -> 2
[2018/10/07 10:05:29:5972] USER: pending_call_notify: received 'Connecting'
[2018/10/07 10:05:31:3387] NOTICE: state_transition: 0x5679720: from state 2 -> 3
[2018/10/07 10:05:33:6672] USER: filter: Received 'd #B0DC51 115 177 166 283;'
[2018/10/07 10:05:35:9723] USER: filter: Received 'd #E87CCD 9 192 106 235;'
[2018/10/07 10:05:38:2784] USER: filter: Received 'd #E2A9E3 379 290 427 62;'
[2018/10/07 10:05:39:5833] USER: filter: Received 'd #B127F8 52 126 60 226;'
[2018/10/07 10:05:41:8908] USER: filter: Received 'd #0E0F76 429 267 8 11;'
...
```
## ws proxy DBUS details
### Fixed details
Item|Value
---|---
Address|unix:abstract=org.libwebsockets.wsclientproxy
Interface|org.libwebsockets.wsclientproxy
Bus Name|org.libwebsockets.wsclientproxy
Object path|/org/libwebsockets/wsclientproxy
### Interface Methods
Method|Arguments|Returns
---|---|---
Connect|s: ws URI, s: ws subprotocol name|"Bad Uri", "Connecting" or "Failed"
Send|s: payload|Empty message if no problem, or error message
When Connecting, the actual connection happens asynchronously if the initial
connection attempt doesn't fail immediately. If it's continuing in the
background, the reply will have been "Connecting".
### Signals
Signal Name|Argument|Meaning
---|---|---
Receive|s: payload|Received data from the ws link
Status|s: status|See table below
Status String|Meaning
---|---
"ws client connection error"|The ws connection attempt ended with a fatal error
"ws client connection established"|The ws connection attempt succeeded
"ws client connection closed"|The ws connection has closed

View File

@ -0,0 +1,101 @@
/*
* lws-minimal-dbus-ws-proxy
*
* Copyright (C) 2018 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 session dbus server that uses the lws event loop,
* and allows proxying ws client connections via DBUS.
*/
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>
#include <libwebsockets/lws-dbus.h>
#define LWS_PLUGIN_STATIC
#include "protocol_lws_minimal_dbus_ws_proxy.c"
static int interrupted;
static struct lws_protocols protocols[] = {
LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY,
{ NULL, NULL, 0, 0 } /* terminator */
};
/*
* we pass the dbus address to connect to proxy with from outside the
* protocol plugin... eg if built as a plugin for lwsws, you would instead
* set this pvo in the lwsws JSON config.
*/
static const struct lws_protocol_vhost_options pvo_ads = {
NULL,
NULL,
"ads", /* pvo name */
(void *)"unix:abstract=org.libwebsockets.wsclientproxy" /* pvo value */
};
static const struct lws_protocol_vhost_options pvo = {
NULL, /* "next" pvo linked-list */
&pvo_ads, /* "child" pvo linked-list */
"lws-minimal-dbus-wsproxy", /* protocol name we belong to on this vhost */
"" /* ignored */
};
void sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, const char **argv)
{
static struct lws_context *context;
struct lws_context_creation_info info;
const char *p;
int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
/* for LLL_ verbosity above NOTICE to be built into lws,
* lws must have been configured and built with
* -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */
/* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */
/* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */
/* | LLL_DEBUG */ /* | LLL_THREAD */;
signal(SIGINT, sigint_handler);
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS DBUS ws client proxy\n");
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.port = CONTEXT_PORT_NO_LISTEN;
info.ws_ping_pong_interval = 30;
info.protocols = protocols;
info.pvo = &pvo;
context = lws_create_context(&info);
if (!context) {
lwsl_err("lws init failed\n");
return 1;
}
/* lws event loop (default poll one) */
while (n >= 0 && !interrupted)
n = lws_service(context, 1000);
lws_context_destroy(context);
lwsl_notice("Exiting cleanly\n");
return 0;
}

View File

@ -0,0 +1,14 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="org.libwebsockets.wsclientproxy"/>
<allow send_destination="org.libwebsockets.wsclientproxy"/>
</policy>
<policy context="default">
<deny own="org.libwebsockets.wsclientproxy"/>
<deny send_destination="org.libwebsockets.wsclientproxy"/>
</policy>
</busconfig>

View File

@ -0,0 +1,827 @@
/*
* ws protocol handler plugin for dbus ws proxy
*
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* This proxies outgoing ws client connections on DBUS. So a DBUS client can
* reach out and get remote WS payloads in both directions.
*
* DEVELOPER NOTE
*
* Two worlds, dbus and ws, collide in this file.
*
* There main thing keeping it sane is both worlds are running in the same
* thread and on the same event loop. Although things may happen completely
* asynchronously in both worlds, the logical reaction to those events are
* serialized in a single event loop doing one thing at a time.
*
* So while you are servicing an event in the ws world, you can be certain the
* logical state of any related dbus thing cannot change underneath you, until
* you return back to the event loop, and vice versa. So other-world objects
* can't be freed, other-world handles can't close etc while you are servicing
* in your world.
*
* Since all bets are off what happens next, and in which world, after you
* return back to the event loop though, an additional rule is needed: worlds
* must not allocate in objects owned by the other world. They must generate
* their own objects in their world and use those for allocations and state.
*
* For example in the dbus-world there is a struct lws_dbus_ctx_wsproxy with
* various state, but he is subject to deletion by events in dbus-world. If
* the ws-world stored things there, they are subject to going out of scope
* at the whim of the dbus connection without the ws world hearing about it and
* cleanly deallocaing them. So the ws world must keep his own pss that remains
* in scope until the ws link closes for allocations from ws-world.
*
* In this application there's a point of contact between the worlds, a ring
* buffer allocated in ws world when the ws connection is established, and
* deallocated when the ws connection is closed. The DBUS world needs to put
* things in this ringbuffer. But the way lws_ring works, when the message
* allocated in DBUS world is queued on the ringbuffer, the ringbuffer itself
* takes responsibility for deallocation. So there is no problem.
*/
#if !defined (LWS_PLUGIN_STATIC)
#define LWS_DLL
#define LWS_INTERNAL
#include <libwebsockets.h>
#include <libwebsockets/lws-dbus.h>
#endif
#include <string.h>
#include <assert.h>
#include <signal.h>
/*
* dbus accepted connections create these larger context structs that start
* with the lws dbus context
*/
struct vhd_dbus_proxy;
struct msg {
void *payload; /* is malloc'd */
size_t len;
char binary;
char first;
char final;
};
struct pss_dbus_proxy {
struct lws_ring *ring_out;
uint32_t ring_out_tail;
};
struct lws_dbus_ctx_wsproxy {
struct lws_dbus_ctx ctx;
struct lws *cwsi;
struct vhd_dbus_proxy *vhd;
struct pss_dbus_proxy *pss;
};
struct vhd_dbus_proxy {
struct lws_context *context;
struct lws_vhost *vhost;
/*
* Because the listener ctx is composed in the vhd, we can always get a
* pointer to the outer vhd from a pointer to ctx_listener inside.
*/
struct lws_dbus_ctx ctx_listener;
struct lws_dbus_ctx_wsproxy dctx;
const char *dbus_listen_ads;
};
#define THIS_INTERFACE "org.libwebsockets.wsclientproxy"
#define THIS_OBJECT "/org/libwebsockets/wsclientproxy"
#define THIS_BUSNAME "org.libwebsockets.wsclientproxy"
static const char *version = "0.1";
static const char *server_introspection_xml =
DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
"<node>\n"
" <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>\n"
" <method name='Introspect'>\n"
" <arg name='data' type='s' direction='out' />\n"
" </method>\n"
" </interface>\n"
" <interface name='" DBUS_INTERFACE_PROPERTIES "'>\n"
" <method name='Get'>\n"
" <arg name='interface' type='s' direction='in' />\n"
" <arg name='property' type='s' direction='in' />\n"
" <arg name='value' type='s' direction='out' />\n"
" </method>\n"
" <method name='GetAll'>\n"
" <arg name='interface' type='s' direction='in'/>\n"
" <arg name='properties' type='a{sv}' direction='out'/>\n"
" </method>\n"
" </interface>\n"
" <interface name='"THIS_INTERFACE"'>\n"
" <property name='Version' type='s' access='read' />\n"
" <method name='Connect' >\n"
" <arg name='url' type='s' direction='in' />\n"
" <arg name='subprotocol' type='s' direction='in' />\n"
" </method>\n"
" <method name='Send'>\n"
" <arg name='payload' type='s' direction='in' />\n"
" </method>\n"
" <signal name='Receive'>\n"
" </signal>"
" <signal name='Status'>\n"
" </signal>"
" </interface>\n"
"</node>\n";
static void
destroy_message(void *_msg)
{
struct msg *msg = _msg;
free(msg->payload);
msg->payload = NULL;
msg->len = 0;
}
/*
* DBUS WORLD
*/
static DBusHandlerResult
dmh_introspect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
{
dbus_message_append_args(*reply,
DBUS_TYPE_STRING, &server_introspection_xml,
DBUS_TYPE_INVALID);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
dmh_get(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
{
const char *interface, *property;
DBusError err;
dbus_error_init(&err);
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &interface,
DBUS_TYPE_STRING, &property,
DBUS_TYPE_INVALID)) {
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, err.name, err.message);
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_HANDLED;
}
if (strcmp(property, "Version")) /* Unknown property */
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
dbus_message_append_args(*reply, DBUS_TYPE_STRING, &version,
DBUS_TYPE_INVALID);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
dmh_getall(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
{
DBusMessageIter arr, di, iter, va;
const char *property = "Version";
dbus_message_iter_init_append(*reply, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &arr);
/* Append all properties name/value pairs */
dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, NULL, &di);
dbus_message_iter_append_basic(&di, DBUS_TYPE_STRING, &property);
dbus_message_iter_open_container(&di, DBUS_TYPE_VARIANT, "s", &va);
dbus_message_iter_append_basic(&va, DBUS_TYPE_STRING, &version);
dbus_message_iter_close_container(&di, &va);
dbus_message_iter_close_container(&arr, &di);
dbus_message_iter_close_container(&iter, &arr);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult
dmh_connect(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
{
struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
const char *prot = "", *ads = "", *path = "", *baduri = "Bad Uri",
*connecting = "Connecting", *failed = "Failed", **pp;
struct lws_client_connect_info i;
char host[128], uri_copy[512];
const char *uri, *subprotocol;
DBusError err;
int port = 0;
dbus_error_init(&err);
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &uri,
DBUS_TYPE_STRING, &subprotocol,
DBUS_TYPE_INVALID)) {
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, err.name, err.message);
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_HANDLED;
}
strncpy(uri_copy, uri, sizeof(uri_copy) - 1);
uri_copy[sizeof(uri_copy) - 1] = '\0';
if (lws_parse_uri(uri_copy, &prot, &ads, &port, &path)) {
pp = &baduri;
goto send_reply;
}
lws_snprintf(host, sizeof(host), "%s:%u", ads, port);
memset(&i, 0, sizeof(i));
assert(wspctx);
assert(wspctx->vhd);
i.context = wspctx->vhd->context;
i.port = port;
i.address = ads;
i.path = path;
i.host = host;
i.origin = host;
i.ssl_connection = !strcmp(prot, "https") || !strcmp(prot, "wss");
i.vhost = wspctx->ctx.vh;
i.protocol = subprotocol;
i.local_protocol_name = "lws-minimal-dbus-wsproxy";
i.pwsi = &wspctx->cwsi;
lwsl_user("%s: connecting to %s://%s:%d%s\n", __func__, prot,
i.address, i.port, i.path);
if (!lws_client_connect_via_info(&i)) {
lwsl_notice("%s: client connect failed\n", __func__);
pp = &failed;
goto send_reply;
}
lws_set_opaque_parent_data(wspctx->cwsi, wspctx);
lwsl_notice("%s: client connecting...\n", __func__);
pp = &connecting;
send_reply:
dbus_message_append_args(*reply, DBUS_TYPE_STRING, pp,
DBUS_TYPE_INVALID);
return DBUS_HANDLER_RESULT_HANDLED;
}
static int
issue_dbus_signal(struct lws *wsi, const char *signame, const char *string)
{
struct lws_dbus_ctx_wsproxy *wspctx =
lws_get_opaque_parent_data(wsi);
DBusMessage *m;
if (!wspctx)
return 1;
m = dbus_message_new_signal(THIS_OBJECT, THIS_INTERFACE, signame);
if (!m) {
lwsl_err("%s: new signal failed\n", __func__);
return 1;
}
dbus_message_append_args(m, DBUS_TYPE_STRING, &string,
DBUS_TYPE_INVALID);
if (!dbus_connection_send(wspctx->ctx.conn, m, NULL))
lwsl_err("%s: unable to send\n", __func__);
dbus_message_unref(m);
return 0;
}
static DBusHandlerResult
dmh_send(DBusConnection *c, DBusMessage *m, DBusMessage **reply, void *d)
{
struct lws_dbus_ctx_wsproxy *wspctx = (struct lws_dbus_ctx_wsproxy *)d;
const char *payload;
struct msg amsg;
DBusError err;
dbus_error_init(&err);
if (!wspctx->cwsi || !wspctx->pss) {
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, "Send Fail", "No ws conn");
return DBUS_HANDLER_RESULT_HANDLED;
}
if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &payload,
DBUS_TYPE_INVALID)) {
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, err.name, err.message);
dbus_error_free(&err);
return DBUS_HANDLER_RESULT_HANDLED;
}
/*
* we allocate on the ringbuffer in ws world, but responsibility for
* freeing it is understood by lws_ring.
*/
amsg.len = strlen(payload);
/* notice we over-allocate by LWS_PRE */
amsg.payload = malloc(LWS_PRE + amsg.len);
if (!amsg.payload) {
lwsl_user("OOM: dropping\n");
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, "Send Fail", "OOM");
return DBUS_HANDLER_RESULT_HANDLED;
}
amsg.binary = 0;
amsg.first = 1;
amsg.final = 1;
memcpy((char *)amsg.payload + LWS_PRE, payload, amsg.len);
if (!lws_ring_insert(wspctx->pss->ring_out, &amsg, 1)) {
destroy_message(&amsg);
lwsl_user("Ring Full!\n");
dbus_message_unref(*reply);
*reply = dbus_message_new_error(m, "Send Fail", "Ring full");
return DBUS_HANDLER_RESULT_HANDLED;
}
if (wspctx->cwsi)
lws_callback_on_writable(wspctx->cwsi);
return DBUS_HANDLER_RESULT_HANDLED;
}
struct lws_dbus_methods {
const char *inter;
const char *call;
lws_dbus_message_handler handler;
} meths[] = {
{ DBUS_INTERFACE_INTROSPECTABLE, "Introspect", dmh_introspect },
{ DBUS_INTERFACE_PROPERTIES, "Get", dmh_get },
{ DBUS_INTERFACE_PROPERTIES, "GetAll", dmh_getall },
{ THIS_INTERFACE, "Connect", dmh_connect },
{ THIS_INTERFACE, "Send", dmh_send },
};
static DBusHandlerResult
server_message_handler(DBusConnection *conn, DBusMessage *message, void *data)
{
struct lws_dbus_methods *mp = meths;
DBusMessage *reply = NULL;
DBusHandlerResult result;
size_t n;
assert(data);
lwsl_info("%s: Got D-Bus request: %s.%s on %s\n", __func__,
dbus_message_get_interface(message),
dbus_message_get_member(message),
dbus_message_get_path(message));
for (n = 0; n < LWS_ARRAY_SIZE(meths); n++) {
if (dbus_message_is_method_call(message, mp->inter, mp->call)) {
reply = dbus_message_new_method_return(message);
if (!reply)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
result = mp->handler(conn, message, &reply, data);
if (result == DBUS_HANDLER_RESULT_HANDLED &&
!dbus_connection_send(conn, reply, NULL))
result = DBUS_HANDLER_RESULT_NEED_MEMORY;
dbus_message_unref(reply);
return result;
}
mp++;
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
static const DBusObjectPathVTable vtable = {
.message_function = server_message_handler
};
static void
destroy_dbus_server_conn(struct lws_dbus_ctx_wsproxy *wsctx)
{
if (!wsctx->ctx.conn)
return;
lwsl_notice("%s\n", __func__);
dbus_connection_unregister_object_path(wsctx->ctx.conn, THIS_OBJECT);
lws_dll_remove(&wsctx->ctx.next);
dbus_connection_unref(wsctx->ctx.conn);
}
/*
* This is the client dbus side going away. We need to stop the associated
* client ws part and make sure it can't dereference us now we are gone.
*/
static void
cb_closing(struct lws_dbus_ctx *ctx)
{
struct lws_dbus_ctx_wsproxy *wspctx =
(struct lws_dbus_ctx_wsproxy *)ctx;
lwsl_err("%s: closing\n", __func__);
/*
* We have to take care that the associated proxy wsi knows our
* dbus ctx is going out of scope after we return from here.
*
* We do it by setting its pointer to our dbus ctx to NULL.
*/
if (wspctx->cwsi) {
lws_set_opaque_parent_data(wspctx->cwsi, NULL);
lws_set_timeout(wspctx->cwsi,
PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE,
LWS_TO_KILL_ASYNC);
}
destroy_dbus_server_conn(wspctx);
free(wspctx);
}
static void
new_conn(DBusServer *server, DBusConnection *conn, void *d)
{
struct lws_dbus_ctx_wsproxy *conn_wspctx, /* the new conn context */
/* the listener context */
*wspctx = (struct lws_dbus_ctx_wsproxy *)d;
struct vhd_dbus_proxy *vhd = lws_container_of(d,
struct vhd_dbus_proxy, ctx_listener);
assert(vhd->vhost == wspctx->ctx.vh);
lwsl_notice("%s\n", __func__);
conn_wspctx = malloc(sizeof(*conn_wspctx));
if (!conn_wspctx)
return;
memset(conn_wspctx, 0, sizeof(*conn_wspctx));
conn_wspctx->ctx.tsi = conn_wspctx->ctx.tsi;
conn_wspctx->ctx.vh = wspctx->ctx.vh;
conn_wspctx->ctx.conn = conn;
conn_wspctx->vhd = vhd; /* let accepted connections also know the vhd */
assert(conn_wspctx->vhd);
if (lws_dbus_connection_setup(&conn_wspctx->ctx, conn, cb_closing)) {
lwsl_err("%s: connection bind to lws failed\n", __func__);
goto bail;
}
if (!dbus_connection_register_object_path(conn, THIS_OBJECT, &vtable,
conn_wspctx)) {
lwsl_err("%s: Failed to register object path\n", __func__);
goto bail;
}
lws_dll_add_front(&conn_wspctx->ctx.next, &wspctx->ctx.next);
/* we take on responsibility for explicit close / unref with this... */
dbus_connection_ref(conn);
return;
bail:
free(conn_wspctx);
}
static int
create_dbus_listener(struct vhd_dbus_proxy *vhd, int tsi)
{
DBusError e;
dbus_error_init(&e);
#if 0
vhd->dctx.ctx.tsi = tsi;
vhd->dctx.ctx.vh = vhd->vhost;
vhd->dctx.ctx.next.prev = NULL;
vhd->dctx.ctx.next.next = NULL;
vhd->dctx.vhd = vhd;
vhd->dctx.cwsi = NULL;
/* connect to the SYSTEM bus */
vhd->dctx.ctx.conn = dbus_bus_get(DBUS_BUS_SYSTEM, &e);
if (!vhd->dctx.ctx.conn) {
lwsl_notice("%s: Failed to get a session DBus connection: '%s'"
", continuing with daemon listener only\n",
__func__, e.message);
dbus_error_free(&e);
dbus_error_init(&e);
goto daemon;
}
/*
* by default dbus will call exit() when this connection closes...
* we have to shut down other things cleanly, so disable that
*/
dbus_connection_set_exit_on_disconnect(vhd->dctx.ctx.conn, 0);
if (dbus_bus_request_name(vhd->dctx.ctx.conn, THIS_BUSNAME,
DBUS_NAME_FLAG_REPLACE_EXISTING, &e) !=
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
lwsl_notice("%s: Failed to request name on bus: '%s',"
" continuing with daemon listener only\n",
__func__, e.message);
dbus_connection_unref(vhd->dctx.ctx.conn);
vhd->dctx.ctx.conn = NULL;
dbus_error_free(&e);
dbus_error_init(&e);
goto daemon;
}
if (!dbus_connection_register_object_path(vhd->dctx.ctx.conn,
THIS_OBJECT, &vtable,
&vhd->dctx)) {
lwsl_err("%s: Failed to register object path\n", __func__);
goto fail;
}
/*
* This is the part that binds the connection to lws watcher and
* timeout handling provided by lws
*/
if (lws_dbus_connection_setup(&vhd->dctx.ctx, vhd->dctx.ctx.conn,
cb_closing)) {
lwsl_err("%s: connection bind to lws failed\n", __func__);
goto fail;
}
daemon:
#endif
vhd->ctx_listener.vh = vhd->vhost;
vhd->ctx_listener.tsi = tsi;
if (!lws_dbus_server_listen(&vhd->ctx_listener, vhd->dbus_listen_ads,
&e, new_conn)) {
lwsl_err("%s: failed\n", __func__);
dbus_error_free(&e);
return 1;
}
lwsl_notice("%s: created DBUS listener on %s\n", __func__,
vhd->dbus_listen_ads);
return 0;
#if 0
fail:
dbus_error_free(&e);
return 1;
#endif
}
static void
destroy_dbus_server_listener(struct vhd_dbus_proxy *vhd)
{
dbus_server_disconnect(vhd->ctx_listener.dbs);
lws_start_foreach_dll_safe(struct lws_dll *, rdt, nx,
vhd->ctx_listener.next.next) {
struct lws_dbus_ctx *r = lws_container_of(rdt,
struct lws_dbus_ctx, next);
dbus_connection_close(r->conn);
dbus_connection_unref(r->conn);
free(r);
} lws_end_foreach_dll_safe(rdt, nx);
if (vhd->dctx.ctx.conn)
dbus_connection_unref(vhd->dctx.ctx.conn);
dbus_server_unref(vhd->ctx_listener.dbs);
}
/*
* WS WORLD
*/
static int
callback_minimal_dbus_wsproxy(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct pss_dbus_proxy *pss = (struct pss_dbus_proxy *)user;
struct vhd_dbus_proxy *vhd = (struct vhd_dbus_proxy *)
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
lws_get_protocol(wsi));
struct lws_dbus_ctx_wsproxy *wspctx;
const struct msg *pmsg;
int flags, m;
switch (reason) {
case LWS_CALLBACK_PROTOCOL_INIT:
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
lws_get_protocol(wsi), sizeof(*vhd));
if (!vhd)
return -1;
vhd->context = lws_get_context(wsi);
vhd->vhost = lws_get_vhost(wsi);
if (lws_pvo_get_str(in, "ads", &vhd->dbus_listen_ads)) {
lwsl_err("%s: pvo 'ads' must be set\n", __func__);
return -1;
}
if (create_dbus_listener(vhd, 0)) {
lwsl_err("%s: create_dbus_listener failed\n", __func__);
return -1;
}
break;
case LWS_CALLBACK_PROTOCOL_DESTROY:
destroy_dbus_server_listener(vhd);
/* this is required for valgrind-cleanliness */
dbus_shutdown();
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
lwsl_user("LWS_CALLBACK_CLIENT_ESTABLISHED\n");
/*
* create the send ringbuffer now the ws connection is
* established.
*/
wspctx = lws_get_opaque_parent_data(wsi);
if (!wspctx)
break;
wspctx->pss = pss;
pss->ring_out_tail = 0;
pss->ring_out = lws_ring_create(sizeof(struct msg), 8,
destroy_message);
if (!pss->ring_out) {
lwsl_err("OOM\n");
return -1;
}
issue_dbus_signal(wsi, "Status",
"ws client connection established");
break;
case LWS_CALLBACK_CLIENT_WRITEABLE:
lwsl_user("LWS_CALLBACK_CLIENT_WRITEABLE:\n");
pmsg = lws_ring_get_element(pss->ring_out, &pss->ring_out_tail);
if (!pmsg) {
lwsl_user(" (nothing in ring)\n");
break;
}
flags = lws_write_ws_flags(
pmsg->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
pmsg->first, pmsg->final);
/* notice we allowed for LWS_PRE in the payload already */
m = lws_write(wsi, pmsg->payload + LWS_PRE, pmsg->len, flags);
if (m < (int)pmsg->len) {
lwsl_err("ERROR %d writing to ws socket\n", m);
return -1;
}
lwsl_user(" wrote %d: flags: 0x%x first: %d final %d\n",
m, flags, pmsg->first, pmsg->final);
lws_ring_consume_single_tail(pss->ring_out,
&pss->ring_out_tail, 1);
/* more to do for us? */
if (lws_ring_get_element(pss->ring_out, &pss->ring_out_tail))
/* come back as soon as we can write more */
lws_callback_on_writable(wsi);
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE: %4d "
"(rpp %5d, first %d, last %d, bin %d)\n",
(int)len, (int)lws_remaining_packet_payload(wsi),
lws_is_first_fragment(wsi),
lws_is_final_fragment(wsi),
lws_frame_is_binary(wsi));
{
char strbuf[256];
int l = len;
if (l > (int)sizeof(strbuf) - 1)
l = sizeof(strbuf) - 1;
memcpy(strbuf, in, l);
strbuf[l] = '\0';
issue_dbus_signal(wsi, "Receive", strbuf);
}
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
in ? (char *)in : "(null)");
issue_dbus_signal(wsi, "Status", "ws client connection error");
break;
case LWS_CALLBACK_CLIENT_CLOSED:
lwsl_err("LWS_CALLBACK_CLIENT_CLOSED ()\n");
issue_dbus_signal(wsi, "Status", "ws client connection closed");
/* destroy any ringbuffer and pending messages */
lws_ring_destroy(pss->ring_out);
wspctx = lws_get_opaque_parent_data(wsi);
if (!wspctx)
break;
/*
* the wspctx cannot refer to its child wsi any longer, it is
* about to go out of scope.
*/
wspctx->cwsi = NULL;
wspctx->pss = NULL;
break;
default:
break;
}
return 0;
}
#define LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY \
{ \
"lws-minimal-dbus-wsproxy", \
callback_minimal_dbus_wsproxy, \
sizeof(struct pss_dbus_proxy), \
1024, \
0, NULL, 0 \
}
#if !defined (LWS_PLUGIN_STATIC)
/* boilerplate needed if we are built as a dynamic plugin */
static const struct lws_protocols protocols[] = {
LWS_PLUGIN_PROTOCOL_MINIMAL_DBUS_WSPROXY
};
LWS_EXTERN LWS_VISIBLE int
init_protocol_minimal_dbus_wsproxy(struct lws_context *context,
struct lws_plugin_capability *c)
{
if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
c->api_magic);
return 1;
}
c->protocols = protocols;
c->count_protocols = LWS_ARRAY_SIZE(protocols);
c->extensions = NULL;
c->count_extensions = 0;
return 0;
}
LWS_EXTERN LWS_VISIBLE int
destroy_protocol_minimal_dbus_wsproxy(struct lws_context *context)
{
return 0;
}
#endif

34
scripts/test-dbus-proxy.sh Executable file
View File

@ -0,0 +1,34 @@
#!/bin/sh
echo "Starting $0"
bin/lws-minimal-dbus-ws-proxy 2> /tmp/dbuss&
echo " server starting"
sleep 1s
PID_PROX=$!
echo " client starting"
bin/lws-minimal-dbus-ws-proxy-testclient -x 10 2> /tmp/dbusc
R=$?
kill -2 $PID_PROX
if [ $R -ne 0 ] ; then
echo "$0 FAILED"
cat /tmp/dbuss
cat /tmp/dbusc
exit 1
fi
if [ -z "`cat /tmp/dbusc | grep 'rx: 9, tx: 9'`" ] ; then
echo "$0 FAILED"
cat /tmp/dbuss
cat /tmp/dbusc
exit 1
fi
echo "$0 PASSED"
exit 0

View File

@ -15,6 +15,7 @@ else
cmake --build . &&
sudo make install &&
../minimal-examples/selftests.sh &&
../scripts/test-dbus-proxy.sh &&
../scripts/h2spec.sh &&
../scripts/attack.sh &&
../scripts/h2load.sh &&

View File

@ -8,7 +8,7 @@ then
if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
then
sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev
sudo apt-get install -y -qq realpath libjemalloc1 libev4 libuv-dev libdbus-1-dev
sudo apt-get remove python-six
sudo pip install six>=1.9
sudo pip install Twisted==16.0.0
@ -56,6 +56,12 @@ fi
if [ "$TRAVIS_OS_NAME" == "osx" ];
then
if [ "$LWS_METHOD" == "lwsws" -o "$LWS_METHOD" == "lwsws2" ];
then
brew update;
brew install dbus;
fi
if [ "$LWS_METHOD" == "libev" ];
then
brew update;