zeek/zeek-websocket-rs
GitHub: zeek/zeek-websocket-rs
为 Zeek 的 WebSocket API 提供多语言(Rust、Python、C)类型绑定和事件交互接口的客户端库。
Stars: 2 | 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
```
# 连接异步客户端到 Zeek WebSocket API 端点。
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)
# 运行客户端,直到其显式断开连接或遇到致命错误。
await Service.run(Client(), "client", mock_server, ["/ping"])
```
#### 示例:同步 API
```
# 连接同步客户端到 Zeek WebSocket API 端点。
client = Client(
"client", endpoint_uri="ws://127.0.0.1:80/v1/messages/json", topics=["/topic1"])
# 尝试接收一个事件。如果没有显式的 `timeout`,此操作会阻塞,直到接收到某些
# 数据,但仍可能返回 `None`。
# # 注意:如果我们期望 Zeek 向我们发送
# 任何数据,则应定期调用此函数,例如,如果我们订阅了任何主题,请确保消费
# 由 WebSocket 客户端库接收到的消息。否则可能会
# 溢出,从而导致连接断开。
if recv := client.receive():
topic, event = recv
print(f"Received {event} on {topic}")
# 发布一个 `ping` 事件。这假定 Zeek 端的事件被声明为
# # 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 enum 和类的特殊处理
Zeek WebSocket API 可以表示 Zeek 的 `enum` 和 `record` 值,但其 schema 并不属于该协议的数据负载(payload)。这是为了支持客户端可能处于不同版本的 schema,甚至可能完全不知道具体 Zeek 类型的情况。因此,Python 绑定始终可以接收任何 `enum` 或 `record` 值。
尽管如此,检查和构造这类值仍然比较繁琐,因此该库提供了将 Zeek 的 `enum` 和 `record` 值转换为原生 Python 类型的功能,前提是存在自定义的 Python 类型。
##### Records
虽然我们支持从任何 Python 类构建 `Value`,例如,
```
# 注意:不推荐使用,见下文。
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 实例:
```
# 注意:与上面的示例等效,但功能更强大。
@dataclasses.dataclass
class X:
a: int
b: str
x = make_value(X(4711, "abc")) # Record({"a": Count(4711), "b": String("abc")}).
# 通过提供目标类型转换为具体的 Python 类型。
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, C绑定, ffi, IP 地址批量处理, PyO3, Python绑定, Rootkit, Rust, WebSocket, XML 请求, Zeek, 依赖分析, 可视化界面, 同步接口, 安全开发, 开源, 异步编程, 网络安全, 网络流量审计, 逆向工具, 通知系统, 隐私保护