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, 依赖分析, 协议分析, 可视化界面, 多语言支持, 安全工具开发, 安全测试框架, 异步编程, 无后门, 权限提升, 网络安全, 网络流量审计, 网络编程, 网络通信, 软件开发包, 连接器, 逆向工具, 通知系统, 隐私保护