bbannier/zeek-websocket-rs

GitHub: bbannier/zeek-websocket-rs

为 Zeek 的 WebSocket API 提供 Rust 核心库及 Python、C 语言绑定,支持事件订阅、发布和类型转换。

Stars: 1 | Forks: 0

# 用于通过 WebSocket 与 Zeek 交互的 Rust 类型 该库提供了用于与 [Zeek](https://zeek.org) 的 WebSocket API 交互的类型。有关更多详细信息,请参阅[文档](https://bbannier.github.io/zeek-websocket-rs/zeek_websocket/index.html)。 ## 语言绑定 虽然这主要是一个 Rust 库,但我们为 [Python](#python-bindings) 和 [C](#c-bindings) 提供了绑定。 ### Python 绑定 Python 绑定是使用 [PyO3](https://github.com/PyO3/pyo3) 生成的,这使得用户可以完全透明地使用 Rust。 我们提供了两种与 Zeek 交互的方式: - [`ZeekClient`](bindings/python/zeek_websocket/zeek_websocket.pyi) 用于异步接口 - [`Client`](bindings/python/zeek_websocket/__init__.py) 用于同步接口 如果可能,我们建议使用 `ZeekClient`。 `ZeekClient` 和 `Client` 都允许作为 [`Event`](bindings/python/zeek_websocket/zeek_websocket.pyi) 值接收和发送 Zeek 事件。 #### 示例:异步 API ``` # Connect an asynchronous client to the Zeek WebSocket API endpoint. class Client(ZeekClient): async def connected(self, ack: dict[str, str]) -> None: print(f"Client connected to endpoint {ack}") # Once connected publish a "ping" event. await self.publish("/ping", Event("ping", ["hi"], ())) async def event(self, topic: str, event: Event) -> None: print(f"Received {event} on {topic}") # Stop the client once we have seen an event. self.disconnect() async def error(self, error: str) -> None: raise NotImplementedError(error) # Run the client until it either explicitly disconnects, or hits a fatal error. await Service.run(Client(), "client", mock_server, ["/ping"]) ``` #### 示例:同步 API ``` # Connect a synchronous client to the Zeek WebSocket API endpoint. client = Client( "client", endpoint_uri="ws://127.0.0.1:80/v1/messages/json", topics=["/topic1"]) # Try to receive an event. Without explicit `timeout` this blocks until some # data was received, but might still return `None`. # # NOTE: This function should be called regularly if we expect Zeek to send us # _any_ data, e.g., if we subscribed to any topics to ensure that messages # received by the WebSocket client library are consumed. Otherwise it might # overflow which would lead to disconnects. if recv := client.receive(): topic, event = recv print(f"Received {event} on {topic}") # Publish a `ping` event. This assumes the Zeek-side event is declared as # # global ping: event(n: count); # ping = Event(name="ping", args=(4711, ), metadata=()) client.publish(topic="/topic1", ping) ``` #### Python 与 Zeek WebSocket API 类型之间的数据映射 Zeek WebSocket API 中使用的类型与原生 Python 类型并非一一对应,因此需要显式的类型转换。该库公开了 [`Value`](bindings/python/zeek_websocket/__init__.py) 类型,它代表 Zeek API 能理解的数据值。`Value` 具有多个表示更具体类型的基类,例如,Zeek `int` 表示为 `Value.Integer`, ``` print(f"{Value.Integer(4711)}") # Prints 'Integer(4711)'. ``` 库的 [stub 文件](bindings/python/zeek_websocket/zeek_websocket.pyi) 中记录了受支持类型的完整列表。 该库提供了一个便捷函数 `make_value`,可用于自动推断匹配的 `Value` 变体, ``` print(f"{make_value("abc")}") # Prints 'String("abc")'. ``` 在上一节创建 `Event` 时,我们传递了参数 `(4711,)`,这也利用了隐式类型转换,`4711` 被隐式映射为 `Value.Integer`, ``` ping = Event(name="ping", args=(4711, ), metadata=()) print(ping) # Event { name: "ping", args: [Integer(4711)], metadata: [] } ``` 我们本可以显式地使用 ``` ping = Event(name="ping", args=(Value.Integer(4711), ), metadata=()) print(ping) # Event { name: "ping", args: [Integer(4711)], metadata: [] } ``` 可以通过 `value` 属性将 `Value` 映射为原生 Python 值,例如, ``` x = make_value("abc") # Creates a `Value.String`. assert x.value == "abc" assert type(x.value) == str ``` #### Python 枚举和类的特殊处理 Zeek WebSocket API 可以表示 Zeek `enum` 和 `record` 值,但其 schema 并不属于协议的数据负载。这是为了支持客户端可能处于不同 schema 版本,甚至完全不知道具体 Zeek 类型的情况。因此,Python 绑定始终可以接收任何 `enum` 或 `record` 值。 但这使得检查和构造此类值变得繁琐,因此如果存在自定义 Python 类型,该库提供了将 Zeek `enum` 和 `record` 值转换为原生 Python 类型的功。 ##### 记录 (Records) 虽然我们支持从任何 Python 类构造 `Value`,例如, ``` # NOTE: Discouraged, see below. class X: def __init__(self, a: int, b: str): self.a = a self.b = b print(make_value(X(4711, "abc"))) # Prints 'Record({"a": Count(4711), "b": String("abc")})'. ``` 但我们只支持通过 `as_record` 将 `Value` 转换为 dataclass 的 Python 实例: ``` # NOTE: Equivalent to example above, but more powerful. @dataclasses.dataclass class X: a: int b: str x = make_value(X(4711, "abc")) # Record({"a": Count(4711), "b": String("abc")}). # Convert to a concrete Python type by providing the target type. print(x.as_record(X)) # Prints 'X(a=4711, b='abc')'. ``` ##### 枚举 (Enums) 我们支持与 `enum.Enum` 值的实例进行相互转换,例如, ``` class E(enum.Enum): a = 1 b = 2 e = E.a x = Value.Enum(e.name) # Or `make_value(e)`. assert x.as_enum(E) == E.a ``` ### C 绑定 C 绑定是使用 [cbindgen](https://github.com/mozilla/cbindgen/) 动态创建的,并通过 [corrosion-rs](https://github.com/corrosion-rs/corrosion) 实现自动化,以便在 CMake 中使用。我们提供静态存档和共享库,用于在 CMake 的 `STATIC` 或 `SHARED` 配置中进行构建。 构建该库需要 Rust 工具链。我们需要相当新的 Rust 版本,建议使用 [rustup](https://rustup.rs/) 安装 Rust,许多包管理器中都提供该工具。可以使用 rustup 安装最小但足够的工具链, ``` rustup toolchain install stable --profile minimal ``` 代码库在 [`bindings/c/examples/`](bindings/c/examples/CMakeLists.txt) 中包含示例 CMake 配置。为了演示,我们还提供了 [C](bindings/c/examples/example.c) 和 [C++](bindings/c/examples/example.cc) 的示例客户端。 这两个示例都包含库提供的头文件 `zeek-websocket.h`,其中包含额外的文档。由于它是在依赖项需要时生成的,因此它存在于 CMake 构建文件夹中,可能位于路径 `/_deps/zeekwebsocket-build/corrosion_generated/cbindgen/zeek_websocket_c/include/zeek-websocket.h`。 可以通过构建目标 `_corrosion_cbindgen_zeek_websocket_c_bindings_zeek_websocket_h` 手动生成它。
标签:API 绑定, Bash脚本, C/C++, IPv6支持, PyO3, Python, Rootkit, Rust, WebSocket, Zeek, 事件驱动, 事务性I/O, 依赖分析, 协议分析, 可视化界面, 多语言支持, 安全工具开发, 安全测试框架, 异步编程, 无后门, 权限提升, 网络安全, 网络流量审计, 网络编程, 网络通信, 软件开发包, 连接器, 逆向工具, 通知系统, 隐私保护