mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
Merge remote-tracking branch 'origin/topic/awelzel/4136-cluster-websocket-support'
* origin/topic/awelzel/4136-cluster-websocket-support: ci/opensuse-tumpleweed: Bust cache ci/macos/prepare: Install python@3 explicitly cluster/websocket: Implement WebSocket server cluster/websocket: Add IXWebsocket submodule ci/alpine: Install openssl package for testing ci: Install websockets from pip for all distros auxil/libunistd: Bump for ssize_t typedef auxil/broker: Bump to latest master version cluster/zeromq: Catch log_push.send() exception cluster/zeromq: Catch exceptions as const zmq::error_t& cluster/zeromq: No assert on inproc handling cluster/zeromq: Support configuring IO threads for proxy thread cluster/zeromq: Move variable lookups from DoInit() to DoInitPostScript() cluster/zeromq: Handle EINTR at shutdown cluster/zeromq: Queue one message at a time cluster/Backend: Queue a single message only cluster/zeromq: Adapt for OnLoopProcess changes cluster/ThreadedBackend: Switch to OnLoopProcess cluster/OnLoop: Introduce helper template class serializer/broker: Expose to_broker_event() and to_zeek_event()
This commit is contained in:
commit
bb58148c64
97 changed files with 3300 additions and 170 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -79,3 +79,6 @@
|
|||
[submodule "src/cluster/backend/zeromq/auxil/cppzmq"]
|
||||
path = src/cluster/backend/zeromq/auxil/cppzmq
|
||||
url = https://github.com/zeromq/cppzmq
|
||||
[submodule "src/cluster/websocket/auxil/IXWebSocket"]
|
||||
path = src/cluster/websocket/auxil/IXWebSocket
|
||||
url = https://github.com/zeek/IXWebSocket.git
|
||||
|
|
78
CHANGES
78
CHANGES
|
@ -1,3 +1,81 @@
|
|||
7.2.0-dev.310 | 2025-03-11 10:50:49 +0100
|
||||
|
||||
* ci/opensuse-tumpleweed: Bust cache (Arne Welzel, Corelight)
|
||||
|
||||
Got a build failure because the old container images had python3-devel
|
||||
for Python3.11 or something older, but then prepare.sh would install
|
||||
Python 3.13 and Zeek's configure failing due to trying to find the
|
||||
devel headers from python313-devel which wasn't installed by prepare.sh
|
||||
|
||||
* ci/macos/prepare: Install python@3 explicitly (Arne Welzel, Corelight)
|
||||
|
||||
It seems Homebrew's Python 3.12 doesn't install default symlinks or
|
||||
python3 symlinks[1]. I believe this results in prepare.sh using the
|
||||
system's Python rather than Homebrew's. Install python@3 explicitly
|
||||
to put the symlinks in place.
|
||||
|
||||
[1] https://stackoverflow.com/a/77655631
|
||||
|
||||
* cluster/websocket: Implement WebSocket server (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/websocket: Add IXWebsocket submodule (Arne Welzel, Corelight)
|
||||
|
||||
* ci/alpine: Install openssl package for testing (Arne Welzel, Corelight)
|
||||
|
||||
* ci: Install websockets from pip for all distros (Arne Welzel, Corelight)
|
||||
|
||||
The cluster/websocket tests were developed against websockets 14.2,
|
||||
but Ubuntu and Alpine ship too old versions. Switch to installing
|
||||
the latest version from pip instead, so we don't need to bother making
|
||||
tests compatible with very old Python packages shipped by distributions.
|
||||
|
||||
* auxil/libunistd: Bump for ssize_t typedef (Arne Welzel, Corelight)
|
||||
|
||||
* auxil/broker: Bump to latest master version (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/zeromq: Catch log_push.send() exception (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/zeromq: Catch exceptions as const zmq::error_t& (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/zeromq: No assert on inproc handling (Arne Welzel, Corelight)
|
||||
|
||||
This might happen if we didn't succeed in completely sending a multipart
|
||||
message and stop early.
|
||||
|
||||
* cluster/zeromq: Support configuring IO threads for proxy thread (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/zeromq: Move variable lookups from DoInit() to DoInitPostScript() (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/zeromq: Handle EINTR at shutdown (Arne Welzel, Corelight)
|
||||
|
||||
Read ::signal_val and early exit a DoPublish() in case termination
|
||||
happened while blocked in inproc.send()
|
||||
|
||||
* cluster/zeromq: Queue one message at a time (Arne Welzel, Corelight)
|
||||
|
||||
Queueing multiple messages can easily overload the IO loop without
|
||||
creating any backpressure.
|
||||
|
||||
* cluster/Backend: Queue a single message only (Arne Welzel, Corelight)
|
||||
|
||||
The ZeroMQ backend would accumulate multiple messages and enqueue them
|
||||
all at once. However, as this could potentially result in huge batches
|
||||
of events being queued into the event loop at once, switch to a one
|
||||
message at a time model. If there's too many messages queued already,
|
||||
OnLoop::QueueForProcessing() will block the ZeroMQ thread until
|
||||
there's room available again.
|
||||
|
||||
* cluster/zeromq: Adapt for OnLoopProcess changes (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/ThreadedBackend: Switch to OnLoopProcess (Arne Welzel, Corelight)
|
||||
|
||||
* cluster/OnLoop: Introduce helper template class (Arne Welzel, Corelight)
|
||||
|
||||
* serializer/broker: Expose to_broker_event() and to_zeek_event() (Arne Welzel, Corelight)
|
||||
|
||||
This is useful for reuse by WebSocket clients that use
|
||||
the JSON v1 encoding.
|
||||
|
||||
7.2.0-dev.288 | 2025-03-10 19:16:57 +0100
|
||||
|
||||
* btest/javascript: Add file_sniff() and file_state_remove() test (Arne Welzel, Corelight)
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
7.2.0-dev.288
|
||||
7.2.0-dev.310
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b40f3fd3e93e906fe4f134a4d3279eacd6dbdb45
|
||||
Subproject commit c99696a69e5ced0a91bf7c19098d391a57f279ce
|
|
@ -1 +1 @@
|
|||
Subproject commit b38e9c8ebff08959a712a5663ba25e0624a3af00
|
||||
Subproject commit d2bfec929540c1fec5d1d45f0bcee3cff1eb7fa5
|
|
@ -2,7 +2,7 @@ FROM alpine:latest
|
|||
|
||||
# A version field to invalidate Cirrus's build cache when needed, as suggested in
|
||||
# https://github.com/cirruslabs/cirrus-ci-docs/issues/544#issuecomment-566066822
|
||||
ENV DOCKERFILE_VERSION 20241024
|
||||
ENV DOCKERFILE_VERSION 20250306
|
||||
|
||||
RUN apk add --no-cache \
|
||||
bash \
|
||||
|
@ -23,13 +23,13 @@ RUN apk add --no-cache \
|
|||
linux-headers \
|
||||
make \
|
||||
openssh-client \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
procps \
|
||||
py3-pip \
|
||||
py3-websockets \
|
||||
python3 \
|
||||
python3-dev \
|
||||
swig \
|
||||
zlib-dev
|
||||
|
||||
RUN pip3 install --break-system-packages junit2html
|
||||
RUN pip3 install --break-system-packages websockets junit2html
|
||||
|
|
|
@ -28,7 +28,6 @@ RUN apt-get update && apt-get -y install \
|
|||
python3 \
|
||||
python3-dev \
|
||||
python3-pip\
|
||||
python3-websockets \
|
||||
sqlite3 \
|
||||
swig \
|
||||
wget \
|
||||
|
@ -39,4 +38,4 @@ RUN apt-get update && apt-get -y install \
|
|||
|
||||
# Debian bookworm really doesn't like using pip to install system wide stuff, but
|
||||
# doesn't seem there's a python3-junit2html package, so not sure what we'd break.
|
||||
RUN pip3 install --break-system-packages junit2html
|
||||
RUN pip3 install --break-system-packages websockets junit2html
|
||||
|
|
|
@ -7,8 +7,9 @@ set -x
|
|||
|
||||
brew update
|
||||
brew upgrade cmake
|
||||
brew install cppzmq openssl@3 swig bison flex ccache libmaxminddb dnsmasq
|
||||
brew install cppzmq openssl@3 python@3 swig bison flex ccache libmaxminddb dnsmasq
|
||||
|
||||
which python3
|
||||
python3 --version
|
||||
|
||||
# Upgrade pip so we have the --break-system-packages option.
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --user --break-system-packages websockets
|
||||
|
|
|
@ -2,7 +2,7 @@ FROM opensuse/tumbleweed
|
|||
|
||||
# A version field to invalidate Cirrus's build cache when needed, as suggested in
|
||||
# https://github.com/cirruslabs/cirrus-ci-docs/issues/544#issuecomment-566066822
|
||||
ENV DOCKERFILE_VERSION 20241024
|
||||
ENV DOCKERFILE_VERSION 20250311
|
||||
|
||||
# Remove the repo-openh264 repository, it caused intermittent issues
|
||||
# and we should not be needing any packages from it.
|
||||
|
@ -32,7 +32,6 @@ RUN zypper refresh \
|
|||
python3 \
|
||||
python3-devel \
|
||||
python3-pip \
|
||||
python3-websockets \
|
||||
swig \
|
||||
tar \
|
||||
util-linux \
|
||||
|
@ -40,4 +39,4 @@ RUN zypper refresh \
|
|||
zlib-devel \
|
||||
&& rm -rf /var/cache/zypp
|
||||
|
||||
RUN pip3 install --break-system-packages junit2html
|
||||
RUN pip3 install --break-system-packages websockets junit2html
|
||||
|
|
|
@ -31,7 +31,6 @@ RUN apt-get update && apt-get -y install \
|
|||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-websockets \
|
||||
ruby \
|
||||
sqlite3 \
|
||||
swig \
|
||||
|
@ -43,7 +42,7 @@ RUN apt-get update && apt-get -y install \
|
|||
&& apt autoclean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip3 install --break-system-packages junit2html
|
||||
RUN pip3 install --break-system-packages websockets junit2html
|
||||
RUN gem install coveralls-lcov
|
||||
|
||||
# Download a newer pre-built ccache version that recognizes -fprofile-update=atomic
|
||||
|
|
|
@ -31,7 +31,6 @@ RUN apt-get update && apt-get -y install \
|
|||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-websockets \
|
||||
ruby \
|
||||
sqlite3 \
|
||||
swig \
|
||||
|
@ -43,5 +42,5 @@ RUN apt-get update && apt-get -y install \
|
|||
&& apt autoclean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN pip3 install --break-system-packages junit2html
|
||||
RUN pip3 install --break-system-packages websockets junit2html
|
||||
RUN gem install coveralls-lcov
|
||||
|
|
|
@ -327,11 +327,62 @@ export {
|
|||
## The arguments for the event.
|
||||
args: vector of any;
|
||||
};
|
||||
|
||||
## The TLS options for a WebSocket server.
|
||||
##
|
||||
## If cert_file and key_file are set, TLS is enabled. If both
|
||||
## are unset, TLS is disabled. Any other combination is an error.
|
||||
type WebSocketTLSOptions: record {
|
||||
## The cert file to use.
|
||||
cert_file: string &optional;
|
||||
## The key file to use.
|
||||
key_file: string &optional;
|
||||
## Expect peers to send client certificates.
|
||||
enable_peer_verification: bool &default=F;
|
||||
## The CA certificate or CA bundle used for peer verification.
|
||||
## Empty will use the implementations's default when
|
||||
## ``enable_peer_verification`` is T.
|
||||
ca_file: string &default="";
|
||||
## The ciphers to use. Empty will use the implementation's defaults.
|
||||
ciphers: string &default="";
|
||||
};
|
||||
|
||||
## WebSocket server options to pass to :zeek:see:`Cluster::listen_websocket`.
|
||||
type WebSocketServerOptions: record {
|
||||
## The host address to listen on.
|
||||
listen_host: string;
|
||||
## The port the WebSocket server is supposed to listen on.
|
||||
listen_port: port;
|
||||
## The TLS options used for this WebSocket server. By default,
|
||||
## TLS is disabled. See also :zeek:see:`Cluster::WebSocketTLSOptions`.
|
||||
tls_options: WebSocketTLSOptions &default=WebSocketTLSOptions();
|
||||
};
|
||||
|
||||
## Start listening on a WebSocket address.
|
||||
##
|
||||
## options: The server :zeek:see:`Cluster::WebSocketServerOptions` to use.
|
||||
##
|
||||
## Returns: T on success, else F.
|
||||
global listen_websocket: function(options: WebSocketServerOptions): bool;
|
||||
|
||||
## Network information of an endpoint.
|
||||
type NetworkInfo: record {
|
||||
## The IP address or hostname where the endpoint listens.
|
||||
address: string;
|
||||
## The port where the endpoint is bound to.
|
||||
bound_port: port;
|
||||
};
|
||||
|
||||
## Information about a WebSocket endpoint.
|
||||
type EndpointInfo: record {
|
||||
id: string;
|
||||
network: NetworkInfo;
|
||||
};
|
||||
}
|
||||
|
||||
# Needs declaration of Cluster::Event type.
|
||||
@load base/bif/cluster.bif
|
||||
|
||||
@load base/bif/plugins/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
|
||||
# Track active nodes per type.
|
||||
global active_node_ids: table[NodeType] of set[string];
|
||||
|
@ -597,3 +648,22 @@ function unsubscribe(topic: string): bool
|
|||
{
|
||||
return Cluster::__unsubscribe(topic);
|
||||
}
|
||||
|
||||
function listen_websocket(options: WebSocketServerOptions): bool
|
||||
{
|
||||
return Cluster::__listen_websocket(options);
|
||||
}
|
||||
|
||||
event websocket_client_added(endpoint: EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
local msg = fmt("WebSocket client '%s' (%s:%d) subscribed to %s",
|
||||
endpoint$id, endpoint$network$address, endpoint$network$bound_port, subscriptions);
|
||||
Cluster::log(msg);
|
||||
}
|
||||
|
||||
event websocket_client_lost(endpoint: EndpointInfo)
|
||||
{
|
||||
local msg = fmt("WebSocket client '%s' (%s:%d) gone",
|
||||
endpoint$id, endpoint$network$address, endpoint$network$bound_port);
|
||||
Cluster::log(msg);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,14 @@ export {
|
|||
## By default, this is set to ``T`` on the manager and ``F`` elsewhere.
|
||||
const run_proxy_thread: bool = F &redef;
|
||||
|
||||
## How many IO threads to configure for the ZeroMQ context that
|
||||
## acts as a central broker.
|
||||
|
||||
## See ZeroMQ's `ZMQ_IO_THREADS documentation <http://api.zeromq.org/4-2:zmq-ctx-set#toc4>`_
|
||||
## and the `I/O threads <https://zguide.zeromq.org/docs/chapter2/#I-O-Threads>`
|
||||
## section in the ZeroMQ guide for details.
|
||||
const proxy_io_threads = 2 &redef;
|
||||
|
||||
## XSUB listen endpoint for the central broker.
|
||||
##
|
||||
## This setting is used for the XSUB socket of the central broker started
|
||||
|
@ -134,12 +142,12 @@ export {
|
|||
## Do not silently drop messages if high-water-mark is reached.
|
||||
##
|
||||
## Whether to configure ``ZMQ_XPUB_NODROP`` on the XPUB socket
|
||||
## to detect when sending a message fails due to reaching
|
||||
## the high-water-mark.
|
||||
## connecting to the proxy to detect when sending a message fails
|
||||
## due to reaching the high-water-mark.
|
||||
##
|
||||
## See ZeroMQ's `ZMQ_XPUB_NODROP documentation <http://api.zeromq.org/4-2:zmq-setsockopt#toc61>`_
|
||||
## for more details.
|
||||
const xpub_nodrop: bool = T &redef;
|
||||
const connect_xpub_nodrop: bool = T &redef;
|
||||
|
||||
## Do not silently drop messages if high-water-mark is reached.
|
||||
##
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
#include "zeek/Func.h"
|
||||
#include "zeek/Reporter.h"
|
||||
#include "zeek/Type.h"
|
||||
#include "zeek/cluster/OnLoop.h"
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
#include "zeek/iosource/Manager.h"
|
||||
#include "zeek/logging/Manager.h"
|
||||
#include "zeek/util.h"
|
||||
|
||||
|
@ -158,64 +158,37 @@ bool ThreadedBackend::ProcessBackendMessage(int tag, detail::byte_buffer_span pa
|
|||
return DoProcessBackendMessage(tag, payload);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool register_io_source(zeek::iosource::IOSource* src, int fd, bool dont_count) {
|
||||
constexpr bool manage_lifetime = true;
|
||||
|
||||
zeek::iosource_mgr->Register(src, dont_count, manage_lifetime);
|
||||
|
||||
if ( ! zeek::iosource_mgr->RegisterFd(fd, src) ) {
|
||||
zeek::reporter->Error("Failed to register messages_flare with IO manager");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
ThreadedBackend::ThreadedBackend(std::unique_ptr<EventSerializer> es, std::unique_ptr<LogSerializer> ls,
|
||||
std::unique_ptr<detail::EventHandlingStrategy> ehs)
|
||||
: Backend(std::move(es), std::move(ls), std::move(ehs)) {
|
||||
onloop = new zeek::detail::OnLoopProcess<ThreadedBackend, QueueMessage>(this, "ThreadedBackend");
|
||||
onloop->Register(true); // Register as don't count first
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool ThreadedBackend::DoInit() {
|
||||
// Register as counting during DoInit() to avoid Zeek from shutting down.
|
||||
return register_io_source(this, messages_flare.FD(), false);
|
||||
// Have the backend count so Zeek does not terminate.
|
||||
onloop->Register(/*dont_count=*/false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ThreadedBackend::DoInitPostScript() {
|
||||
// Register non-counting after parsing scripts.
|
||||
register_io_source(this, messages_flare.FD(), true);
|
||||
void ThreadedBackend::DoTerminate() {
|
||||
if ( onloop ) {
|
||||
onloop->Close();
|
||||
onloop = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedBackend::QueueForProcessing(QueueMessages&& qmessages) {
|
||||
bool fire = false;
|
||||
|
||||
// Enqueue under lock.
|
||||
{
|
||||
std::scoped_lock lock(messages_mtx);
|
||||
fire = messages.empty();
|
||||
|
||||
if ( messages.empty() ) {
|
||||
messages = std::move(qmessages);
|
||||
}
|
||||
else {
|
||||
messages.reserve(messages.size() + qmessages.size());
|
||||
for ( auto& qmsg : qmessages )
|
||||
messages.emplace_back(std::move(qmsg));
|
||||
}
|
||||
}
|
||||
|
||||
if ( fire )
|
||||
messages_flare.Fire();
|
||||
void ThreadedBackend::QueueForProcessing(QueueMessage&& qmessages) {
|
||||
if ( onloop )
|
||||
onloop->QueueForProcessing(std::move(qmessages));
|
||||
}
|
||||
|
||||
void ThreadedBackend::Process() {
|
||||
QueueMessages to_process;
|
||||
{
|
||||
std::scoped_lock lock(messages_mtx);
|
||||
to_process = std::move(messages);
|
||||
messages_flare.Extinguish();
|
||||
messages.clear();
|
||||
}
|
||||
if ( onloop )
|
||||
onloop->Process();
|
||||
}
|
||||
|
||||
for ( const auto& msg : to_process ) {
|
||||
void ThreadedBackend::Process(QueueMessage&& msg) {
|
||||
// sonarlint wants to use std::visit. not sure...
|
||||
if ( auto* emsg = std::get_if<EventMessage>(&msg) ) {
|
||||
ProcessEventMessage(emsg->topic, emsg->format, emsg->payload_span());
|
||||
|
@ -229,5 +202,4 @@ void ThreadedBackend::Process() {
|
|||
else {
|
||||
zeek::reporter->FatalError("Unimplemented QueueMessage %zu", msg.index());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,17 +5,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include "zeek/EventHandler.h"
|
||||
#include "zeek/Flare.h"
|
||||
#include "zeek/IntrusivePtr.h"
|
||||
#include "zeek/Span.h"
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
#include "zeek/iosource/IOSource.h"
|
||||
#include "zeek/logging/Types.h"
|
||||
|
||||
namespace zeek {
|
||||
|
@ -28,6 +25,11 @@ class Val;
|
|||
using ValPtr = IntrusivePtr<Val>;
|
||||
using ArgsSpan = Span<const ValPtr>;
|
||||
|
||||
namespace detail {
|
||||
template<class Proc, class Work>
|
||||
class OnLoopProcess;
|
||||
}
|
||||
|
||||
namespace cluster {
|
||||
|
||||
namespace detail {
|
||||
|
@ -459,17 +461,19 @@ struct BackendMessage {
|
|||
};
|
||||
|
||||
using QueueMessage = std::variant<EventMessage, LogMessage, BackendMessage>;
|
||||
using QueueMessages = std::vector<QueueMessage>;
|
||||
|
||||
/**
|
||||
* Support for backends that use background threads or invoke
|
||||
* callbacks on non-main threads.
|
||||
*/
|
||||
class ThreadedBackend : public Backend, public zeek::iosource::IOSource {
|
||||
public:
|
||||
using Backend::Backend;
|
||||
|
||||
class ThreadedBackend : public Backend {
|
||||
protected:
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ThreadedBackend(std::unique_ptr<EventSerializer> es, std::unique_ptr<LogSerializer> ls,
|
||||
std::unique_ptr<detail::EventHandlingStrategy> ehs);
|
||||
|
||||
/**
|
||||
* To be used by implementations to enqueue messages for processing on the IO loop.
|
||||
*
|
||||
|
@ -477,22 +481,13 @@ protected:
|
|||
*
|
||||
* @param messages Messages to be enqueued.
|
||||
*/
|
||||
void QueueForProcessing(QueueMessages&& messages);
|
||||
|
||||
void Process() override;
|
||||
|
||||
double GetNextTimeout() override { return -1; }
|
||||
void QueueForProcessing(QueueMessage&& messages);
|
||||
|
||||
/**
|
||||
* The DoInitPostScript() implementation of ThreadedBackend
|
||||
* registers itself as a non-counting IO source.
|
||||
*
|
||||
* Classes deriving from ThreadedBackend and providing their
|
||||
* own DoInitPostScript() method should invoke the ThreadedBackend's
|
||||
* implementation to register themselves as a non-counting
|
||||
* IO source with the IO loop.
|
||||
* Delegate to onloop->Process() to trigger processing
|
||||
* of outstanding queued messages explicitly, if any.
|
||||
*/
|
||||
void DoInitPostScript() override;
|
||||
void Process();
|
||||
|
||||
/**
|
||||
* The default DoInit() implementation of ThreadedBackend
|
||||
|
@ -506,6 +501,8 @@ protected:
|
|||
*/
|
||||
bool DoInit() override;
|
||||
|
||||
void DoTerminate() override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Process a backend specific message queued as BackendMessage.
|
||||
|
@ -518,10 +515,16 @@ private:
|
|||
*/
|
||||
virtual bool DoProcessBackendMessage(int tag, detail::byte_buffer_span payload) { return false; };
|
||||
|
||||
/**
|
||||
* Hook method for OnLooProcess.
|
||||
*/
|
||||
void Process(QueueMessage&& messages);
|
||||
|
||||
// Allow access to Process(QueueMessages)
|
||||
friend class zeek::detail::OnLoopProcess<ThreadedBackend, QueueMessage>;
|
||||
|
||||
// Members used for communication with the main thread.
|
||||
std::mutex messages_mtx;
|
||||
std::vector<QueueMessage> messages;
|
||||
zeek::detail::Flare messages_flare;
|
||||
zeek::detail::OnLoopProcess<ThreadedBackend, QueueMessage>* onloop = nullptr;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ zeek::RecordValPtr make_event(zeek::ArgsSpan args) {
|
|||
}
|
||||
|
||||
const auto func = zeek::FuncValPtr{zeek::NewRef{}, maybe_func_val->AsFuncVal()};
|
||||
auto checked_args = cluster::detail::check_args(func, args.subspan(1));
|
||||
auto checked_args = zeek::cluster::detail::check_args(func, args.subspan(1));
|
||||
if ( ! checked_args )
|
||||
return rec;
|
||||
|
||||
|
@ -109,7 +109,7 @@ zeek::ValPtr publish_event(const zeek::ValPtr& topic, zeek::ArgsSpan args) {
|
|||
}
|
||||
else if ( args[0]->GetType()->Tag() == zeek::TYPE_RECORD ) {
|
||||
if ( args[0]->GetType() == cluster_event_type ) { // Handling Cluster::Event record type
|
||||
auto ev = to_cluster_event(cast_intrusive<zeek::RecordVal>(args[0]));
|
||||
auto ev = to_cluster_event(zeek::cast_intrusive<zeek::RecordVal>(args[0]));
|
||||
if ( ! ev )
|
||||
return zeek::val_mgr->False();
|
||||
|
||||
|
@ -148,4 +148,32 @@ bool is_cluster_pool(const zeek::Val* pool) {
|
|||
|
||||
return pool->GetType() == pool_type;
|
||||
}
|
||||
|
||||
zeek::RecordValPtr make_endpoint_info(const std::string& id, const std::string& address, uint32_t port,
|
||||
TransportProto proto) {
|
||||
static const auto ep_info_type = zeek::id::find_type<zeek::RecordType>("Cluster::EndpointInfo");
|
||||
static const auto net_info_type = zeek::id::find_type<zeek::RecordType>("Cluster::NetworkInfo");
|
||||
|
||||
auto net_rec = zeek::make_intrusive<zeek::RecordVal>(net_info_type);
|
||||
net_rec->Assign(0, address);
|
||||
net_rec->Assign(1, zeek::val_mgr->Port(port, proto));
|
||||
|
||||
auto ep_rec = zeek::make_intrusive<zeek::RecordVal>(ep_info_type);
|
||||
ep_rec->Assign(0, id);
|
||||
ep_rec->Assign(1, net_rec);
|
||||
|
||||
return ep_rec;
|
||||
}
|
||||
|
||||
zeek::VectorValPtr make_string_vec(zeek::Span<const std::string> strings) {
|
||||
static const auto string_vec_type = zeek::id::find_type<zeek::VectorType>("string_vec");
|
||||
auto vec = zeek::make_intrusive<zeek::VectorVal>(string_vec_type);
|
||||
vec->Reserve(strings.size());
|
||||
|
||||
for ( const auto& s : strings )
|
||||
vec->Append(zeek::make_intrusive<zeek::StringVal>(s));
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
} // namespace zeek::cluster::detail::bif
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "zeek/IntrusivePtr.h"
|
||||
#include "zeek/Span.h"
|
||||
#include "zeek/net_util.h"
|
||||
|
||||
// Helpers for cluster.bif
|
||||
|
||||
|
@ -15,6 +16,8 @@ class Frame;
|
|||
|
||||
class RecordVal;
|
||||
using RecordValPtr = IntrusivePtr<RecordVal>;
|
||||
class VectorVal;
|
||||
using VectorValPtr = IntrusivePtr<VectorVal>;
|
||||
|
||||
class Val;
|
||||
using ValPtr = IntrusivePtr<Val>;
|
||||
|
@ -46,6 +49,28 @@ zeek::ValPtr publish_event(const zeek::ValPtr& topic, zeek::ArgsSpan args);
|
|||
|
||||
bool is_cluster_pool(const zeek::Val* pool);
|
||||
|
||||
/**
|
||||
* Create a Cluster::EndpointInfo record with a nested Cluster::NetworkInfo record.
|
||||
*
|
||||
* @param id The string to use as id in the record.
|
||||
* @param address The string to use as address in the network record.
|
||||
* @param port The port to use in the network record.
|
||||
* @param proto The proto for the given port value.
|
||||
*
|
||||
* @returns A record value of type Cluster::EndpointInfo filled with the provided info.
|
||||
*/
|
||||
zeek::RecordValPtr make_endpoint_info(const std::string& id, const std::string& address, uint32_t port,
|
||||
TransportProto proto);
|
||||
|
||||
/**
|
||||
* Helper to go from a vector or array of std::strings to a zeek::VectorVal.
|
||||
*
|
||||
* @param strings The std::string instances.
|
||||
*
|
||||
* @return a VectorVal instance of type string_vec filled with strings.
|
||||
*/
|
||||
zeek::VectorValPtr make_string_vec(zeek::Span<const std::string> strings);
|
||||
|
||||
} // namespace cluster::detail::bif
|
||||
|
||||
} // namespace zeek
|
||||
|
|
|
@ -13,3 +13,4 @@ zeek_add_subdir_library(
|
|||
|
||||
add_subdirectory(backend)
|
||||
add_subdirectory(serializer)
|
||||
add_subdirectory(websocket)
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
#include "zeek/cluster/Manager.h"
|
||||
|
||||
#include "zeek/Func.h"
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
#include "zeek/cluster/websocket/WebSocket.h"
|
||||
#include "zeek/util.h"
|
||||
|
||||
using namespace zeek::cluster;
|
||||
|
||||
|
@ -11,6 +14,15 @@ Manager::Manager()
|
|||
event_serializers(plugin::ComponentManager<EventSerializerComponent>("Cluster", "EventSerializerTag")),
|
||||
log_serializers(plugin::ComponentManager<LogSerializerComponent>("Cluster", "LogSerializerTag")) {}
|
||||
|
||||
// Force destructor definition into compilation unit to avoid needing the
|
||||
// full websocket::Server declaration in cluster/Manager.h.
|
||||
Manager::~Manager() = default;
|
||||
|
||||
void Manager::Terminate() {
|
||||
for ( const auto& [_, entry] : websocket_servers )
|
||||
entry.server->Terminate();
|
||||
}
|
||||
|
||||
std::unique_ptr<Backend> Manager::InstantiateBackend(
|
||||
const zeek::EnumValPtr& tag, std::unique_ptr<EventSerializer> event_serializer,
|
||||
std::unique_ptr<LogSerializer> log_serializer,
|
||||
|
@ -30,3 +42,25 @@ std::unique_ptr<LogSerializer> Manager::InstantiateLogSerializer(const zeek::Enu
|
|||
const LogSerializerComponent* c = LogSerializers().Lookup(tag);
|
||||
return c ? c->Factory()() : nullptr;
|
||||
}
|
||||
|
||||
bool Manager::ListenWebSocket(const websocket::detail::ServerOptions& options) {
|
||||
WebSocketServerKey key{options.host, options.port};
|
||||
|
||||
if ( websocket_servers.count(key) != 0 ) {
|
||||
const auto& entry = websocket_servers[key];
|
||||
if ( entry.options == options )
|
||||
return true;
|
||||
|
||||
zeek::emit_builtin_error(zeek::util::fmt("Already listening on %s:%d", options.host.c_str(), options.port));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto server =
|
||||
websocket::detail::StartServer(std::make_unique<websocket::detail::WebSocketEventDispatcher>(), options);
|
||||
|
||||
if ( ! server )
|
||||
return false;
|
||||
|
||||
websocket_servers.insert({key, WebSocketServerEntry{options, std::move(server)}});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "zeek/cluster/Component.h"
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
#include "zeek/cluster/websocket/WebSocket.h"
|
||||
#include "zeek/plugin/ComponentManager.h"
|
||||
|
||||
namespace zeek::cluster {
|
||||
|
@ -19,6 +21,15 @@ namespace zeek::cluster {
|
|||
class Manager {
|
||||
public:
|
||||
Manager();
|
||||
~Manager();
|
||||
|
||||
/**
|
||||
* Terminate the cluster manager.
|
||||
*
|
||||
* This shuts down any WebSocket servers that were started
|
||||
* at termination time.
|
||||
*/
|
||||
void Terminate();
|
||||
|
||||
/**
|
||||
* Instantiate a cluster backend with the given enum value and
|
||||
|
@ -69,10 +80,26 @@ public:
|
|||
*/
|
||||
plugin::ComponentManager<LogSerializerComponent>& LogSerializers() { return log_serializers; };
|
||||
|
||||
/**
|
||||
* Start a WebSocket server for the given address and port pair.
|
||||
*
|
||||
* @param options The options to use for the WebSocket server.
|
||||
*
|
||||
* @return True on success, else false.
|
||||
*/
|
||||
bool ListenWebSocket(const websocket::detail::ServerOptions& options);
|
||||
|
||||
private:
|
||||
plugin::ComponentManager<BackendComponent> backends;
|
||||
plugin::ComponentManager<EventSerializerComponent> event_serializers;
|
||||
plugin::ComponentManager<LogSerializerComponent> log_serializers;
|
||||
|
||||
using WebSocketServerKey = std::pair<std::string, uint16_t>;
|
||||
struct WebSocketServerEntry {
|
||||
websocket::detail::ServerOptions options;
|
||||
std::unique_ptr<websocket::detail::WebSocketServer> server;
|
||||
};
|
||||
std::map<WebSocketServerKey, WebSocketServerEntry> websocket_servers;
|
||||
};
|
||||
|
||||
// This manager instance only exists for plugins to register components,
|
||||
|
|
186
src/cluster/OnLoop.h
Normal file
186
src/cluster/OnLoop.h
Normal file
|
@ -0,0 +1,186 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "zeek/Flare.h"
|
||||
#include "zeek/Reporter.h"
|
||||
#include "zeek/iosource/IOSource.h"
|
||||
#include "zeek/iosource/Manager.h"
|
||||
|
||||
namespace zeek::detail {
|
||||
/**
|
||||
* Template class allowing work items to be queued by threads and processed
|
||||
* in Zeek's main thread.
|
||||
*
|
||||
* This is similar to MsgThread::SendOut(), but doesn't require usage of BasicThread
|
||||
* or MsgThread instances. Some libraries spawn their own threads or invoke callbacks
|
||||
* from arbitrary threads. OnLoopProcess::QueueForProcessing() can be used to transfer
|
||||
* work from such callbacks onto Zeek's main thread.
|
||||
*
|
||||
* There's currently no explicit way to transfer a result back. If this is needed,
|
||||
* have the queueing thread block on a semaphore or condition variable and update
|
||||
* it from Process().
|
||||
*
|
||||
* Note that QueueForProcessing() puts the queueing thread to sleep if there's
|
||||
* too many items in the queue.
|
||||
*/
|
||||
template<class Proc, class Work>
|
||||
class OnLoopProcess : public zeek::iosource::IOSource {
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param proc The instance processing.
|
||||
* @param tag The tag to use as the IOSource's tag.
|
||||
*/
|
||||
OnLoopProcess(Proc* proc, std::string tag, size_t max_queue_size = 10,
|
||||
std::chrono::microseconds block_duration = std::chrono::microseconds(100),
|
||||
std::thread::id main_thread_id = std::this_thread::get_id())
|
||||
: max_queue_size(max_queue_size),
|
||||
block_duration(block_duration),
|
||||
proc(proc),
|
||||
tag(std::move(tag)),
|
||||
main_thread_id(main_thread_id) {}
|
||||
|
||||
/**
|
||||
* Register this instance with the IO loop.
|
||||
*
|
||||
* The IO loop will manage the lifetime of this
|
||||
* IO source instance.
|
||||
*
|
||||
* @param dont_count If false, prevents Zeek from terminating as long as the IO source is open.
|
||||
*/
|
||||
void Register(bool dont_count = true) {
|
||||
zeek::iosource_mgr->Register(this, dont_count, /*manage_lifetime=*/true);
|
||||
|
||||
if ( ! zeek::iosource_mgr->RegisterFd(flare.FD(), this) )
|
||||
zeek::reporter->InternalError("Failed to register IO source FD %d for OnLoopProcess %s", flare.FD(),
|
||||
tag.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the IO source.
|
||||
*/
|
||||
void Close() {
|
||||
zeek::iosource_mgr->UnregisterFd(flare.FD(), this);
|
||||
|
||||
{
|
||||
// Close under lock to guarantee visibility for
|
||||
// any pending queuers QueueForProcessing() calls.
|
||||
std::scoped_lock lock(mtx);
|
||||
SetClosed(true);
|
||||
|
||||
// Don't attempt to Process anymore.
|
||||
proc = nullptr;
|
||||
}
|
||||
|
||||
// Wait for any active queuers to vanish, should be quick.
|
||||
while ( queuers > 0 )
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IOSource::Process()
|
||||
*
|
||||
* Runs in Zeek's main thread, invoked by the IO loop.
|
||||
*/
|
||||
void Process() override {
|
||||
std::list<Work> to_process;
|
||||
{
|
||||
std::scoped_lock lock(mtx);
|
||||
to_process.splice(to_process.end(), queue);
|
||||
flare.Extinguish();
|
||||
}
|
||||
|
||||
// We've been closed, so proc will most likely
|
||||
// be invalid at this point and we'll discard
|
||||
// whatever was left to do.
|
||||
if ( ! IsOpen() )
|
||||
return;
|
||||
|
||||
for ( auto& work : to_process )
|
||||
proc->Process(std::move(work));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IOSource::Tag()
|
||||
*/
|
||||
const char* Tag() override { return tag.c_str(); }
|
||||
|
||||
/**
|
||||
* Implements IOSource::GetNextTimeout()
|
||||
*/
|
||||
double GetNextTimeout() override { return -1; };
|
||||
|
||||
/**
|
||||
* Queue the given Work item to be processed on Zeek's main thread.
|
||||
*
|
||||
* If there's too many items in the queue, this method sleeps using
|
||||
* std::this_thread::sleep() for the *block_duration* passed to the
|
||||
* constructor.
|
||||
*
|
||||
* Calling this method from the main thread will result in an abort().
|
||||
*/
|
||||
void QueueForProcessing(Work&& work) {
|
||||
++queuers;
|
||||
std::list<Work> to_queue{std::move(work)};
|
||||
|
||||
if ( std::this_thread::get_id() == main_thread_id ) {
|
||||
fprintf(stderr, "OnLoopProcess::QueueForProcessing() called by main thread!");
|
||||
abort();
|
||||
}
|
||||
|
||||
bool fire = false;
|
||||
size_t qs = 0;
|
||||
|
||||
while ( ! to_queue.empty() ) {
|
||||
{
|
||||
std::scoped_lock lock(mtx);
|
||||
|
||||
if ( ! IsOpen() ) {
|
||||
// IO Source is being removed.
|
||||
fire = false;
|
||||
break;
|
||||
}
|
||||
|
||||
qs = queue.size();
|
||||
if ( qs < max_queue_size ) {
|
||||
queue.splice(queue.end(), to_queue);
|
||||
fire = fire || qs == 0;
|
||||
assert(to_queue.empty());
|
||||
assert(! queue.empty());
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! to_queue.empty() ) {
|
||||
std::this_thread::sleep_for(block_duration);
|
||||
fire = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( fire )
|
||||
flare.Fire();
|
||||
|
||||
--queuers;
|
||||
}
|
||||
|
||||
private:
|
||||
zeek::detail::Flare flare;
|
||||
std::mutex mtx;
|
||||
std::list<Work> queue;
|
||||
size_t max_queue_size;
|
||||
std::chrono::microseconds block_duration;
|
||||
Proc* proc;
|
||||
std::string tag;
|
||||
std::atomic<int> queuers = 0;
|
||||
std::thread::id main_thread_id;
|
||||
};
|
||||
|
||||
|
||||
} // namespace zeek::detail
|
|
@ -36,6 +36,8 @@ void thread_fun(ProxyThread::Args* args) {
|
|||
} // namespace
|
||||
|
||||
bool ProxyThread::Start() {
|
||||
ctx.set(zmq::ctxopt::io_threads, io_threads);
|
||||
|
||||
zmq::socket_t xpub(ctx, zmq::socket_type::xpub);
|
||||
zmq::socket_t xsub(ctx, zmq::socket_type::xsub);
|
||||
|
||||
|
|
|
@ -21,8 +21,11 @@ public:
|
|||
* @param xsub_endpoint the XSUB socket address to listen on.
|
||||
* @param xpub_nodrop the xpub_nodrop option to use on the XPUB socket.
|
||||
*/
|
||||
ProxyThread(std::string xpub_endpoint, std::string xsub_endpoint, int xpub_nodrop)
|
||||
: xpub_endpoint(std::move(xpub_endpoint)), xsub_endpoint(std::move(xsub_endpoint)), xpub_nodrop(xpub_nodrop) {}
|
||||
ProxyThread(std::string xpub_endpoint, std::string xsub_endpoint, int xpub_nodrop, int io_threads)
|
||||
: xpub_endpoint(std::move(xpub_endpoint)),
|
||||
xsub_endpoint(std::move(xsub_endpoint)),
|
||||
xpub_nodrop(xpub_nodrop),
|
||||
io_threads(io_threads) {}
|
||||
|
||||
|
||||
~ProxyThread() { Shutdown(); }
|
||||
|
@ -52,5 +55,6 @@ private:
|
|||
std::string xpub_endpoint;
|
||||
std::string xsub_endpoint;
|
||||
int xpub_nodrop = 1;
|
||||
int io_threads = 2;
|
||||
};
|
||||
} // namespace zeek::cluster::zeromq
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "zeek/cluster/backend/zeromq/ZeroMQ-Proxy.h"
|
||||
#include "zeek/util.h"
|
||||
|
||||
extern int signal_val;
|
||||
|
||||
namespace zeek {
|
||||
|
||||
namespace plugin::Zeek_Cluster_Backend_ZeroMQ {
|
||||
|
@ -77,8 +79,6 @@ ZeroMQBackend::~ZeroMQBackend() {
|
|||
}
|
||||
|
||||
void ZeroMQBackend::DoInitPostScript() {
|
||||
ThreadedBackend::DoInitPostScript();
|
||||
|
||||
listen_xpub_endpoint =
|
||||
zeek::id::find_val<zeek::StringVal>("Cluster::Backend::ZeroMQ::listen_xpub_endpoint")->ToStdString();
|
||||
listen_xsub_endpoint =
|
||||
|
@ -89,17 +89,23 @@ void ZeroMQBackend::DoInitPostScript() {
|
|||
zeek::id::find_val<zeek::StringVal>("Cluster::Backend::ZeroMQ::connect_xpub_endpoint")->ToStdString();
|
||||
connect_xsub_endpoint =
|
||||
zeek::id::find_val<zeek::StringVal>("Cluster::Backend::ZeroMQ::connect_xsub_endpoint")->ToStdString();
|
||||
connect_xpub_nodrop =
|
||||
zeek::id::find_val<zeek::BoolVal>("Cluster::Backend::ZeroMQ::connect_xpub_nodrop")->AsBool() ? 1 : 0;
|
||||
listen_log_endpoint =
|
||||
zeek::id::find_val<zeek::StringVal>("Cluster::Backend::ZeroMQ::listen_log_endpoint")->ToStdString();
|
||||
|
||||
linger_ms = static_cast<int>(zeek::id::find_val<zeek::IntVal>("Cluster::Backend::ZeroMQ::linger_ms")->AsInt());
|
||||
poll_max_messages = zeek::id::find_val<zeek::CountVal>("Cluster::Backend::ZeroMQ::poll_max_messages")->Get();
|
||||
debug_flags = zeek::id::find_val<zeek::CountVal>("Cluster::Backend::ZeroMQ::debug_flags")->Get();
|
||||
proxy_io_threads =
|
||||
static_cast<int>(zeek::id::find_val<zeek::CountVal>("Cluster::Backend::ZeroMQ::proxy_io_threads")->Get());
|
||||
|
||||
event_unsubscription = zeek::event_registry->Register("Cluster::Backend::ZeroMQ::unsubscription");
|
||||
event_subscription = zeek::event_registry->Register("Cluster::Backend::ZeroMQ::subscription");
|
||||
}
|
||||
|
||||
|
||||
void ZeroMQBackend::DoTerminate() {
|
||||
ThreadedBackend::DoTerminate();
|
||||
ZEROMQ_DEBUG("Shutting down ctx");
|
||||
ctx.shutdown();
|
||||
ZEROMQ_DEBUG("Joining self_thread");
|
||||
|
@ -131,16 +137,13 @@ bool ZeroMQBackend::DoInit() {
|
|||
log_pull = zmq::socket_t(ctx, zmq::socket_type::pull);
|
||||
child_inproc = zmq::socket_t(ctx, zmq::socket_type::pair);
|
||||
|
||||
auto linger_ms = static_cast<int>(zeek::id::find_val<zeek::IntVal>("Cluster::Backend::ZeroMQ::linger_ms")->AsInt());
|
||||
int xpub_nodrop = zeek::id::find_val<zeek::BoolVal>("Cluster::Backend::ZeroMQ::xpub_nodrop")->AsBool() ? 1 : 0;
|
||||
|
||||
xpub.set(zmq::sockopt::linger, linger_ms);
|
||||
xpub.set(zmq::sockopt::xpub_nodrop, xpub_nodrop);
|
||||
|
||||
// Enable XPUB_VERBOSE unconditional to enforce nodes receiving
|
||||
// notifications about any new subscriptions, even if they have
|
||||
// seen them before. This is needed to for the subscribe callback
|
||||
// functionality to work reliably.
|
||||
xpub.set(zmq::sockopt::xpub_nodrop, connect_xpub_nodrop);
|
||||
xpub.set(zmq::sockopt::xpub_verbose, 1);
|
||||
|
||||
try {
|
||||
|
@ -239,7 +242,8 @@ bool ZeroMQBackend::DoInit() {
|
|||
}
|
||||
|
||||
bool ZeroMQBackend::SpawnZmqProxyThread() {
|
||||
proxy_thread = std::make_unique<ProxyThread>(listen_xpub_endpoint, listen_xsub_endpoint, listen_xpub_nodrop);
|
||||
proxy_thread =
|
||||
std::make_unique<ProxyThread>(listen_xpub_endpoint, listen_xsub_endpoint, listen_xpub_nodrop, proxy_io_threads);
|
||||
return proxy_thread->Start();
|
||||
}
|
||||
|
||||
|
@ -268,7 +272,21 @@ bool ZeroMQBackend::DoPublishEvent(const std::string& topic, const std::string&
|
|||
// This should never fail, it will instead block
|
||||
// when HWM is reached. I guess we need to see if
|
||||
// and how this can happen :-/
|
||||
try {
|
||||
main_inproc.send(parts[i], flags);
|
||||
} catch ( const zmq::error_t& err ) {
|
||||
// If send() was interrupted and Zeek caught an interrupt or term signal,
|
||||
// fail the publish as we're about to shutdown. There's nothing the user
|
||||
// can do, but it indicates an overload situation as send() was blocking.
|
||||
if ( err.num() == EINTR && (signal_val == SIGINT || signal_val == SIGTERM) ) {
|
||||
zeek::reporter->Error("Failed publish() using ZeroMQ backend at shutdown: %s (signal_val=%d)",
|
||||
err.what(), signal_val);
|
||||
return false;
|
||||
}
|
||||
|
||||
zeek::reporter->Error("Unexpected ZeroMQ::DoPublishEvent() error: %s", err.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -281,7 +299,7 @@ bool ZeroMQBackend::DoSubscribe(const std::string& topic_prefix, SubscribeCallba
|
|||
// This is the XSUB API instead of setsockopt(ZMQ_SUBSCRIBE).
|
||||
std::string msg = "\x01" + topic_prefix;
|
||||
main_inproc.send(zmq::const_buffer(msg.data(), msg.size()));
|
||||
} catch ( zmq::error_t& err ) {
|
||||
} catch ( const zmq::error_t& err ) {
|
||||
zeek::reporter->Error("Failed to subscribe to topic %s: %s", topic_prefix.c_str(), err.what());
|
||||
if ( cb )
|
||||
cb(topic_prefix, {CallbackStatus::Error, err.what()});
|
||||
|
@ -303,7 +321,7 @@ bool ZeroMQBackend::DoUnsubscribe(const std::string& topic_prefix) {
|
|||
// This is the XSUB API instead of setsockopt(ZMQ_SUBSCRIBE).
|
||||
std::string msg = '\0' + topic_prefix;
|
||||
main_inproc.send(zmq::const_buffer(msg.data(), msg.size()));
|
||||
} catch ( zmq::error_t& err ) {
|
||||
} catch ( const zmq::error_t& err ) {
|
||||
zeek::reporter->Error("Failed to unsubscribe from topic %s: %s", topic_prefix.c_str(), err.what());
|
||||
return false;
|
||||
}
|
||||
|
@ -329,13 +347,19 @@ bool ZeroMQBackend::DoPublishLogWrites(const logging::detail::LogWriteHeader& he
|
|||
zmq::const_buffer{buf.data(), buf.size()},
|
||||
};
|
||||
|
||||
zmq::send_result_t result;
|
||||
for ( size_t i = 0; i < parts.size(); i++ ) {
|
||||
zmq::send_flags flags = zmq::send_flags::dontwait;
|
||||
if ( i < parts.size() - 1 )
|
||||
flags = flags | zmq::send_flags::sndmore;
|
||||
|
||||
zmq::send_result_t result;
|
||||
try {
|
||||
result = log_push.send(parts[i], flags);
|
||||
} catch ( const zmq::error_t& err ) {
|
||||
zeek::reporter->Error("Failed to send log write part %zu: %s", i, err.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! result ) {
|
||||
// XXX: Not exactly clear what we should do if we reach HWM.
|
||||
// we could block and hope a logger comes along that empties
|
||||
|
@ -368,9 +392,6 @@ void ZeroMQBackend::Run() {
|
|||
using MultipartMessage = std::vector<zmq::message_t>;
|
||||
|
||||
auto HandleLogMessages = [this](const std::vector<MultipartMessage>& msgs) {
|
||||
QueueMessages qmsgs;
|
||||
qmsgs.reserve(msgs.size());
|
||||
|
||||
for ( const auto& msg : msgs ) {
|
||||
// sender, format, type, payload
|
||||
if ( msg.size() != 4 ) {
|
||||
|
@ -379,22 +400,21 @@ void ZeroMQBackend::Run() {
|
|||
}
|
||||
|
||||
detail::byte_buffer payload{msg[3].data<std::byte>(), msg[3].data<std::byte>() + msg[3].size()};
|
||||
qmsgs.emplace_back(LogMessage{.format = std::string(msg[2].data<const char>(), msg[2].size()),
|
||||
.payload = std::move(payload)});
|
||||
}
|
||||
LogMessage lm{.format = std::string(msg[2].data<const char>(), msg[2].size()),
|
||||
.payload = std::move(payload)};
|
||||
|
||||
QueueForProcessing(std::move(qmsgs));
|
||||
QueueForProcessing(std::move(lm));
|
||||
}
|
||||
};
|
||||
|
||||
auto HandleInprocMessages = [this](std::vector<MultipartMessage>& msgs) {
|
||||
// Forward messages from the inprocess bridge to XSUB for subscription
|
||||
// subscription handling (1 part) or XPUB for publishing (4 parts).
|
||||
for ( auto& msg : msgs ) {
|
||||
assert(msg.size() == 1 || msg.size() == 4);
|
||||
if ( msg.size() == 1 ) {
|
||||
xsub.send(msg[0], zmq::send_flags::none);
|
||||
}
|
||||
else {
|
||||
else if ( msg.size() == 4 ) {
|
||||
for ( auto& part : msg ) {
|
||||
zmq::send_flags flags = zmq::send_flags::dontwait;
|
||||
if ( part.more() )
|
||||
|
@ -432,13 +452,13 @@ void ZeroMQBackend::Run() {
|
|||
} while ( ! result );
|
||||
}
|
||||
}
|
||||
else {
|
||||
ZEROMQ_THREAD_PRINTF("inproc: error: expected 1 or 4 parts, have %zu!\n", msg.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto HandleXPubMessages = [this](const std::vector<MultipartMessage>& msgs) {
|
||||
QueueMessages qmsgs;
|
||||
qmsgs.reserve(msgs.size());
|
||||
|
||||
for ( const auto& msg : msgs ) {
|
||||
if ( msg.size() != 1 ) {
|
||||
ZEROMQ_THREAD_PRINTF("xpub: error: expected 1 part, have %zu!\n", msg.size());
|
||||
|
@ -464,17 +484,12 @@ void ZeroMQBackend::Run() {
|
|||
continue;
|
||||
}
|
||||
|
||||
qmsgs.emplace_back(std::move(qm));
|
||||
QueueForProcessing(std::move(qm));
|
||||
}
|
||||
}
|
||||
|
||||
QueueForProcessing(std::move(qmsgs));
|
||||
};
|
||||
|
||||
auto HandleXSubMessages = [this](const std::vector<MultipartMessage>& msgs) {
|
||||
QueueMessages qmsgs;
|
||||
qmsgs.reserve(msgs.size());
|
||||
|
||||
for ( const auto& msg : msgs ) {
|
||||
if ( msg.size() != 4 ) {
|
||||
ZEROMQ_THREAD_PRINTF("xsub: error: expected 4 parts, have %zu!\n", msg.size());
|
||||
|
@ -487,12 +502,12 @@ void ZeroMQBackend::Run() {
|
|||
continue;
|
||||
|
||||
detail::byte_buffer payload{msg[3].data<std::byte>(), msg[3].data<std::byte>() + msg[3].size()};
|
||||
qmsgs.emplace_back(EventMessage{.topic = std::string(msg[0].data<const char>(), msg[0].size()),
|
||||
EventMessage em{.topic = std::string(msg[0].data<const char>(), msg[0].size()),
|
||||
.format = std::string(msg[2].data<const char>(), msg[2].size()),
|
||||
.payload = std::move(payload)});
|
||||
}
|
||||
.payload = std::move(payload)};
|
||||
|
||||
QueueForProcessing(std::move(qmsgs));
|
||||
QueueForProcessing(std::move(em));
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class running at destruction.
|
||||
|
|
|
@ -62,18 +62,18 @@ private:
|
|||
bool DoPublishLogWrites(const logging::detail::LogWriteHeader& header, const std::string& format,
|
||||
cluster::detail::byte_buffer& buf) override;
|
||||
|
||||
const char* Tag() override { return "ZeroMQ"; }
|
||||
|
||||
bool DoProcessBackendMessage(int tag, detail::byte_buffer_span payload) override;
|
||||
|
||||
// Script level variables.
|
||||
std::string connect_xsub_endpoint;
|
||||
std::string connect_xpub_endpoint;
|
||||
int connect_xpub_nodrop = 1;
|
||||
std::string listen_xsub_endpoint;
|
||||
std::string listen_xpub_endpoint;
|
||||
std::string listen_log_endpoint;
|
||||
int listen_xpub_nodrop = 1;
|
||||
|
||||
int linger_ms = 0;
|
||||
zeek_uint_t poll_max_messages = 0;
|
||||
zeek_uint_t debug_flags = 0;
|
||||
|
||||
|
@ -99,6 +99,7 @@ private:
|
|||
|
||||
std::thread self_thread;
|
||||
|
||||
int proxy_io_threads = 2;
|
||||
std::unique_ptr<ProxyThread> proxy_thread;
|
||||
|
||||
// Tracking the subscriptions on the local XPUB socket.
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "zeek/cluster/Backend.h"
|
||||
#include "zeek/cluster/BifSupport.h"
|
||||
#include "zeek/cluster/Manager.h"
|
||||
#include "zeek/cluster/websocket/WebSocket.h"
|
||||
|
||||
using namespace zeek::cluster::detail::bif;
|
||||
|
||||
|
@ -11,6 +13,7 @@ using namespace zeek::cluster::detail::bif;
|
|||
module Cluster;
|
||||
|
||||
type Cluster::Event: record;
|
||||
type Cluster::WebSocketTLSOptions: record;
|
||||
|
||||
## Publishes an event to a given topic.
|
||||
##
|
||||
|
@ -147,3 +150,54 @@ function Cluster::publish_hrw%(pool: Pool, key: any, ...%): bool
|
|||
ScriptLocationScope scope{frame};
|
||||
return publish_event(topic, args);
|
||||
%}
|
||||
|
||||
function Cluster::__listen_websocket%(options: WebSocketServerOptions%): bool
|
||||
%{
|
||||
using namespace zeek::cluster::websocket::detail;
|
||||
|
||||
const auto& server_options_type = zeek::id::find_type<zeek::RecordType>("Cluster::WebSocketServerOptions");
|
||||
const auto& tls_options_type = zeek::id::find_type<zeek::RecordType>("Cluster::WebSocketTLSOptions");
|
||||
|
||||
if ( options->GetType() != server_options_type ) {
|
||||
zeek::emit_builtin_error("expected type Cluster::WebSocketServerOptions for options");
|
||||
return zeek::val_mgr->False();
|
||||
}
|
||||
|
||||
auto options_rec = zeek::IntrusivePtr<zeek::RecordVal>{zeek::NewRef{}, options->AsRecordVal()};
|
||||
auto tls_options_rec = options_rec->GetFieldOrDefault<zeek::RecordVal>("tls_options");
|
||||
|
||||
if ( tls_options_rec->GetType() != tls_options_type ) {
|
||||
zeek::emit_builtin_error("expected type Cluster::WebSocketTLSOptions for tls_options");
|
||||
return zeek::val_mgr->False();
|
||||
}
|
||||
|
||||
bool have_cert = tls_options_rec->HasField("cert_file");
|
||||
bool have_key = tls_options_rec->HasField("key_file");
|
||||
|
||||
if ( (have_cert || have_key) && ! (have_cert && have_key) ) {
|
||||
std::string error = "Invalid tls_options: ";
|
||||
if ( have_cert )
|
||||
error += "No key_file field";
|
||||
else
|
||||
error += "No cert_file field";
|
||||
zeek::emit_builtin_error(error.c_str());
|
||||
return zeek::val_mgr->False();
|
||||
}
|
||||
|
||||
struct TLSOptions tls_options = {
|
||||
have_cert ? std::optional{tls_options_rec->GetField<zeek::StringVal>("cert_file")->ToStdString()} : std::nullopt,
|
||||
have_key ? std::optional{tls_options_rec->GetField<zeek::StringVal>("key_file")->ToStdString()} : std::nullopt,
|
||||
tls_options_rec->GetFieldOrDefault<zeek::BoolVal>("enable_peer_verification")->Get(),
|
||||
tls_options_rec->GetFieldOrDefault<zeek::StringVal>("ca_file")->ToStdString(),
|
||||
tls_options_rec->GetFieldOrDefault<zeek::StringVal>("ciphers")->ToStdString(),
|
||||
};
|
||||
|
||||
struct ServerOptions server_options {
|
||||
options_rec->GetField<zeek::StringVal>("listen_host")->ToStdString(),
|
||||
static_cast<uint16_t>(options_rec->GetField<zeek::PortVal>("listen_port")->Port()),
|
||||
};
|
||||
server_options.tls_options = std::move(tls_options);
|
||||
|
||||
auto result = zeek::cluster::manager->ListenWebSocket(server_options);
|
||||
return zeek::val_mgr->Bool(result);
|
||||
%}
|
||||
|
|
|
@ -20,15 +20,7 @@
|
|||
|
||||
using namespace zeek::cluster;
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* Convert a cluster::detail::Event to a broker::zeek::Event.
|
||||
*
|
||||
* @param ev The cluster::detail::Event
|
||||
* @return A broker::zeek::Event to be serialized, or nullopt in case of errors.
|
||||
*/
|
||||
std::optional<broker::zeek::Event> to_broker_event(const detail::Event& ev) {
|
||||
std::optional<broker::zeek::Event> detail::to_broker_event(const detail::Event& ev) {
|
||||
broker::vector xs;
|
||||
xs.reserve(ev.args.size());
|
||||
|
||||
|
@ -51,15 +43,7 @@ std::optional<broker::zeek::Event> to_broker_event(const detail::Event& ev) {
|
|||
return broker::zeek::Event(ev.HandlerName(), xs, broker::to_timestamp(ev.timestamp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a broker::zeek::Event to cluster::detail::Event by looking
|
||||
* it up in Zeek's event handler registry and converting event arguments
|
||||
* to the appropriate Val instances.
|
||||
*
|
||||
* @param broker_ev The broker side event.
|
||||
* @returns A zeek::cluster::detail::Event instance, or std::nullopt if the conversion failed.
|
||||
*/
|
||||
std::optional<detail::Event> to_zeek_event(const broker::zeek::Event& ev) {
|
||||
std::optional<detail::Event> detail::to_zeek_event(const broker::zeek::Event& ev) {
|
||||
auto&& name = ev.name();
|
||||
auto&& args = ev.args();
|
||||
|
||||
|
@ -117,8 +101,6 @@ std::optional<detail::Event> to_zeek_event(const broker::zeek::Event& ev) {
|
|||
return detail::Event{handler, std::move(vl), ts};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool detail::BrokerBinV1_Serializer::SerializeEvent(detail::byte_buffer& buf, const detail::Event& event) {
|
||||
auto ev = to_broker_event(event);
|
||||
if ( ! ev )
|
||||
|
|
|
@ -4,8 +4,30 @@
|
|||
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
|
||||
namespace broker::zeek {
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace zeek::cluster::detail {
|
||||
|
||||
/**
|
||||
* Convert a broker::zeek::Event to cluster::detail::Event by looking
|
||||
* it up in Zeek's event handler registry and converting event arguments
|
||||
* to the appropriate Val instances.
|
||||
*
|
||||
* @param ev The broker side event.
|
||||
* @returns A zeek::cluster::detail::Event instance, or std::nullopt if the conversion failed.
|
||||
*/
|
||||
std::optional<detail::Event> to_zeek_event(const broker::zeek::Event& ev);
|
||||
|
||||
/**
|
||||
* Convert a cluster::detail::Event to a broker::zeek::Event.
|
||||
*
|
||||
* @param ev The cluster::detail::Event
|
||||
* @return A broker::zeek::Event to be serialized, or nullopt in case of errors.
|
||||
*/
|
||||
std::optional<broker::zeek::Event> to_broker_event(const detail::Event& ev);
|
||||
|
||||
// Implementation of the EventSerializer using the existing broker::detail::val_to_data()
|
||||
// and broker::format::bin::v1::encode().
|
||||
class BrokerBinV1_Serializer : public EventSerializer {
|
||||
|
|
8
src/cluster/websocket/CMakeLists.txt
Normal file
8
src/cluster/websocket/CMakeLists.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
add_subdirectory(auxil)
|
||||
|
||||
zeek_add_plugin(
|
||||
Zeek Cluster_WebSocket
|
||||
INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}
|
||||
DEPENDENCIES ixwebsocket::ixwebsocket
|
||||
SOURCES Plugin.cc WebSocket.cc WebSocket-IXWebSocket.cc
|
||||
BIFS events.bif)
|
19
src/cluster/websocket/Plugin.cc
Normal file
19
src/cluster/websocket/Plugin.cc
Normal file
|
@ -0,0 +1,19 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#include "zeek/cluster/websocket/Plugin.h"
|
||||
|
||||
namespace zeek::plugin::Cluster_WebSocket {
|
||||
// Definition of plugin.
|
||||
Plugin plugin;
|
||||
}; // namespace zeek::plugin::Cluster_WebSocket
|
||||
|
||||
namespace zeek::plugin::Cluster_WebSocket {
|
||||
|
||||
zeek::plugin::Configuration Plugin::Configure() {
|
||||
zeek::plugin::Configuration config;
|
||||
config.name = "Zeek::Cluster_WebSocket";
|
||||
config.description = "Provides WebSocket access to a Zeek cluster";
|
||||
return config;
|
||||
}
|
||||
|
||||
} // namespace zeek::plugin::Cluster_WebSocket
|
16
src/cluster/websocket/Plugin.h
Normal file
16
src/cluster/websocket/Plugin.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "zeek/plugin/Plugin.h"
|
||||
|
||||
namespace zeek::plugin::Cluster_WebSocket {
|
||||
|
||||
class Plugin : public zeek::plugin::Plugin {
|
||||
public:
|
||||
zeek::plugin::Configuration Configure() override;
|
||||
};
|
||||
|
||||
extern Plugin plugin;
|
||||
|
||||
} // namespace zeek::plugin::Cluster_WebSocket
|
5
src/cluster/websocket/README
Normal file
5
src/cluster/websocket/README
Normal file
|
@ -0,0 +1,5 @@
|
|||
A Zeek Cluster's WebSocket interface
|
||||
====================================
|
||||
|
||||
This directory contains code that allows external applications
|
||||
to connect to Zeek using WebSocket connections.
|
170
src/cluster/websocket/WebSocket-IXWebSocket.cc
Normal file
170
src/cluster/websocket/WebSocket-IXWebSocket.cc
Normal file
|
@ -0,0 +1,170 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
// Implementation of a WebSocket server and clients using the IXWebSocket client library.
|
||||
#include "zeek/cluster/websocket/WebSocket.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "zeek/Reporter.h"
|
||||
|
||||
#include "ixwebsocket/IXConnectionState.h"
|
||||
#include "ixwebsocket/IXSocketTLSOptions.h"
|
||||
#include "ixwebsocket/IXWebSocket.h"
|
||||
#include "ixwebsocket/IXWebSocketSendData.h"
|
||||
#include "ixwebsocket/IXWebSocketServer.h"
|
||||
|
||||
namespace zeek::cluster::websocket::detail::ixwebsocket {
|
||||
|
||||
/**
|
||||
* Implementation of WebSocketClient for the IXWebsocket library.
|
||||
*/
|
||||
class IxWebSocketClient : public WebSocketClient {
|
||||
public:
|
||||
IxWebSocketClient(std::shared_ptr<ix::ConnectionState> cs, std::shared_ptr<ix::WebSocket> ws)
|
||||
: cs(std::move(cs)), ws(std::move(ws)) {
|
||||
if ( ! this->cs || ! this->ws )
|
||||
throw std::invalid_argument("expected ws and cs to be set");
|
||||
}
|
||||
|
||||
bool IsTerminated() const override {
|
||||
if ( cs->isTerminated() )
|
||||
return true;
|
||||
|
||||
auto rs = ws->getReadyState();
|
||||
return rs == ix::ReadyState::Closing || rs == ix::ReadyState::Closed;
|
||||
}
|
||||
|
||||
void Close(uint16_t code, const std::string& reason) override { ws->close(code, reason); }
|
||||
|
||||
SendInfo SendText(std::string_view sv) override {
|
||||
if ( cs->isTerminated() )
|
||||
return {true}; // small lie
|
||||
|
||||
auto send_info = ws->sendUtf8Text(ix::IXWebSocketSendData{sv.data(), sv.size()});
|
||||
return SendInfo{send_info.success};
|
||||
}
|
||||
|
||||
const std::string& getId() override { return cs->getId(); }
|
||||
const std::string& getRemoteIp() override { return cs->getRemoteIp(); }
|
||||
int getRemotePort() override { return cs->getRemotePort(); }
|
||||
|
||||
private:
|
||||
std::shared_ptr<ix::ConnectionState> cs;
|
||||
std::shared_ptr<ix::WebSocket> ws;
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of WebSocketServer using the IXWebsocket library.
|
||||
*/
|
||||
class IXWebSocketServer : public WebSocketServer {
|
||||
public:
|
||||
IXWebSocketServer(std::unique_ptr<WebSocketEventDispatcher> dispatcher, std::unique_ptr<ix::WebSocketServer> server)
|
||||
: WebSocketServer(std::move(dispatcher)), server(std::move(server)) {}
|
||||
|
||||
private:
|
||||
void DoTerminate() override {
|
||||
// Stop the server.
|
||||
server->stop();
|
||||
}
|
||||
|
||||
std::unique_ptr<ix::WebSocketServer> server;
|
||||
};
|
||||
|
||||
std::unique_ptr<WebSocketServer> StartServer(std::unique_ptr<WebSocketEventDispatcher> dispatcher,
|
||||
const ServerOptions& options) {
|
||||
auto server =
|
||||
std::make_unique<ix::WebSocketServer>(options.port, options.host, ix::SocketServer::kDefaultTcpBacklog,
|
||||
options.max_connections,
|
||||
ix::WebSocketServer::kDefaultHandShakeTimeoutSecs,
|
||||
ix::SocketServer::kDefaultAddressFamily, options.ping_interval_seconds);
|
||||
|
||||
if ( ! options.per_message_deflate )
|
||||
server->disablePerMessageDeflate();
|
||||
|
||||
const auto& tls_options = options.tls_options;
|
||||
if ( tls_options.TlsEnabled() ) {
|
||||
ix::SocketTLSOptions ix_tls_options{};
|
||||
ix_tls_options.tls = true;
|
||||
ix_tls_options.certFile = tls_options.cert_file.value();
|
||||
ix_tls_options.keyFile = tls_options.key_file.value();
|
||||
|
||||
if ( tls_options.enable_peer_verification ) {
|
||||
if ( ! tls_options.ca_file.empty() )
|
||||
ix_tls_options.caFile = tls_options.ca_file;
|
||||
}
|
||||
else {
|
||||
// This is the IXWebSocket library's way of
|
||||
// disabling peer verification.
|
||||
ix_tls_options.caFile = "NONE";
|
||||
}
|
||||
|
||||
if ( ! tls_options.ciphers.empty() )
|
||||
ix_tls_options.ciphers = tls_options.ciphers;
|
||||
|
||||
server->setTLSOptions(ix_tls_options);
|
||||
}
|
||||
|
||||
// Using the legacy IXWebsocketAPI API to acquire a shared_ptr to the ix::WebSocket instance.
|
||||
ix::WebSocketServer::OnConnectionCallback connection_callback =
|
||||
[dispatcher = dispatcher.get()](std::weak_ptr<ix::WebSocket> websocket,
|
||||
std::shared_ptr<ix::ConnectionState> cs) -> void {
|
||||
// Hold a shared_ptr to the WebSocket object until we see the close.
|
||||
std::shared_ptr<ix::WebSocket> ws = websocket.lock();
|
||||
|
||||
// Client already gone or terminated? Weird...
|
||||
if ( ! ws || cs->isTerminated() )
|
||||
return;
|
||||
|
||||
auto id = cs->getId();
|
||||
int remotePort = cs->getRemotePort();
|
||||
std::string remoteIp = cs->getRemoteIp();
|
||||
|
||||
auto ixws = std::make_shared<IxWebSocketClient>(std::move(cs), ws);
|
||||
|
||||
// These callbacks run in per client threads. The actual processing happens
|
||||
// on the main thread via a single WebSocketDemux instance.
|
||||
ix::OnMessageCallback message_callback = [dispatcher, id, remotePort, remoteIp,
|
||||
ixws](const ix::WebSocketMessagePtr& msg) mutable {
|
||||
if ( msg->type == ix::WebSocketMessageType::Open ) {
|
||||
dispatcher->QueueForProcessing(
|
||||
WebSocketOpen{id, msg->openInfo.uri, msg->openInfo.protocol, std::move(ixws)});
|
||||
}
|
||||
else if ( msg->type == ix::WebSocketMessageType::Message ) {
|
||||
dispatcher->QueueForProcessing(WebSocketMessage{id, msg->str});
|
||||
}
|
||||
else if ( msg->type == ix::WebSocketMessageType::Close ) {
|
||||
dispatcher->QueueForProcessing(WebSocketClose{id});
|
||||
}
|
||||
else if ( msg->type == ix::WebSocketMessageType::Error ) {
|
||||
dispatcher->QueueForProcessing(WebSocketClose{id});
|
||||
}
|
||||
};
|
||||
|
||||
ws->setOnMessageCallback(message_callback);
|
||||
};
|
||||
|
||||
server->setOnConnectionCallback(connection_callback);
|
||||
|
||||
const auto [success, reason] = server->listen();
|
||||
if ( ! success ) {
|
||||
zeek::reporter->Error("WebSocket: Unable to listen on %s:%d: %s", options.host.c_str(), options.port,
|
||||
reason.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
server->start();
|
||||
|
||||
return std::make_unique<IXWebSocketServer>(std::move(dispatcher), std::move(server));
|
||||
}
|
||||
|
||||
|
||||
} // namespace zeek::cluster::websocket::detail::ixwebsocket
|
||||
|
||||
using namespace zeek::cluster::websocket::detail;
|
||||
|
||||
std::unique_ptr<WebSocketServer> zeek::cluster::websocket::detail::StartServer(
|
||||
std::unique_ptr<WebSocketEventDispatcher> dispatcher, const ServerOptions& options) {
|
||||
// Just delegate to the above IXWebSocket specific implementation.
|
||||
return ixwebsocket::StartServer(std::move(dispatcher), options);
|
||||
}
|
504
src/cluster/websocket/WebSocket.cc
Normal file
504
src/cluster/websocket/WebSocket.cc
Normal file
|
@ -0,0 +1,504 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
// Implement Broker's WebSocket client handling in Zeek.
|
||||
|
||||
#include "zeek/cluster/websocket/WebSocket.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
|
||||
#include "zeek/Reporter.h"
|
||||
#include "zeek/cluster/Backend.h"
|
||||
#include "zeek/cluster/BifSupport.h"
|
||||
#include "zeek/cluster/Manager.h"
|
||||
#include "zeek/cluster/OnLoop.h"
|
||||
#include "zeek/cluster/Serializer.h"
|
||||
#include "zeek/cluster/serializer/broker/Serializer.h"
|
||||
#include "zeek/cluster/websocket/Plugin.h"
|
||||
#include "zeek/cluster/websocket/events.bif.h"
|
||||
#include "zeek/net_util.h"
|
||||
#include "zeek/threading/MsgThread.h"
|
||||
|
||||
#include "broker/data.bif.h"
|
||||
#include "broker/data_envelope.hh"
|
||||
#include "broker/error.hh"
|
||||
#include "broker/format/json.hh"
|
||||
#include "broker/zeek.hh"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/rapidjson.h"
|
||||
|
||||
|
||||
#define WS_DEBUG(...) PLUGIN_DBG_LOG(zeek::plugin::Cluster_WebSocket::plugin, __VA_ARGS__)
|
||||
|
||||
namespace zeek {
|
||||
const char* zeek_version();
|
||||
}
|
||||
|
||||
using namespace zeek::cluster::websocket::detail;
|
||||
|
||||
namespace {
|
||||
|
||||
class WebSocketEventHandlingStrategy : public zeek::cluster::detail::EventHandlingStrategy {
|
||||
public:
|
||||
WebSocketEventHandlingStrategy(std::shared_ptr<WebSocketClient> ws, WebSocketEventDispatcher* dispatcher)
|
||||
: wsc(std::move(ws)), dispatcher(dispatcher) {}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Any received remote event is encoded into Broker's JSON v1 format and
|
||||
* send over to the WebSocket client.
|
||||
*
|
||||
* We leverage low-level Broker encoding functions here directly. This
|
||||
* will need some abstractions if client's can opt to use different encodings
|
||||
* of events in the future.
|
||||
*/
|
||||
bool DoHandleRemoteEvent(std::string_view topic, zeek::cluster::detail::Event e) override {
|
||||
// If the client has left, no point in sending it any pending event.
|
||||
if ( wsc->IsTerminated() )
|
||||
return true;
|
||||
|
||||
|
||||
// Any events received from the backend before an Ack was sent
|
||||
// are discarded.
|
||||
if ( ! wsc->IsAcked() )
|
||||
return true;
|
||||
|
||||
// XXX The serialization is somewhat slow, it would be good to offload
|
||||
// it to a thread, or try to go from Val's directly to JSON and see
|
||||
// if that's faster.
|
||||
auto ev = zeek::cluster::detail::to_broker_event(e);
|
||||
if ( ! ev ) {
|
||||
fprintf(stderr, "[ERROR] Unable to go from detail::Event to broker::event\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
auto envelope = broker::data_envelope::make(topic, ev->as_data());
|
||||
broker::format::json::v1::encode(envelope, std::back_inserter(buffer));
|
||||
|
||||
dispatcher->QueueReply(WebSocketSendReply{wsc, buffer});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Events from backends aren't enqueued into the event loop when
|
||||
* running for WebSocket clients.
|
||||
*/
|
||||
void DoEnqueueLocalEvent(zeek::EventHandlerPtr h, zeek::Args args) override {}
|
||||
|
||||
std::string buffer;
|
||||
std::shared_ptr<WebSocketClient> wsc;
|
||||
WebSocketEventDispatcher* dispatcher;
|
||||
};
|
||||
|
||||
class ReplyInputMessage : public zeek::threading::BasicInputMessage {
|
||||
public:
|
||||
ReplyInputMessage(WebSocketReply work) : zeek::threading::BasicInputMessage("ReplyInput"), work(std::move(work)) {};
|
||||
bool Process() override {
|
||||
return std::visit([this](auto& item) -> bool { return Process(item); }, work);
|
||||
};
|
||||
|
||||
private:
|
||||
bool Process(const WebSocketSendReply& sr) {
|
||||
const auto& wsc = sr.wsc;
|
||||
if ( wsc->IsTerminated() )
|
||||
return true;
|
||||
|
||||
auto send_info = wsc->SendText(sr.msg);
|
||||
if ( ! send_info.success && ! wsc->IsTerminated() )
|
||||
fprintf(stderr, "[ERROR] Failed to send reply to WebSocket client %s (%s:%d)\n", wsc->getId().c_str(),
|
||||
wsc->getRemoteIp().c_str(), wsc->getRemotePort());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Process(const WebSocketCloseReply& cr) {
|
||||
const auto& wsc = cr.wsc;
|
||||
if ( ! wsc->IsTerminated() )
|
||||
wsc->Close(cr.code, cr.reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WebSocketReply work;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// Inspired by broker/internal/json_client.cc
|
||||
WebSocketClient::SendInfo WebSocketClient::SendError(std::string_view code, std::string_view message) {
|
||||
std::string buf;
|
||||
buf.reserve(code.size() + message.size() + 32);
|
||||
auto out = std::back_inserter(buf);
|
||||
*out++ = '{';
|
||||
broker::format::json::v1::append_field("type", "error", out);
|
||||
*out++ = ',';
|
||||
broker::format::json::v1::append_field("code", code, out);
|
||||
*out++ = ',';
|
||||
broker::format::json::v1::append_field("message", message, out);
|
||||
*out++ = '}';
|
||||
return SendText(buf);
|
||||
}
|
||||
|
||||
// Inspired by broker/internal/json_client.cc
|
||||
WebSocketClient::SendInfo WebSocketClient::SendAck(std::string_view endpoint, std::string_view version) {
|
||||
std::string buf;
|
||||
buf.reserve(endpoint.size() + version.size() + 32);
|
||||
auto out = std::back_inserter(buf);
|
||||
*out++ = '{';
|
||||
broker::format::json::v1::append_field("type", "ack", out);
|
||||
*out++ = ',';
|
||||
broker::format::json::v1::append_field("endpoint", endpoint, out);
|
||||
*out++ = ',';
|
||||
broker::format::json::v1::append_field("version", version, out);
|
||||
*out++ = '}';
|
||||
auto r = SendText(buf);
|
||||
acked = true;
|
||||
return r;
|
||||
}
|
||||
|
||||
void WebSocketClient::SetSubscriptions(const std::vector<std::string>& topic_prefixes) {
|
||||
for ( const auto& topic_prefix : topic_prefixes )
|
||||
subscriptions_state[topic_prefix] = false;
|
||||
}
|
||||
|
||||
void WebSocketClient::SetSubscriptionActive(const std::string& topic_prefix) {
|
||||
if ( subscriptions_state.count(topic_prefix) == 0 ) {
|
||||
zeek::reporter->InternalWarning("Unknown topic_prefix for WebSocket client %s!", topic_prefix.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptions_state[topic_prefix] = true;
|
||||
}
|
||||
|
||||
bool WebSocketClient::AllSubscriptionsActive() const {
|
||||
for ( const auto& [_, status] : subscriptions_state ) {
|
||||
if ( ! status )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<std::string> WebSocketClient::GetSubscriptions() const {
|
||||
std::vector<std::string> subs;
|
||||
subs.reserve(subscriptions_state.size());
|
||||
|
||||
for ( const auto& [topic, _] : subscriptions_state )
|
||||
subs.emplace_back(topic);
|
||||
|
||||
return subs;
|
||||
}
|
||||
|
||||
class zeek::cluster::websocket::detail::ReplyMsgThread : public zeek::threading::MsgThread {
|
||||
public:
|
||||
ReplyMsgThread() : zeek::threading::MsgThread() { SetName("ws-reply-thread"); }
|
||||
|
||||
void Run() override {
|
||||
zeek::util::detail::set_thread_name("zk/ws-reply-thread");
|
||||
MsgThread::Run();
|
||||
}
|
||||
|
||||
bool OnHeartbeat(double network_time, double current_time) override { return true; }
|
||||
|
||||
bool OnFinish(double network_time) override { return true; }
|
||||
};
|
||||
|
||||
WebSocketEventDispatcher::WebSocketEventDispatcher() {
|
||||
onloop =
|
||||
new zeek::detail::OnLoopProcess<WebSocketEventDispatcher, WebSocketEvent>(this, "WebSocketEventDispatcher");
|
||||
// Register the onloop instance the IO loop. Lifetime will be managed by the loop.
|
||||
onloop->Register(false);
|
||||
|
||||
reply_msg_thread = new ReplyMsgThread();
|
||||
reply_msg_thread->Start();
|
||||
}
|
||||
|
||||
WebSocketEventDispatcher::~WebSocketEventDispatcher() {
|
||||
// Freed by threading manager.
|
||||
reply_msg_thread = nullptr;
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::Terminate() {
|
||||
WS_DEBUG("Terminating WebSocketEventDispatcher");
|
||||
|
||||
for ( auto& [_, client] : clients ) {
|
||||
const auto& wsc = client.wsc;
|
||||
const auto& backend = client.backend;
|
||||
WS_DEBUG("Sending close to WebSocket client %s (%s:%d)", wsc->getId().c_str(), wsc->getRemoteIp().c_str(),
|
||||
wsc->getRemotePort());
|
||||
|
||||
QueueReply(WebSocketCloseReply{wsc, 1001, "Terminating"});
|
||||
backend->Terminate();
|
||||
}
|
||||
|
||||
clients.clear();
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::QueueForProcessing(WebSocketEvent&& event) {
|
||||
// Just delegate to onloop. The work will be done in Process()
|
||||
onloop->QueueForProcessing(std::move(event));
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::QueueReply(WebSocketReply&& reply) {
|
||||
// Delegate to the reply thread.
|
||||
reply_msg_thread->SendIn(new ReplyInputMessage(std::move(reply)));
|
||||
}
|
||||
|
||||
// WebSocketDemux::Process() runs in the main thread.
|
||||
//
|
||||
// XXX: How is this going to work with class broker? With
|
||||
// ZeroMQ, each WebSocket client has its own XPUB/XSUB
|
||||
// connectivity to a central broker and similarly with NATS.
|
||||
// But with broker we need to do something different.
|
||||
// Maybe connect to the local endpoint.
|
||||
//
|
||||
// We cannot actually instantiate a Broker backend :-(
|
||||
//
|
||||
// We could also have InitPostScript() recognize Broker
|
||||
// and start its internal server instead.
|
||||
void WebSocketEventDispatcher::Process(const WebSocketEvent& event) {
|
||||
std::visit([this](auto&& arg) { Process(arg); }, event);
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::Process(const WebSocketOpen& open) {
|
||||
const auto& wsc = open.wsc;
|
||||
const auto& id = open.id;
|
||||
const auto& it = clients.find(id);
|
||||
if ( it != clients.end() ) {
|
||||
// This shouldn't happen!
|
||||
reporter->Error("Open for existing WebSocket client with id %s!", id.c_str());
|
||||
QueueReply(WebSocketCloseReply{wsc, 1001, "Internal error"});
|
||||
return;
|
||||
}
|
||||
|
||||
// As of now, terminate clients coming to anything other than /v1/messages/json.
|
||||
if ( open.uri != "/v1/messages/json" ) {
|
||||
open.wsc->SendError("invalid_uri", "Invalid URI - use /v1/messages/json");
|
||||
open.wsc->Close(1008, "Invalid URI - use /v1/messages/json");
|
||||
|
||||
// Still create an entry as we might see messages and close events coming in.
|
||||
clients[id] = WebSocketClientEntry{id, wsc, nullptr};
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate an ID for this client.
|
||||
auto ws_id = cluster::backend->NodeId() + "-websocket-" + id;
|
||||
|
||||
const auto& event_serializer_val = id::find_val<zeek::EnumVal>("Cluster::event_serializer");
|
||||
auto event_serializer = cluster::manager->InstantiateEventSerializer(event_serializer_val);
|
||||
const auto& cluster_backend_val = id::find_val<zeek::EnumVal>("Cluster::backend");
|
||||
auto event_handling_strategy = std::make_unique<WebSocketEventHandlingStrategy>(wsc, this);
|
||||
auto backend = zeek::cluster::manager->InstantiateBackend(cluster_backend_val, std::move(event_serializer), nullptr,
|
||||
std::move(event_handling_strategy));
|
||||
|
||||
WS_DEBUG("New WebSocket client %s (%s:%d) - using id %s backend=%p", id.c_str(), wsc->getRemoteIp().c_str(),
|
||||
wsc->getRemotePort(), ws_id.c_str(), backend.get());
|
||||
|
||||
// XXX: We call InitPostScript to populate member vars required for connectivity.
|
||||
backend->InitPostScript();
|
||||
backend->Init(std::move(ws_id));
|
||||
|
||||
clients[id] = WebSocketClientEntry{id, wsc, std::move(backend)};
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::Process(const WebSocketClose& close) {
|
||||
const auto& id = close.id;
|
||||
const auto& it = clients.find(id);
|
||||
|
||||
if ( it == clients.end() ) {
|
||||
reporter->Error("Close from non-existing WebSocket client with id %s!", id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& wsc = it->second.wsc;
|
||||
auto& backend = it->second.backend;
|
||||
|
||||
WS_DEBUG("Close from client %s (%s:%d) backend=%p", wsc->getId().c_str(), wsc->getRemoteIp().c_str(),
|
||||
wsc->getRemotePort(), backend.get());
|
||||
|
||||
// If the client doesn't have a backend, it wasn't ever properly instantiated.
|
||||
if ( backend ) {
|
||||
auto rec = zeek::cluster::detail::bif::make_endpoint_info(backend->NodeId(), wsc->getRemoteIp(),
|
||||
wsc->getRemotePort(), TRANSPORT_TCP);
|
||||
zeek::event_mgr.Enqueue(Cluster::websocket_client_lost, std::move(rec));
|
||||
|
||||
backend->Terminate();
|
||||
}
|
||||
|
||||
clients.erase(it);
|
||||
}
|
||||
|
||||
// SubscribeFinished is produced internally.
|
||||
void WebSocketEventDispatcher::Process(const WebSocketSubscribeFinished& fin) {
|
||||
const auto& it = clients.find(fin.id);
|
||||
if ( it == clients.end() ) {
|
||||
reporter->Error("Subscribe finished from non-existing WebSocket client with id %s!", fin.id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& entry = it->second;
|
||||
auto& wsc = entry.wsc;
|
||||
|
||||
entry.wsc->SetSubscriptionActive(fin.topic_prefix);
|
||||
|
||||
if ( ! entry.wsc->AllSubscriptionsActive() ) {
|
||||
// More subscriptions to come.
|
||||
return;
|
||||
}
|
||||
|
||||
auto rec = zeek::cluster::detail::bif::make_endpoint_info(backend->NodeId(), wsc->getRemoteIp(),
|
||||
wsc->getRemotePort(), TRANSPORT_TCP);
|
||||
auto subscriptions_vec = zeek::cluster::detail::bif::make_string_vec(wsc->GetSubscriptions());
|
||||
zeek::event_mgr.Enqueue(Cluster::websocket_client_added, std::move(rec), std::move(subscriptions_vec));
|
||||
|
||||
entry.wsc->SendAck(entry.backend->NodeId(), zeek::zeek_version());
|
||||
|
||||
WS_DEBUG("Sent Ack to %s %s\n", fin.id.c_str(), entry.backend->NodeId().c_str());
|
||||
|
||||
// Process any queued messages now.
|
||||
for ( auto& msg : entry.queue ) {
|
||||
assert(entry.msg_count > 1);
|
||||
Process(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::HandleSubscriptions(WebSocketClientEntry& entry, std::string_view buf) {
|
||||
rapidjson::Document doc;
|
||||
doc.Parse(buf.data(), buf.size());
|
||||
if ( ! doc.IsArray() ) {
|
||||
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "subscriptions not an array");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> subscriptions;
|
||||
|
||||
for ( rapidjson::SizeType i = 0; i < doc.Size(); i++ ) {
|
||||
if ( ! doc[i].IsString() ) {
|
||||
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed),
|
||||
"individual subscription not a string");
|
||||
return;
|
||||
}
|
||||
|
||||
subscriptions.emplace_back(doc[i].GetString());
|
||||
}
|
||||
|
||||
entry.wsc->SetSubscriptions(subscriptions);
|
||||
|
||||
auto cb = [this, id = entry.id, wsc = entry.wsc](const std::string& topic,
|
||||
const Backend::SubscriptionCallbackInfo& info) {
|
||||
if ( info.status == Backend::CallbackStatus::Error ) {
|
||||
zeek::reporter->Error("Subscribe for WebSocket client failed!");
|
||||
|
||||
// Is this going to work out?
|
||||
QueueReply(WebSocketCloseReply{wsc, 1011, "Could not subscribe. Something bad happened!"});
|
||||
}
|
||||
else {
|
||||
Process(WebSocketSubscribeFinished{id, topic});
|
||||
}
|
||||
};
|
||||
|
||||
for ( const auto& subscription : subscriptions ) {
|
||||
if ( ! entry.backend->Subscribe(subscription, cb) ) {
|
||||
zeek::reporter->Error("Subscribe for WebSocket client failed!");
|
||||
QueueReply(WebSocketCloseReply{entry.wsc, 1011, "Could not subscribe. Something bad happened!"});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketEventDispatcher::HandleEvent(WebSocketClientEntry& entry, std::string_view buf) {
|
||||
// Unserialize the message as an event.
|
||||
broker::variant res;
|
||||
auto err = broker::format::json::v1::decode(buf, res);
|
||||
if ( err ) {
|
||||
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "failed to decode JSON object");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string topic = std::string(res->shared_envelope()->topic());
|
||||
|
||||
if ( topic == broker::topic::reserved ) {
|
||||
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "no topic in top-level JSON object");
|
||||
return;
|
||||
}
|
||||
|
||||
broker::zeek::Event broker_ev(std::move(res));
|
||||
|
||||
// This is not guaranteed to work! If the node running the WebSocket
|
||||
// API does not have the declaration of the event that another node
|
||||
// is sending, it cannot instantiate the zeek::cluster::Event for
|
||||
// re-publishing to a cluster backend.
|
||||
//
|
||||
// Does that make conceptional sense? Basically the WebSocket API
|
||||
// has Zeek-script awareness.
|
||||
//
|
||||
// It works with Broker today because Broker treats messages opaquely.
|
||||
// It knows how to convert from JSON into Broker binary format as these
|
||||
// are compatible.
|
||||
//
|
||||
// However, the broker format is under specified (vectors are used for various
|
||||
// types without being tagged explicitly), so it's not possible to determine
|
||||
// the final Zeek type without having access to the script-layer.
|
||||
//
|
||||
// I'm not sure this is a real problem, other than it being unfortunate that
|
||||
// the Zeek process running the WebSocket API requires access to all declarations
|
||||
// of events being transmitted via WebSockets. Though this might be a given anyhow.
|
||||
//
|
||||
// See broker/Data.cc for broker::vector conversion to see the collisions:
|
||||
// vector, list, func, record, pattern, opaque are all encoded using
|
||||
// broker::vector rather than dedicated types.
|
||||
//
|
||||
// Switching to a JSON v2 format that ensures all Zeek types are represented
|
||||
// explicitly would help.
|
||||
const auto& zeek_ev = cluster::detail::to_zeek_event(broker_ev);
|
||||
if ( ! zeek_ev ) {
|
||||
entry.wsc->SendError(broker::enum_str(broker::ec::deserialization_failed), "failed to create Zeek event");
|
||||
return;
|
||||
}
|
||||
|
||||
WS_DEBUG("Publishing event %s to topic '%s'", std::string(zeek_ev->HandlerName()).c_str(), topic.c_str());
|
||||
entry.backend->PublishEvent(topic, *zeek_ev);
|
||||
}
|
||||
|
||||
// Process a WebSocket message from a client.
|
||||
//
|
||||
// If it's the first message, the code is expecting a subscriptions
|
||||
// array, otherwise it'll be a remote event.
|
||||
void WebSocketEventDispatcher::Process(const WebSocketMessage& msg) {
|
||||
const auto& id = msg.id;
|
||||
|
||||
const auto& it = clients.find(id);
|
||||
if ( it == clients.end() ) {
|
||||
reporter->Error("WebSocket message from non-existing WebSocket client %s", id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Client without backend wasn't accepted, just discard its message.
|
||||
if ( ! it->second.backend )
|
||||
return;
|
||||
|
||||
auto& entry = it->second;
|
||||
const auto& wsc = entry.wsc;
|
||||
entry.msg_count++;
|
||||
|
||||
WS_DEBUG("Message %" PRIu64 " size=%zu from %s (%s:%d) backend=%p", entry.msg_count, msg.msg.size(),
|
||||
wsc->getId().c_str(), wsc->getRemoteIp().c_str(), wsc->getRemotePort(), entry.backend.get());
|
||||
|
||||
// First message is the subscription message.
|
||||
if ( entry.msg_count == 1 ) {
|
||||
WS_DEBUG("Subscriptions from client: %s: (%s:%d)\n", id.c_str(), wsc->getRemoteIp().c_str(),
|
||||
wsc->getRemotePort());
|
||||
HandleSubscriptions(entry, msg.msg);
|
||||
}
|
||||
else {
|
||||
if ( ! wsc->IsAcked() ) {
|
||||
WS_DEBUG("Client sending messages before receiving ack!");
|
||||
entry.queue.push_back(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
HandleEvent(entry, msg.msg);
|
||||
}
|
||||
}
|
321
src/cluster/websocket/WebSocket.h
Normal file
321
src/cluster/websocket/WebSocket.h
Normal file
|
@ -0,0 +1,321 @@
|
|||
// See the file "COPYING" in the main distribution directory for copyright.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace zeek {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template<class Proc, class Work>
|
||||
class OnLoopProcess;
|
||||
}
|
||||
|
||||
namespace cluster {
|
||||
|
||||
class Backend;
|
||||
|
||||
namespace websocket::detail {
|
||||
|
||||
|
||||
/**
|
||||
* Library independent interface for a WebSocket client.
|
||||
*
|
||||
* All methods should be safe to be called from Zeek's
|
||||
* main thread, though some may fail if the client has vanished
|
||||
* or vanishes during an operation.
|
||||
*/
|
||||
class WebSocketClient {
|
||||
public:
|
||||
virtual ~WebSocketClient() = default;
|
||||
|
||||
/**
|
||||
* @returns true if the WebSocket client has terminated
|
||||
*/
|
||||
virtual bool IsTerminated() const = 0;
|
||||
|
||||
/**
|
||||
* Close the WebSocket connection with the given code/reason.
|
||||
*/
|
||||
virtual void Close(uint16_t code = 1000, const std::string& reason = "Normal closure") = 0;
|
||||
|
||||
/**
|
||||
* Information about the send operation.
|
||||
*/
|
||||
struct SendInfo {
|
||||
bool success;
|
||||
};
|
||||
|
||||
/**
|
||||
* Thread safe sending.
|
||||
*
|
||||
* This might be called from Zeek's main thread and
|
||||
* must be safe to be called whether or not the connection
|
||||
* with the client is still alive.
|
||||
*
|
||||
* @param sv The buffer to send as a WebSocket message.
|
||||
*/
|
||||
virtual SendInfo SendText(std::string_view sv) = 0;
|
||||
|
||||
/**
|
||||
* Send an error in Broker JSON/v1 format to the client.
|
||||
*/
|
||||
SendInfo SendError(std::string_view code, std::string_view ctx);
|
||||
|
||||
/**
|
||||
* Send an ACK message Broker JSON/v1 format to the client.
|
||||
*/
|
||||
SendInfo SendAck(std::string_view endpoint, std::string_view version);
|
||||
|
||||
/**
|
||||
* @return - has an ACK been sent to the client?
|
||||
*/
|
||||
bool IsAcked() const { return acked; }
|
||||
|
||||
/**
|
||||
* @return The WebSocket client's identifier.
|
||||
*/
|
||||
virtual const std::string& getId() = 0;
|
||||
|
||||
/**
|
||||
* @return The WebSocket client's remote IP address.
|
||||
*/
|
||||
virtual const std::string& getRemoteIp() = 0;
|
||||
|
||||
/**
|
||||
* @return The WebSocket client's remote port.
|
||||
*/
|
||||
virtual int getRemotePort() = 0;
|
||||
|
||||
/**
|
||||
* Store the client's subscriptions as "not active".
|
||||
*/
|
||||
void SetSubscriptions(const std::vector<std::string>& topic_prefixes);
|
||||
|
||||
/**
|
||||
* @return The client's subscriptions.
|
||||
*/
|
||||
const std::vector<std::string> GetSubscriptions() const;
|
||||
|
||||
/**
|
||||
* Store the client's subscriptions as "not active".
|
||||
*/
|
||||
void SetSubscriptionActive(const std::string& topic_prefix);
|
||||
|
||||
/**
|
||||
* @return true if all subscriptions have an active status.
|
||||
*/
|
||||
bool AllSubscriptionsActive() const;
|
||||
|
||||
private:
|
||||
bool acked = false;
|
||||
std::map<std::string, bool> subscriptions_state;
|
||||
};
|
||||
|
||||
// An new WebSocket client connected. Client is locally identified by `id`.
|
||||
struct WebSocketOpen {
|
||||
std::string id;
|
||||
std::string uri;
|
||||
std::string protocol;
|
||||
std::shared_ptr<WebSocketClient> wsc;
|
||||
};
|
||||
|
||||
// A WebSocket client disconnected.
|
||||
struct WebSocketClose {
|
||||
std::string id;
|
||||
};
|
||||
|
||||
// A WebSocket client send a message.
|
||||
struct WebSocketMessage {
|
||||
std::string id;
|
||||
std::string msg;
|
||||
};
|
||||
|
||||
// Produced internally when a WebSocket client's
|
||||
// subscription has completed.
|
||||
struct WebSocketSubscribeFinished {
|
||||
std::string id;
|
||||
std::string topic_prefix;
|
||||
};
|
||||
|
||||
using WebSocketEvent = std::variant<WebSocketOpen, WebSocketSubscribeFinished, WebSocketClose, WebSocketMessage>;
|
||||
|
||||
struct WebSocketSendReply {
|
||||
std::shared_ptr<WebSocketClient> wsc;
|
||||
std::string msg;
|
||||
};
|
||||
|
||||
struct WebSocketCloseReply {
|
||||
std::shared_ptr<WebSocketClient> wsc;
|
||||
uint16_t code = 1000;
|
||||
std::string reason = "Normal closure";
|
||||
};
|
||||
|
||||
using WebSocketReply = std::variant<WebSocketSendReply, WebSocketCloseReply>;
|
||||
|
||||
|
||||
class ReplyMsgThread;
|
||||
|
||||
/**
|
||||
* Handle events produced by WebSocket clients.
|
||||
*
|
||||
* Any thread may call QueueForProcessing(). Process() runs
|
||||
* on Zeek's main thread.
|
||||
*/
|
||||
class WebSocketEventDispatcher {
|
||||
public:
|
||||
WebSocketEventDispatcher();
|
||||
|
||||
~WebSocketEventDispatcher();
|
||||
|
||||
/**
|
||||
* Called shutting down a WebSocket server.
|
||||
*/
|
||||
void Terminate();
|
||||
|
||||
/**
|
||||
* Queue the given WebSocket event to be processed on Zeek's main loop.
|
||||
*
|
||||
* @param work The WebSocket event to process.
|
||||
*/
|
||||
void QueueForProcessing(WebSocketEvent&& event);
|
||||
|
||||
/**
|
||||
* Send a reply to the given websocket client.
|
||||
*
|
||||
* The dispatcher has an internal thread for serializing
|
||||
* and sending out the event.
|
||||
*/
|
||||
void QueueReply(WebSocketReply&& reply);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Main processing function of the dispatcher.
|
||||
*
|
||||
* This runs on Zeek's main thread.
|
||||
*/
|
||||
void Process(const WebSocketEvent& event);
|
||||
|
||||
void Process(const WebSocketOpen& open);
|
||||
void Process(const WebSocketSubscribeFinished& fin);
|
||||
void Process(const WebSocketMessage& msg);
|
||||
void Process(const WebSocketClose& close);
|
||||
|
||||
|
||||
/**
|
||||
* Data structure for tracking WebSocket clients.
|
||||
*/
|
||||
struct WebSocketClientEntry {
|
||||
std::string id;
|
||||
std::shared_ptr<WebSocketClient> wsc;
|
||||
std::shared_ptr<zeek::cluster::Backend> backend;
|
||||
uint64_t msg_count = 0;
|
||||
std::list<WebSocketMessage> queue;
|
||||
};
|
||||
|
||||
|
||||
void HandleSubscriptions(WebSocketClientEntry& entry, std::string_view buf);
|
||||
void HandleEvent(WebSocketClientEntry& entry, std::string_view buf);
|
||||
|
||||
// Allow access to Process(WebSocketEvent)
|
||||
friend zeek::detail::OnLoopProcess<WebSocketEventDispatcher, WebSocketEvent>;
|
||||
|
||||
// Clients that this dispatcher is tracking.
|
||||
std::map<std::string, WebSocketClientEntry> clients;
|
||||
|
||||
// Connector to the IO loop.
|
||||
zeek::detail::OnLoopProcess<WebSocketEventDispatcher, WebSocketEvent>* onloop = nullptr;
|
||||
|
||||
// Thread replying to clients. Zeek's threading manager takes ownership.
|
||||
ReplyMsgThread* reply_msg_thread = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract WebSocket server.
|
||||
*/
|
||||
class WebSocketServer {
|
||||
public:
|
||||
WebSocketServer(std::unique_ptr<WebSocketEventDispatcher> demux) : dispatcher(std::move(demux)) {}
|
||||
virtual ~WebSocketServer() = default;
|
||||
|
||||
/**
|
||||
* Stop this server.
|
||||
*/
|
||||
void Terminate() {
|
||||
dispatcher->Terminate();
|
||||
|
||||
DoTerminate();
|
||||
}
|
||||
|
||||
WebSocketEventDispatcher& Dispatcher() { return *dispatcher; }
|
||||
|
||||
private:
|
||||
/**
|
||||
* Hook to be implemented when a server is terminated.
|
||||
*/
|
||||
virtual void DoTerminate() = 0;
|
||||
|
||||
std::unique_ptr<WebSocketEventDispatcher> dispatcher;
|
||||
};
|
||||
|
||||
/**
|
||||
* TLS configuration for a WebSocket server.
|
||||
*/
|
||||
struct TLSOptions {
|
||||
std::optional<std::string> cert_file;
|
||||
std::optional<std::string> key_file;
|
||||
bool enable_peer_verification = false;
|
||||
std::string ca_file;
|
||||
std::string ciphers;
|
||||
|
||||
/**
|
||||
* Is TLS enabled?
|
||||
*/
|
||||
bool TlsEnabled() const { return cert_file.has_value() && key_file.has_value(); }
|
||||
|
||||
bool operator==(const TLSOptions& o) const {
|
||||
return cert_file == o.cert_file && key_file == o.key_file &&
|
||||
enable_peer_verification == o.enable_peer_verification && ca_file == o.ca_file && ciphers == o.ciphers;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for a WebSocket server.
|
||||
*/
|
||||
struct ServerOptions {
|
||||
std::string host;
|
||||
uint16_t port = 0;
|
||||
int ping_interval_seconds = 5;
|
||||
int max_connections = 100;
|
||||
bool per_message_deflate = false;
|
||||
struct TLSOptions tls_options;
|
||||
|
||||
bool operator==(const ServerOptions& o) const {
|
||||
return host == o.host && port == o.port && ping_interval_seconds == o.ping_interval_seconds &&
|
||||
max_connections == o.max_connections && per_message_deflate == o.per_message_deflate &&
|
||||
tls_options == o.tls_options;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Start a WebSocket server.
|
||||
*
|
||||
* @param dispatcher The dispatcher to use for the server.
|
||||
* @param options Options for the server.
|
||||
*
|
||||
* @return Pointer to a new WebSocketServer instance or nullptr on error.
|
||||
*/
|
||||
std::unique_ptr<WebSocketServer> StartServer(std::unique_ptr<WebSocketEventDispatcher> dispatcher,
|
||||
const ServerOptions& options);
|
||||
|
||||
} // namespace websocket::detail
|
||||
} // namespace cluster
|
||||
} // namespace zeek
|
7
src/cluster/websocket/auxil/CMakeLists.txt
Normal file
7
src/cluster/websocket/auxil/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
option(IXWEBSOCKET_INSTALL "Install IXWebSocket" OFF)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(USE_TLS ON)
|
||||
set(USE_OPEN_SSL ON)
|
||||
|
||||
add_subdirectory(IXWebSocket)
|
1
src/cluster/websocket/auxil/IXWebSocket
Submodule
1
src/cluster/websocket/auxil/IXWebSocket
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 80e6c4fe48dcad816a0e684dbb269957f9073e79
|
13
src/cluster/websocket/events.bif
Normal file
13
src/cluster/websocket/events.bif
Normal file
|
@ -0,0 +1,13 @@
|
|||
module Cluster;
|
||||
|
||||
## Generated when a new WebSocket client has connected.
|
||||
##
|
||||
## endpoint: Various information about the WebSocket client.
|
||||
##
|
||||
## subscriptions: The WebSocket client's subscriptions as provided in the handshake.
|
||||
event websocket_client_added%(endpoint: EndpointInfo, subscriptions: string_vec%);
|
||||
|
||||
## Generated when a WebSocket client was lost.
|
||||
##
|
||||
## endpoint: Various information about the WebSocket client.
|
||||
event websocket_client_lost%(endpoint: EndpointInfo%);
|
|
@ -74,6 +74,7 @@ static std::unordered_map<std::string, unsigned int> func_attrs = {
|
|||
{"Analyzer::__tag", ATTR_FOLDABLE},
|
||||
{"Cluster::Backend::ZeroMQ::spawn_zmq_proxy_thread", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
{"Cluster::Backend::__init", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
{"Cluster::__listen_websocket", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
{"Cluster::__subscribe", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
{"Cluster::__unsubscribe", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
{"Cluster::make_event", ATTR_NO_SCRIPT_SIDE_EFFECTS},
|
||||
|
|
|
@ -378,6 +378,7 @@ static void terminate_zeek() {
|
|||
notifier::detail::registry.Terminate();
|
||||
log_mgr->Terminate();
|
||||
input_mgr->Terminate();
|
||||
cluster::manager->Terminate();
|
||||
thread_mgr->Terminate();
|
||||
|
||||
broker_mgr->Terminate();
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
error in <...>/main.zeek, line 654: Already listening on 127.0.0.1:<port> (Cluster::__listen_websocket(ws_opts_x))
|
||||
error in <...>/main.zeek, line 654: Already listening on 127.0.0.1:<port> (Cluster::__listen_websocket(ws_opts_wss_port))
|
||||
received termination signal
|
|
@ -0,0 +1,3 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
error in <...>/main.zeek, line 654: Invalid tls_options: No key_file field (Cluster::__listen_websocket(Cluster::options.0))
|
||||
error in <...>/main.zeek, line 654: Invalid tls_options: No cert_file field (Cluster::__listen_websocket(Cluster::options.3))
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,7 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
err1 {'type': 'error', 'code': 'deserialization_failed', 'message': 'failed to decode JSON object'}
|
||||
err2 {'type': 'error', 'code': 'deserialization_failed', 'message': 'failed to create Zeek event'}
|
||||
err3 {'type': 'error', 'code': 'deserialization_failed', 'message': 'failed to create Zeek event'}
|
||||
pong {'@data-type': 'string', 'data': 'pong'} {'@data-type': 'vector', 'data': [{'@data-type': 'string', 'data': 'Hello'}, {'@data-type': 'count', 'data': 42}]}
|
||||
err4 {'type': 'error', 'code': 'deserialization_failed', 'message': 'failed to decode JSON object'}
|
|
@ -0,0 +1,4 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
error: Unserialize error 'ping' arg_types.size()=2 and args.size()=1
|
||||
error: Unserialize error for event 'ping': broker value '42' type 'count' to Zeek type 'string string' failed
|
||||
received termination signal
|
|
@ -0,0 +1,4 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, [/zeek/event/my_topic]
|
||||
got ping: Hello, 42
|
||||
Cluster::websocket_client_lost
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,5 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
broken array response {'type': 'error', 'code': 'deserialization_failed', 'message': 'subscriptions not an array'}
|
||||
non string error {'type': 'error', 'code': 'deserialization_failed', 'message': 'individual subscription not a string'}
|
||||
mix error {'type': 'error', 'code': 'deserialization_failed', 'message': 'individual subscription not a string'}
|
||||
ack True
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,6 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_lost, 1
|
||||
Cluster::websocket_client_lost, 2
|
||||
Cluster::websocket_client_lost, 3
|
||||
Cluster::websocket_client_added, 1, [/duplicate, /is/okay, /topic/good]
|
||||
Cluster::websocket_client_lost, 4
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,4 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected ws_good!
|
||||
Connected ws_bad!
|
||||
Error for ws_bad {'type': 'error', 'code': 'invalid_uri', 'message': 'Invalid URI - use /v1/messages/json'}
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,3 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, [hello-good]
|
||||
Cluster::websocket_client_lost
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,3 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
unique ids 3
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,7 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) subscribed to [/topic/ws/1, /topic/ws/all]
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) subscribed to [/topic/ws/2, /topic/ws/all]
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) subscribed to [/topic/ws/3, /topic/ws/all]
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) gone
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) gone
|
||||
manager WebSocket client <nodeid> (127.0.0.1:<port>) gone
|
|
@ -0,0 +1,7 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, 1, [/topic/ws/1, /topic/ws/all]
|
||||
Cluster::websocket_client_added, 2, [/topic/ws/2, /topic/ws/all]
|
||||
Cluster::websocket_client_added, 3, [/topic/ws/3, /topic/ws/all]
|
||||
Cluster::websocket_client_lost, 1
|
||||
Cluster::websocket_client_lost, 2
|
||||
Cluster::websocket_client_lost, 3
|
|
@ -0,0 +1,4 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
error in <...>/listen-idempotent.zeek, line 43: Already listening on 127.0.0.1:<port> (Cluster::listen_websocket(ws_opts_x))
|
||||
error in <...>/listen-idempotent.zeek, line 47: Already listening on 127.0.0.1:<port> (Cluster::listen_websocket(ws_opts_wss_port))
|
||||
received termination signal
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,17 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
Sending ping 0
|
||||
Sending ping 1
|
||||
Sending ping 2
|
||||
Sending ping 3
|
||||
Sending ping 4
|
||||
Receiving pong 0
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 1}]
|
||||
Receiving pong 1
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 2}]
|
||||
Receiving pong 2
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 3}]
|
||||
Receiving pong 3
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 4}]
|
||||
Receiving pong 4
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 5}]
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,8 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, [/zeek/event/my_topic]
|
||||
got ping: python-websocket-client, 0
|
||||
got ping: python-websocket-client, 1
|
||||
got ping: python-websocket-client, 2
|
||||
got ping: python-websocket-client, 3
|
||||
got ping: python-websocket-client, 4
|
||||
Cluster::websocket_client_lost
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
17
testing/btest/Baseline/cluster.websocket.one/..client.out
Normal file
17
testing/btest/Baseline/cluster.websocket.one/..client.out
Normal file
|
@ -0,0 +1,17 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
Sending ping 0
|
||||
Receiving pong 0
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 1}]
|
||||
Sending ping 1
|
||||
Receiving pong 1
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 2}]
|
||||
Sending ping 2
|
||||
Receiving pong 2
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 3}]
|
||||
Sending ping 3
|
||||
Receiving pong 3
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 4}]
|
||||
Sending ping 4
|
||||
Receiving pong 4
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 5}]
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,8 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, [/zeek/event/my_topic]
|
||||
got ping: python-websocket-client, 0
|
||||
got ping: python-websocket-client, 1
|
||||
got ping: python-websocket-client, 2
|
||||
got ping: python-websocket-client, 3
|
||||
got ping: python-websocket-client, 4
|
||||
Cluster::websocket_client_lost
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
83
testing/btest/Baseline/cluster.websocket.three/..client.out
Normal file
83
testing/btest/Baseline/cluster.websocket.three/..client.out
Normal file
|
@ -0,0 +1,83 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
unique ids 3
|
||||
ws1 sending ping 0
|
||||
receiving pong 0
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 1}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 1}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 1}]
|
||||
ws2 sending ping 1
|
||||
receiving pong 1
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 2}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 2}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 2}]
|
||||
ws3 sending ping 2
|
||||
receiving pong 2
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 3}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 3}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 3}]
|
||||
ws1 sending ping 3
|
||||
receiving pong 3
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 4}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 4}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 4}]
|
||||
ws2 sending ping 4
|
||||
receiving pong 4
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 5}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 5}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 5}]
|
||||
ws3 sending ping 5
|
||||
receiving pong 5
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 6}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 6}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 6}]
|
||||
ws1 sending ping 6
|
||||
receiving pong 6
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 7}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 7}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 7}]
|
||||
ws2 sending ping 7
|
||||
receiving pong 7
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 8}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 8}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 8}]
|
||||
ws3 sending ping 8
|
||||
receiving pong 8
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 9}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 9}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 9}]
|
||||
ws1 sending ping 9
|
||||
receiving pong 9
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 10}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 10}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 10}]
|
||||
ws2 sending ping 10
|
||||
receiving pong 10
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 11}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 11}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 11}]
|
||||
ws3 sending ping 11
|
||||
receiving pong 11
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 12}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 12}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 12}]
|
||||
ws1 sending ping 12
|
||||
receiving pong 12
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 13}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 13}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 13}]
|
||||
ws2 sending ping 13
|
||||
receiving pong 13
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 14}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 14}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws2'}, {'@data-type': 'count', 'data': 14}]
|
||||
ws3 sending ping 14
|
||||
receiving pong 14
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 15}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 15}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws3'}, {'@data-type': 'count', 'data': 15}]
|
||||
ws1 sending ping 15
|
||||
receiving pong 15
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 16}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 16}]
|
||||
ev: topic /test/clients event name pong args [{'@data-type': 'string', 'data': 'orig_msg=ws1'}, {'@data-type': 'count', 'data': 16}]
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
23
testing/btest/Baseline/cluster.websocket.three/..manager.out
Normal file
23
testing/btest/Baseline/cluster.websocket.three/..manager.out
Normal file
|
@ -0,0 +1,23 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, 1, [/test/clients]
|
||||
Cluster::websocket_client_added, 2, [/test/clients]
|
||||
Cluster::websocket_client_added, 3, [/test/clients]
|
||||
got ping: ws1, 0
|
||||
got ping: ws2, 1
|
||||
got ping: ws3, 2
|
||||
got ping: ws1, 3
|
||||
got ping: ws2, 4
|
||||
got ping: ws3, 5
|
||||
got ping: ws1, 6
|
||||
got ping: ws2, 7
|
||||
got ping: ws3, 8
|
||||
got ping: ws1, 9
|
||||
got ping: ws2, 10
|
||||
got ping: ws3, 11
|
||||
got ping: ws1, 12
|
||||
got ping: ws2, 13
|
||||
got ping: ws3, 14
|
||||
got ping: ws1, 15
|
||||
Cluster::websocket_client_lost, 1
|
||||
Cluster::websocket_client_lost, 2
|
||||
Cluster::websocket_client_lost, 3
|
|
@ -0,0 +1,3 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
error in <...>/tls-usage-error.zeek, line 17: Invalid tls_options: No key_file field (Cluster::listen_websocket((coerce [$listen_host=127.0.0.1, $listen_port=1234/tcp, $tls_options=tls_options_no_key] to Cluster::WebSocketServerOptions)))
|
||||
error in <...>/tls-usage-error.zeek, line 18: Invalid tls_options: No cert_file field (Cluster::listen_websocket((coerce [$listen_host=127.0.0.1, $listen_port=1234/tcp, $tls_options=tls_options_no_cert] to Cluster::WebSocketServerOptions)))
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
17
testing/btest/Baseline/cluster.websocket.tls/..client.out
Normal file
17
testing/btest/Baseline/cluster.websocket.tls/..client.out
Normal file
|
@ -0,0 +1,17 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
Sending ping 0
|
||||
Receiving pong 0
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 1}]
|
||||
Sending ping 1
|
||||
Receiving pong 1
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 2}]
|
||||
Sending ping 2
|
||||
Receiving pong 2
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 3}]
|
||||
Sending ping 3
|
||||
Receiving pong 3
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 4}]
|
||||
Sending ping 4
|
||||
Receiving pong 4
|
||||
topic /zeek/event/my_topic event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 5}]
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,8 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Cluster::websocket_client_added, [/zeek/event/my_topic]
|
||||
got ping: python-websocket-client, 0
|
||||
got ping: python-websocket-client, 1
|
||||
got ping: python-websocket-client, 2
|
||||
got ping: python-websocket-client, 3
|
||||
got ping: python-websocket-client, 4
|
||||
Cluster::websocket_client_lost
|
|
@ -0,0 +1 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
|
@ -0,0 +1,54 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
Connected!
|
||||
Sending ping 0 - ws1
|
||||
Sending ping 0 - ws2
|
||||
Sending ping 1 - ws1
|
||||
Sending ping 1 - ws2
|
||||
Sending ping 2 - ws1
|
||||
Sending ping 2 - ws2
|
||||
Sending ping 3 - ws1
|
||||
Sending ping 3 - ws2
|
||||
Sending ping 4 - ws1
|
||||
Sending ping 4 - ws2
|
||||
Receiving ack - ws1
|
||||
Receiving ack - ws2
|
||||
Receiving pong 0 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 1}]
|
||||
Receiving pong 0 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 1}]
|
||||
Receiving pong 1 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 2}]
|
||||
Receiving pong 1 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 2}]
|
||||
Receiving pong 2 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 3}]
|
||||
Receiving pong 2 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 3}]
|
||||
Receiving pong 3 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 4}]
|
||||
Receiving pong 3 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 4}]
|
||||
Receiving pong 4 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 5}]
|
||||
Receiving pong 4 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 5}]
|
||||
Receiving pong 5 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 6}]
|
||||
Receiving pong 5 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 6}]
|
||||
Receiving pong 6 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 7}]
|
||||
Receiving pong 6 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 7}]
|
||||
Receiving pong 7 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 8}]
|
||||
Receiving pong 7 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 8}]
|
||||
Receiving pong 8 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 9}]
|
||||
Receiving pong 8 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 9}]
|
||||
Receiving pong 9 - ws0
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 10}]
|
||||
Receiving pong 9 - ws1
|
||||
topic /zeek/event/to_client event name pong args [{'@data-type': 'string', 'data': 'my-message'}, {'@data-type': 'count', 'data': 10}]
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
received termination signal
|
|
@ -0,0 +1,15 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
A Cluster::websocket_client_added, 1, [/zeek/event/to_client]
|
||||
A Cluster::websocket_client_added, 2, [/zeek/event/to_client]
|
||||
B got ping: ws1, 0
|
||||
B got ping: ws1, 1
|
||||
B got ping: ws1, 2
|
||||
B got ping: ws1, 3
|
||||
B got ping: ws1, 4
|
||||
B got ping: ws2, 0
|
||||
B got ping: ws2, 1
|
||||
B got ping: ws2, 2
|
||||
B got ping: ws2, 3
|
||||
B got ping: ws2, 4
|
||||
C Cluster::websocket_client_lost
|
||||
C Cluster::websocket_client_lost
|
|
@ -137,6 +137,7 @@ scripts/base/init-frameworks-and-bifs.zeek
|
|||
scripts/base/frameworks/control/__load__.zeek
|
||||
scripts/base/frameworks/control/main.zeek
|
||||
build/scripts/base/bif/cluster.bif.zeek
|
||||
build/scripts/base/bif/plugins/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
scripts/base/frameworks/cluster/pools.zeek
|
||||
scripts/base/utils/hash_hrw.zeek
|
||||
scripts/base/frameworks/config/__load__.zeek
|
||||
|
|
|
@ -137,6 +137,7 @@ scripts/base/init-frameworks-and-bifs.zeek
|
|||
scripts/base/frameworks/control/__load__.zeek
|
||||
scripts/base/frameworks/control/main.zeek
|
||||
build/scripts/base/bif/cluster.bif.zeek
|
||||
build/scripts/base/bif/plugins/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
scripts/base/frameworks/cluster/pools.zeek
|
||||
scripts/base/utils/hash_hrw.zeek
|
||||
scripts/base/frameworks/config/__load__.zeek
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
545 seen BiFs, 0 unseen BiFs (), 0 new BiFs ()
|
||||
546 seen BiFs, 0 unseen BiFs (), 0 new BiFs ()
|
||||
|
|
|
@ -339,6 +339,7 @@
|
|||
0.000000 MetaHookPost LoadFile(0, ./Zeek_BinaryReader.binary.bif.zeek, <...>/Zeek_BinaryReader.binary.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_BitTorrent.events.bif.zeek, <...>/Zeek_BitTorrent.events.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek, <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_ConfigReader.config.bif.zeek, <...>/Zeek_ConfigReader.config.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_ConnSize.events.bif.zeek, <...>/Zeek_ConnSize.events.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, ./Zeek_ConnSize.functions.bif.zeek, <...>/Zeek_ConnSize.functions.bif.zeek) -> -1
|
||||
|
@ -533,6 +534,7 @@
|
|||
0.000000 MetaHookPost LoadFile(0, base/init-frameworks-and-bifs.zeek, <...>/init-frameworks-and-bifs.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base/packet-protocols, <...>/packet-protocols) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base<...>/CPP-load.bif, <...>/CPP-load.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base<...>/Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base<...>/Zeek_GTPv1.events.bif, <...>/Zeek_GTPv1.events.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base<...>/Zeek_GTPv1.functions.bif, <...>/Zeek_GTPv1.functions.bif.zeek) -> -1
|
||||
0.000000 MetaHookPost LoadFile(0, base<...>/Zeek_KRB.types.bif, <...>/Zeek_KRB.types.bif.zeek) -> -1
|
||||
|
@ -647,6 +649,7 @@
|
|||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_BinaryReader.binary.bif.zeek, <...>/Zeek_BinaryReader.binary.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_BitTorrent.events.bif.zeek, <...>/Zeek_BitTorrent.events.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek, <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_ConfigReader.config.bif.zeek, <...>/Zeek_ConfigReader.config.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_ConnSize.events.bif.zeek, <...>/Zeek_ConnSize.events.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, ./Zeek_ConnSize.functions.bif.zeek, <...>/Zeek_ConnSize.functions.bif.zeek) -> (-1, <no content>)
|
||||
|
@ -841,6 +844,7 @@
|
|||
0.000000 MetaHookPost LoadFileExtended(0, base/init-frameworks-and-bifs.zeek, <...>/init-frameworks-and-bifs.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base/packet-protocols, <...>/packet-protocols) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base<...>/CPP-load.bif, <...>/CPP-load.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base<...>/Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base<...>/Zeek_GTPv1.events.bif, <...>/Zeek_GTPv1.events.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base<...>/Zeek_GTPv1.functions.bif, <...>/Zeek_GTPv1.functions.bif.zeek) -> (-1, <no content>)
|
||||
0.000000 MetaHookPost LoadFileExtended(0, base<...>/Zeek_KRB.types.bif, <...>/Zeek_KRB.types.bif.zeek) -> (-1, <no content>)
|
||||
|
@ -1288,6 +1292,7 @@
|
|||
0.000000 MetaHookPre LoadFile(0, ./Zeek_BinaryReader.binary.bif.zeek, <...>/Zeek_BinaryReader.binary.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_BitTorrent.events.bif.zeek, <...>/Zeek_BitTorrent.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek, <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_ConfigReader.config.bif.zeek, <...>/Zeek_ConfigReader.config.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_ConnSize.events.bif.zeek, <...>/Zeek_ConnSize.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, ./Zeek_ConnSize.functions.bif.zeek, <...>/Zeek_ConnSize.functions.bif.zeek)
|
||||
|
@ -1482,6 +1487,7 @@
|
|||
0.000000 MetaHookPre LoadFile(0, base/init-frameworks-and-bifs.zeek, <...>/init-frameworks-and-bifs.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, base/packet-protocols, <...>/packet-protocols)
|
||||
0.000000 MetaHookPre LoadFile(0, base<...>/CPP-load.bif, <...>/CPP-load.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, base<...>/Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, base<...>/Zeek_GTPv1.events.bif, <...>/Zeek_GTPv1.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, base<...>/Zeek_GTPv1.functions.bif, <...>/Zeek_GTPv1.functions.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFile(0, base<...>/Zeek_KRB.types.bif, <...>/Zeek_KRB.types.bif.zeek)
|
||||
|
@ -1596,6 +1602,7 @@
|
|||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_BinaryReader.binary.bif.zeek, <...>/Zeek_BinaryReader.binary.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_BitTorrent.events.bif.zeek, <...>/Zeek_BitTorrent.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek, <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_ConfigReader.config.bif.zeek, <...>/Zeek_ConfigReader.config.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_ConnSize.events.bif.zeek, <...>/Zeek_ConnSize.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, ./Zeek_ConnSize.functions.bif.zeek, <...>/Zeek_ConnSize.functions.bif.zeek)
|
||||
|
@ -1790,6 +1797,7 @@
|
|||
0.000000 MetaHookPre LoadFileExtended(0, base/init-frameworks-and-bifs.zeek, <...>/init-frameworks-and-bifs.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base/packet-protocols, <...>/packet-protocols)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base<...>/CPP-load.bif, <...>/CPP-load.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base<...>/Zeek_Cluster_WebSocket.events.bif.zeek, <...>/Zeek_Cluster_WebSocket.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base<...>/Zeek_GTPv1.events.bif, <...>/Zeek_GTPv1.events.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base<...>/Zeek_GTPv1.functions.bif, <...>/Zeek_GTPv1.functions.bif.zeek)
|
||||
0.000000 MetaHookPre LoadFileExtended(0, base<...>/Zeek_KRB.types.bif, <...>/Zeek_KRB.types.bif.zeek)
|
||||
|
@ -2236,6 +2244,7 @@
|
|||
0.000000 | HookLoadFile ./Zeek_BinaryReader.binary.bif.zeek <...>/Zeek_BinaryReader.binary.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_BitTorrent.events.bif.zeek <...>/Zeek_BitTorrent.events.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_Cluster_WebSocket.events.bif.zeek <...>/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_ConfigReader.config.bif.zeek <...>/Zeek_ConfigReader.config.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_ConnSize.events.bif.zeek <...>/Zeek_ConnSize.events.bif.zeek
|
||||
0.000000 | HookLoadFile ./Zeek_ConnSize.functions.bif.zeek <...>/Zeek_ConnSize.functions.bif.zeek
|
||||
|
@ -2443,6 +2452,7 @@
|
|||
0.000000 | HookLoadFile base/init-frameworks-and-bifs.zeek <...>/init-frameworks-and-bifs.zeek
|
||||
0.000000 | HookLoadFile base/packet-protocols <...>/packet-protocols
|
||||
0.000000 | HookLoadFile base<...>/CPP-load.bif <...>/CPP-load.bif.zeek
|
||||
0.000000 | HookLoadFile base<...>/Zeek_Cluster_WebSocket.events.bif.zeek <...>/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
0.000000 | HookLoadFile base<...>/Zeek_GTPv1.events.bif <...>/Zeek_GTPv1.events.bif.zeek
|
||||
0.000000 | HookLoadFile base<...>/Zeek_GTPv1.functions.bif <...>/Zeek_GTPv1.functions.bif.zeek
|
||||
0.000000 | HookLoadFile base<...>/Zeek_KRB.types.bif <...>/Zeek_KRB.types.bif.zeek
|
||||
|
@ -2544,6 +2554,7 @@
|
|||
0.000000 | HookLoadFileExtended ./Zeek_BinaryReader.binary.bif.zeek <...>/Zeek_BinaryReader.binary.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_BitTorrent.events.bif.zeek <...>/Zeek_BitTorrent.events.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek <...>/Zeek_Cluster_Backend_ZeroMQ.cluster_backend_zeromq.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_Cluster_WebSocket.events.bif.zeek <...>/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_ConfigReader.config.bif.zeek <...>/Zeek_ConfigReader.config.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_ConnSize.events.bif.zeek <...>/Zeek_ConnSize.events.bif.zeek
|
||||
0.000000 | HookLoadFileExtended ./Zeek_ConnSize.functions.bif.zeek <...>/Zeek_ConnSize.functions.bif.zeek
|
||||
|
@ -2751,6 +2762,7 @@
|
|||
0.000000 | HookLoadFileExtended base/init-frameworks-and-bifs.zeek <...>/init-frameworks-and-bifs.zeek
|
||||
0.000000 | HookLoadFileExtended base/packet-protocols <...>/packet-protocols
|
||||
0.000000 | HookLoadFileExtended base<...>/CPP-load.bif <...>/CPP-load.bif.zeek
|
||||
0.000000 | HookLoadFileExtended base<...>/Zeek_Cluster_WebSocket.events.bif.zeek <...>/Zeek_Cluster_WebSocket.events.bif.zeek
|
||||
0.000000 | HookLoadFileExtended base<...>/Zeek_GTPv1.events.bif <...>/Zeek_GTPv1.events.bif.zeek
|
||||
0.000000 | HookLoadFileExtended base<...>/Zeek_GTPv1.functions.bif <...>/Zeek_GTPv1.functions.bif.zeek
|
||||
0.000000 | HookLoadFileExtended base<...>/Zeek_KRB.types.bif <...>/Zeek_KRB.types.bif.zeek
|
||||
|
|
126
testing/btest/cluster/websocket/bad-event-args.zeek
Normal file
126
testing/btest/cluster/websocket/bad-event-args.zeek
Normal file
|
@ -0,0 +1,126 @@
|
|||
# @TEST-DOC: A WebSocket client sending invalid data for an event.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, msg, n);
|
||||
Cluster::publish("/zeek/event/my_topic", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
print "Cluster::websocket_client_added", subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
print "Cluster::websocket_client_lost";
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
def make_ping(event_args):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": topic,
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": event_args },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws:
|
||||
print("Connected!")
|
||||
# Send subscriptions
|
||||
ws.send(json.dumps([topic]))
|
||||
ack = json.loads(ws.recv())
|
||||
assert "type" in ack
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack
|
||||
assert "version" in ack
|
||||
|
||||
ws.send(json.dumps(make_ping(42)))
|
||||
err1 = json.loads(ws.recv())
|
||||
print("err1", err1)
|
||||
ws.send(json.dumps(make_ping([{"@data-type": "string", "data": "Hello"}])))
|
||||
err2 = json.loads(ws.recv())
|
||||
print("err2", err2)
|
||||
ws.send(json.dumps(make_ping([{"@data-type": "count", "data": 42}, {"@data-type": "string", "data": "Hello"}])))
|
||||
err3 = json.loads(ws.recv())
|
||||
print("err3", err3)
|
||||
|
||||
# This should be good ping(string, count)
|
||||
ws.send(json.dumps(make_ping([{"@data-type": "string", "data": "Hello"}, {"@data-type": "count", "data": 42}])))
|
||||
pong = json.loads(ws.recv())
|
||||
name, args, _ = pong["data"][2]["data"]
|
||||
print("pong", name, args)
|
||||
|
||||
# This one fails again
|
||||
ws.send(json.dumps(make_ping([{"@data-type": "money", "data": 0}])))
|
||||
err4 = json.loads(ws.recv())
|
||||
print("err4", err4)
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
102
testing/btest/cluster/websocket/bad-subscriptions.zeek
Normal file
102
testing/btest/cluster/websocket/bad-subscriptions.zeek
Normal file
|
@ -0,0 +1,102 @@
|
|||
# @TEST-DOC: Clients sends broken subscription arrays
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global event_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
global added = 0;
|
||||
global lost = 0;
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
++added;
|
||||
print "Cluster::websocket_client_added", added, subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
++lost;
|
||||
print "Cluster::websocket_client_lost", lost;
|
||||
if ( lost == 4 )
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws:
|
||||
ws.send('["broken", "brrr')
|
||||
err = json.loads(ws.recv())
|
||||
print("broken array response", err)
|
||||
|
||||
with connect(ws_url) as ws:
|
||||
ws.send('[1, 2]')
|
||||
err = json.loads(ws.recv())
|
||||
print("non string error", err)
|
||||
|
||||
with connect(ws_url) as ws:
|
||||
ws.send('[1, "/my_topic"]')
|
||||
err = json.loads(ws.recv())
|
||||
print("mix error", err)
|
||||
|
||||
# This should work - maybe duplicate isn't great, but good for testing.
|
||||
with connect(ws_url) as ws:
|
||||
ws.send('["/topic/good", "/duplicate", "/duplicate", "/is/okay"]')
|
||||
ack = json.loads(ws.recv())
|
||||
print("ack", ack["type"] == "ack")
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
101
testing/btest/cluster/websocket/bad-url.zeek
Normal file
101
testing/btest/cluster/websocket/bad-url.zeek
Normal file
|
@ -0,0 +1,101 @@
|
|||
# @TEST-DOC: Run a single node cluster (manager) with a websocket server and have a single client connect.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 5
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, "my-message", ping_count);
|
||||
Cluster::publish("/zeek/event/my_topic", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
print "Cluster::websocket_client_added", subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
print "Cluster::websocket_client_lost";
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
import websockets.exceptions
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_prefix = f'ws://127.0.0.1:{ws_port}'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
|
||||
def run(ws_prefix):
|
||||
with connect(ws_prefix + '/v1/messages/json') as ws_good:
|
||||
print('Connected ws_good!')
|
||||
with connect(ws_prefix + '/v0/messages/json') as ws_bad:
|
||||
print('Connected ws_bad!')
|
||||
try:
|
||||
err = json.loads(ws_bad.recv())
|
||||
except websockets.exceptions.ConnectionClosedError as e:
|
||||
pass
|
||||
|
||||
print('Error for ws_bad', err)
|
||||
|
||||
ws_good.send(json.dumps(['hello-good']))
|
||||
ack = json.loads(ws_good.recv())
|
||||
assert 'type' in ack
|
||||
assert ack['type'] == 'ack'
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_prefix)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@TEST-END-FILE
|
111
testing/btest/cluster/websocket/cluster-log.zeek
Normal file
111
testing/btest/cluster/websocket/cluster-log.zeek
Normal file
|
@ -0,0 +1,111 @@
|
|||
# @TEST-DOC: Test websocket clients appearing in cluster.log
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: zeek-cut node message < ./manager/cluster.log | sed -r "s/client '.+' /client <nodeid> /g" | sed -r "s/:[0-9]+/:<port>/g" > ./manager/cluster.log.cannonified
|
||||
# @TEST-EXEC: btest-diff ./manager/cluster.log.cannonified
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
# Have the manager create cluster.log
|
||||
redef Log::enable_local_logging = T;
|
||||
redef Log::default_rotation_interval = 0sec;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/test/manager");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, fmt("orig_msg=%s", msg), ping_count);
|
||||
Cluster::publish("/test/clients", e);
|
||||
}
|
||||
|
||||
global added = 0;
|
||||
global lost = 0;
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
++added;
|
||||
print "Cluster::websocket_client_added", added, subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
++lost;
|
||||
print "Cluster::websocket_client_lost", lost;
|
||||
if ( lost == 3 )
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws1:
|
||||
with connect(ws_url) as ws2:
|
||||
with connect(ws_url) as ws3:
|
||||
clients = [ws1, ws2, ws3]
|
||||
print("Connected!")
|
||||
ids = set()
|
||||
for i, c in enumerate(clients, 1):
|
||||
c.send(json.dumps([f"/topic/ws/{i}", "/topic/ws/all"]))
|
||||
ack = json.loads(c.recv())
|
||||
assert "type" in ack, repr(ack)
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack, repr(ack)
|
||||
assert "version" in ack
|
||||
ids.add(ack["endpoint"])
|
||||
|
||||
print("unique ids", len(ids))
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
50
testing/btest/cluster/websocket/listen-idempotent.zeek
Normal file
50
testing/btest/cluster/websocket/listen-idempotent.zeek
Normal file
|
@ -0,0 +1,50 @@
|
|||
# @TEST-DOC: Allow listening with the same tls_options on the same port, but fail for disagreeing tls_options.
|
||||
#
|
||||
# @TEST-EXEC: zeek -b %INPUT
|
||||
# @TEST-EXEC: TEST_DIFF_CANONIFIER='sed -E "s/127.0.0.1:[0-9]+/127.0.0.1:<port>/g" | $SCRIPTS/diff-remove-abspath' btest-diff .stderr
|
||||
#
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
# @TEST-PORT: WEBSOCKET_SECURE_PORT
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
local tls_options = Cluster::WebSocketTLSOptions(
|
||||
$cert_file="../localhost.crt",
|
||||
$key_file="../localhost.key",
|
||||
);
|
||||
|
||||
local tls_options_2 = Cluster::WebSocketTLSOptions(
|
||||
$cert_file="../localhost.crt",
|
||||
$key_file="../localhost.key",
|
||||
);
|
||||
local ws_port = to_port(getenv("WEBSOCKET_PORT"));
|
||||
local wss_port = to_port(getenv("WEBSOCKET_SECURE_PORT"));
|
||||
|
||||
local ws_opts = Cluster::WebSocketServerOptions($listen_host="127.0.0.1", $listen_port=ws_port);
|
||||
local ws_opts_x = copy(ws_opts);
|
||||
ws_opts_x$tls_options = tls_options;
|
||||
|
||||
local ws_opts_wss_port = Cluster::WebSocketServerOptions($listen_host="127.0.0.1", $listen_port=wss_port);
|
||||
|
||||
local ws_tls_opts = Cluster::WebSocketServerOptions(
|
||||
$listen_host="127.0.0.1",
|
||||
$listen_port=wss_port,
|
||||
$tls_options=tls_options,
|
||||
);
|
||||
# Same as ws_tls_opts
|
||||
local ws_tls_opts_copy = Cluster::WebSocketServerOptions(
|
||||
$listen_host="127.0.0.1",
|
||||
$listen_port=wss_port,
|
||||
$tls_options=tls_options_2,
|
||||
);
|
||||
|
||||
assert Cluster::listen_websocket(ws_opts);
|
||||
assert Cluster::listen_websocket(ws_opts);
|
||||
assert ! Cluster::listen_websocket(ws_opts_x);
|
||||
assert Cluster::listen_websocket(ws_tls_opts);
|
||||
assert Cluster::listen_websocket(ws_tls_opts);
|
||||
assert Cluster::listen_websocket(ws_tls_opts_copy);
|
||||
assert ! Cluster::listen_websocket(ws_opts_wss_port);
|
||||
|
||||
terminate();
|
||||
}
|
123
testing/btest/cluster/websocket/one-pipelining.zeek
Normal file
123
testing/btest/cluster/websocket/one-pipelining.zeek
Normal file
|
@ -0,0 +1,123 @@
|
|||
# @TEST-DOC: Send subscriptions and events without waiting for pong, should be okay, the websocket server will queue this a bit.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, "my-message", ping_count);
|
||||
Cluster::publish("/zeek/event/my_topic", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
print "Cluster::websocket_client_added", subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
print "Cluster::websocket_client_lost";
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
def make_ping(c):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": topic,
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": [ # event args
|
||||
{"@data-type": "string", "data": f"python-websocket-client"},
|
||||
{"@data-type": "count", "data": c},
|
||||
], },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws:
|
||||
print("Connected!")
|
||||
# Send subscriptions
|
||||
ws.send(json.dumps([topic]))
|
||||
|
||||
for i in range(5):
|
||||
print("Sending ping", i)
|
||||
ws.send(json.dumps(make_ping(i)))
|
||||
|
||||
ack = json.loads(ws.recv())
|
||||
assert "type" in ack
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack
|
||||
assert "version" in ack
|
||||
|
||||
for i in range(5):
|
||||
print("Receiving pong", i)
|
||||
pong = json.loads(ws.recv())
|
||||
assert pong["@data-type"] == "vector"
|
||||
ev = pong["data"][2]["data"]
|
||||
print("topic", pong["topic"], "event name", ev[0]["data"], "args", ev[1]["data"])
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
120
testing/btest/cluster/websocket/one.zeek
Normal file
120
testing/btest/cluster/websocket/one.zeek
Normal file
|
@ -0,0 +1,120 @@
|
|||
# @TEST-DOC: Run a single node cluster (manager) with a websocket server and have a single client connect.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, "my-message", ping_count);
|
||||
Cluster::publish("/zeek/event/my_topic", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
print "Cluster::websocket_client_added", subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
print "Cluster::websocket_client_lost";
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
def make_ping(c):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": topic,
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": [ # event args
|
||||
{"@data-type": "string", "data": f"python-websocket-client"},
|
||||
{"@data-type": "count", "data": c},
|
||||
], },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws:
|
||||
print("Connected!")
|
||||
# Send subscriptions
|
||||
ws.send(json.dumps([topic]))
|
||||
ack = json.loads(ws.recv())
|
||||
assert "type" in ack
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack
|
||||
assert "version" in ack
|
||||
|
||||
for i in range(5):
|
||||
print("Sending ping", i)
|
||||
ws.send(json.dumps(make_ping(i)))
|
||||
print("Receiving pong", i)
|
||||
pong = json.loads(ws.recv())
|
||||
assert pong["@data-type"] == "vector"
|
||||
ev = pong["data"][2]["data"]
|
||||
print("topic", pong["topic"], "event name", ev[0]["data"], "args", ev[1]["data"])
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
137
testing/btest/cluster/websocket/three.zeek
Normal file
137
testing/btest/cluster/websocket/three.zeek
Normal file
|
@ -0,0 +1,137 @@
|
|||
# @TEST-DOC: Run a single node cluster (manager) with a websocket server, have three clients connect.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/test/manager");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, fmt("orig_msg=%s", msg), ping_count);
|
||||
Cluster::publish("/test/clients", e);
|
||||
}
|
||||
|
||||
global added = 0;
|
||||
global lost = 0;
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
++added;
|
||||
print "Cluster::websocket_client_added", added, subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
++lost;
|
||||
print "Cluster::websocket_client_lost", lost;
|
||||
if ( lost == 3 )
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/test/clients'
|
||||
|
||||
def make_ping(c, who):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": "/test/manager",
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": [ # event args
|
||||
{"@data-type": "string", "data": who},
|
||||
{"@data-type": "count", "data": c},
|
||||
], },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws1:
|
||||
with connect(ws_url) as ws2:
|
||||
with connect(ws_url) as ws3:
|
||||
clients = [ws1, ws2, ws3]
|
||||
print("Connected!")
|
||||
ids = set()
|
||||
for c in clients:
|
||||
c.send(json.dumps([topic]))
|
||||
for c in clients:
|
||||
ack = json.loads(c.recv())
|
||||
assert "type" in ack, repr(ack)
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack, repr(ack)
|
||||
assert "version" in ack
|
||||
ids.add(ack["endpoint"])
|
||||
|
||||
print("unique ids", len(ids))
|
||||
|
||||
for i in range(16):
|
||||
c = clients[i % len(clients)]
|
||||
name = f"ws{(i % len(clients)) + 1}"
|
||||
print(name, "sending ping", i)
|
||||
c.send(json.dumps(make_ping(i, name)))
|
||||
|
||||
print("receiving pong", i)
|
||||
for c in clients:
|
||||
pong = json.loads(c.recv())
|
||||
ev = pong["data"][2]["data"]
|
||||
print("ev: topic", pong["topic"], "event name", ev[0]["data"], "args", ev[1]["data"])
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
19
testing/btest/cluster/websocket/tls-usage-error.zeek
Normal file
19
testing/btest/cluster/websocket/tls-usage-error.zeek
Normal file
|
@ -0,0 +1,19 @@
|
|||
# @TEST-DOC: Calling listen_websocket() with badly configured WebSocketTLSOptions.
|
||||
#
|
||||
# @TEST-EXEC: zeek -b %INPUT
|
||||
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr
|
||||
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
local tls_options_no_key = Cluster::WebSocketTLSOptions(
|
||||
$cert_file="../localhost.crt",
|
||||
);
|
||||
|
||||
local tls_options_no_cert = Cluster::WebSocketTLSOptions(
|
||||
$key_file="../localhost.key",
|
||||
);
|
||||
|
||||
assert ! Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=1234/tcp, $tls_options=tls_options_no_key]);
|
||||
assert ! Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=1234/tcp, $tls_options=tls_options_no_cert]);
|
||||
}
|
151
testing/btest/cluster/websocket/tls.zeek
Normal file
151
testing/btest/cluster/websocket/tls.zeek
Normal file
|
@ -0,0 +1,151 @@
|
|||
# @TEST-DOC: Run a single node cluster (manager) with a websocket server that has TLS enabled.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.asyncio'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: chmod +x gen-localhost-certs.sh
|
||||
# @TEST-EXEC: ./gen-localhost-certs.sh
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: btest-diff ./manager/out
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/my_topic");
|
||||
|
||||
local tls_options = Cluster::WebSocketTLSOptions(
|
||||
$cert_file="../localhost.crt",
|
||||
$key_file="../localhost.key",
|
||||
);
|
||||
|
||||
local ws_server_options = Cluster::WebSocketServerOptions(
|
||||
$listen_host="127.0.0.1",
|
||||
$listen_port=to_port(getenv("WEBSOCKET_PORT")),
|
||||
$tls_options=tls_options,
|
||||
);
|
||||
|
||||
Cluster::listen_websocket(ws_server_options);
|
||||
}
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
++ping_count;
|
||||
print fmt("got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, "my-message", ping_count);
|
||||
Cluster::publish("/zeek/event/my_topic", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
print "Cluster::websocket_client_added", subscriptions;
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
print "Cluster::websocket_client_lost";
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import asyncio, json, os, socket, time
|
||||
from websockets.asyncio.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'wss://localhost:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/my_topic'
|
||||
|
||||
# Make the websockets library use the custom server cert.
|
||||
# https://stackoverflow.com/a/55856969
|
||||
os.environ["SSL_CERT_FILE"] = "../localhost.crt"
|
||||
|
||||
def make_ping(c):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": topic,
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": [ # event args
|
||||
{"@data-type": "string", "data": f"python-websocket-client"},
|
||||
{"@data-type": "count", "data": c},
|
||||
], },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
async def run():
|
||||
async with connect(ws_url, family=socket.AF_INET) as ws:
|
||||
print("Connected!")
|
||||
# Send subscriptions
|
||||
await ws.send(json.dumps([topic]))
|
||||
ack = json.loads(await ws.recv())
|
||||
assert "type" in ack
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack
|
||||
assert "version" in ack
|
||||
|
||||
for i in range(5):
|
||||
print("Sending ping", i)
|
||||
await ws.send(json.dumps(make_ping(i)))
|
||||
print("Receiving pong", i)
|
||||
pong = json.loads(await ws.recv())
|
||||
assert pong["@data-type"] == "vector"
|
||||
ev = pong["data"][2]["data"]
|
||||
print("topic", pong["topic"], "event name", ev[0]["data"], "args", ev[1]["data"])
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
asyncio.run(run())
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
||||
|
||||
# The cert and key were generated with OpenSSL using the following command,
|
||||
# taken from https://letsencrypt.org/docs/certificates-for-localhost/
|
||||
#
|
||||
# The test will generate the script, but the certificate is valid
|
||||
# for 10 years.
|
||||
@TEST-START-FILE gen-localhost-certs.sh
|
||||
#!/usr/bin/env bash
|
||||
openssl req -x509 -out localhost.crt -keyout localhost.key \
|
||||
-newkey rsa:2048 -nodes -sha256 -days 3650 \
|
||||
-subj '/CN=localhost' -extensions EXT -config <( \
|
||||
printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nbasicConstraints=CA:TRUE")
|
||||
@TEST-END-FILE
|
158
testing/btest/cluster/websocket/two-pipelining.zeek
Normal file
158
testing/btest/cluster/websocket/two-pipelining.zeek
Normal file
|
@ -0,0 +1,158 @@
|
|||
# @TEST-DOC: Send subscriptions and events without waiting for pong, should be okay, the websocket server will queue this a bit.
|
||||
#
|
||||
# @TEST-REQUIRES: have-zeromq
|
||||
# @TEST-REQUIRES: python3 -c 'import websockets.sync'
|
||||
#
|
||||
# @TEST-GROUP: cluster-zeromq
|
||||
#
|
||||
# @TEST-PORT: XPUB_PORT
|
||||
# @TEST-PORT: XSUB_PORT
|
||||
# @TEST-PORT: LOG_PULL_PORT
|
||||
# @TEST-PORT: WEBSOCKET_PORT
|
||||
#
|
||||
# @TEST-EXEC: cp $FILES/zeromq/cluster-layout-simple.zeek cluster-layout.zeek
|
||||
# @TEST-EXEC: cp $FILES/zeromq/test-bootstrap.zeek zeromq-test-bootstrap.zeek
|
||||
#
|
||||
# @TEST-EXEC: zeek -b --parse-only manager.zeek
|
||||
# @TEST-EXEC: python3 -m py_compile client.py
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-run manager "ZEEKPATH=$ZEEKPATH:.. && CLUSTER_NODE=manager zeek -b ../manager.zeek >out"
|
||||
# @TEST-EXEC: btest-bg-run client "python3 ../client.py >out"
|
||||
#
|
||||
# @TEST-EXEC: btest-bg-wait 30
|
||||
# @TEST-EXEC: sort ./manager/out > ./manager/out.sorted
|
||||
# @TEST-EXEC: btest-diff ./manager/out.sorted
|
||||
# @TEST-EXEC: btest-diff ./manager/.stderr
|
||||
# @TEST-EXEC: btest-diff ./client/out
|
||||
# @TEST-EXEC: btest-diff ./client/.stderr
|
||||
|
||||
# @TEST-START-FILE manager.zeek
|
||||
@load ./zeromq-test-bootstrap
|
||||
redef exit_only_after_terminate = T;
|
||||
|
||||
global ping_count = 0;
|
||||
|
||||
global ping: event(msg: string, c: count) &is_used;
|
||||
global pong: event(msg: string, c: count) &is_used;
|
||||
|
||||
event zeek_init()
|
||||
{
|
||||
Cluster::subscribe("/zeek/event/to_manager");
|
||||
Cluster::listen_websocket([$listen_host="127.0.0.1", $listen_port=to_port(getenv("WEBSOCKET_PORT"))]);
|
||||
}
|
||||
|
||||
global added = 0;
|
||||
global lost = 0;
|
||||
|
||||
type Item: record {
|
||||
msg: string;
|
||||
n: count;
|
||||
};
|
||||
|
||||
global queue: vector of Item;
|
||||
|
||||
event ping(msg: string, n: count) &is_used
|
||||
{
|
||||
# Queue the pings if we haven't seen both clients yet.
|
||||
if ( added < 2 )
|
||||
{
|
||||
queue += Item($msg=msg, $n=n);
|
||||
return;
|
||||
}
|
||||
|
||||
++ping_count;
|
||||
print fmt("B got ping: %s, %s", msg, n);
|
||||
local e = Cluster::make_event(pong, "my-message", ping_count);
|
||||
Cluster::publish("/zeek/event/to_client", e);
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_added(info: Cluster::EndpointInfo, subscriptions: string_vec)
|
||||
{
|
||||
++added;
|
||||
print "A Cluster::websocket_client_added", added, subscriptions;
|
||||
|
||||
if ( added == 2 )
|
||||
{
|
||||
# Anything in the queue?
|
||||
for ( _, item in queue )
|
||||
event ping(item$msg, item$n);
|
||||
}
|
||||
}
|
||||
|
||||
event Cluster::websocket_client_lost(info: Cluster::EndpointInfo)
|
||||
{
|
||||
++lost;
|
||||
print "C Cluster::websocket_client_lost";
|
||||
if ( lost == 2 )
|
||||
terminate();
|
||||
}
|
||||
# @TEST-END-FILE
|
||||
|
||||
|
||||
@TEST-START-FILE client.py
|
||||
import json, os, time
|
||||
from websockets.sync.client import connect
|
||||
|
||||
ws_port = os.environ['WEBSOCKET_PORT'].split('/')[0]
|
||||
ws_url = f'ws://127.0.0.1:{ws_port}/v1/messages/json'
|
||||
topic = '/zeek/event/to_client'
|
||||
|
||||
def make_ping(c, who):
|
||||
return {
|
||||
"type": "data-message",
|
||||
"topic": "/zeek/event/to_manager",
|
||||
"@data-type": "vector",
|
||||
"data": [
|
||||
{"@data-type": "count", "data": 1}, # Format
|
||||
{"@data-type": "count", "data": 1}, # Type
|
||||
{"@data-type": "vector", "data": [
|
||||
{ "@data-type": "string", "data": "ping"}, # Event name
|
||||
{ "@data-type": "vector", "data": [ # event args
|
||||
{"@data-type": "string", "data": who},
|
||||
{"@data-type": "count", "data": c},
|
||||
], },
|
||||
], },
|
||||
],
|
||||
}
|
||||
|
||||
def run(ws_url):
|
||||
with connect(ws_url) as ws1:
|
||||
with connect(ws_url) as ws2:
|
||||
clients = [ws1, ws2]
|
||||
print("Connected!")
|
||||
# Send subscriptions
|
||||
for ws in clients:
|
||||
ws.send(json.dumps([topic]))
|
||||
|
||||
for i in range(5):
|
||||
for c, ws in enumerate(clients, 1):
|
||||
print(f"Sending ping {i} - ws{c}")
|
||||
ws.send(json.dumps(make_ping(i, f"ws{c}")))
|
||||
|
||||
for c, ws in enumerate(clients, 1):
|
||||
print(f"Receiving ack - ws{c}")
|
||||
ack = json.loads(ws.recv())
|
||||
assert "type" in ack
|
||||
assert ack["type"] == "ack"
|
||||
assert "endpoint" in ack
|
||||
assert "version" in ack
|
||||
|
||||
for i in range(10):
|
||||
for c, ws in enumerate(clients):
|
||||
print(f"Receiving pong {i} - ws{c}")
|
||||
pong = json.loads(ws.recv())
|
||||
assert pong["@data-type"] == "vector"
|
||||
ev = pong["data"][2]["data"]
|
||||
print("topic", pong["topic"], "event name", ev[0]["data"], "args", ev[1]["data"])
|
||||
|
||||
def main():
|
||||
for _ in range(100):
|
||||
try:
|
||||
run(ws_url)
|
||||
break
|
||||
except ConnectionRefusedError:
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@TEST-END-FILE
|
|
@ -98,6 +98,7 @@ global known_BiFs = set(
|
|||
"Broker::make_event",
|
||||
"Broker::publish",
|
||||
"Cluster::Backend::__init",
|
||||
"Cluster::__listen_websocket",
|
||||
"Cluster::__subscribe",
|
||||
"Cluster::__unsubscribe",
|
||||
"Cluster::make_event",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue