mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-04 18:15:39 +00:00
refactor(core): migrate packet processing from pnet to smoltcp (#2456)
Replace pnet_packet parsing and mutation across gateway packet paths with the existing smoltcp wire APIs. Preserve length validation, fragmentation classification, TCP flags, and checksum behavior while removing the core pnet_packet feature dependency. Reject stale non-initiator OSPF sync sessions: only initiator requests may create missing sessions, and a rejection clears the old initiator role only when the remote session generation is unchanged. This fixes an unbounded RPC storm caused by a delayed route sync recreating a session after both peers relinquished the initiator role, with regression tests for session creation and response reordering.
This commit is contained in:
Generated
+3
-63
@@ -2345,7 +2345,7 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pnet",
|
"pnet_datalink",
|
||||||
"prost 0.14.3",
|
"prost 0.14.3",
|
||||||
"quanta",
|
"quanta",
|
||||||
"quinn",
|
"quinn",
|
||||||
@@ -2362,6 +2362,7 @@ dependencies = [
|
|||||||
"serial_test",
|
"serial_test",
|
||||||
"service-manager",
|
"service-manager",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
|
"smoltcp",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"strum 0.27.2",
|
"strum 0.27.2",
|
||||||
"stun_codec",
|
"stun_codec",
|
||||||
@@ -2445,7 +2446,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pnet_packet",
|
|
||||||
"prefix-trie",
|
"prefix-trie",
|
||||||
"prost 0.14.3",
|
"prost 0.14.3",
|
||||||
"prost-types 0.14.3",
|
"prost-types 0.14.3",
|
||||||
@@ -2486,6 +2486,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thunk-rs",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
@@ -6573,20 +6574,6 @@ dependencies = [
|
|||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d"
|
|
||||||
dependencies = [
|
|
||||||
"ipnetwork",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_datalink",
|
|
||||||
"pnet_packet",
|
|
||||||
"pnet_sys",
|
|
||||||
"pnet_transport",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pnet_base"
|
name = "pnet_base"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
@@ -6594,7 +6581,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"no-std-net",
|
"no-std-net",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6607,43 +6593,9 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"pnet_base",
|
"pnet_base",
|
||||||
"pnet_sys",
|
"pnet_sys",
|
||||||
"serde",
|
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_macros"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"syn 2.0.117",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_macros_support"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
|
|
||||||
dependencies = [
|
|
||||||
"pnet_base",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_packet"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
|
|
||||||
dependencies = [
|
|
||||||
"glob",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_macros",
|
|
||||||
"pnet_macros_support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pnet_sys"
|
name = "pnet_sys"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
@@ -6654,18 +6606,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_transport"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_packet",
|
|
||||||
"pnet_sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.17.16"
|
version = "0.17.16"
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
# QUIC TCP Proxy 内存对比(2026-07-27)
|
||||||
|
|
||||||
|
## 结论
|
||||||
|
|
||||||
|
在相同的双节点 network namespace 环境中,当前分支相对 2.6.4:
|
||||||
|
|
||||||
|
- 空闲且未建立 TCP proxy 连接时,两端合计 USS 从 15.95 MiB
|
||||||
|
降至 11.66 MiB,下降 26.9%;
|
||||||
|
- 66 条空闲 TCP proxy 连接时,两端合计 USS 从 19.45 MiB
|
||||||
|
降至 13.85 MiB,下降 28.8%;
|
||||||
|
- 固定 1 Gbit/s 的单流 TCP proxy 传输中,两端平均 USS 从
|
||||||
|
20.69 MiB 降至 14.75 MiB,下降 28.7%,同步峰值从
|
||||||
|
21.50 MiB 降至 15.02 MiB;
|
||||||
|
- 从 0 增长到 66 条空闲连接推算,每条连接在两个 core 上合计
|
||||||
|
增加约 33.9 KiB USS;2.6.4 为 54.2 KiB,下降 37.4%;
|
||||||
|
- 当前分支的匿名内存下降约 40% 至 44%,说明堆和连接缓冲区开销
|
||||||
|
确实降低。
|
||||||
|
|
||||||
|
当前分支的 RSS 比 2.6.4 高约 5% 至 11%,但这部分差异没有出现在
|
||||||
|
Anonymous 中,主要体现为非匿名或共享驻留页。PSS 在高连接数及
|
||||||
|
固定吞吐场景基本持平,USS 和 Anonymous 则显著更低。因此不能
|
||||||
|
只根据 RSS 判断发生了内存回退。
|
||||||
|
|
||||||
|
## 测试对象
|
||||||
|
|
||||||
|
| 版本 | 标识 | 二进制 |
|
||||||
|
|---|---|---|
|
||||||
|
| 当前分支 | commit `9e2ed33aeb37`,版本 `2.6.4-9e2ed33a` | `target/x86_64-unknown-linux-musl/release/easytier-core` |
|
||||||
|
| 2.6.4 | 版本 `2.6.4-8428a89d` | `/data/tickets/easytier/easytier-linux-x86_64/easytier-core` |
|
||||||
|
|
||||||
|
当前分支使用以下命令重新构建,确保被测二进制准确对应 HEAD:
|
||||||
|
|
||||||
|
```console
|
||||||
|
cargo build --release \
|
||||||
|
--target x86_64-unknown-linux-musl \
|
||||||
|
-p easytier \
|
||||||
|
--features jemalloc \
|
||||||
|
--bin easytier-core \
|
||||||
|
--bin easytier-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
两个二进制均为 stripped static PIE。当前分支明确使用 musl 和
|
||||||
|
jemalloc。
|
||||||
|
|
||||||
|
## 测试拓扑
|
||||||
|
|
||||||
|
- 两个 `easytier-core` 分别运行在独立的 network namespace;
|
||||||
|
- namespace 通过 Linux bridge 和 veth 连接;
|
||||||
|
- underlay 地址为 `10.251.89.10/24` 和 `10.251.89.11/24`;
|
||||||
|
- EasyTier 虚拟地址为 `10.144.144.1/24` 和
|
||||||
|
`10.144.144.2/24`;
|
||||||
|
- 两个节点之间使用 UDP listener 建立 EasyTier peer 连接;
|
||||||
|
- 源节点启用 `--enable-quic-proxy true`;
|
||||||
|
- 两个节点均保留默认 QUIC input;
|
||||||
|
- TCP client 从 `10.144.144.1` 访问绑定在
|
||||||
|
`10.144.144.2` 上的 server;
|
||||||
|
- `tcp_proxy_connect` 指标的 `protocol` 标签确认为 `QUIC`;
|
||||||
|
- 66 条连接场景通过两端各 132 个 established TCP socket 条目
|
||||||
|
确认当前连接数。
|
||||||
|
|
||||||
|
## 采样口径
|
||||||
|
|
||||||
|
数据读取自 `/proc/<pid>/smaps_rollup`:
|
||||||
|
|
||||||
|
- RSS:进程映射的全部驻留页,包含共享代码页;
|
||||||
|
- PSS:共享页按共享进程数量分摊后的驻留内存;
|
||||||
|
- USS:`Private_Clean + Private_Dirty`,表示进程独占内存;
|
||||||
|
- Anonymous:匿名页,主要反映堆、栈和运行时缓冲区。
|
||||||
|
|
||||||
|
空闲场景每隔 2 秒采样一次,共 5 次,表格记录均值。固定吞吐场景
|
||||||
|
持续 20 秒,每隔 2 秒采样一次,共 8 次,同时记录均值和峰值。
|
||||||
|
所有容量单位均为 MiB。
|
||||||
|
|
||||||
|
## 空闲连接结果
|
||||||
|
|
||||||
|
以下数据均为两个 EasyTier core 的合计值:
|
||||||
|
|
||||||
|
| 当前连接数 | 当前 RSS | 2.6.4 RSS | 当前 PSS | 2.6.4 PSS | 当前 USS | 2.6.4 USS | USS 变化 | 当前 Anonymous | 2.6.4 Anonymous |
|
||||||
|
|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 40.42 | 36.33 | 26.03 | 24.50 | 11.66 | 15.95 | -26.9% | 8.89 | 15.62 |
|
||||||
|
| 1 | 41.52 | 38.46 | 26.49 | 25.98 | 11.46 | 16.82 | -31.9% | 9.08 | 16.23 |
|
||||||
|
| 10 | 41.63 | 38.79 | 26.59 | 26.32 | 11.56 | 17.15 | -32.6% | 9.19 | 16.55 |
|
||||||
|
| 66 | 43.68 | 41.11 | 28.76 | 28.63 | 13.85 | 19.45 | -28.8% | 11.22 | 18.79 |
|
||||||
|
|
||||||
|
0 条和 1 条连接之间的小幅反向波动属于分配器回收和采样时序噪声,
|
||||||
|
不能解释为连接产生负开销。使用 0 到 66 条连接的跨度估算单位
|
||||||
|
连接成本更稳定。
|
||||||
|
|
||||||
|
### 分节点 USS
|
||||||
|
|
||||||
|
| 当前连接数 | 当前源端 | 当前目的端 | 2.6.4 源端 | 2.6.4 目的端 |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 5.45 | 6.21 | 8.07 | 7.88 |
|
||||||
|
| 1 | 5.43 | 6.03 | 8.68 | 8.14 |
|
||||||
|
| 10 | 5.49 | 6.07 | 8.90 | 8.25 |
|
||||||
|
| 66 | 6.60 | 7.25 | 10.19 | 9.25 |
|
||||||
|
|
||||||
|
### 单位连接增量
|
||||||
|
|
||||||
|
以 0 到 66 条连接的 USS 增量计算:
|
||||||
|
|
||||||
|
| 版本 | 两端 USS 增量 | 每连接两端合计 | 每连接单端平均 |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| 当前分支 | 2.19 MiB | 33.9 KiB | 17.0 KiB |
|
||||||
|
| 2.6.4 | 3.50 MiB | 54.2 KiB | 27.1 KiB |
|
||||||
|
|
||||||
|
当前分支的每连接独占内存增量下降约 37.4%。
|
||||||
|
|
||||||
|
## 固定 1 Gbit/s 活跃流量
|
||||||
|
|
||||||
|
为排除两个版本最大吞吐不同造成的缓冲区差异,使用
|
||||||
|
`iperf3 -b 1G -P 1 -t 20` 将两个版本都限制为 1 Gbit/s。
|
||||||
|
两次测试均实际完成 2.33 GiB 传输,接收端报告 1000 Mbit/s。
|
||||||
|
|
||||||
|
### 平均值
|
||||||
|
|
||||||
|
| 版本 | 节点 | RSS | PSS | USS | Anonymous |
|
||||||
|
|---|---|---:|---:|---:|---:|
|
||||||
|
| 当前分支 | 源端 | 22.89 | 15.41 | 7.94 | 6.88 |
|
||||||
|
| 当前分支 | 目的端 | 21.77 | 14.29 | 6.82 | 5.27 |
|
||||||
|
| 当前分支 | 两端合计 | 44.67 | 29.70 | 14.75 | 12.14 |
|
||||||
|
| 2.6.4 | 源端 | 22.59 | 16.34 | 11.70 | 11.35 |
|
||||||
|
| 2.6.4 | 目的端 | 19.95 | 13.67 | 8.99 | 8.77 |
|
||||||
|
| 2.6.4 | 两端合计 | 42.53 | 30.01 | 20.69 | 20.13 |
|
||||||
|
|
||||||
|
### 对比
|
||||||
|
|
||||||
|
| 指标 | 当前分支 | 2.6.4 | 变化 |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| 两端平均 RSS | 44.67 | 42.53 | +5.0% |
|
||||||
|
| 两端平均 PSS | 29.70 | 30.01 | -1.0% |
|
||||||
|
| 两端平均 USS | 14.75 | 20.69 | -28.7% |
|
||||||
|
| 两端平均 Anonymous | 12.14 | 20.13 | -39.7% |
|
||||||
|
| 两端同步峰值 USS | 15.02 | 21.50 | -30.2% |
|
||||||
|
|
||||||
|
## 分节点原始统计
|
||||||
|
|
||||||
|
下表保留各场景所有样本计算出的均值;`max_uss` 是该节点采样期间
|
||||||
|
的最大 USS。
|
||||||
|
|
||||||
|
| 版本 | 场景 | 节点 | 样本数 | mean_rss | mean_pss | mean_uss | mean_anon | max_uss |
|
||||||
|
|---|---|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| 当前 | 0 连接 | 源端 | 5 | 19.830 | 12.636 | 5.451 | 4.314 | 5.582 |
|
||||||
|
| 当前 | 0 连接 | 目的端 | 5 | 20.587 | 13.393 | 6.208 | 4.579 | 6.320 |
|
||||||
|
| 当前 | 1 连接 | 源端 | 5 | 20.463 | 12.944 | 5.432 | 4.401 | 5.465 |
|
||||||
|
| 当前 | 1 连接 | 目的端 | 5 | 21.061 | 13.541 | 6.030 | 4.682 | 6.051 |
|
||||||
|
| 当前 | 10 连接 | 源端 | 5 | 20.522 | 13.002 | 5.491 | 4.459 | 5.496 |
|
||||||
|
| 当前 | 10 连接 | 目的端 | 5 | 21.105 | 13.585 | 6.073 | 4.726 | 6.086 |
|
||||||
|
| 当前 | 66 连接 | 源端 | 5 | 21.513 | 14.050 | 6.595 | 5.498 | 6.672 |
|
||||||
|
| 当前 | 66 连接 | 目的端 | 5 | 22.169 | 14.706 | 7.251 | 5.723 | 7.375 |
|
||||||
|
| 当前 | 1 Gbit/s | 源端 | 8 | 22.893 | 15.410 | 7.936 | 6.877 | 8.188 |
|
||||||
|
| 当前 | 1 Gbit/s | 目的端 | 8 | 21.773 | 14.291 | 6.816 | 5.266 | 6.832 |
|
||||||
|
| 2.6.4 | 0 连接 | 源端 | 5 | 18.278 | 12.355 | 8.071 | 7.876 | 8.328 |
|
||||||
|
| 2.6.4 | 0 连接 | 目的端 | 5 | 18.048 | 12.144 | 7.880 | 7.747 | 8.203 |
|
||||||
|
| 2.6.4 | 1 连接 | 源端 | 5 | 19.535 | 13.279 | 8.676 | 8.262 | 8.727 |
|
||||||
|
| 2.6.4 | 1 连接 | 目的端 | 5 | 18.920 | 12.705 | 8.143 | 7.971 | 8.191 |
|
||||||
|
| 2.6.4 | 10 连接 | 源端 | 5 | 19.762 | 13.506 | 8.902 | 8.473 | 8.910 |
|
||||||
|
| 2.6.4 | 10 连接 | 目的端 | 5 | 19.027 | 12.812 | 8.250 | 8.078 | 8.297 |
|
||||||
|
| 2.6.4 | 66 连接 | 源端 | 5 | 21.069 | 14.805 | 10.194 | 9.702 | 10.320 |
|
||||||
|
| 2.6.4 | 66 连接 | 目的端 | 5 | 20.045 | 13.822 | 9.252 | 9.088 | 9.293 |
|
||||||
|
| 2.6.4 | 1 Gbit/s | 源端 | 8 | 22.588 | 16.343 | 11.697 | 11.354 | 12.258 |
|
||||||
|
| 2.6.4 | 1 Gbit/s | 目的端 | 8 | 19.946 | 13.670 | 8.993 | 8.774 | 9.277 |
|
||||||
|
|
||||||
|
## 解释和限制
|
||||||
|
|
||||||
|
1. 以固定 1 Gbit/s 场景为例,当前分支 RSS 增加 5.0%,但
|
||||||
|
Anonymous 下降 39.7%,PSS 下降 1.0%。这说明差异主要体现
|
||||||
|
在非匿名或共享驻留页;本次没有保存逐 VMA 数据,因此不进一步
|
||||||
|
将它归因到某一个具体映射。
|
||||||
|
2. 两个相同版本进程运行在同一宿主机时会共享可执行文件代码页,
|
||||||
|
所以 PSS 比 RSS 更适合估算该测试拓扑的宿主机总成本,USS 和
|
||||||
|
Anonymous 更适合判断 EasyTier 私有堆及缓冲区的变化。
|
||||||
|
3. 这是一轮受控 A/B 测试,而不是长期统计分布。数值可用于确认
|
||||||
|
差异方向和量级;若作为发布门禁,应固定机器负载并增加多轮重复。
|
||||||
|
4. 本文只比较 QUIC TCP proxy 内存,不使用未限速吞吐结果推断性能,
|
||||||
|
避免吞吐差异污染内存结论。
|
||||||
+12
-187
@@ -981,17 +981,6 @@ dependencies = [
|
|||||||
"thiserror 2.0.16",
|
"thiserror 2.0.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "delegate"
|
|
||||||
version = "0.13.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
@@ -1001,17 +990,6 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "derivative"
|
|
||||||
version = "2.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_arbitrary"
|
name = "derive_arbitrary"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@@ -1151,7 +1129,6 @@ version = "2.6.4"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"ariadne",
|
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"atomic-shim",
|
"atomic-shim",
|
||||||
@@ -1192,11 +1169,10 @@ dependencies = [
|
|||||||
"igd-next",
|
"igd-next",
|
||||||
"indoc",
|
"indoc",
|
||||||
"kcp-sys",
|
"kcp-sys",
|
||||||
|
"log",
|
||||||
"machine-uid",
|
"machine-uid",
|
||||||
"moka",
|
"moka",
|
||||||
"natpmp",
|
"natpmp",
|
||||||
"netlink-packet-core",
|
|
||||||
"netlink-packet-route 0.21.0",
|
|
||||||
"netlink-sys",
|
"netlink-sys",
|
||||||
"network-interface",
|
"network-interface",
|
||||||
"nix 0.29.0",
|
"nix 0.29.0",
|
||||||
@@ -1205,16 +1181,14 @@ dependencies = [
|
|||||||
"paste",
|
"paste",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pnet",
|
"pnet_datalink",
|
||||||
"prost 0.14.3",
|
"prost 0.14.3",
|
||||||
"prost-reflect 0.16.4",
|
|
||||||
"quanta",
|
"quanta",
|
||||||
"quinn",
|
"quinn",
|
||||||
"quinn-proto",
|
"quinn-proto",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"regex",
|
"regex",
|
||||||
"ring",
|
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
"rustls",
|
"rustls",
|
||||||
"seahash",
|
"seahash",
|
||||||
@@ -1222,6 +1196,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"service-manager",
|
"service-manager",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
|
"smoltcp",
|
||||||
"socket2 0.5.10",
|
"socket2 0.5.10",
|
||||||
"strum",
|
"strum",
|
||||||
"sys-locale",
|
"sys-locale",
|
||||||
@@ -1229,14 +1204,12 @@ dependencies = [
|
|||||||
"terminal_size",
|
"terminal_size",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"thunk-rs",
|
"thunk-rs",
|
||||||
"time",
|
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tokio-websockets",
|
"tokio-websockets",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
|
||||||
"tun-easytier",
|
"tun-easytier",
|
||||||
"unicode-width 0.1.11",
|
"unicode-width 0.1.11",
|
||||||
"url",
|
"url",
|
||||||
@@ -1253,7 +1226,6 @@ dependencies = [
|
|||||||
name = "easytier-core"
|
name = "easytier-core"
|
||||||
version = "2.6.4"
|
version = "2.6.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"ariadne",
|
"ariadne",
|
||||||
@@ -1265,7 +1237,7 @@ dependencies = [
|
|||||||
"bitflags 2.9.4",
|
"bitflags 2.9.4",
|
||||||
"bytecodec",
|
"bytecodec",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chacha20poly1305",
|
"chrono",
|
||||||
"cidr",
|
"cidr",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1283,13 +1255,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pnet_packet",
|
|
||||||
"prefix-trie",
|
"prefix-trie",
|
||||||
"prost 0.14.3",
|
"prost 0.14.3",
|
||||||
"prost-reflect 0.16.4",
|
"prost-types 0.14.3",
|
||||||
"prost-wkt-types",
|
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"ring",
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1327,7 +1298,7 @@ dependencies = [
|
|||||||
"napi-derive-ohos",
|
"napi-derive-ohos",
|
||||||
"napi-ohos",
|
"napi-ohos",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"prost-reflect 0.14.7",
|
"prost-reflect",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1350,9 +1321,6 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
"cidr",
|
"cidr",
|
||||||
"delegate",
|
|
||||||
"derivative",
|
|
||||||
"derive_more",
|
|
||||||
"hmac",
|
"hmac",
|
||||||
"indoc",
|
"indoc",
|
||||||
"pbjson",
|
"pbjson",
|
||||||
@@ -1360,8 +1328,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"prost 0.14.3",
|
"prost 0.14.3",
|
||||||
"prost-build",
|
"prost-build",
|
||||||
"prost-reflect 0.16.4",
|
"prost-types 0.14.3",
|
||||||
"prost-reflect-build",
|
|
||||||
"prost-wkt-types",
|
"prost-wkt-types",
|
||||||
"quote",
|
"quote",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@@ -2989,7 +2956,7 @@ dependencies = [
|
|||||||
"ipnet",
|
"ipnet",
|
||||||
"libc",
|
"libc",
|
||||||
"netlink-packet-core",
|
"netlink-packet-core",
|
||||||
"netlink-packet-route 0.17.1",
|
"netlink-packet-route",
|
||||||
"netlink-sys",
|
"netlink-sys",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
@@ -3021,21 +2988,6 @@ dependencies = [
|
|||||||
"netlink-packet-utils",
|
"netlink-packet-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "netlink-packet-route"
|
|
||||||
version = "0.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "483325d4bfef65699214858f097d504eb812c38ce7077d165f301ec406c3066e"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bitflags 2.9.4",
|
|
||||||
"byteorder",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"netlink-packet-core",
|
|
||||||
"netlink-packet-utils",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "netlink-packet-utils"
|
name = "netlink-packet-utils"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -3061,9 +3013,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "network-interface"
|
name = "network-interface"
|
||||||
version = "2.0.3"
|
version = "2.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07709a6d4eba90ab10ec170a0530b3aafc81cb8a2d380e4423ae41fc55fe5745"
|
checksum = "4ddcb8865ad3d9950f22f42ffa0ef0aecbfbf191867b3122413602b0a360b2a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -3166,15 +3118,6 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_threads"
|
|
||||||
version = "0.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -3247,15 +3190,6 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ordered-float"
|
|
||||||
version = "2.10.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ordered_hash_map"
|
name = "ordered_hash_map"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -3402,20 +3336,6 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d"
|
|
||||||
dependencies = [
|
|
||||||
"ipnetwork",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_datalink",
|
|
||||||
"pnet_packet",
|
|
||||||
"pnet_sys",
|
|
||||||
"pnet_transport",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pnet_base"
|
name = "pnet_base"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
@@ -3423,7 +3343,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"no-std-net",
|
"no-std-net",
|
||||||
"serde",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3436,43 +3355,9 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"pnet_base",
|
"pnet_base",
|
||||||
"pnet_sys",
|
"pnet_sys",
|
||||||
"serde",
|
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_macros"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"syn 2.0.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_macros_support"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab"
|
|
||||||
dependencies = [
|
|
||||||
"pnet_base",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_packet"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f"
|
|
||||||
dependencies = [
|
|
||||||
"glob",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_macros",
|
|
||||||
"pnet_macros_support",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pnet_sys"
|
name = "pnet_sys"
|
||||||
version = "0.35.0"
|
version = "0.35.0"
|
||||||
@@ -3483,18 +3368,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pnet_transport"
|
|
||||||
version = "0.35.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"pnet_base",
|
|
||||||
"pnet_packet",
|
|
||||||
"pnet_sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
@@ -3721,34 +3594,10 @@ checksum = "7b5edd582b62f5cde844716e66d92565d7faf7ab1445c8cebce6e00fba83ddb2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"prost 0.13.5",
|
"prost 0.13.5",
|
||||||
"prost-reflect-derive 0.14.0",
|
"prost-reflect-derive",
|
||||||
"prost-types 0.13.5",
|
"prost-types 0.13.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prost-reflect"
|
|
||||||
version = "0.16.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "590aa145fee8f7a26b5a6055365e7c5e89a5c1caae9869de76ec0ee73181a2f9"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"prost 0.14.3",
|
|
||||||
"prost-reflect-derive 0.16.0",
|
|
||||||
"prost-types 0.14.3",
|
|
||||||
"serde",
|
|
||||||
"serde-value",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prost-reflect-build"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8214ae2c30bbac390db0134d08300e770ef89b6d4e5abf855e8d300eded87e28"
|
|
||||||
dependencies = [
|
|
||||||
"prost-build",
|
|
||||||
"prost-reflect 0.16.4",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-reflect-derive"
|
name = "prost-reflect-derive"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -3760,17 +3609,6 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prost-reflect-derive"
|
|
||||||
version = "0.16.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7b6d90e29fa6c0d13c2c19ba5e4b3fb0efbf5975d27bcf4e260b7b15455bcabe"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.13.5"
|
version = "0.13.5"
|
||||||
@@ -4449,16 +4287,6 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde-value"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
|
||||||
dependencies = [
|
|
||||||
"ordered-float",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.226"
|
version = "1.0.226"
|
||||||
@@ -4935,9 +4763,7 @@ checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
"libc",
|
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"num_threads",
|
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
@@ -5252,7 +5078,6 @@ dependencies = [
|
|||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
"time",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ parking_lot = "0.12.1"
|
|||||||
percent-encoding = "2.3.1"
|
percent-encoding = "2.3.1"
|
||||||
petgraph = "0.8.1"
|
petgraph = "0.8.1"
|
||||||
pin-project-lite = "0.2.13"
|
pin-project-lite = "0.2.13"
|
||||||
pnet_packet = { version = "0.35.0", optional = true }
|
|
||||||
prefix-trie = { version = "0.7.0", features = ["cidr"] }
|
prefix-trie = { version = "0.7.0", features = ["cidr"] }
|
||||||
prost = "0.14.3"
|
prost = "0.14.3"
|
||||||
prost-types = "0.14.3"
|
prost-types = "0.14.3"
|
||||||
@@ -116,7 +115,6 @@ rich-config-errors = ["dep:ariadne"]
|
|||||||
tcp-hole-punch = []
|
tcp-hole-punch = []
|
||||||
proxy-packet = [
|
proxy-packet = [
|
||||||
"wrapped-transport",
|
"wrapped-transport",
|
||||||
"dep:pnet_packet",
|
|
||||||
"dep:smoltcp",
|
"dep:smoltcp",
|
||||||
"smoltcp/std",
|
"smoltcp/std",
|
||||||
"smoltcp/proto-ipv4",
|
"smoltcp/proto-ipv4",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use std::{
|
|||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use pnet_packet::{Packet, ip::IpNextHeaderProtocols, ipv4::Ipv4Packet, tcp::TcpPacket};
|
use smoltcp::wire::{IpProtocol, Ipv4Packet, TcpPacket};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
select,
|
select,
|
||||||
sync::{Mutex, mpsc},
|
sync::{Mutex, mpsc},
|
||||||
@@ -74,7 +74,7 @@ use self::{
|
|||||||
deadline::{DataPlaneDeadline, DataPlaneIoDeadline},
|
deadline::{DataPlaneDeadline, DataPlaneIoDeadline},
|
||||||
error::DataPlaneResult,
|
error::DataPlaneResult,
|
||||||
flow::{FlowKey, FlowKind, FlowLease, FlowTable},
|
flow::{FlowKey, FlowKind, FlowLease, FlowTable},
|
||||||
packet::PeerPacketRoute,
|
packet::{PeerPacketRoute, tcp_flags},
|
||||||
resource::{DataPlaneConsumers, DataPlaneIoGuard, DataPlaneLease},
|
resource::{DataPlaneConsumers, DataPlaneIoGuard, DataPlaneLease},
|
||||||
route::{
|
route::{
|
||||||
DataPlaneRoutePolicy, DataPlaneTcpRoute, DataPlaneTcpRouteInput,
|
DataPlaneRoutePolicy, DataPlaneTcpRoute, DataPlaneTcpRouteInput,
|
||||||
@@ -218,15 +218,16 @@ where
|
|||||||
|| x == PacketType::DataWithQuicSrcModified as u8
|
|| x == PacketType::DataWithQuicSrcModified as u8
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if let Some(ipv4) = Ipv4Packet::new(packet.payload()) {
|
if let Ok(ipv4) = Ipv4Packet::new_checked(packet.payload()) {
|
||||||
let (tcp_src_port, tcp_dst_port, tcp_flags) =
|
let (tcp_src_port, tcp_dst_port, tcp_flags) =
|
||||||
if ipv4.get_next_level_protocol() == IpNextHeaderProtocols::Tcp {
|
if ipv4.next_header() == IpProtocol::Tcp {
|
||||||
TcpPacket::new(ipv4.payload())
|
TcpPacket::new_checked(ipv4.payload())
|
||||||
|
.ok()
|
||||||
.map(|tcp| {
|
.map(|tcp| {
|
||||||
(
|
(
|
||||||
Some(tcp.get_source()),
|
Some(tcp.src_port()),
|
||||||
Some(tcp.get_destination()),
|
Some(tcp.dst_port()),
|
||||||
Some(tcp.get_flags()),
|
Some(tcp_flags(&tcp)),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.unwrap_or((None, None, None))
|
.unwrap_or((None, None, None))
|
||||||
@@ -237,9 +238,9 @@ where
|
|||||||
packet_type = hdr.packet_type,
|
packet_type = hdr.packet_type,
|
||||||
from_peer_id = hdr.from_peer_id.get(),
|
from_peer_id = hdr.from_peer_id.get(),
|
||||||
to_peer_id = hdr.to_peer_id.get(),
|
to_peer_id = hdr.to_peer_id.get(),
|
||||||
ipv4_src = %ipv4.get_source(),
|
ipv4_src = %ipv4.src_addr(),
|
||||||
ipv4_dst = %ipv4.get_destination(),
|
ipv4_dst = %ipv4.dst_addr(),
|
||||||
next_protocol = ?ipv4.get_next_level_protocol(),
|
next_protocol = ?ipv4.next_header(),
|
||||||
?tcp_src_port,
|
?tcp_src_port,
|
||||||
?tcp_dst_port,
|
?tcp_dst_port,
|
||||||
?tcp_flags,
|
?tcp_flags,
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
use pnet_packet::{
|
use smoltcp::wire::{IPV4_HEADER_LEN, IpProtocol, Ipv4Packet, TcpPacket, UdpPacket};
|
||||||
Packet, ip::IpNextHeaderProtocols, ipv4::Ipv4Packet, tcp::TcpPacket, udp::UdpPacket,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
gateway::proxy::ip_reassembler::{IpReassembler, SmolIpv4Packet},
|
gateway::proxy::ip_reassembler::IpReassembler,
|
||||||
packet::{PacketType, ZCPacket},
|
packet::{PacketType, ZCPacket},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,21 +43,21 @@ pub(crate) enum PeerPacketRoute {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
fn classify_peer_ipv4_payload(payload: &[u8]) -> ClassifiedPeerPacket {
|
fn classify_peer_ipv4_payload(payload: &[u8]) -> ClassifiedPeerPacket {
|
||||||
let Some(ipv4) = Ipv4Packet::new(payload) else {
|
let Ok(ipv4) = Ipv4Packet::new_checked(payload) else {
|
||||||
return ClassifiedPeerPacket::Unsupported;
|
return ClassifiedPeerPacket::Unsupported;
|
||||||
};
|
};
|
||||||
if ipv4.get_version() != 4 {
|
if ipv4.version() != 4 || usize::from(ipv4.header_len()) < IPV4_HEADER_LEN {
|
||||||
return ClassifiedPeerPacket::Unsupported;
|
return ClassifiedPeerPacket::Unsupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
match ipv4.get_next_level_protocol() {
|
match ipv4.next_header() {
|
||||||
IpNextHeaderProtocols::Tcp => {
|
IpProtocol::Tcp => {
|
||||||
let Some(tcp) = TcpPacket::new(ipv4.payload()) else {
|
let Ok(tcp) = TcpPacket::new_checked(ipv4.payload()) else {
|
||||||
return ClassifiedPeerPacket::Unsupported;
|
return ClassifiedPeerPacket::Unsupported;
|
||||||
};
|
};
|
||||||
let entry = FlowKey {
|
let entry = FlowKey {
|
||||||
dst: SocketAddr::new(ipv4.get_source().into(), tcp.get_source()),
|
dst: SocketAddr::new(ipv4.src_addr().into(), tcp.src_port()),
|
||||||
src: SocketAddr::new(ipv4.get_destination().into(), tcp.get_destination()),
|
src: SocketAddr::new(ipv4.dst_addr().into(), tcp.dst_port()),
|
||||||
kind: FlowKind::Tcp,
|
kind: FlowKind::Tcp,
|
||||||
};
|
};
|
||||||
let listen_entry = FlowKey {
|
let listen_entry = FlowKey {
|
||||||
@@ -70,23 +68,22 @@ fn classify_peer_ipv4_payload(payload: &[u8]) -> ClassifiedPeerPacket {
|
|||||||
ClassifiedPeerPacket::Tcp {
|
ClassifiedPeerPacket::Tcp {
|
||||||
entry,
|
entry,
|
||||||
listen_entry,
|
listen_entry,
|
||||||
flags: tcp.get_flags(),
|
flags: tcp_flags(&tcp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IpNextHeaderProtocols::Udp => {
|
IpProtocol::Udp => {
|
||||||
let smol_ipv4 = SmolIpv4Packet::new_unchecked(ipv4.packet());
|
if IpReassembler::is_packet_fragmented(&ipv4) {
|
||||||
if IpReassembler::is_packet_fragmented(&smol_ipv4) {
|
|
||||||
return ClassifiedPeerPacket::FragmentedUdp {
|
return ClassifiedPeerPacket::FragmentedUdp {
|
||||||
source: ipv4.get_source(),
|
source: ipv4.src_addr(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let Some(udp) = UdpPacket::new(ipv4.payload()) else {
|
let Ok(udp) = UdpPacket::new_checked(ipv4.payload()) else {
|
||||||
return ClassifiedPeerPacket::Unsupported;
|
return ClassifiedPeerPacket::Unsupported;
|
||||||
};
|
};
|
||||||
ClassifiedPeerPacket::Udp {
|
ClassifiedPeerPacket::Udp {
|
||||||
entry: FlowKey {
|
entry: FlowKey {
|
||||||
dst: SocketAddr::new(ipv4.get_source().into(), udp.get_source()),
|
dst: SocketAddr::new(ipv4.src_addr().into(), udp.src_port()),
|
||||||
src: SocketAddr::new(ipv4.get_destination().into(), udp.get_destination()),
|
src: SocketAddr::new(ipv4.dst_addr().into(), udp.dst_port()),
|
||||||
kind: FlowKind::Udp,
|
kind: FlowKind::Udp,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -94,6 +91,17 @@ fn classify_peer_ipv4_payload(payload: &[u8]) -> ClassifiedPeerPacket {
|
|||||||
_ => ClassifiedPeerPacket::Unsupported,
|
_ => ClassifiedPeerPacket::Unsupported,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn tcp_flags<T: AsRef<[u8]>>(tcp: &TcpPacket<T>) -> u8 {
|
||||||
|
u8::from(tcp.fin())
|
||||||
|
| (u8::from(tcp.syn()) << 1)
|
||||||
|
| (u8::from(tcp.rst()) << 2)
|
||||||
|
| (u8::from(tcp.psh()) << 3)
|
||||||
|
| (u8::from(tcp.ack()) << 4)
|
||||||
|
| (u8::from(tcp.urg()) << 5)
|
||||||
|
| (u8::from(tcp.ece()) << 6)
|
||||||
|
| (u8::from(tcp.cwr()) << 7)
|
||||||
|
}
|
||||||
impl<V> FlowTable<V> {
|
impl<V> FlowTable<V> {
|
||||||
pub fn route_peer_packet(
|
pub fn route_peer_packet(
|
||||||
&self,
|
&self,
|
||||||
@@ -158,37 +166,32 @@ impl<V> FlowTable<V> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
use pnet_packet::{
|
|
||||||
MutablePacket,
|
|
||||||
ip::IpNextHeaderProtocols,
|
|
||||||
ipv4::MutableIpv4Packet,
|
|
||||||
tcp::{MutableTcpPacket, TcpFlags},
|
|
||||||
udp::MutableUdpPacket,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::packet::{PacketType, ZCPacket};
|
use crate::packet::{PacketType, ZCPacket};
|
||||||
|
|
||||||
fn ipv4_packet(protocol: pnet_packet::ip::IpNextHeaderProtocol, payload_len: usize) -> Vec<u8> {
|
const TCP_SYN: u8 = 0x02;
|
||||||
|
|
||||||
|
fn ipv4_packet(protocol: IpProtocol, payload_len: usize) -> Vec<u8> {
|
||||||
let mut packet = vec![0; 20 + payload_len];
|
let mut packet = vec![0; 20 + payload_len];
|
||||||
let packet_len = packet.len() as u16;
|
let packet_len = packet.len() as u16;
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
ipv4.set_version(4);
|
ipv4.set_version(4);
|
||||||
ipv4.set_header_length(5);
|
ipv4.set_header_len(20);
|
||||||
ipv4.set_total_length(packet_len);
|
ipv4.set_total_len(packet_len);
|
||||||
ipv4.set_source(Ipv4Addr::new(10, 1, 1, 2));
|
ipv4.set_src_addr(Ipv4Addr::new(10, 1, 1, 2));
|
||||||
ipv4.set_destination(Ipv4Addr::new(10, 2, 2, 3));
|
ipv4.set_dst_addr(Ipv4Addr::new(10, 2, 2, 3));
|
||||||
ipv4.set_next_level_protocol(protocol);
|
ipv4.set_next_header(protocol);
|
||||||
packet
|
packet
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn classifies_tcp_and_listen_keys() {
|
fn classifies_tcp_and_listen_keys() {
|
||||||
let mut packet = ipv4_packet(IpNextHeaderProtocols::Tcp, 20);
|
let mut packet = ipv4_packet(IpProtocol::Tcp, 20);
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
let mut tcp = MutableTcpPacket::new(ipv4.payload_mut()).unwrap();
|
let mut tcp = TcpPacket::new_unchecked(ipv4.payload_mut());
|
||||||
tcp.set_source(1234);
|
tcp.set_src_port(1234);
|
||||||
tcp.set_destination(4321);
|
tcp.set_dst_port(4321);
|
||||||
tcp.set_flags(TcpFlags::SYN);
|
tcp.set_header_len(20);
|
||||||
|
tcp.set_syn(true);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
classify_peer_ipv4_payload(&packet),
|
classify_peer_ipv4_payload(&packet),
|
||||||
@@ -203,17 +206,18 @@ mod tests {
|
|||||||
dst: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
|
dst: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 0),
|
||||||
kind: FlowKind::TcpListen,
|
kind: FlowKind::TcpListen,
|
||||||
},
|
},
|
||||||
flags: TcpFlags::SYN,
|
flags: TCP_SYN,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn classifies_udp_and_fragmented_udp() {
|
fn classifies_udp_and_fragmented_udp() {
|
||||||
let mut packet = ipv4_packet(IpNextHeaderProtocols::Udp, 8);
|
let mut packet = ipv4_packet(IpProtocol::Udp, 8);
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
let mut udp = MutableUdpPacket::new(ipv4.payload_mut()).unwrap();
|
let mut udp = UdpPacket::new_unchecked(ipv4.payload_mut());
|
||||||
udp.set_source(1234);
|
udp.set_src_port(1234);
|
||||||
udp.set_destination(4321);
|
udp.set_dst_port(4321);
|
||||||
|
udp.set_len(8);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
classify_peer_ipv4_payload(&packet),
|
classify_peer_ipv4_payload(&packet),
|
||||||
ClassifiedPeerPacket::Udp {
|
ClassifiedPeerPacket::Udp {
|
||||||
@@ -225,10 +229,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut fragmented = ipv4_packet(IpNextHeaderProtocols::Udp, 8);
|
let mut fragmented = ipv4_packet(IpProtocol::Udp, 8);
|
||||||
MutableIpv4Packet::new(&mut fragmented)
|
Ipv4Packet::new_unchecked(&mut fragmented).set_frag_offset(8);
|
||||||
.unwrap()
|
|
||||||
.set_fragment_offset(1);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
classify_peer_ipv4_payload(&fragmented),
|
classify_peer_ipv4_payload(&fragmented),
|
||||||
ClassifiedPeerPacket::FragmentedUdp {
|
ClassifiedPeerPacket::FragmentedUdp {
|
||||||
@@ -243,18 +245,35 @@ mod tests {
|
|||||||
ClassifiedPeerPacket::Unsupported
|
ClassifiedPeerPacket::Unsupported
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
classify_peer_ipv4_payload(&ipv4_packet(IpNextHeaderProtocols::Icmp, 8)),
|
classify_peer_ipv4_payload(&ipv4_packet(IpProtocol::Icmp, 8)),
|
||||||
|
ClassifiedPeerPacket::Unsupported
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_ipv4_header_shorter_than_minimum() {
|
||||||
|
let mut packet = ipv4_packet(IpProtocol::Tcp, 20);
|
||||||
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
|
ipv4.set_header_len(16);
|
||||||
|
let mut tcp = TcpPacket::new_unchecked(ipv4.payload_mut());
|
||||||
|
tcp.set_src_port(1234);
|
||||||
|
tcp.set_dst_port(4321);
|
||||||
|
tcp.set_header_len(20);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
classify_peer_ipv4_payload(&packet),
|
||||||
ClassifiedPeerPacket::Unsupported
|
ClassifiedPeerPacket::Unsupported
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn flow_table_routes_tcp_exact_and_listen_fallback() {
|
fn flow_table_routes_tcp_exact_and_listen_fallback() {
|
||||||
let mut packet = ipv4_packet(IpNextHeaderProtocols::Tcp, 20);
|
let mut packet = ipv4_packet(IpProtocol::Tcp, 20);
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
let mut tcp = MutableTcpPacket::new(ipv4.payload_mut()).unwrap();
|
let mut tcp = TcpPacket::new_unchecked(ipv4.payload_mut());
|
||||||
tcp.set_source(1234);
|
tcp.set_src_port(1234);
|
||||||
tcp.set_destination(4321);
|
tcp.set_dst_port(4321);
|
||||||
tcp.set_flags(TcpFlags::SYN);
|
tcp.set_header_len(20);
|
||||||
|
tcp.set_syn(true);
|
||||||
|
|
||||||
let exact = FlowKey {
|
let exact = FlowKey {
|
||||||
src: "10.2.2.3:4321".parse().unwrap(),
|
src: "10.2.2.3:4321".parse().unwrap(),
|
||||||
@@ -272,7 +291,7 @@ mod tests {
|
|||||||
table.route_peer_ipv4_payload(&packet, false),
|
table.route_peer_ipv4_payload(&packet, false),
|
||||||
PeerPacketRoute::Unmatched {
|
PeerPacketRoute::Unmatched {
|
||||||
entry: exact.clone(),
|
entry: exact.clone(),
|
||||||
tcp_flags: Some(TcpFlags::SYN),
|
tcp_flags: Some(TCP_SYN),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -281,7 +300,7 @@ mod tests {
|
|||||||
table.route_peer_ipv4_payload(&packet, true),
|
table.route_peer_ipv4_payload(&packet, true),
|
||||||
PeerPacketRoute::Deliver {
|
PeerPacketRoute::Deliver {
|
||||||
entry: listen,
|
entry: listen,
|
||||||
tcp_flags: Some(TcpFlags::SYN),
|
tcp_flags: Some(TCP_SYN),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -290,16 +309,14 @@ mod tests {
|
|||||||
table.route_peer_ipv4_payload(&packet, true),
|
table.route_peer_ipv4_payload(&packet, true),
|
||||||
PeerPacketRoute::Deliver {
|
PeerPacketRoute::Deliver {
|
||||||
entry: exact,
|
entry: exact,
|
||||||
tcp_flags: Some(TcpFlags::SYN),
|
tcp_flags: Some(TCP_SYN),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn flow_table_routes_fragmented_udp_by_source_ip() {
|
fn flow_table_routes_fragmented_udp_by_source_ip() {
|
||||||
let mut packet = ipv4_packet(IpNextHeaderProtocols::Udp, 8);
|
let mut packet = ipv4_packet(IpProtocol::Udp, 8);
|
||||||
MutableIpv4Packet::new(&mut packet)
|
Ipv4Packet::new_unchecked(&mut packet).set_frag_offset(8);
|
||||||
.unwrap()
|
|
||||||
.set_fragment_offset(1);
|
|
||||||
let table = FlowTable::default();
|
let table = FlowTable::default();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -328,11 +345,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn flow_table_routes_loopback_modified_source_packets() {
|
fn flow_table_routes_loopback_modified_source_packets() {
|
||||||
let mut payload = ipv4_packet(IpNextHeaderProtocols::Tcp, 20);
|
let mut payload = ipv4_packet(IpProtocol::Tcp, 20);
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut payload).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut payload);
|
||||||
let mut tcp = MutableTcpPacket::new(ipv4.payload_mut()).unwrap();
|
let mut tcp = TcpPacket::new_unchecked(ipv4.payload_mut());
|
||||||
tcp.set_source(1234);
|
tcp.set_src_port(1234);
|
||||||
tcp.set_destination(4321);
|
tcp.set_dst_port(4321);
|
||||||
|
tcp.set_header_len(20);
|
||||||
let entry = FlowKey {
|
let entry = FlowKey {
|
||||||
src: "10.2.2.3:4321".parse().unwrap(),
|
src: "10.2.2.3:4321".parse().unwrap(),
|
||||||
dst: "10.1.1.2:1234".parse().unwrap(),
|
dst: "10.1.1.2:1234".parse().unwrap(),
|
||||||
@@ -359,8 +377,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn flow_table_passes_non_loopback_or_malformed_modified_source_packets() {
|
fn flow_table_passes_non_loopback_or_malformed_modified_source_packets() {
|
||||||
let table = FlowTable::<()>::default();
|
let table = FlowTable::<()>::default();
|
||||||
let mut non_loopback =
|
let mut non_loopback = ZCPacket::new_with_payload(&ipv4_packet(IpProtocol::Tcp, 20));
|
||||||
ZCPacket::new_with_payload(&ipv4_packet(IpNextHeaderProtocols::Tcp, 20));
|
|
||||||
non_loopback.fill_peer_manager_hdr(7, 8, PacketType::DataWithKcpSrcModified as u8);
|
non_loopback.fill_peer_manager_hdr(7, 8, PacketType::DataWithKcpSrcModified as u8);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
table.route_peer_packet(&non_loopback, false),
|
table.route_peer_packet(&non_loopback, false),
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
sync::{Arc, Weak},
|
sync::{Arc, Weak},
|
||||||
};
|
};
|
||||||
|
|
||||||
use pnet_packet::ipv4::Ipv4Packet;
|
use smoltcp::wire::Ipv4Packet;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{Mutex, mpsc},
|
sync::{Mutex, mpsc},
|
||||||
task::JoinSet,
|
task::JoinSet,
|
||||||
@@ -54,11 +54,11 @@ impl SmoltcpPlane {
|
|||||||
|
|
||||||
forward_tasks.spawn(async move {
|
forward_tasks.spawn(async move {
|
||||||
while let Some(data) = stack_stream.recv().await {
|
while let Some(data) = stack_stream.recv().await {
|
||||||
let Some(ipv4) = Ipv4Packet::new(&data) else {
|
let Ok(ipv4) = Ipv4Packet::new_checked(&data) else {
|
||||||
tracing::error!(?data, "smoltcp emitted a non-IPv4 packet");
|
tracing::error!(?data, "smoltcp emitted a non-IPv4 packet");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let destination = ipv4.get_destination();
|
let destination = ipv4.dst_addr();
|
||||||
let Some(peer_manager) = peer_manager.upgrade() else {
|
let Some(peer_manager) = peer_manager.upgrade() else {
|
||||||
tracing::debug!("smoltcp-to-peer bridge lost PeerManager");
|
tracing::debug!("smoltcp-to-peer bridge lost PeerManager");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
||||||
use pnet_packet::{
|
use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Packet, TcpPacket};
|
||||||
MutablePacket,
|
|
||||||
ip::IpNextHeaderProtocols,
|
|
||||||
ipv4::{self, MutableIpv4Packet},
|
|
||||||
tcp::{self, MutableTcpPacket, TcpFlags},
|
|
||||||
};
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -177,28 +172,25 @@ fn build_tcp_packet(src: SocketAddr, dst: SocketAddr) -> Vec<u8> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut ip_packet = MutableIpv4Packet::new(&mut buf).unwrap();
|
let mut ip_packet = Ipv4Packet::new_unchecked(&mut buf);
|
||||||
ip_packet.set_version(4);
|
ip_packet.set_version(4);
|
||||||
ip_packet.set_header_length(5);
|
ip_packet.set_header_len(20);
|
||||||
ip_packet.set_total_length(40);
|
ip_packet.set_total_len(40);
|
||||||
ip_packet.set_ttl(64);
|
ip_packet.set_hop_limit(64);
|
||||||
ip_packet.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
ip_packet.set_next_header(IpProtocol::Tcp);
|
||||||
ip_packet.set_source(src_ip);
|
ip_packet.set_src_addr(src_ip);
|
||||||
ip_packet.set_destination(dst_ip);
|
ip_packet.set_dst_addr(dst_ip);
|
||||||
|
|
||||||
let mut tcp_packet = MutableTcpPacket::new(ip_packet.payload_mut()).unwrap();
|
let mut tcp_packet = TcpPacket::new_unchecked(ip_packet.payload_mut());
|
||||||
tcp_packet.set_source(src.port());
|
tcp_packet.set_src_port(src.port());
|
||||||
tcp_packet.set_destination(dst.port());
|
tcp_packet.set_dst_port(dst.port());
|
||||||
tcp_packet.set_data_offset(5);
|
tcp_packet.set_header_len(20);
|
||||||
tcp_packet.set_flags(TcpFlags::SYN | TcpFlags::ACK);
|
tcp_packet.set_syn(true);
|
||||||
tcp_packet.set_window(65535);
|
tcp_packet.set_ack(true);
|
||||||
tcp_packet.set_checksum(tcp::ipv4_checksum(
|
tcp_packet.set_window_len(65535);
|
||||||
&tcp_packet.to_immutable(),
|
tcp_packet.fill_checksum(&IpAddress::Ipv4(src_ip), &IpAddress::Ipv4(dst_ip));
|
||||||
&src_ip,
|
|
||||||
&dst_ip,
|
|
||||||
));
|
|
||||||
|
|
||||||
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
|
ip_packet.fill_checksum();
|
||||||
}
|
}
|
||||||
|
|
||||||
buf
|
buf
|
||||||
@@ -207,20 +199,20 @@ fn build_tcp_packet(src: SocketAddr, dst: SocketAddr) -> Vec<u8> {
|
|||||||
fn build_udp_followup_fragment(src: Ipv4Addr, dst: Ipv4Addr) -> Vec<u8> {
|
fn build_udp_followup_fragment(src: Ipv4Addr, dst: Ipv4Addr) -> Vec<u8> {
|
||||||
let mut buf = vec![0u8; 28];
|
let mut buf = vec![0u8; 28];
|
||||||
{
|
{
|
||||||
let mut ip_packet = MutableIpv4Packet::new(&mut buf).unwrap();
|
let mut ip_packet = Ipv4Packet::new_unchecked(&mut buf);
|
||||||
ip_packet.set_version(4);
|
ip_packet.set_version(4);
|
||||||
ip_packet.set_header_length(5);
|
ip_packet.set_header_len(20);
|
||||||
ip_packet.set_total_length(28);
|
ip_packet.set_total_len(28);
|
||||||
ip_packet.set_ttl(64);
|
ip_packet.set_hop_limit(64);
|
||||||
ip_packet.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
ip_packet.set_next_header(IpProtocol::Udp);
|
||||||
ip_packet.set_fragment_offset(1);
|
ip_packet.set_frag_offset(8);
|
||||||
ip_packet.set_source(src);
|
ip_packet.set_src_addr(src);
|
||||||
ip_packet.set_destination(dst);
|
ip_packet.set_dst_addr(dst);
|
||||||
ip_packet
|
ip_packet
|
||||||
.payload_mut()
|
.payload_mut()
|
||||||
.copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
|
.copy_from_slice(&[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]);
|
||||||
|
|
||||||
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
|
ip_packet.fill_checksum();
|
||||||
}
|
}
|
||||||
|
|
||||||
buf
|
buf
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
use std::{future::Future, net::Ipv4Addr};
|
use std::{future::Future, net::Ipv4Addr};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use pnet_packet::{
|
use smoltcp::wire::{
|
||||||
MutablePacket, Packet,
|
IPV4_HEADER_LEN, Icmpv4Message, Icmpv4Packet, IpAddress, IpProtocol, Ipv4Packet,
|
||||||
icmp::{self, IcmpPacket, IcmpTypes, MutableIcmpPacket},
|
UDP_HEADER_LEN, UdpPacket,
|
||||||
ip::IpNextHeaderProtocols,
|
|
||||||
ipv4::{self, Ipv4Flags, Ipv4Packet, MutableIpv4Packet},
|
|
||||||
udp::{self, MutableUdpPacket, UdpPacket},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -119,43 +116,45 @@ where
|
|||||||
if packet.peer_manager_header().is_none() {
|
if packet.peer_manager_header().is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let Some(ip_packet) = Ipv4Packet::new(packet.payload()) else {
|
if packet.payload().len() < IPV4_HEADER_LEN {
|
||||||
return false;
|
return false;
|
||||||
};
|
}
|
||||||
if ip_packet.get_version() != 4 || ip_packet.get_destination() != fake_ip {
|
let ip_packet = Ipv4Packet::new_unchecked(packet.payload());
|
||||||
|
if ip_packet.version() != 4 || ip_packet.dst_addr() != fake_ip {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ip_header_length = ip_packet.get_header_length() as usize * 4;
|
let ip_header_length = ip_packet.header_len() as usize;
|
||||||
let ip_total_length = ip_packet.get_total_length() as usize;
|
let ip_total_length = ip_packet.total_len() as usize;
|
||||||
if ip_header_length < MutableIpv4Packet::minimum_packet_size()
|
if ip_header_length < IPV4_HEADER_LEN
|
||||||
|| ip_header_length > ip_total_length
|
|| ip_header_length > ip_total_length
|
||||||
|| ip_total_length != packet.payload().len()
|
|| ip_total_length != packet.payload().len()
|
||||||
|| ip_packet.get_fragment_offset() != 0
|
|| ip_packet.frag_offset() != 0
|
||||||
|| ip_packet.get_flags() & Ipv4Flags::MoreFragments != 0
|
|| ip_packet.more_frags()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let protocol = ip_packet.get_next_level_protocol();
|
let protocol = ip_packet.next_header();
|
||||||
let source_ip = ip_packet.get_source();
|
let source_ip = ip_packet.src_addr();
|
||||||
let destination_ip = ip_packet.get_destination();
|
let destination_ip = ip_packet.dst_addr();
|
||||||
|
|
||||||
match protocol {
|
match protocol {
|
||||||
IpNextHeaderProtocols::Udp => {
|
IpProtocol::Udp => {
|
||||||
let ip_payload = &packet.payload()[ip_header_length..ip_total_length];
|
let ip_payload = &packet.payload()[ip_header_length..ip_total_length];
|
||||||
let Some(udp_packet) = UdpPacket::new(ip_payload) else {
|
if ip_payload.len() < UDP_HEADER_LEN {
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let udp_length = udp_packet.get_length() as usize;
|
|
||||||
if udp_length != ip_payload.len() || udp_length < UdpPacket::minimum_packet_size() {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if udp_packet.get_destination() != 53 {
|
let udp_packet = UdpPacket::new_unchecked(ip_payload);
|
||||||
|
let udp_length = udp_packet.len() as usize;
|
||||||
|
if udp_length != ip_payload.len() || udp_length < UDP_HEADER_LEN {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let source_port = udp_packet.get_source();
|
if udp_packet.dst_port() != 53 {
|
||||||
let destination_port = udp_packet.get_destination();
|
return false;
|
||||||
|
}
|
||||||
|
let source_port = udp_packet.src_port();
|
||||||
|
let destination_port = udp_packet.dst_port();
|
||||||
let query = MagicDnsQuery {
|
let query = MagicDnsQuery {
|
||||||
source: std::net::SocketAddr::from((source_ip, source_port)),
|
source: std::net::SocketAddr::from((source_ip, source_port)),
|
||||||
payload: udp_packet.payload().to_vec(),
|
payload: udp_packet.payload().to_vec(),
|
||||||
@@ -175,30 +174,26 @@ where
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IpNextHeaderProtocols::Icmp => {
|
IpProtocol::Icmp => {
|
||||||
let Some(icmp_packet) = IcmpPacket::new(&packet.payload()[ip_header_length..]) else {
|
let Ok(icmp_packet) = Icmpv4Packet::new_checked(&packet.payload()[ip_header_length..])
|
||||||
return false;
|
|
||||||
};
|
|
||||||
if icmp_packet.get_icmp_type() != IcmpTypes::EchoRequest {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let Some(mut icmp_packet) =
|
|
||||||
MutableIcmpPacket::new(&mut packet.mut_payload()[ip_header_length..])
|
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
icmp_packet.set_icmp_type(IcmpTypes::EchoReply);
|
if icmp_packet.msg_type() != Icmpv4Message::EchoRequest {
|
||||||
icmp_packet.set_checksum(icmp::checksum(&icmp_packet.to_immutable()));
|
return false;
|
||||||
|
}
|
||||||
|
let mut icmp_packet =
|
||||||
|
Icmpv4Packet::new_unchecked(&mut packet.mut_payload()[ip_header_length..]);
|
||||||
|
icmp_packet.set_msg_type(Icmpv4Message::EchoReply);
|
||||||
|
icmp_packet.fill_checksum();
|
||||||
}
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(mut ip_packet) = MutableIpv4Packet::new(packet.mut_payload()) else {
|
let mut ip_packet = Ipv4Packet::new_unchecked(packet.mut_payload());
|
||||||
return false;
|
ip_packet.set_src_addr(destination_ip);
|
||||||
};
|
ip_packet.set_dst_addr(source_ip);
|
||||||
ip_packet.set_source(destination_ip);
|
ip_packet.fill_checksum();
|
||||||
ip_packet.set_destination(source_ip);
|
|
||||||
ip_packet.set_checksum(ipv4::checksum(&ip_packet.to_immutable()));
|
|
||||||
let payload_length = packet.payload().len() as u32;
|
let payload_length = packet.payload().len() as u32;
|
||||||
let Some(header) = packet.mut_peer_manager_header() else {
|
let Some(header) = packet.mut_peer_manager_header() else {
|
||||||
return false;
|
return false;
|
||||||
@@ -218,7 +213,7 @@ fn apply_udp_response(
|
|||||||
ip_header_length: usize,
|
ip_header_length: usize,
|
||||||
response: &[u8],
|
response: &[u8],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(udp_length) = UdpPacket::minimum_packet_size().checked_add(response.len()) else {
|
let Some(udp_length) = UDP_HEADER_LEN.checked_add(response.len()) else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let Some(ip_length) = ip_header_length.checked_add(udp_length) else {
|
let Some(ip_length) = ip_header_length.checked_add(udp_length) else {
|
||||||
@@ -237,26 +232,21 @@ fn apply_udp_response(
|
|||||||
if packet.mut_inner().capacity() < inner_length {
|
if packet.mut_inner().capacity() < inner_length {
|
||||||
packet
|
packet
|
||||||
.mut_inner()
|
.mut_inner()
|
||||||
.truncate(header_length + ip_header_length + UdpPacket::minimum_packet_size());
|
.truncate(header_length + ip_header_length + UDP_HEADER_LEN);
|
||||||
}
|
}
|
||||||
packet.mut_inner().resize(inner_length, 0);
|
packet.mut_inner().resize(inner_length, 0);
|
||||||
|
|
||||||
let Some(mut ip_packet) = MutableIpv4Packet::new(packet.mut_payload()) else {
|
let mut ip_packet = Ipv4Packet::new_unchecked(packet.mut_payload());
|
||||||
return false;
|
ip_packet.set_total_len(ip_length as u16);
|
||||||
};
|
let mut udp_packet = UdpPacket::new_unchecked(ip_packet.payload_mut());
|
||||||
ip_packet.set_total_length(ip_length as u16);
|
udp_packet.set_len(udp_length as u16);
|
||||||
let Some(mut udp_packet) = MutableUdpPacket::new(ip_packet.payload_mut()) else {
|
udp_packet.set_src_port(destination_port);
|
||||||
return false;
|
udp_packet.set_dst_port(source_port);
|
||||||
};
|
|
||||||
udp_packet.set_length(udp_length as u16);
|
|
||||||
udp_packet.set_source(destination_port);
|
|
||||||
udp_packet.set_destination(source_port);
|
|
||||||
udp_packet.payload_mut().copy_from_slice(response);
|
udp_packet.payload_mut().copy_from_slice(response);
|
||||||
udp_packet.set_checksum(udp::ipv4_checksum(
|
udp_packet.fill_checksum(
|
||||||
&udp_packet.to_immutable(),
|
&IpAddress::Ipv4(destination_ip),
|
||||||
&destination_ip,
|
&IpAddress::Ipv4(source_ip),
|
||||||
&source_ip,
|
);
|
||||||
));
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,17 +257,17 @@ mod tests {
|
|||||||
fn udp_query(payload: &[u8], destination_port: u16) -> ZCPacket {
|
fn udp_query(payload: &[u8], destination_port: u16) -> ZCPacket {
|
||||||
let mut bytes = vec![0; 20 + 8 + payload.len()];
|
let mut bytes = vec![0; 20 + 8 + payload.len()];
|
||||||
{
|
{
|
||||||
let mut ip = MutableIpv4Packet::new(&mut bytes).unwrap();
|
let mut ip = Ipv4Packet::new_unchecked(&mut bytes);
|
||||||
ip.set_version(4);
|
ip.set_version(4);
|
||||||
ip.set_header_length(5);
|
ip.set_header_len(20);
|
||||||
ip.set_total_length((20 + 8 + payload.len()) as u16);
|
ip.set_total_len((20 + 8 + payload.len()) as u16);
|
||||||
ip.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
ip.set_next_header(IpProtocol::Udp);
|
||||||
ip.set_source("10.0.0.2".parse().unwrap());
|
ip.set_src_addr("10.0.0.2".parse().unwrap());
|
||||||
ip.set_destination("100.100.100.101".parse().unwrap());
|
ip.set_dst_addr("100.100.100.101".parse().unwrap());
|
||||||
let mut udp = MutableUdpPacket::new(ip.payload_mut()).unwrap();
|
let mut udp = UdpPacket::new_unchecked(ip.payload_mut());
|
||||||
udp.set_source(53000);
|
udp.set_src_port(53000);
|
||||||
udp.set_destination(destination_port);
|
udp.set_dst_port(destination_port);
|
||||||
udp.set_length((8 + payload.len()) as u16);
|
udp.set_len((8 + payload.len()) as u16);
|
||||||
udp.payload_mut().copy_from_slice(payload);
|
udp.payload_mut().copy_from_slice(payload);
|
||||||
}
|
}
|
||||||
ZCPacket::new_with_payload(&bytes)
|
ZCPacket::new_with_payload(&bytes)
|
||||||
@@ -286,15 +276,15 @@ mod tests {
|
|||||||
fn icmp_echo_request() -> ZCPacket {
|
fn icmp_echo_request() -> ZCPacket {
|
||||||
let mut bytes = vec![0; 20 + 8];
|
let mut bytes = vec![0; 20 + 8];
|
||||||
{
|
{
|
||||||
let mut ip = MutableIpv4Packet::new(&mut bytes).unwrap();
|
let mut ip = Ipv4Packet::new_unchecked(&mut bytes);
|
||||||
ip.set_version(4);
|
ip.set_version(4);
|
||||||
ip.set_header_length(5);
|
ip.set_header_len(20);
|
||||||
ip.set_total_length(28);
|
ip.set_total_len(28);
|
||||||
ip.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
ip.set_next_header(IpProtocol::Icmp);
|
||||||
ip.set_source("10.0.0.2".parse().unwrap());
|
ip.set_src_addr("10.0.0.2".parse().unwrap());
|
||||||
ip.set_destination("100.100.100.101".parse().unwrap());
|
ip.set_dst_addr("100.100.100.101".parse().unwrap());
|
||||||
let mut icmp = MutableIcmpPacket::new(ip.payload_mut()).unwrap();
|
let mut icmp = Icmpv4Packet::new_unchecked(ip.payload_mut());
|
||||||
icmp.set_icmp_type(IcmpTypes::EchoRequest);
|
icmp.set_msg_type(Icmpv4Message::EchoRequest);
|
||||||
}
|
}
|
||||||
ZCPacket::new_with_payload(&bytes)
|
ZCPacket::new_with_payload(&bytes)
|
||||||
}
|
}
|
||||||
@@ -314,18 +304,15 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(handled);
|
assert!(handled);
|
||||||
let ip = Ipv4Packet::new(packet.payload()).unwrap();
|
let ip = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ip.get_source(),
|
ip.src_addr(),
|
||||||
"100.100.100.101".parse::<Ipv4Addr>().unwrap()
|
"100.100.100.101".parse::<Ipv4Addr>().unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(ip.dst_addr(), "10.0.0.2".parse::<Ipv4Addr>().unwrap());
|
||||||
ip.get_destination(),
|
let udp = UdpPacket::new_checked(ip.payload()).unwrap();
|
||||||
"10.0.0.2".parse::<Ipv4Addr>().unwrap()
|
assert_eq!(udp.src_port(), 53);
|
||||||
);
|
assert_eq!(udp.dst_port(), 53000);
|
||||||
let udp = UdpPacket::new(ip.payload()).unwrap();
|
|
||||||
assert_eq!(udp.get_source(), 53);
|
|
||||||
assert_eq!(udp.get_destination(), 53000);
|
|
||||||
assert_eq!(udp.payload(), b"response");
|
assert_eq!(udp.payload(), b"response");
|
||||||
assert_eq!(packet.get_dst_peer_id(), Some(42));
|
assert_eq!(packet.get_dst_peer_id(), Some(42));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -337,9 +324,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn packet_engine_rejects_invalid_ipv4_header_without_mutation() {
|
async fn packet_engine_rejects_invalid_ipv4_header_without_mutation() {
|
||||||
let mut packet = udp_query(b"query", 53);
|
let mut packet = udp_query(b"query", 53);
|
||||||
MutableIpv4Packet::new(packet.mut_payload())
|
Ipv4Packet::new_unchecked(packet.mut_payload()).set_header_len(60);
|
||||||
.unwrap()
|
|
||||||
.set_header_length(15);
|
|
||||||
let original = packet.payload().to_vec();
|
let original = packet.payload().to_vec();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -373,10 +358,8 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn packet_engine_rejects_inconsistent_udp_length_without_mutation() {
|
async fn packet_engine_rejects_inconsistent_udp_length_without_mutation() {
|
||||||
let mut packet = udp_query(b"query", 53);
|
let mut packet = udp_query(b"query", 53);
|
||||||
let mut ip = MutableIpv4Packet::new(packet.mut_payload()).unwrap();
|
let mut ip = Ipv4Packet::new_unchecked(packet.mut_payload());
|
||||||
MutableUdpPacket::new(ip.payload_mut())
|
UdpPacket::new_unchecked(ip.payload_mut()).set_len(8);
|
||||||
.unwrap()
|
|
||||||
.set_length(8);
|
|
||||||
let original = packet.payload().to_vec();
|
let original = packet.payload().to_vec();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -394,9 +377,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn packet_engine_rejects_fragmented_packets_without_mutation() {
|
async fn packet_engine_rejects_fragmented_packets_without_mutation() {
|
||||||
let mut packet = udp_query(b"query", 53);
|
let mut packet = udp_query(b"query", 53);
|
||||||
MutableIpv4Packet::new(packet.mut_payload())
|
Ipv4Packet::new_unchecked(packet.mut_payload()).set_more_frags(true);
|
||||||
.unwrap()
|
|
||||||
.set_flags(Ipv4Flags::MoreFragments);
|
|
||||||
let original = packet.payload().to_vec();
|
let original = packet.payload().to_vec();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@@ -442,13 +423,13 @@ mod tests {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert!(handled);
|
assert!(handled);
|
||||||
let ip = Ipv4Packet::new(packet.payload()).unwrap();
|
let ip = Ipv4Packet::new_checked(packet.payload()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
ip.get_source(),
|
ip.src_addr(),
|
||||||
"100.100.100.101".parse::<Ipv4Addr>().unwrap()
|
"100.100.100.101".parse::<Ipv4Addr>().unwrap()
|
||||||
);
|
);
|
||||||
let icmp = pnet_packet::icmp::IcmpPacket::new(ip.payload()).unwrap();
|
let icmp = Icmpv4Packet::new_checked(ip.payload()).unwrap();
|
||||||
assert_eq!(icmp.get_icmp_type(), IcmpTypes::EchoReply);
|
assert_eq!(icmp.msg_type(), Icmpv4Message::EchoReply);
|
||||||
assert_eq!(packet.get_dst_peer_id(), Some(7));
|
assert_eq!(packet.get_dst_peer_id(), Some(7));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
use std::{net::Ipv4Addr, sync::Arc, time::Duration};
|
use std::{net::Ipv4Addr, sync::Arc, time::Duration};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use pnet_packet::{
|
|
||||||
Packet,
|
|
||||||
icmp::{self, IcmpCode, IcmpTypes, MutableIcmpPacket, echo_reply::MutableEchoReplyPacket},
|
|
||||||
ip::IpNextHeaderProtocols,
|
|
||||||
ipv4::Ipv4Packet,
|
|
||||||
};
|
|
||||||
use quanta::Instant;
|
use quanta::Instant;
|
||||||
|
use smoltcp::wire::{IPV4_HEADER_LEN, Icmpv4Message, Icmpv4Packet, Ipv4Packet};
|
||||||
|
|
||||||
use crate::packet::{PacketType, ZCPacket};
|
use crate::packet::{PacketType, ZCPacket};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
cidr_table::ProxyCidrTable,
|
cidr_table::ProxyCidrTable,
|
||||||
ip_reassembler::{
|
ip_reassembler::{ComposeIpv4PacketArgs, IpProtocol, IpReassembler, compose_ipv4_packet},
|
||||||
ComposeIpv4PacketArgs, IpProtocol, IpReassembler, SmolIpv4Packet, compose_ipv4_packet,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
@@ -51,6 +44,8 @@ struct IcmpNatEntry {
|
|||||||
started_at: Instant,
|
started_at: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ICMP_ECHO_HEADER_LEN: usize = 8;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IcmpProxyEngine {
|
pub struct IcmpProxyEngine {
|
||||||
cidr_table: Arc<ProxyCidrTable>,
|
cidr_table: Arc<ProxyCidrTable>,
|
||||||
@@ -84,15 +79,17 @@ impl IcmpProxyEngine {
|
|||||||
if header.packet_type != PacketType::Data as u8 || header.is_no_proxy() {
|
if header.packet_type != PacketType::Data as u8 || header.is_no_proxy() {
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
}
|
}
|
||||||
let Some(ipv4) = Ipv4Packet::new(packet.payload()) else {
|
let Ok(ipv4) = Ipv4Packet::new_checked(packet.payload()) else {
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
};
|
};
|
||||||
if ipv4.get_version() != 4 || ipv4.get_next_level_protocol() != IpNextHeaderProtocols::Icmp
|
if ipv4.version() != 4
|
||||||
|
|| usize::from(ipv4.header_len()) < IPV4_HEADER_LEN
|
||||||
|
|| ipv4.next_header() != IpProtocol::Icmp
|
||||||
{
|
{
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mapped_destination = ipv4.get_destination();
|
let mapped_destination = ipv4.dst_addr();
|
||||||
let real_destination = self.cidr_table.lookup_v4(mapped_destination);
|
let real_destination = self.cidr_table.lookup_v4(mapped_destination);
|
||||||
let is_local_no_tun = context.no_tun && mapped_destination == virtual_ipv4;
|
let is_local_no_tun = context.no_tun && mapped_destination == virtual_ipv4;
|
||||||
if real_destination.is_none() && !header.is_exit_node() && !is_local_no_tun {
|
if real_destination.is_none() && !header.is_exit_node() && !is_local_no_tun {
|
||||||
@@ -100,33 +97,27 @@ impl IcmpProxyEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reassembled;
|
let reassembled;
|
||||||
let smol_ipv4 = SmolIpv4Packet::new_unchecked(ipv4.packet());
|
let request_bytes = if IpReassembler::is_packet_fragmented(&ipv4) {
|
||||||
let request = if IpReassembler::is_packet_fragmented(&smol_ipv4) {
|
reassembled = self.reassembler.add_fragment(&ipv4);
|
||||||
let Ok(smol_ipv4) = SmolIpv4Packet::new_checked(ipv4.packet()) else {
|
|
||||||
return IcmpProxyAction::Pass;
|
|
||||||
};
|
|
||||||
reassembled = self.reassembler.add_fragment(&smol_ipv4);
|
|
||||||
let Some(reassembled) = reassembled.as_ref() else {
|
let Some(reassembled) = reassembled.as_ref() else {
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
};
|
};
|
||||||
let Some(request) = icmp::echo_request::EchoRequestPacket::new(reassembled) else {
|
reassembled.as_slice()
|
||||||
return IcmpProxyAction::Pass;
|
|
||||||
};
|
|
||||||
request
|
|
||||||
} else {
|
} else {
|
||||||
let Some(request) = icmp::echo_request::EchoRequestPacket::new(ipv4.payload()) else {
|
ipv4.payload()
|
||||||
|
};
|
||||||
|
if request_bytes.len() < ICMP_ECHO_HEADER_LEN {
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
};
|
}
|
||||||
request
|
let request = Icmpv4Packet::new_unchecked(request_bytes);
|
||||||
};
|
if request.msg_type() != Icmpv4Message::EchoRequest {
|
||||||
if request.get_icmp_type() != IcmpTypes::EchoRequest {
|
|
||||||
return IcmpProxyAction::Pass;
|
return IcmpProxyAction::Pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_local_no_tun {
|
if is_local_no_tun {
|
||||||
return self.local_reply(
|
return self.local_reply(
|
||||||
mapped_destination,
|
mapped_destination,
|
||||||
ipv4.get_source(),
|
ipv4.src_addr(),
|
||||||
header.to_peer_id.get(),
|
header.to_peer_id.get(),
|
||||||
header.from_peer_id.get(),
|
header.from_peer_id.get(),
|
||||||
&request,
|
&request,
|
||||||
@@ -136,15 +127,15 @@ impl IcmpProxyEngine {
|
|||||||
let real_destination = real_destination.unwrap_or(mapped_destination);
|
let real_destination = real_destination.unwrap_or(mapped_destination);
|
||||||
let key = IcmpNatKey {
|
let key = IcmpNatKey {
|
||||||
real_destination,
|
real_destination,
|
||||||
identifier: request.get_identifier(),
|
identifier: request.echo_ident(),
|
||||||
sequence: request.get_sequence_number(),
|
sequence: request.echo_seq_no(),
|
||||||
};
|
};
|
||||||
self.nat_table.insert(
|
self.nat_table.insert(
|
||||||
key,
|
key,
|
||||||
IcmpNatEntry {
|
IcmpNatEntry {
|
||||||
source_peer_id: header.from_peer_id.get(),
|
source_peer_id: header.from_peer_id.get(),
|
||||||
local_peer_id: header.to_peer_id.get(),
|
local_peer_id: header.to_peer_id.get(),
|
||||||
source_ip: ipv4.get_source(),
|
source_ip: ipv4.src_addr(),
|
||||||
mapped_destination,
|
mapped_destination,
|
||||||
started_at: Instant::now(),
|
started_at: Instant::now(),
|
||||||
},
|
},
|
||||||
@@ -152,35 +143,35 @@ impl IcmpProxyEngine {
|
|||||||
|
|
||||||
IcmpProxyAction::SendToSocket {
|
IcmpProxyAction::SendToSocket {
|
||||||
destination: real_destination,
|
destination: real_destination,
|
||||||
packet: request.packet().to_vec(),
|
packet: request.as_ref().to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_socket_response(&self, peer_ip: Ipv4Addr, packet: &mut [u8]) -> Vec<ZCPacket> {
|
pub fn handle_socket_response(&self, peer_ip: Ipv4Addr, packet: &mut [u8]) -> Vec<ZCPacket> {
|
||||||
let Some(ipv4) = Ipv4Packet::new(packet) else {
|
let Ok(ipv4) = Ipv4Packet::new_checked(&*packet) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let Some(reply) = icmp::echo_reply::EchoReplyPacket::new(ipv4.payload()) else {
|
if usize::from(ipv4.header_len()) < IPV4_HEADER_LEN
|
||||||
|
|| ipv4.payload().len() < ICMP_ECHO_HEADER_LEN
|
||||||
|
{
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
}
|
||||||
if reply.get_icmp_type() != IcmpTypes::EchoReply {
|
let reply = Icmpv4Packet::new_unchecked(ipv4.payload());
|
||||||
|
if reply.msg_type() != Icmpv4Message::EchoReply {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let key = IcmpNatKey {
|
let key = IcmpNatKey {
|
||||||
real_destination: peer_ip,
|
real_destination: peer_ip,
|
||||||
identifier: reply.get_identifier(),
|
identifier: reply.echo_ident(),
|
||||||
sequence: reply.get_sequence_number(),
|
sequence: reply.echo_seq_no(),
|
||||||
};
|
};
|
||||||
let Some((_, entry)) = self.nat_table.remove(&key) else {
|
let Some((_, entry)) = self.nat_table.remove(&key) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let Some(payload_len) = packet
|
let Some(payload_len) = packet.len().checked_sub(ipv4.header_len() as usize) else {
|
||||||
.len()
|
|
||||||
.checked_sub(ipv4.get_header_length() as usize * 4)
|
|
||||||
else {
|
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
let ip_id = ipv4.get_identification();
|
let ip_id = ipv4.ident();
|
||||||
let mut responses = Vec::new();
|
let mut responses = Vec::new();
|
||||||
let _ = compose_ipv4_packet(
|
let _ = compose_ipv4_packet(
|
||||||
ComposeIpv4PacketArgs {
|
ComposeIpv4PacketArgs {
|
||||||
@@ -226,17 +217,16 @@ impl IcmpProxyEngine {
|
|||||||
destination: Ipv4Addr,
|
destination: Ipv4Addr,
|
||||||
source_peer_id: u32,
|
source_peer_id: u32,
|
||||||
destination_peer_id: u32,
|
destination_peer_id: u32,
|
||||||
request: &icmp::echo_request::EchoRequestPacket<'_>,
|
request: &Icmpv4Packet<&[u8]>,
|
||||||
) -> IcmpProxyAction {
|
) -> IcmpProxyAction {
|
||||||
let mut buffer = vec![0_u8; request.packet().len() + 20];
|
let mut buffer = vec![0_u8; request.as_ref().len() + 20];
|
||||||
let mut reply = MutableEchoReplyPacket::new(&mut buffer[20..]).unwrap();
|
let mut reply = Icmpv4Packet::new_unchecked(&mut buffer[20..]);
|
||||||
reply.set_icmp_type(IcmpTypes::EchoReply);
|
reply.set_msg_type(Icmpv4Message::EchoReply);
|
||||||
reply.set_icmp_code(IcmpCode::new(0));
|
reply.set_msg_code(0);
|
||||||
reply.set_identifier(request.get_identifier());
|
reply.set_echo_ident(request.echo_ident());
|
||||||
reply.set_sequence_number(request.get_sequence_number());
|
reply.set_echo_seq_no(request.echo_seq_no());
|
||||||
reply.set_payload(request.payload());
|
reply.data_mut().copy_from_slice(request.data());
|
||||||
let mut reply = MutableIcmpPacket::new(&mut buffer[20..]).unwrap();
|
reply.fill_checksum();
|
||||||
reply.set_checksum(icmp::checksum(&reply.to_immutable()));
|
|
||||||
|
|
||||||
let payload_len = buffer.len() - 20;
|
let payload_len = buffer.len() - 20;
|
||||||
let mut responses = Vec::new();
|
let mut responses = Vec::new();
|
||||||
@@ -267,12 +257,6 @@ impl IcmpProxyEngine {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pnet_packet::{
|
|
||||||
MutablePacket as _,
|
|
||||||
icmp::{MutableIcmpPacket, echo_request::MutableEchoRequestPacket},
|
|
||||||
ipv4::{self, MutableIpv4Packet},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::gateway::proxy::cidr_table::{ProxyCidrRule, ProxyCidrSnapshot};
|
use crate::gateway::proxy::cidr_table::{ProxyCidrRule, ProxyCidrSnapshot};
|
||||||
|
|
||||||
@@ -283,25 +267,24 @@ mod tests {
|
|||||||
) -> ZCPacket {
|
) -> ZCPacket {
|
||||||
let mut bytes = vec![0_u8; 20 + 8 + payload.len()];
|
let mut bytes = vec![0_u8; 20 + 8 + payload.len()];
|
||||||
{
|
{
|
||||||
let mut request = MutableEchoRequestPacket::new(&mut bytes[20..]).unwrap();
|
let mut request = Icmpv4Packet::new_unchecked(&mut bytes[20..]);
|
||||||
request.set_icmp_type(IcmpTypes::EchoRequest);
|
request.set_msg_type(Icmpv4Message::EchoRequest);
|
||||||
request.set_identifier(7);
|
request.set_echo_ident(7);
|
||||||
request.set_sequence_number(11);
|
request.set_echo_seq_no(11);
|
||||||
request.set_payload(payload);
|
request.data_mut().copy_from_slice(payload);
|
||||||
let mut icmp = MutableIcmpPacket::new(&mut bytes[20..]).unwrap();
|
request.fill_checksum();
|
||||||
icmp.set_checksum(icmp::checksum(&icmp.to_immutable()));
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let packet_len = bytes.len() as u16;
|
let packet_len = bytes.len() as u16;
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut bytes).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut bytes);
|
||||||
ipv4.set_version(4);
|
ipv4.set_version(4);
|
||||||
ipv4.set_header_length(5);
|
ipv4.set_header_len(20);
|
||||||
ipv4.set_total_length(packet_len);
|
ipv4.set_total_len(packet_len);
|
||||||
ipv4.set_ttl(64);
|
ipv4.set_hop_limit(64);
|
||||||
ipv4.set_next_level_protocol(IpNextHeaderProtocols::Icmp);
|
ipv4.set_next_header(IpProtocol::Icmp);
|
||||||
ipv4.set_source(source);
|
ipv4.set_src_addr(source);
|
||||||
ipv4.set_destination(destination);
|
ipv4.set_dst_addr(destination);
|
||||||
ipv4.set_checksum(ipv4::checksum(&ipv4.to_immutable()));
|
ipv4.fill_checksum();
|
||||||
}
|
}
|
||||||
let mut packet = ZCPacket::new_with_payload(&bytes);
|
let mut packet = ZCPacket::new_with_payload(&bytes);
|
||||||
packet.fill_peer_manager_hdr(101, 202, PacketType::Data as u8);
|
packet.fill_peer_manager_hdr(101, 202, PacketType::Data as u8);
|
||||||
@@ -357,16 +340,32 @@ mod tests {
|
|||||||
let header = reply.peer_manager_header().unwrap();
|
let header = reply.peer_manager_header().unwrap();
|
||||||
assert_eq!(header.from_peer_id.get(), 202);
|
assert_eq!(header.from_peer_id.get(), 202);
|
||||||
assert_eq!(header.to_peer_id.get(), 101);
|
assert_eq!(header.to_peer_id.get(), 101);
|
||||||
let ipv4 = Ipv4Packet::new(reply.payload()).unwrap();
|
let ipv4 = Ipv4Packet::new_checked(reply.payload()).unwrap();
|
||||||
assert_eq!(ipv4.get_source(), "10.0.0.1".parse::<Ipv4Addr>().unwrap());
|
assert_eq!(ipv4.src_addr(), "10.0.0.1".parse::<Ipv4Addr>().unwrap());
|
||||||
assert_eq!(
|
assert_eq!(ipv4.dst_addr(), "10.0.0.2".parse::<Ipv4Addr>().unwrap());
|
||||||
ipv4.get_destination(),
|
let reply = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
|
||||||
"10.0.0.2".parse::<Ipv4Addr>().unwrap()
|
assert_eq!(reply.echo_ident(), 7);
|
||||||
);
|
assert_eq!(reply.echo_seq_no(), 11);
|
||||||
let reply = icmp::echo_reply::EchoReplyPacket::new(ipv4.payload()).unwrap();
|
assert_eq!(reply.data(), b"ping");
|
||||||
assert_eq!(reply.get_identifier(), 7);
|
}
|
||||||
assert_eq!(reply.get_sequence_number(), 11);
|
|
||||||
assert_eq!(reply.payload(), b"ping");
|
#[test]
|
||||||
|
fn peer_packet_rejects_ipv4_header_shorter_than_minimum() {
|
||||||
|
let engine = engine(None);
|
||||||
|
let mut packet = echo_request("10.0.0.2".parse().unwrap(), "8.0.0.1".parse().unwrap());
|
||||||
|
Ipv4Packet::new_unchecked(packet.mut_payload()).set_header_len(16);
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
engine.handle_peer_packet(
|
||||||
|
&packet,
|
||||||
|
IcmpProxyContext {
|
||||||
|
virtual_ipv4: Some("8.0.0.1".parse().unwrap()),
|
||||||
|
no_tun: true,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
IcmpProxyAction::Pass
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -391,20 +390,19 @@ mod tests {
|
|||||||
panic!("expected socket request");
|
panic!("expected socket request");
|
||||||
};
|
};
|
||||||
assert_eq!(destination, "127.0.0.42".parse::<Ipv4Addr>().unwrap());
|
assert_eq!(destination, "127.0.0.42".parse::<Ipv4Addr>().unwrap());
|
||||||
let request = icmp::echo_request::EchoRequestPacket::new(&request).unwrap();
|
let request = Icmpv4Packet::new_checked(&request).unwrap();
|
||||||
assert_eq!(request.payload(), b"ping");
|
assert_eq!(request.data(), b"ping");
|
||||||
|
|
||||||
let mut response = echo_request(destination, "10.0.0.1".parse().unwrap())
|
let mut response = echo_request(destination, "10.0.0.1".parse().unwrap())
|
||||||
.payload()
|
.payload()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
{
|
{
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut response).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut response);
|
||||||
let mut reply = MutableEchoReplyPacket::new(ipv4.payload_mut()).unwrap();
|
let mut reply = Icmpv4Packet::new_unchecked(ipv4.payload_mut());
|
||||||
reply.set_icmp_type(IcmpTypes::EchoReply);
|
reply.set_msg_type(Icmpv4Message::EchoReply);
|
||||||
let mut icmp = MutableIcmpPacket::new(ipv4.payload_mut()).unwrap();
|
reply.fill_checksum();
|
||||||
icmp.set_checksum(icmp::checksum(&icmp.to_immutable()));
|
ipv4.set_src_addr(destination);
|
||||||
ipv4.set_source(destination);
|
ipv4.fill_checksum();
|
||||||
ipv4.set_checksum(ipv4::checksum(&ipv4.to_immutable()));
|
|
||||||
}
|
}
|
||||||
let replies = engine.handle_socket_response(destination, &mut response);
|
let replies = engine.handle_socket_response(destination, &mut response);
|
||||||
let [reply] = replies.as_slice() else {
|
let [reply] = replies.as_slice() else {
|
||||||
@@ -414,15 +412,54 @@ mod tests {
|
|||||||
assert_eq!(header.from_peer_id.get(), 202);
|
assert_eq!(header.from_peer_id.get(), 202);
|
||||||
assert_eq!(header.to_peer_id.get(), 101);
|
assert_eq!(header.to_peer_id.get(), 101);
|
||||||
assert!(header.is_no_proxy());
|
assert!(header.is_no_proxy());
|
||||||
let ipv4 = Ipv4Packet::new(reply.payload()).unwrap();
|
let ipv4 = Ipv4Packet::new_checked(reply.payload()).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(ipv4.src_addr(), "10.10.10.42".parse::<Ipv4Addr>().unwrap());
|
||||||
ipv4.get_source(),
|
assert_eq!(ipv4.dst_addr(), "10.0.0.2".parse::<Ipv4Addr>().unwrap());
|
||||||
"10.10.10.42".parse::<Ipv4Addr>().unwrap()
|
}
|
||||||
);
|
|
||||||
assert_eq!(
|
#[test]
|
||||||
ipv4.get_destination(),
|
fn socket_response_rejects_ipv4_header_shorter_than_minimum() {
|
||||||
"10.0.0.2".parse::<Ipv4Addr>().unwrap()
|
let engine = engine(Some(ProxyCidrRule {
|
||||||
|
cidr: "127.0.0.0/24".parse().unwrap(),
|
||||||
|
mapped_cidr: Some("10.10.10.0/24".parse().unwrap()),
|
||||||
|
}));
|
||||||
|
let destination = "127.0.0.42".parse().unwrap();
|
||||||
|
let request = echo_request("10.0.0.2".parse().unwrap(), "10.10.10.42".parse().unwrap());
|
||||||
|
assert!(matches!(
|
||||||
|
engine.handle_peer_packet(
|
||||||
|
&request,
|
||||||
|
IcmpProxyContext {
|
||||||
|
virtual_ipv4: Some("10.0.0.1".parse().unwrap()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IcmpProxyAction::SendToSocket { .. }
|
||||||
|
));
|
||||||
|
|
||||||
|
let key = IcmpNatKey {
|
||||||
|
real_destination: destination,
|
||||||
|
identifier: 7,
|
||||||
|
sequence: 11,
|
||||||
|
};
|
||||||
|
let mut response = echo_request(destination, "10.0.0.1".parse().unwrap())
|
||||||
|
.payload()
|
||||||
|
.to_vec();
|
||||||
|
{
|
||||||
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut response);
|
||||||
|
ipv4.set_header_len(16);
|
||||||
|
let mut reply = Icmpv4Packet::new_unchecked(ipv4.payload_mut());
|
||||||
|
reply.set_msg_type(Icmpv4Message::EchoReply);
|
||||||
|
reply.set_msg_code(0);
|
||||||
|
reply.set_echo_ident(7);
|
||||||
|
reply.set_echo_seq_no(11);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
engine
|
||||||
|
.handle_socket_response(destination, &mut response)
|
||||||
|
.is_empty()
|
||||||
);
|
);
|
||||||
|
assert!(engine.nat_table.contains_key(&key));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -485,24 +522,23 @@ mod tests {
|
|||||||
.payload()
|
.payload()
|
||||||
.to_vec();
|
.to_vec();
|
||||||
{
|
{
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut response).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut response);
|
||||||
let mut reply = MutableEchoReplyPacket::new(ipv4.payload_mut()).unwrap();
|
let mut reply = Icmpv4Packet::new_unchecked(ipv4.payload_mut());
|
||||||
reply.set_icmp_type(IcmpTypes::EchoReply);
|
reply.set_msg_type(Icmpv4Message::EchoReply);
|
||||||
let mut icmp = MutableIcmpPacket::new(ipv4.payload_mut()).unwrap();
|
reply.fill_checksum();
|
||||||
icmp.set_checksum(icmp::checksum(&icmp.to_immutable()));
|
ipv4.set_src_addr(destination);
|
||||||
ipv4.set_source(destination);
|
|
||||||
// Raw sockets may return a buffer with bytes beyond the IPv4 total
|
// Raw sockets may return a buffer with bytes beyond the IPv4 total
|
||||||
// length. The native implementation composes from the received
|
// length. The native implementation composes from the received
|
||||||
// buffer length, so keep that case covered without changing the
|
// buffer length, so keep that case covered without changing the
|
||||||
// existing in-place composer in this refactor.
|
// existing in-place composer in this refactor.
|
||||||
ipv4.set_total_length(1220);
|
ipv4.set_total_len(1220);
|
||||||
ipv4.set_checksum(ipv4::checksum(&ipv4.to_immutable()));
|
ipv4.fill_checksum();
|
||||||
}
|
}
|
||||||
let ipv4 = Ipv4Packet::new(&response).unwrap();
|
let ipv4 = Ipv4Packet::new_checked(&response).unwrap();
|
||||||
let echo_reply = icmp::echo_reply::EchoReplyPacket::new(ipv4.payload()).unwrap();
|
let echo_reply = Icmpv4Packet::new_checked(ipv4.payload()).unwrap();
|
||||||
assert_eq!(echo_reply.get_icmp_type(), IcmpTypes::EchoReply);
|
assert_eq!(echo_reply.msg_type(), Icmpv4Message::EchoReply);
|
||||||
assert_eq!(echo_reply.get_identifier(), 7);
|
assert_eq!(echo_reply.echo_ident(), 7);
|
||||||
assert_eq!(echo_reply.get_sequence_number(), 11);
|
assert_eq!(echo_reply.echo_seq_no(), 11);
|
||||||
|
|
||||||
let replies = engine.handle_socket_response(destination, &mut response);
|
let replies = engine.handle_socket_response(destination, &mut response);
|
||||||
assert_eq!(replies.len(), 3);
|
assert_eq!(replies.len(), 3);
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
|
pub use smoltcp::wire::IpProtocol;
|
||||||
use smoltcp::wire::Ipv4Packet;
|
use smoltcp::wire::Ipv4Packet;
|
||||||
pub use smoltcp::wire::{IpProtocol, Ipv4Packet as SmolIpv4Packet};
|
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
struct IpReassemblerKey {
|
struct IpReassemblerKey {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use cidr::Ipv4Inet;
|
use cidr::Ipv4Inet;
|
||||||
use pnet_packet::{
|
use smoltcp::wire::{
|
||||||
ip::IpNextHeaderProtocols,
|
IPV4_HEADER_LEN, IpAddress, IpProtocol, Ipv4Packet, UDP_HEADER_LEN, UdpPacket,
|
||||||
ipv4::{self, Ipv4Flags, Ipv4Packet, MutableIpv4Packet},
|
|
||||||
udp::{self, MutableUdpPacket, UdpPacket},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -128,36 +126,37 @@ pub struct UdpPacketSummary {
|
|||||||
|
|
||||||
impl UdpPacketSummary {
|
impl UdpPacketSummary {
|
||||||
pub fn parse(packet: &[u8]) -> Option<Self> {
|
pub fn parse(packet: &[u8]) -> Option<Self> {
|
||||||
let ipv4_packet = Ipv4Packet::new(packet)?;
|
if packet.len() < IPV4_HEADER_LEN {
|
||||||
if ipv4_packet.get_version() != 4
|
return None;
|
||||||
|| ipv4_packet.get_next_level_protocol() != IpNextHeaderProtocols::Udp
|
}
|
||||||
{
|
let ipv4_packet = Ipv4Packet::new_unchecked(packet);
|
||||||
|
if ipv4_packet.version() != 4 || ipv4_packet.next_header() != IpProtocol::Udp {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_len = usize::from(ipv4_packet.get_header_length()) * 4;
|
let header_len = usize::from(ipv4_packet.header_len());
|
||||||
let total_len = usize::from(ipv4_packet.get_total_length());
|
let total_len = usize::from(ipv4_packet.total_len());
|
||||||
if header_len < Ipv4Packet::minimum_packet_size()
|
if header_len < IPV4_HEADER_LEN
|
||||||
|| total_len < header_len + UdpPacket::minimum_packet_size()
|
|| total_len < header_len + UDP_HEADER_LEN
|
||||||
|| total_len > packet.len()
|
|| total_len > packet.len()
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let udp_packet = UdpPacket::new(&packet[header_len..total_len])?;
|
let udp_packet = UdpPacket::new_unchecked(&packet[header_len..total_len]);
|
||||||
let udp_len = usize::from(udp_packet.get_length());
|
let udp_len = usize::from(udp_packet.len());
|
||||||
if udp_len < UdpPacket::minimum_packet_size() || header_len + udp_len != total_len {
|
if udp_len < UDP_HEADER_LEN || header_len + udp_len != total_len {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
src: ipv4_packet.get_source(),
|
src: ipv4_packet.src_addr(),
|
||||||
dst: ipv4_packet.get_destination(),
|
dst: ipv4_packet.dst_addr(),
|
||||||
src_port: udp_packet.get_source(),
|
src_port: udp_packet.src_port(),
|
||||||
dst_port: udp_packet.get_destination(),
|
dst_port: udp_packet.dst_port(),
|
||||||
ip_len: total_len,
|
ip_len: total_len,
|
||||||
udp_len,
|
udp_len,
|
||||||
payload_len: udp_len - UdpPacket::minimum_packet_size(),
|
payload_len: udp_len - UDP_HEADER_LEN,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,30 +233,29 @@ fn parse_udp_broadcast(
|
|||||||
packet: &[u8],
|
packet: &[u8],
|
||||||
config: &BroadcastRelayConfig,
|
config: &BroadcastRelayConfig,
|
||||||
) -> Result<ParsedUdpBroadcastPacket, UdpBroadcastPacketRejection> {
|
) -> Result<ParsedUdpBroadcastPacket, UdpBroadcastPacketRejection> {
|
||||||
let ipv4_packet = Ipv4Packet::new(packet).ok_or(UdpBroadcastPacketRejection::MalformedIpv4)?;
|
if packet.len() < IPV4_HEADER_LEN {
|
||||||
if ipv4_packet.get_version() != 4
|
return Err(UdpBroadcastPacketRejection::MalformedIpv4);
|
||||||
|| ipv4_packet.get_next_level_protocol() != IpNextHeaderProtocols::Udp
|
}
|
||||||
{
|
let ipv4_packet = Ipv4Packet::new_unchecked(packet);
|
||||||
|
if ipv4_packet.version() != 4 || ipv4_packet.next_header() != IpProtocol::Udp {
|
||||||
return Err(UdpBroadcastPacketRejection::NotUdpIpv4);
|
return Err(UdpBroadcastPacketRejection::NotUdpIpv4);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipv4_packet.get_fragment_offset() != 0
|
if ipv4_packet.frag_offset() != 0 || ipv4_packet.more_frags() {
|
||||||
|| ipv4_packet.get_flags() & Ipv4Flags::MoreFragments != 0
|
|
||||||
{
|
|
||||||
return Err(UdpBroadcastPacketRejection::Fragmented);
|
return Err(UdpBroadcastPacketRejection::Fragmented);
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_len = usize::from(ipv4_packet.get_header_length()) * 4;
|
let header_len = usize::from(ipv4_packet.header_len());
|
||||||
let total_len = usize::from(ipv4_packet.get_total_length());
|
let total_len = usize::from(ipv4_packet.total_len());
|
||||||
if header_len < Ipv4Packet::minimum_packet_size()
|
if header_len < IPV4_HEADER_LEN
|
||||||
|| total_len < header_len + UdpPacket::minimum_packet_size()
|
|| total_len < header_len + UDP_HEADER_LEN
|
||||||
|| total_len > packet.len()
|
|| total_len > packet.len()
|
||||||
{
|
{
|
||||||
return Err(UdpBroadcastPacketRejection::BadIpv4Length);
|
return Err(UdpBroadcastPacketRejection::BadIpv4Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
let src = ipv4_packet.get_source();
|
let src = ipv4_packet.src_addr();
|
||||||
let dst = ipv4_packet.get_destination();
|
let dst = ipv4_packet.dst_addr();
|
||||||
if should_ignore_interface_addr(src) {
|
if should_ignore_interface_addr(src) {
|
||||||
return Err(UdpBroadcastPacketRejection::IgnoredSource);
|
return Err(UdpBroadcastPacketRejection::IgnoredSource);
|
||||||
}
|
}
|
||||||
@@ -275,10 +273,9 @@ fn parse_udp_broadcast(
|
|||||||
return Err(UdpBroadcastPacketRejection::LoopbackDestination);
|
return Err(UdpBroadcastPacketRejection::LoopbackDestination);
|
||||||
}
|
}
|
||||||
|
|
||||||
let udp_packet = UdpPacket::new(&packet[header_len..total_len])
|
let udp_packet = UdpPacket::new_unchecked(&packet[header_len..total_len]);
|
||||||
.ok_or(UdpBroadcastPacketRejection::MalformedUdp)?;
|
let udp_len = usize::from(udp_packet.len());
|
||||||
let udp_len = usize::from(udp_packet.get_length());
|
if udp_len < UDP_HEADER_LEN || header_len + udp_len != total_len {
|
||||||
if udp_len < UdpPacket::minimum_packet_size() || header_len + udp_len != total_len {
|
|
||||||
return Err(UdpBroadcastPacketRejection::BadUdpLength);
|
return Err(UdpBroadcastPacketRejection::BadUdpLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,27 +298,23 @@ pub fn normalize_udp_broadcast_packet(
|
|||||||
let mut normalized = packet[..packet_len].to_vec();
|
let mut normalized = packet[..packet_len].to_vec();
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut ipv4_packet = MutableIpv4Packet::new(&mut normalized)
|
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut normalized);
|
||||||
.ok_or(UdpBroadcastPacketRejection::MalformedIpv4)?;
|
ipv4_packet.set_src_addr(virtual_ipv4);
|
||||||
ipv4_packet.set_source(virtual_ipv4);
|
ipv4_packet.set_dst_addr(destination);
|
||||||
ipv4_packet.set_destination(destination);
|
ipv4_packet.set_total_len(packet_len as u16);
|
||||||
ipv4_packet.set_total_length(packet_len as u16);
|
|
||||||
ipv4_packet.set_checksum(0);
|
ipv4_packet.set_checksum(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut udp_packet = MutableUdpPacket::new(&mut normalized[header_len..packet_len])
|
let mut udp_packet = UdpPacket::new_unchecked(&mut normalized[header_len..packet_len]);
|
||||||
.ok_or(UdpBroadcastPacketRejection::MalformedUdp)?;
|
udp_packet.fill_checksum(
|
||||||
udp_packet.set_checksum(0);
|
&IpAddress::Ipv4(virtual_ipv4),
|
||||||
let checksum = udp::ipv4_checksum(&udp_packet.to_immutable(), &virtual_ipv4, &destination);
|
&IpAddress::Ipv4(destination),
|
||||||
udp_packet.set_checksum(checksum);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut ipv4_packet = MutableIpv4Packet::new(&mut normalized)
|
Ipv4Packet::new_unchecked(&mut normalized).fill_checksum();
|
||||||
.ok_or(UdpBroadcastPacketRejection::MalformedIpv4)?;
|
|
||||||
let checksum = ipv4::checksum(&ipv4_packet.to_immutable());
|
|
||||||
ipv4_packet.set_checksum(checksum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(NormalizedPacket {
|
Ok(NormalizedPacket {
|
||||||
@@ -373,7 +366,6 @@ impl UdpBroadcastRelayStats {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use pnet_packet::{MutablePacket, Packet};
|
|
||||||
|
|
||||||
fn config() -> BroadcastRelayConfig {
|
fn config() -> BroadcastRelayConfig {
|
||||||
BroadcastRelayConfig::new(
|
BroadcastRelayConfig::new(
|
||||||
@@ -385,47 +377,38 @@ mod tests {
|
|||||||
fn build_udp_packet(src: Ipv4Addr, dst: Ipv4Addr, payload: &[u8]) -> Vec<u8> {
|
fn build_udp_packet(src: Ipv4Addr, dst: Ipv4Addr, payload: &[u8]) -> Vec<u8> {
|
||||||
let mut packet = vec![0; 20 + 8 + payload.len()];
|
let mut packet = vec![0; 20 + 8 + payload.len()];
|
||||||
{
|
{
|
||||||
let mut ipv4_packet = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
ipv4_packet.set_version(4);
|
ipv4_packet.set_version(4);
|
||||||
ipv4_packet.set_header_length(5);
|
ipv4_packet.set_header_len(20);
|
||||||
ipv4_packet.set_total_length((20 + 8 + payload.len()) as u16);
|
ipv4_packet.set_total_len((20 + 8 + payload.len()) as u16);
|
||||||
ipv4_packet.set_ttl(64);
|
ipv4_packet.set_hop_limit(64);
|
||||||
ipv4_packet.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
ipv4_packet.set_next_header(IpProtocol::Udp);
|
||||||
ipv4_packet.set_source(src);
|
ipv4_packet.set_src_addr(src);
|
||||||
ipv4_packet.set_destination(dst);
|
ipv4_packet.set_dst_addr(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut udp_packet = MutableUdpPacket::new(&mut packet[20..]).unwrap();
|
let mut udp_packet = UdpPacket::new_unchecked(&mut packet[20..]);
|
||||||
udp_packet.set_source(12345);
|
udp_packet.set_src_port(12345);
|
||||||
udp_packet.set_destination(37020);
|
udp_packet.set_dst_port(37020);
|
||||||
udp_packet.set_length((8 + payload.len()) as u16);
|
udp_packet.set_len((8 + payload.len()) as u16);
|
||||||
udp_packet.payload_mut().copy_from_slice(payload);
|
udp_packet.payload_mut().copy_from_slice(payload);
|
||||||
let checksum = udp::ipv4_checksum(&udp_packet.to_immutable(), &src, &dst);
|
udp_packet.fill_checksum(&IpAddress::Ipv4(src), &IpAddress::Ipv4(dst));
|
||||||
udp_packet.set_checksum(checksum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
Ipv4Packet::new_unchecked(&mut packet).fill_checksum();
|
||||||
let mut ipv4_packet = MutableIpv4Packet::new(&mut packet).unwrap();
|
|
||||||
let checksum = ipv4::checksum(&ipv4_packet.to_immutable());
|
|
||||||
ipv4_packet.set_checksum(checksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
packet
|
packet
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_valid_checksums(packet: &[u8]) {
|
fn assert_valid_checksums(packet: &[u8]) {
|
||||||
let ipv4_packet = Ipv4Packet::new(packet).unwrap();
|
let ipv4_packet = Ipv4Packet::new_checked(packet).unwrap();
|
||||||
assert_eq!(ipv4::checksum(&ipv4_packet), ipv4_packet.get_checksum());
|
assert!(ipv4_packet.verify_checksum());
|
||||||
let udp_packet = UdpPacket::new(ipv4_packet.payload()).unwrap();
|
let udp_packet = UdpPacket::new_checked(ipv4_packet.payload()).unwrap();
|
||||||
assert_eq!(
|
assert!(udp_packet.verify_checksum(
|
||||||
udp::ipv4_checksum(
|
&IpAddress::Ipv4(ipv4_packet.src_addr()),
|
||||||
&udp_packet,
|
&IpAddress::Ipv4(ipv4_packet.dst_addr()),
|
||||||
&ipv4_packet.get_source(),
|
));
|
||||||
&ipv4_packet.get_destination()
|
|
||||||
),
|
|
||||||
udp_packet.get_checksum()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -433,11 +416,11 @@ mod tests {
|
|||||||
let packet = build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), Ipv4Addr::BROADCAST, b"hello");
|
let packet = build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), Ipv4Addr::BROADCAST, b"hello");
|
||||||
|
|
||||||
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
||||||
let ipv4_packet = Ipv4Packet::new(&normalized.packet).unwrap();
|
let ipv4_packet = Ipv4Packet::new_checked(&normalized.packet).unwrap();
|
||||||
|
|
||||||
assert_eq!(normalized.destination, Ipv4Addr::BROADCAST);
|
assert_eq!(normalized.destination, Ipv4Addr::BROADCAST);
|
||||||
assert_eq!(ipv4_packet.get_source(), Ipv4Addr::new(10, 144, 144, 1));
|
assert_eq!(ipv4_packet.src_addr(), Ipv4Addr::new(10, 144, 144, 1));
|
||||||
assert_eq!(ipv4_packet.get_destination(), Ipv4Addr::BROADCAST);
|
assert_eq!(ipv4_packet.dst_addr(), Ipv4Addr::BROADCAST);
|
||||||
assert_eq!(&ipv4_packet.payload()[8..], b"hello");
|
assert_eq!(&ipv4_packet.payload()[8..], b"hello");
|
||||||
assert_valid_checksums(&normalized.packet);
|
assert_valid_checksums(&normalized.packet);
|
||||||
}
|
}
|
||||||
@@ -451,14 +434,11 @@ mod tests {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
||||||
let ipv4_packet = Ipv4Packet::new(&normalized.packet).unwrap();
|
let ipv4_packet = Ipv4Packet::new_checked(&normalized.packet).unwrap();
|
||||||
|
|
||||||
assert_eq!(normalized.destination, Ipv4Addr::new(10, 144, 144, 255));
|
assert_eq!(normalized.destination, Ipv4Addr::new(10, 144, 144, 255));
|
||||||
assert_eq!(ipv4_packet.get_source(), Ipv4Addr::new(10, 144, 144, 1));
|
assert_eq!(ipv4_packet.src_addr(), Ipv4Addr::new(10, 144, 144, 1));
|
||||||
assert_eq!(
|
assert_eq!(ipv4_packet.dst_addr(), Ipv4Addr::new(10, 144, 144, 255));
|
||||||
ipv4_packet.get_destination(),
|
|
||||||
Ipv4Addr::new(10, 144, 144, 255)
|
|
||||||
);
|
|
||||||
assert_eq!(&ipv4_packet.payload()[8..], b"directed");
|
assert_eq!(&ipv4_packet.payload()[8..], b"directed");
|
||||||
assert_valid_checksums(&normalized.packet);
|
assert_valid_checksums(&normalized.packet);
|
||||||
}
|
}
|
||||||
@@ -469,11 +449,11 @@ mod tests {
|
|||||||
let packet = build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), multicast, b"multicast");
|
let packet = build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), multicast, b"multicast");
|
||||||
|
|
||||||
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
let normalized = normalize_udp_broadcast_packet(&packet, &config()).unwrap();
|
||||||
let ipv4_packet = Ipv4Packet::new(&normalized.packet).unwrap();
|
let ipv4_packet = Ipv4Packet::new_checked(&normalized.packet).unwrap();
|
||||||
|
|
||||||
assert_eq!(normalized.destination, multicast);
|
assert_eq!(normalized.destination, multicast);
|
||||||
assert_eq!(ipv4_packet.get_source(), Ipv4Addr::new(10, 144, 144, 1));
|
assert_eq!(ipv4_packet.src_addr(), Ipv4Addr::new(10, 144, 144, 1));
|
||||||
assert_eq!(ipv4_packet.get_destination(), multicast);
|
assert_eq!(ipv4_packet.dst_addr(), multicast);
|
||||||
assert_eq!(&ipv4_packet.payload()[8..], b"multicast");
|
assert_eq!(&ipv4_packet.payload()[8..], b"multicast");
|
||||||
assert_valid_checksums(&normalized.packet);
|
assert_valid_checksums(&normalized.packet);
|
||||||
}
|
}
|
||||||
@@ -502,8 +482,8 @@ mod tests {
|
|||||||
b"fragment",
|
b"fragment",
|
||||||
);
|
);
|
||||||
{
|
{
|
||||||
let mut ipv4_packet = MutableIpv4Packet::new(&mut packet).unwrap();
|
let mut ipv4_packet = Ipv4Packet::new_unchecked(&mut packet);
|
||||||
ipv4_packet.set_flags(Ipv4Flags::MoreFragments);
|
ipv4_packet.set_more_frags(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -540,9 +520,7 @@ mod tests {
|
|||||||
fn rejects_non_udp_ipv4_packets() {
|
fn rejects_non_udp_ipv4_packets() {
|
||||||
let mut packet =
|
let mut packet =
|
||||||
build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), Ipv4Addr::BROADCAST, b"tcp");
|
build_udp_packet(Ipv4Addr::new(192, 168, 1, 7), Ipv4Addr::BROADCAST, b"tcp");
|
||||||
MutableIpv4Packet::new(&mut packet)
|
Ipv4Packet::new_unchecked(&mut packet).set_next_header(IpProtocol::Tcp);
|
||||||
.unwrap()
|
|
||||||
.set_next_level_protocol(IpNextHeaderProtocols::Tcp);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
normalize_udp_broadcast_packet(&packet, &config()),
|
normalize_udp_broadcast_packet(&packet, &config()),
|
||||||
|
|||||||
@@ -899,6 +899,9 @@ struct SyncedRouteInfo {
|
|||||||
foreign_network: DashMap<ForeignNetworkRouteInfoKey, ForeignNetworkRouteInfoEntry>,
|
foreign_network: DashMap<ForeignNetworkRouteInfoKey, ForeignNetworkRouteInfoEntry>,
|
||||||
group_trust_map: DashMap<PeerId, HashMap<String, Vec<u8>>>,
|
group_trust_map: DashMap<PeerId, HashMap<String, Vec<u8>>>,
|
||||||
group_trust_map_cache: DashMap<PeerId, Arc<Vec<String>>>, // cache for group trust map, should sync with group_trust_map
|
group_trust_map_cache: DashMap<PeerId, Arc<Vec<String>>>, // cache for group trust map, should sync with group_trust_map
|
||||||
|
// Serializes every read-modify-write of the derived group maps. Both
|
||||||
|
// proof verification and credential grants update these maps.
|
||||||
|
group_trust_update_lock: parking_lot::Mutex<()>,
|
||||||
|
|
||||||
// Aggregated trusted credential pubkeys from all admin nodes
|
// Aggregated trusted credential pubkeys from all admin nodes
|
||||||
// Maps pubkey bytes -> TrustedCredentialPubkey
|
// Maps pubkey bytes -> TrustedCredentialPubkey
|
||||||
@@ -928,7 +931,8 @@ impl Debug for SyncedRouteInfo {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl SyncedRouteInfo {
|
impl SyncedRouteInfo {
|
||||||
fn set_peer_groups(&self, peer_id: PeerId, groups: HashMap<String, Vec<u8>>) {
|
// Must be called with group_trust_update_lock held.
|
||||||
|
fn set_peer_groups_locked(&self, peer_id: PeerId, groups: HashMap<String, Vec<u8>>) {
|
||||||
if groups.is_empty() {
|
if groups.is_empty() {
|
||||||
self.group_trust_map.remove(&peer_id);
|
self.group_trust_map.remove(&peer_id);
|
||||||
self.group_trust_map_cache.remove(&peer_id);
|
self.group_trust_map_cache.remove(&peer_id);
|
||||||
@@ -941,7 +945,8 @@ impl SyncedRouteInfo {
|
|||||||
.insert(peer_id, Arc::new(group_names));
|
.insert(peer_id, Arc::new(group_names));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_proof_groups(&self, peer_id: PeerId) -> HashMap<String, Vec<u8>> {
|
// Must be called with group_trust_update_lock held.
|
||||||
|
fn get_proof_groups_locked(&self, peer_id: PeerId) -> HashMap<String, Vec<u8>> {
|
||||||
self.group_trust_map
|
self.group_trust_map
|
||||||
.get(&peer_id)
|
.get(&peer_id)
|
||||||
.map(|groups| {
|
.map(|groups| {
|
||||||
@@ -1161,6 +1166,7 @@ impl SyncedRouteInfo {
|
|||||||
peer_infos: &OrderedHashMap<PeerId, RoutePeerInfo>,
|
peer_infos: &OrderedHashMap<PeerId, RoutePeerInfo>,
|
||||||
all_trusted: &HashMap<Vec<u8>, TrustedCredentialPubkey>,
|
all_trusted: &HashMap<Vec<u8>, TrustedCredentialPubkey>,
|
||||||
) {
|
) {
|
||||||
|
let _group_trust_lock = self.group_trust_update_lock.lock();
|
||||||
for (_, info) in peer_infos.iter() {
|
for (_, info) in peer_infos.iter() {
|
||||||
if info.noise_static_pubkey.is_empty() {
|
if info.noise_static_pubkey.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
@@ -1169,11 +1175,11 @@ impl SyncedRouteInfo {
|
|||||||
let Some(credential) = all_trusted.get(&info.noise_static_pubkey) else {
|
let Some(credential) = all_trusted.get(&info.noise_static_pubkey) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let mut group_map = self.get_proof_groups(info.peer_id);
|
let mut group_map = self.get_proof_groups_locked(info.peer_id);
|
||||||
for group in &credential.groups {
|
for group in &credential.groups {
|
||||||
group_map.entry(group.clone()).or_default();
|
group_map.entry(group.clone()).or_default();
|
||||||
}
|
}
|
||||||
self.set_peer_groups(info.peer_id, group_map);
|
self.set_peer_groups_locked(info.peer_id, group_map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,19 +1268,23 @@ impl SyncedRouteInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let _group_trust_lock = self.group_trust_update_lock.lock();
|
||||||
for peer_id in &peer_ids {
|
for peer_id in &peer_ids {
|
||||||
self.raw_peer_infos.remove(peer_id);
|
|
||||||
self.group_trust_map.remove(peer_id);
|
self.group_trust_map.remove(peer_id);
|
||||||
self.group_trust_map_cache.remove(peer_id);
|
self.group_trust_map_cache.remove(peer_id);
|
||||||
}
|
}
|
||||||
|
shrink_dashmap(&self.group_trust_map, None);
|
||||||
|
shrink_dashmap(&self.group_trust_map_cache, None);
|
||||||
|
}
|
||||||
|
for peer_id in &peer_ids {
|
||||||
|
self.raw_peer_infos.remove(peer_id);
|
||||||
|
}
|
||||||
self.foreign_network
|
self.foreign_network
|
||||||
.retain(|k, _| !peer_ids.contains(&k.peer_id));
|
.retain(|k, _| !peer_ids.contains(&k.peer_id));
|
||||||
|
|
||||||
shrink_dashmap(&self.raw_peer_infos, None);
|
shrink_dashmap(&self.raw_peer_infos, None);
|
||||||
shrink_dashmap(&self.foreign_network, None);
|
shrink_dashmap(&self.foreign_network, None);
|
||||||
shrink_dashmap(&self.group_trust_map, None);
|
|
||||||
shrink_dashmap(&self.group_trust_map_cache, None);
|
|
||||||
|
|
||||||
self.version.inc();
|
self.version.inc();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1652,6 +1662,7 @@ impl SyncedRouteInfo {
|
|||||||
local_group_declarations: &[PeerGroupIdentity],
|
local_group_declarations: &[PeerGroupIdentity],
|
||||||
trust_admin_groups_without_proof: bool,
|
trust_admin_groups_without_proof: bool,
|
||||||
) {
|
) {
|
||||||
|
let _group_trust_lock = self.group_trust_update_lock.lock();
|
||||||
let local_group_declarations = local_group_declarations
|
let local_group_declarations = local_group_declarations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|g| (g.group_name.as_str(), g.group_secret.as_str()))
|
.map(|g| (g.group_name.as_str(), g.group_secret.as_str()))
|
||||||
@@ -1719,14 +1730,59 @@ impl SyncedRouteInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn verify_and_update_current_group_trusts(
|
||||||
|
&self,
|
||||||
|
received_peer_infos: &[RoutePeerInfo],
|
||||||
|
local_group_declarations: &[PeerGroupIdentity],
|
||||||
|
trust_admin_groups_without_proof: bool,
|
||||||
|
) {
|
||||||
|
let peer_ids: HashSet<_> = received_peer_infos
|
||||||
|
.iter()
|
||||||
|
.map(|info| info.peer_id)
|
||||||
|
.collect();
|
||||||
|
let peer_infos_guard = self.peer_infos.read();
|
||||||
|
let current_peer_infos: Vec<_> = peer_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|peer_id| peer_infos_guard.get(peer_id).cloned())
|
||||||
|
.collect();
|
||||||
|
self.verify_and_update_group_trusts(
|
||||||
|
¤t_peer_infos,
|
||||||
|
local_group_declarations,
|
||||||
|
trust_admin_groups_without_proof,
|
||||||
|
);
|
||||||
|
// Keep the authoritative peer-info snapshot locked until its derived
|
||||||
|
// ACL cache has been updated, so another sync cannot interleave a
|
||||||
|
// newer peer version and then be overwritten by this one.
|
||||||
|
drop(peer_infos_guard);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_and_update_all_current_group_trusts(
|
||||||
|
&self,
|
||||||
|
local_group_declarations: &[PeerGroupIdentity],
|
||||||
|
trust_admin_groups_without_proof: bool,
|
||||||
|
) {
|
||||||
|
let peer_infos_guard = self.peer_infos.read();
|
||||||
|
let current_peer_infos: Vec<_> = peer_infos_guard
|
||||||
|
.iter()
|
||||||
|
.map(|(_, info)| info.clone())
|
||||||
|
.collect();
|
||||||
|
self.verify_and_update_group_trusts(
|
||||||
|
¤t_peer_infos,
|
||||||
|
local_group_declarations,
|
||||||
|
trust_admin_groups_without_proof,
|
||||||
|
);
|
||||||
|
drop(peer_infos_guard);
|
||||||
|
}
|
||||||
|
|
||||||
fn update_my_group_trusts(&self, my_peer_id: PeerId, groups: &[PeerGroupInfo]) {
|
fn update_my_group_trusts(&self, my_peer_id: PeerId, groups: &[PeerGroupInfo]) {
|
||||||
|
let _group_trust_lock = self.group_trust_update_lock.lock();
|
||||||
let mut my_group_map = HashMap::new();
|
let mut my_group_map = HashMap::new();
|
||||||
|
|
||||||
for group in groups.iter() {
|
for group in groups.iter() {
|
||||||
my_group_map.insert(group.group_name.clone(), group.group_proof.clone());
|
my_group_map.insert(group.group_name.clone(), group.group_proof.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_peer_groups(my_peer_id, my_group_map);
|
self.set_peer_groups_locked(my_peer_id, my_group_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect trusted credential pubkeys from admin nodes (network_secret holders)
|
/// Collect trusted credential pubkeys from admin nodes (network_secret holders)
|
||||||
@@ -1835,6 +1891,13 @@ type SessionId = u64;
|
|||||||
|
|
||||||
type AtomicSessionId = atomic_shim::AtomicU64;
|
type AtomicSessionId = atomic_shim::AtomicU64;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
struct SyncRequestSnapshot {
|
||||||
|
my_session_id: SessionId,
|
||||||
|
state_revision: u64,
|
||||||
|
is_initiator: bool,
|
||||||
|
}
|
||||||
|
|
||||||
struct SessionTask {
|
struct SessionTask {
|
||||||
my_peer_id: PeerId,
|
my_peer_id: PeerId,
|
||||||
task: Arc<std::sync::Mutex<Option<JoinHandle<()>>>>,
|
task: Arc<std::sync::Mutex<Option<JoinHandle<()>>>>,
|
||||||
@@ -1921,6 +1984,13 @@ impl VersionAndTouchTime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A responder session sends no keepalives when there is no new route data,
|
||||||
|
// so the only sign that the initiator still owns the session is its
|
||||||
|
// periodic inbound syncs (forced at least every ~10s by session_task).
|
||||||
|
// Treat a longer silence as the initiator having lost this session (e.g.
|
||||||
|
// restarted) without telling us.
|
||||||
|
const INITIATOR_SESSION_LIVENESS_TIMEOUT: Duration = Duration::from_secs(45);
|
||||||
|
|
||||||
// if we need to sync route info with one peer, we create a SyncRouteSession with that peer.
|
// if we need to sync route info with one peer, we create a SyncRouteSession with that peer.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -1937,6 +2007,11 @@ struct SyncRouteSession {
|
|||||||
|
|
||||||
last_sync_succ_timestamp: AtomicCell<Option<SystemTime>>,
|
last_sync_succ_timestamp: AtomicCell<Option<SystemTime>>,
|
||||||
|
|
||||||
|
// Last time any sync interaction (inbound request or successful
|
||||||
|
// response) confirmed the peer still holds this session. Drives the
|
||||||
|
// responder liveness timeout; initialized at session creation.
|
||||||
|
last_contact_instant: AtomicCell<Instant>,
|
||||||
|
|
||||||
my_session_id: AtomicSessionId,
|
my_session_id: AtomicSessionId,
|
||||||
dst_session_id: AtomicSessionId,
|
dst_session_id: AtomicSessionId,
|
||||||
|
|
||||||
@@ -1944,6 +2019,11 @@ struct SyncRouteSession {
|
|||||||
we_are_initiator: AtomicBool,
|
we_are_initiator: AtomicBool,
|
||||||
dst_is_initiator: AtomicBool,
|
dst_is_initiator: AtomicBool,
|
||||||
|
|
||||||
|
// Serializes the compound session state observed by an outbound RPC.
|
||||||
|
// A response may only commit while this revision still matches the
|
||||||
|
// request snapshot captured before the await.
|
||||||
|
state_revision: AtomicU64,
|
||||||
|
|
||||||
need_sync_initiator_info: AtomicBool,
|
need_sync_initiator_info: AtomicBool,
|
||||||
|
|
||||||
rpc_tx_count: AtomicU32,
|
rpc_tx_count: AtomicU32,
|
||||||
@@ -1968,11 +2048,14 @@ impl SyncRouteSession {
|
|||||||
|
|
||||||
last_sync_succ_timestamp: AtomicCell::new(None),
|
last_sync_succ_timestamp: AtomicCell::new(None),
|
||||||
|
|
||||||
|
last_contact_instant: AtomicCell::new(Instant::now()),
|
||||||
|
|
||||||
my_session_id: AtomicSessionId::new(rand::random()),
|
my_session_id: AtomicSessionId::new(rand::random()),
|
||||||
dst_session_id: AtomicSessionId::new(0),
|
dst_session_id: AtomicSessionId::new(0),
|
||||||
|
|
||||||
we_are_initiator: AtomicBool::new(false),
|
we_are_initiator: AtomicBool::new(false),
|
||||||
dst_is_initiator: AtomicBool::new(false),
|
dst_is_initiator: AtomicBool::new(false),
|
||||||
|
state_revision: AtomicU64::new(0),
|
||||||
|
|
||||||
need_sync_initiator_info: AtomicBool::new(false),
|
need_sync_initiator_info: AtomicBool::new(false),
|
||||||
|
|
||||||
@@ -2111,24 +2194,117 @@ impl SyncRouteSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_initiator_flag(&self, is_initiator: bool) {
|
fn update_initiator_flag(&self, is_initiator: bool) {
|
||||||
|
let _session_lock = self.lock.lock();
|
||||||
|
if self.we_are_initiator.load(Ordering::Relaxed) != is_initiator {
|
||||||
self.we_are_initiator.store(is_initiator, Ordering::Relaxed);
|
self.we_are_initiator.store(is_initiator, Ordering::Relaxed);
|
||||||
|
self.state_revision.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
self.need_sync_initiator_info.store(true, Ordering::Relaxed);
|
self.need_sync_initiator_info.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// return whether session id is updated
|
// Must be called with the session lock held.
|
||||||
fn update_dst_session_id(&self, session_id: SessionId) {
|
fn update_remote_state_locked(&self, session_id: SessionId, is_initiator: bool) {
|
||||||
if session_id != self.dst_session_id.load(Ordering::Relaxed) {
|
let session_id_changed = session_id != self.dst_session_id.load(Ordering::Relaxed);
|
||||||
|
let initiator_changed = is_initiator != self.dst_is_initiator.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
if session_id_changed {
|
||||||
tracing::warn!(?self, ?session_id, "session id mismatch, clear saved info.");
|
tracing::warn!(?self, ?session_id, "session id mismatch, clear saved info.");
|
||||||
self.dst_session_id.store(session_id, Ordering::Relaxed);
|
self.dst_session_id.store(session_id, Ordering::Relaxed);
|
||||||
self.dst_saved_conn_info_version.clear();
|
self.dst_saved_conn_info_version.clear();
|
||||||
self.dst_saved_peer_info_versions.clear();
|
self.dst_saved_peer_info_versions.clear();
|
||||||
|
self.dst_saved_foreign_network_versions.clear();
|
||||||
|
|
||||||
// update_dst_session_id is always called with session lock held, so clear
|
|
||||||
// last_sync_succ_timestamp and unreachable_peers non-atomic is safe.
|
|
||||||
self.last_sync_succ_timestamp.store(None);
|
self.last_sync_succ_timestamp.store(None);
|
||||||
self.unreachable_peers_for_peer_info.lock().clear();
|
self.unreachable_peers_for_peer_info.lock().clear();
|
||||||
self.unreachable_peers_for_conn_info.lock().clear();
|
self.unreachable_peers_for_conn_info.lock().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if initiator_changed {
|
||||||
|
self.dst_is_initiator.store(is_initiator, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if session_id_changed || initiator_changed {
|
||||||
|
self.state_revision.fetch_add(1, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the session lock held. A different generation may
|
||||||
|
// only take over through an initiator request after the previous remote
|
||||||
|
// initiator role has been relinquished or expired.
|
||||||
|
fn admit_inbound_locked(&self, session_id: SessionId, is_initiator: bool) -> bool {
|
||||||
|
let current_session_id = self.dst_session_id.load(Ordering::Relaxed);
|
||||||
|
if session_id == current_session_id {
|
||||||
|
self.update_remote_state_locked(session_id, is_initiator);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_session_id == 0 && !is_initiator && self.we_are_initiator.load(Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
// The responder can send its first reverse sync before our
|
||||||
|
// initiating RPC response arrives and teaches us its session ID.
|
||||||
|
self.update_remote_state_locked(session_id, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_initiator || self.dst_is_initiator.load(Ordering::Relaxed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_remote_state_locked(session_id, true);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the session lock held.
|
||||||
|
fn request_snapshot_locked(&self) -> SyncRequestSnapshot {
|
||||||
|
SyncRequestSnapshot {
|
||||||
|
my_session_id: self.my_session_id.load(Ordering::Relaxed),
|
||||||
|
state_revision: self.state_revision.load(Ordering::Relaxed),
|
||||||
|
is_initiator: self.we_are_initiator.load(Ordering::Relaxed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the session lock held.
|
||||||
|
fn request_is_current_locked(&self, snapshot: SyncRequestSnapshot) -> bool {
|
||||||
|
snapshot.my_session_id == self.my_session_id.load(Ordering::Relaxed)
|
||||||
|
&& snapshot.state_revision == self.state_revision.load(Ordering::Relaxed)
|
||||||
|
&& snapshot.is_initiator == self.we_are_initiator.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the session lock held.
|
||||||
|
fn clear_dst_initiator_if_session_unchanged_locked(
|
||||||
|
&self,
|
||||||
|
expected_dst_session_id: SessionId,
|
||||||
|
) -> bool {
|
||||||
|
if self.dst_session_id.load(Ordering::Relaxed) != expected_dst_session_id {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_remote_state_locked(expected_dst_session_id, false);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Responder sessions have no outbound keepalive, so the only sign that
|
||||||
|
// the initiator still owns the session is its periodic inbound syncs.
|
||||||
|
// If no sync interaction arrived within INITIATOR_SESSION_LIVENESS_TIMEOUT,
|
||||||
|
// assume the initiator lost this session without telling us and clear the
|
||||||
|
// stale role so the election loop can re-establish the edge. Only the
|
||||||
|
// flag is cleared; the election loop takes over from there. Returns true
|
||||||
|
// if the initiator role was cleared.
|
||||||
|
fn clear_stale_dst_initiator(&self) -> bool {
|
||||||
|
let _session_lock = self.lock.lock();
|
||||||
|
if !self.dst_is_initiator.load(Ordering::Relaxed)
|
||||||
|
|| self.we_are_initiator.load(Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.last_contact_instant.load().elapsed() < INITIATOR_SESSION_LIVENESS_TIMEOUT {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session_id = self.dst_session_id.load(Ordering::Relaxed);
|
||||||
|
self.update_remote_state_locked(session_id, false);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean_dst_saved_map(&self) {
|
fn clean_dst_saved_map(&self) {
|
||||||
@@ -2256,6 +2432,7 @@ impl PeerRouteServiceImpl {
|
|||||||
foreign_network: DashMap::new(),
|
foreign_network: DashMap::new(),
|
||||||
group_trust_map: DashMap::new(),
|
group_trust_map: DashMap::new(),
|
||||||
group_trust_map_cache: DashMap::new(),
|
group_trust_map_cache: DashMap::new(),
|
||||||
|
group_trust_update_lock: parking_lot::Mutex::new(()),
|
||||||
trusted_credential_pubkeys: DashMap::new(),
|
trusted_credential_pubkeys: DashMap::new(),
|
||||||
non_reusable_credential_owners: DashMap::new(),
|
non_reusable_credential_owners: DashMap::new(),
|
||||||
suppressed_non_reusable_credential_peers: DashMap::new(),
|
suppressed_non_reusable_credential_peers: DashMap::new(),
|
||||||
@@ -2312,8 +2489,32 @@ impl PeerRouteServiceImpl {
|
|||||||
self.sessions.get(&dst_peer_id).map(|x| x.value().clone())
|
self.sessions.get(&dst_peer_id).map(|x| x.value().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_current_session(&self, dst_peer_id: PeerId, expected: &Arc<SyncRouteSession>) -> bool {
|
||||||
|
self.sessions
|
||||||
|
.get(&dst_peer_id)
|
||||||
|
.is_some_and(|current| Arc::ptr_eq(current.value(), expected))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be called with the expected session lock held. remove_session
|
||||||
|
// takes the same lock before changing the map, so this check remains
|
||||||
|
// valid until the caller finishes committing the RPC result.
|
||||||
|
fn sync_request_is_current_locked(
|
||||||
|
&self,
|
||||||
|
dst_peer_id: PeerId,
|
||||||
|
expected: &Arc<SyncRouteSession>,
|
||||||
|
snapshot: SyncRequestSnapshot,
|
||||||
|
) -> bool {
|
||||||
|
self.is_current_session(dst_peer_id, expected)
|
||||||
|
&& expected.request_is_current_locked(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_session(&self, dst_peer_id: PeerId) {
|
fn remove_session(&self, dst_peer_id: PeerId) {
|
||||||
self.sessions.remove(&dst_peer_id);
|
let Some(session) = self.get_session(dst_peer_id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
self.sessions
|
||||||
|
.remove_if(&dst_peer_id, |_, current| Arc::ptr_eq(current, &session));
|
||||||
shrink_dashmap(&self.sessions, None);
|
shrink_dashmap(&self.sessions, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2804,15 +3005,8 @@ impl PeerRouteServiceImpl {
|
|||||||
let trust_admin_groups_without_proof =
|
let trust_admin_groups_without_proof =
|
||||||
self.context.network_identity().network_secret.is_none();
|
self.context.network_identity().network_secret.is_none();
|
||||||
|
|
||||||
let peer_infos: Vec<_> = self
|
self.synced_route_info
|
||||||
.synced_route_info
|
.verify_and_update_all_current_group_trusts(
|
||||||
.peer_infos
|
|
||||||
.read()
|
|
||||||
.iter()
|
|
||||||
.map(|(_, info)| info.clone())
|
|
||||||
.collect();
|
|
||||||
self.synced_route_info.verify_and_update_group_trusts(
|
|
||||||
&peer_infos,
|
|
||||||
&self.context.acl_group_declarations(),
|
&self.context.acl_group_declarations(),
|
||||||
trust_admin_groups_without_proof,
|
trust_admin_groups_without_proof,
|
||||||
);
|
);
|
||||||
@@ -3028,6 +3222,8 @@ impl PeerRouteServiceImpl {
|
|||||||
|
|
||||||
let next_last_sync_succ_timestamp =
|
let next_last_sync_succ_timestamp =
|
||||||
self.synced_route_info.get_next_last_sync_succ_timestamp();
|
self.synced_route_info.get_next_last_sync_succ_timestamp();
|
||||||
|
let request_snapshot = session.request_snapshot_locked();
|
||||||
|
let expected_dst_session_id = session.dst_session_id.load(Ordering::Relaxed);
|
||||||
let (peer_infos, conn_info, foreign_network) =
|
let (peer_infos, conn_info, foreign_network) =
|
||||||
self.build_sync_request(&session, dst_peer_id);
|
self.build_sync_request(&session, dst_peer_id);
|
||||||
if peer_infos.is_none()
|
if peer_infos.is_none()
|
||||||
@@ -3064,8 +3260,8 @@ impl PeerRouteServiceImpl {
|
|||||||
|
|
||||||
let sync_route_info_req = SyncRouteInfoRequest {
|
let sync_route_info_req = SyncRouteInfoRequest {
|
||||||
my_peer_id,
|
my_peer_id,
|
||||||
my_session_id: session.my_session_id.load(Ordering::Relaxed),
|
my_session_id: request_snapshot.my_session_id,
|
||||||
is_initiator: session.we_are_initiator.load(Ordering::Relaxed),
|
is_initiator: request_snapshot.is_initiator,
|
||||||
peer_infos: peer_infos.clone().map(|x| RoutePeerInfos { items: x }),
|
peer_infos: peer_infos.clone().map(|x| RoutePeerInfos { items: x }),
|
||||||
conn_info: conn_info.clone(),
|
conn_info: conn_info.clone(),
|
||||||
foreign_network_infos: foreign_network.clone(),
|
foreign_network_infos: foreign_network.clone(),
|
||||||
@@ -3100,6 +3296,16 @@ impl PeerRouteServiceImpl {
|
|||||||
next_last_sync_succ_timestamp
|
next_last_sync_succ_timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !self.sync_request_is_current_locked(dst_peer_id, &session, request_snapshot) {
|
||||||
|
tracing::debug!(
|
||||||
|
?my_peer_id,
|
||||||
|
?dst_peer_id,
|
||||||
|
?request_snapshot,
|
||||||
|
"discard stale route sync response"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
match ret.as_ref() {
|
match ret.as_ref() {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
@@ -3115,7 +3321,17 @@ impl PeerRouteServiceImpl {
|
|||||||
}
|
}
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
if let Some(err) = resp.error {
|
if let Some(err) = resp.error {
|
||||||
if err == Error::DuplicatePeerId as i32 {
|
if err == Error::Stopped as i32 && !sync_route_info_req.is_initiator {
|
||||||
|
let cleared = session.clear_dst_initiator_if_session_unchanged_locked(
|
||||||
|
expected_dst_session_id,
|
||||||
|
);
|
||||||
|
tracing::debug!(
|
||||||
|
?my_peer_id,
|
||||||
|
?dst_peer_id,
|
||||||
|
?cleared,
|
||||||
|
"stale non-initiator route sync rejected"
|
||||||
|
);
|
||||||
|
} else if err == Error::DuplicatePeerId as i32 {
|
||||||
if !self.context.feature_flags().is_public_server {
|
if !self.context.feature_flags().is_public_server {
|
||||||
panic!("duplicate peer id");
|
panic!("duplicate peer id");
|
||||||
}
|
}
|
||||||
@@ -3127,12 +3343,9 @@ impl PeerRouteServiceImpl {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.rpc_tx_count.fetch_add(1, Ordering::Relaxed);
|
session.rpc_tx_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
session.last_contact_instant.store(Instant::now());
|
||||||
|
|
||||||
session
|
session.update_remote_state_locked(resp.session_id, resp.is_initiator);
|
||||||
.dst_is_initiator
|
|
||||||
.store(resp.is_initiator, Ordering::Relaxed);
|
|
||||||
|
|
||||||
session.update_dst_session_id(resp.session_id);
|
|
||||||
|
|
||||||
if let Some(peer_infos) = &peer_infos {
|
if let Some(peer_infos) = &peer_infos {
|
||||||
session.update_dst_saved_peer_info_version(peer_infos, dst_peer_id);
|
session.update_dst_saved_peer_info_version(peer_infos, dst_peer_id);
|
||||||
@@ -3399,6 +3612,23 @@ impl RouteSessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect responder sessions whose initiator silently lost the
|
||||||
|
// session (e.g. restarted and elected someone else). Without
|
||||||
|
// this, a responder with no new route data could wait forever
|
||||||
|
// for an initiator that no longer syncs with us. Clearing the
|
||||||
|
// stale role makes the peer an initiator candidate again below.
|
||||||
|
for peer_id in session_peers.iter() {
|
||||||
|
if let Some(session) = service_impl.get_session(*peer_id)
|
||||||
|
&& session.clear_stale_dst_initiator()
|
||||||
|
{
|
||||||
|
tracing::warn!(
|
||||||
|
?peer_id,
|
||||||
|
my_peer_id = ?service_impl.my_peer_id,
|
||||||
|
"initiator route sync liveness timeout, clearing stale initiator role"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// find peer_ids that are not initiators.
|
// find peer_ids that are not initiators.
|
||||||
let mut initiator_candidates = Vec::new();
|
let mut initiator_candidates = Vec::new();
|
||||||
for peer_id in peers.iter().copied() {
|
for peer_id in peers.iter().copied() {
|
||||||
@@ -3573,7 +3803,17 @@ impl RouteSessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let my_peer_id = service_impl.my_peer_id;
|
let my_peer_id = service_impl.my_peer_id;
|
||||||
let session = self.get_or_start_session(from_peer_id)?;
|
let session = if let Some(session) = service_impl.get_session(from_peer_id) {
|
||||||
|
session
|
||||||
|
} else if is_initiator {
|
||||||
|
self.get_or_start_session(from_peer_id)?
|
||||||
|
} else {
|
||||||
|
tracing::debug!(
|
||||||
|
?from_peer_id,
|
||||||
|
"ignore stale route sync from non-initiator without a session"
|
||||||
|
);
|
||||||
|
return Err(Error::Stopped);
|
||||||
|
};
|
||||||
|
|
||||||
let from_identity_type = service_impl
|
let from_identity_type = service_impl
|
||||||
.get_peer_identity_type_from_interface(from_peer_id)
|
.get_peer_identity_type_from_interface(from_peer_id)
|
||||||
@@ -3599,9 +3839,20 @@ impl RouteSessionManager {
|
|||||||
|
|
||||||
let _session_lock = session.lock.lock();
|
let _session_lock = session.lock.lock();
|
||||||
|
|
||||||
session.rpc_rx_count.fetch_add(1, Ordering::Relaxed);
|
if !service_impl.is_current_session(from_peer_id, &session)
|
||||||
|
|| !session.admit_inbound_locked(from_session_id, is_initiator)
|
||||||
|
{
|
||||||
|
tracing::debug!(
|
||||||
|
?from_peer_id,
|
||||||
|
?from_session_id,
|
||||||
|
?is_initiator,
|
||||||
|
"ignore route sync from a stale session generation"
|
||||||
|
);
|
||||||
|
return Err(Error::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
session.update_dst_session_id(from_session_id);
|
session.rpc_rx_count.fetch_add(1, Ordering::Relaxed);
|
||||||
|
session.last_contact_instant.store(Instant::now());
|
||||||
|
|
||||||
let mut need_update_route_table = false;
|
let mut need_update_route_table = false;
|
||||||
let mut untrusted_peers = Vec::new();
|
let mut untrusted_peers = Vec::new();
|
||||||
@@ -3637,7 +3888,7 @@ impl RouteSessionManager {
|
|||||||
)?;
|
)?;
|
||||||
service_impl
|
service_impl
|
||||||
.synced_route_info
|
.synced_route_info
|
||||||
.verify_and_update_group_trusts(
|
.verify_and_update_current_group_trusts(
|
||||||
pi,
|
pi,
|
||||||
&service_impl.context.acl_group_declarations(),
|
&service_impl.context.acl_group_declarations(),
|
||||||
trust_admin_groups_without_proof,
|
trust_admin_groups_without_proof,
|
||||||
@@ -3694,9 +3945,6 @@ impl RouteSessionManager {
|
|||||||
service_impl.route_table
|
service_impl.route_table
|
||||||
);
|
);
|
||||||
|
|
||||||
session
|
|
||||||
.dst_is_initiator
|
|
||||||
.store(is_initiator, Ordering::Relaxed);
|
|
||||||
let is_initiator = session.we_are_initiator.load(Ordering::Relaxed);
|
let is_initiator = session.we_are_initiator.load(Ordering::Relaxed);
|
||||||
let session_id = session.my_session_id.load(Ordering::Relaxed);
|
let session_id = session.my_session_id.load(Ordering::Relaxed);
|
||||||
|
|
||||||
@@ -4385,6 +4633,29 @@ mod tests {
|
|||||||
PeerRouteServiceImpl::new(my_peer_id, Arc::new(NoopPeerContext::default()))
|
PeerRouteServiceImpl::new(my_peer_id, Arc::new(NoopPeerContext::default()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn test_route_with_admin_peer(
|
||||||
|
context: ArcPeerContext,
|
||||||
|
) -> (Arc<PeerRoute>, Arc<PeerRpcManager>) {
|
||||||
|
let peer_rpc = Arc::new(PeerRpcManager::new(TestPeerRpcTransport));
|
||||||
|
let route = PeerRoute::new(
|
||||||
|
1,
|
||||||
|
context,
|
||||||
|
Arc::new(TestPublicIpv6Runtime),
|
||||||
|
peer_rpc.clone(),
|
||||||
|
);
|
||||||
|
*route.service_impl.interface.lock().await = Some(Box::new(CountingInterface {
|
||||||
|
my_peer_id: 1,
|
||||||
|
peers: Arc::new(Mutex::new(vec![2])),
|
||||||
|
peer_identity_types: Arc::new(Mutex::new(HashMap::from([(
|
||||||
|
2,
|
||||||
|
Some(PeerIdentityType::Admin),
|
||||||
|
)]))),
|
||||||
|
list_peers_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
get_peer_identity_type_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
}));
|
||||||
|
(route, peer_rpc)
|
||||||
|
}
|
||||||
|
|
||||||
fn peer(peer_id: PeerId) -> OspfPeerInfo {
|
fn peer(peer_id: PeerId) -> OspfPeerInfo {
|
||||||
OspfPeerInfo {
|
OspfPeerInfo {
|
||||||
peer_id,
|
peer_id,
|
||||||
@@ -4522,6 +4793,381 @@ mod tests {
|
|||||||
assert_eq!(get_peer_identity_type_calls.load(Ordering::Relaxed), 4);
|
assert_eq!(get_peer_identity_type_calls.load(Ordering::Relaxed), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stopped_sync_clears_matching_remote_initiator() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, true);
|
||||||
|
|
||||||
|
assert!(session.clear_dst_initiator_if_session_unchanged_locked(10));
|
||||||
|
assert!(!session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stopped_sync_preserves_newer_remote_initiator() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, true);
|
||||||
|
|
||||||
|
session.update_remote_state_locked(11, true);
|
||||||
|
|
||||||
|
assert!(!session.clear_dst_initiator_if_session_unchanged_locked(10));
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn remote_state_change_invalidates_outbound_snapshot() {
|
||||||
|
let service_impl = Arc::new(test_service_impl(1));
|
||||||
|
let session = service_impl.get_or_create_session(2);
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, false);
|
||||||
|
let snapshot = session.request_snapshot_locked();
|
||||||
|
|
||||||
|
assert!(session.admit_inbound_locked(20, true));
|
||||||
|
assert!(!service_impl.sync_request_is_current_locked(2, &session, snapshot));
|
||||||
|
assert_eq!(session.dst_session_id.load(Ordering::Relaxed), 20);
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
assert_eq!(session.rpc_tx_count.load(Ordering::Relaxed), 0);
|
||||||
|
assert!(session.dst_saved_peer_info_versions.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn replaced_session_invalidates_outbound_snapshot() {
|
||||||
|
let service_impl = Arc::new(test_service_impl(1));
|
||||||
|
let old_session = service_impl.get_or_create_session(2);
|
||||||
|
let snapshot = {
|
||||||
|
let _session_lock = old_session.lock.lock();
|
||||||
|
old_session.request_snapshot_locked()
|
||||||
|
};
|
||||||
|
|
||||||
|
service_impl.remove_session(2);
|
||||||
|
let new_session = service_impl.get_or_create_session(2);
|
||||||
|
let _old_session_lock = old_session.lock.lock();
|
||||||
|
|
||||||
|
assert!(!Arc::ptr_eq(&old_session, &new_session));
|
||||||
|
assert!(!service_impl.sync_request_is_current_locked(2, &old_session, snapshot));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn active_remote_initiator_rejects_different_generation() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
{
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
assert!(session.admit_inbound_locked(20, true));
|
||||||
|
assert!(!session.admit_inbound_locked(10, true));
|
||||||
|
assert_eq!(session.dst_session_id.load(Ordering::Relaxed), 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
session
|
||||||
|
.last_contact_instant
|
||||||
|
.store(Instant::now() - INITIATOR_SESSION_LIVENESS_TIMEOUT - Duration::from_secs(1));
|
||||||
|
assert!(session.clear_stale_dst_initiator());
|
||||||
|
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
assert!(session.admit_inbound_locked(10, true));
|
||||||
|
assert_eq!(session.dst_session_id.load(Ordering::Relaxed), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn local_initiator_accepts_initial_responder_generation() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
session.update_initiator_flag(true);
|
||||||
|
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
assert!(session.admit_inbound_locked(20, false));
|
||||||
|
assert_eq!(session.dst_session_id.load(Ordering::Relaxed), 20);
|
||||||
|
assert!(!session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn stale_responder_session_clears_initiator_after_liveness_timeout() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
{
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, true);
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.last_contact_instant
|
||||||
|
.store(Instant::now() - INITIATOR_SESSION_LIVENESS_TIMEOUT - Duration::from_secs(1));
|
||||||
|
|
||||||
|
assert!(session.clear_stale_dst_initiator());
|
||||||
|
assert!(!session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn healthy_responder_session_keeps_initiator() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
{
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!session.clear_stale_dst_initiator());
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initiator_session_ignores_liveness_timeout() {
|
||||||
|
let session = SyncRouteSession::new(1, 2);
|
||||||
|
session.update_initiator_flag(true);
|
||||||
|
{
|
||||||
|
let _session_lock = session.lock.lock();
|
||||||
|
session.update_remote_state_locked(10, true);
|
||||||
|
}
|
||||||
|
session
|
||||||
|
.last_contact_instant
|
||||||
|
.store(Instant::now() - INITIATOR_SESSION_LIVENESS_TIMEOUT - Duration::from_secs(1));
|
||||||
|
|
||||||
|
assert!(!session.clear_stale_dst_initiator());
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn initiator_sync_creates_session_and_marks_contact() {
|
||||||
|
let peer_rpc = Arc::new(PeerRpcManager::new(TestPeerRpcTransport));
|
||||||
|
let route = PeerRoute::new(
|
||||||
|
1,
|
||||||
|
Arc::new(NoopPeerContext::default()),
|
||||||
|
Arc::new(TestPublicIpv6Runtime),
|
||||||
|
peer_rpc,
|
||||||
|
);
|
||||||
|
let peers = Arc::new(Mutex::new(vec![2]));
|
||||||
|
*route.service_impl.interface.lock().await = Some(Box::new(CountingInterface {
|
||||||
|
my_peer_id: 1,
|
||||||
|
peers,
|
||||||
|
peer_identity_types: Arc::new(Mutex::new(HashMap::from([(
|
||||||
|
2,
|
||||||
|
Some(PeerIdentityType::Admin),
|
||||||
|
)]))),
|
||||||
|
list_peers_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
get_peer_identity_type_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 1, true, None, None, None, None)
|
||||||
|
.await
|
||||||
|
.expect("initiator sync should succeed");
|
||||||
|
|
||||||
|
let session = route
|
||||||
|
.service_impl
|
||||||
|
.get_session(2)
|
||||||
|
.expect("initiator sync should create the session");
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
assert!(
|
||||||
|
session.last_contact_instant.load().elapsed() < Duration::from_secs(5),
|
||||||
|
"inbound sync should refresh the liveness timestamp"
|
||||||
|
);
|
||||||
|
|
||||||
|
route.stop().await;
|
||||||
|
assert!(route.service_impl.sessions.is_empty());
|
||||||
|
assert_eq!(route.task_count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_non_initiator_sync_does_not_create_session() {
|
||||||
|
let peer_rpc = Arc::new(PeerRpcManager::new(TestPeerRpcTransport));
|
||||||
|
let route = PeerRoute::new(
|
||||||
|
1,
|
||||||
|
Arc::new(NoopPeerContext::default()),
|
||||||
|
Arc::new(TestPublicIpv6Runtime),
|
||||||
|
peer_rpc,
|
||||||
|
);
|
||||||
|
let peers = Arc::new(Mutex::new(vec![2]));
|
||||||
|
*route.service_impl.interface.lock().await = Some(Box::new(CountingInterface {
|
||||||
|
my_peer_id: 1,
|
||||||
|
peers,
|
||||||
|
peer_identity_types: Arc::new(Mutex::new(HashMap::from([(
|
||||||
|
2,
|
||||||
|
Some(PeerIdentityType::Admin),
|
||||||
|
)]))),
|
||||||
|
list_peers_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
get_peer_identity_type_calls: Arc::new(AtomicU32::new(0)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let result = route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 1, false, None, None, None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(Error::Stopped)));
|
||||||
|
assert!(route.service_impl.sessions.is_empty());
|
||||||
|
assert_eq!(route.task_count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_non_initiator_sync_preserves_newer_session_generation() {
|
||||||
|
let (route, _peer_rpc) =
|
||||||
|
test_route_with_admin_peer(Arc::new(NoopPeerContext::default())).await;
|
||||||
|
let session = route.service_impl.get_or_create_session(2);
|
||||||
|
|
||||||
|
route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 22, true, None, None, None, None)
|
||||||
|
.await
|
||||||
|
.expect("new initiator generation should be accepted");
|
||||||
|
let contact = session.last_contact_instant.load();
|
||||||
|
let rx_count = session.rpc_rx_count.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
let stale_peer_info = RoutePeerInfo {
|
||||||
|
peer_id: 3,
|
||||||
|
version: 1,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(
|
||||||
|
2,
|
||||||
|
11,
|
||||||
|
false,
|
||||||
|
Some(vec![stale_peer_info.clone()]),
|
||||||
|
Some(vec![raw_route_peer_info(&stale_peer_info)]),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(Error::Stopped)));
|
||||||
|
assert_eq!(session.dst_session_id.load(Ordering::Relaxed), 22);
|
||||||
|
assert!(session.dst_is_initiator.load(Ordering::Relaxed));
|
||||||
|
assert_eq!(session.last_contact_instant.load(), contact);
|
||||||
|
assert_eq!(session.rpc_rx_count.load(Ordering::Relaxed), rx_count);
|
||||||
|
assert!(
|
||||||
|
!route
|
||||||
|
.service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.peer_infos
|
||||||
|
.read()
|
||||||
|
.contains_key(&3)
|
||||||
|
);
|
||||||
|
|
||||||
|
route.stop().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn stale_peer_info_does_not_restore_removed_acl_group() {
|
||||||
|
let context = Arc::new(NoopPeerContext::new(CoreNetworkIdentity::new_credential(
|
||||||
|
"default".to_owned(),
|
||||||
|
)));
|
||||||
|
let (route, _peer_rpc) = test_route_with_admin_peer(context).await;
|
||||||
|
route.service_impl.get_or_create_session(2);
|
||||||
|
|
||||||
|
let peer_info = |version, groups| RoutePeerInfo {
|
||||||
|
peer_id: 3,
|
||||||
|
peer_route_id: 30,
|
||||||
|
version,
|
||||||
|
groups,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let sync_peer_info = |info: RoutePeerInfo| {
|
||||||
|
let raw = raw_route_peer_info(&info);
|
||||||
|
(Some(vec![info]), Some(vec![raw]))
|
||||||
|
};
|
||||||
|
|
||||||
|
let v1 = peer_info(
|
||||||
|
1,
|
||||||
|
vec![PeerGroupInfo {
|
||||||
|
group_name: "legacy".to_owned(),
|
||||||
|
group_proof: Vec::new(),
|
||||||
|
}],
|
||||||
|
);
|
||||||
|
let (peer_infos, raw_peer_infos) = sync_peer_info(v1.clone());
|
||||||
|
route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 22, true, peer_infos, raw_peer_infos, None, None)
|
||||||
|
.await
|
||||||
|
.expect("v1 peer info should be accepted");
|
||||||
|
assert_eq!(route.service_impl.get_peer_groups(3).as_ref(), &["legacy"]);
|
||||||
|
|
||||||
|
let v2 = peer_info(2, Vec::new());
|
||||||
|
let (peer_infos, raw_peer_infos) = sync_peer_info(v2);
|
||||||
|
route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 22, true, peer_infos, raw_peer_infos, None, None)
|
||||||
|
.await
|
||||||
|
.expect("v2 peer info should be accepted");
|
||||||
|
assert!(route.service_impl.get_peer_groups(3).is_empty());
|
||||||
|
|
||||||
|
let (peer_infos, raw_peer_infos) = sync_peer_info(v1);
|
||||||
|
route
|
||||||
|
.session_mgr
|
||||||
|
.do_sync_route_info(2, 22, true, peer_infos, raw_peer_infos, None, None)
|
||||||
|
.await
|
||||||
|
.expect("stale peer info should be ignored");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
route
|
||||||
|
.service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.peer_infos
|
||||||
|
.read()
|
||||||
|
.get(&3)
|
||||||
|
.expect("peer info should remain present")
|
||||||
|
.version,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert!(route.service_impl.get_peer_groups(3).is_empty());
|
||||||
|
|
||||||
|
route.stop().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn credential_group_refresh_does_not_restore_removed_proof_group() {
|
||||||
|
let service_impl = Arc::new(test_service_impl(1));
|
||||||
|
let mut peer_infos = OrderedHashMap::new();
|
||||||
|
peer_infos.insert(
|
||||||
|
3,
|
||||||
|
RoutePeerInfo {
|
||||||
|
peer_id: 3,
|
||||||
|
version: 3,
|
||||||
|
noise_static_pubkey: vec![3; 32],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let all_trusted = HashMap::from([(
|
||||||
|
vec![3; 32],
|
||||||
|
TrustedCredentialPubkey {
|
||||||
|
groups: vec!["credential".to_owned()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let group_trust_lock = service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.group_trust_update_lock
|
||||||
|
.lock();
|
||||||
|
service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.set_peer_groups_locked(3, HashMap::from([("legacy".to_owned(), vec![1])]));
|
||||||
|
|
||||||
|
let (started_tx, started_rx) = std::sync::mpsc::channel();
|
||||||
|
let credential_refresh = std::thread::spawn({
|
||||||
|
let service_impl = service_impl.clone();
|
||||||
|
move || {
|
||||||
|
started_tx.send(()).unwrap();
|
||||||
|
service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.update_credential_groups(&peer_infos, &all_trusted);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
started_rx.recv().unwrap();
|
||||||
|
|
||||||
|
service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.set_peer_groups_locked(3, HashMap::new());
|
||||||
|
drop(group_trust_lock);
|
||||||
|
credential_refresh.join().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
service_impl
|
||||||
|
.synced_route_info
|
||||||
|
.group_trust_map
|
||||||
|
.get(&3)
|
||||||
|
.as_deref(),
|
||||||
|
Some(&HashMap::from([("credential".to_owned(), Vec::new())]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn stop_waits_for_in_flight_route_sync_before_draining_sessions() {
|
async fn stop_waits_for_in_flight_route_sync_before_draining_sessions() {
|
||||||
let peer_rpc = Arc::new(PeerRpcManager::new(TestPeerRpcTransport));
|
let peer_rpc = Arc::new(PeerRpcManager::new(TestPeerRpcTransport));
|
||||||
@@ -4542,7 +5188,7 @@ mod tests {
|
|||||||
let session_mgr = route.session_mgr.clone();
|
let session_mgr = route.session_mgr.clone();
|
||||||
async move {
|
async move {
|
||||||
session_mgr
|
session_mgr
|
||||||
.do_sync_route_info(2, 1, false, None, None, None, None)
|
.do_sync_route_info(2, 1, true, None, None, None, None)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+22
-3
@@ -152,7 +152,14 @@ socket2 = { version = "0.5.10", features = ["all"] }
|
|||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
pnet = { version = "0.35.0", features = ["serde"] }
|
pnet_datalink = { version = "0.35.0", optional = true }
|
||||||
|
smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git", rev = "0a926767a68bc88d5512afefa7529c5ecdade4ea", optional = true, default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"medium-ethernet",
|
||||||
|
"proto-ipv4",
|
||||||
|
"proto-ipv6",
|
||||||
|
"socket-raw",
|
||||||
|
] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
||||||
clap = { version = "4.5.30", features = [
|
clap = { version = "4.5.30", features = [
|
||||||
@@ -167,7 +174,7 @@ clap_complete_nushell = { version = "4.5.10" }
|
|||||||
|
|
||||||
async-recursion = "1.0.5"
|
async-recursion = "1.0.5"
|
||||||
|
|
||||||
network-interface = "2.0"
|
network-interface = "2.0.5"
|
||||||
|
|
||||||
# for wireguard
|
# for wireguard
|
||||||
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
|
boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true }
|
||||||
@@ -287,6 +294,13 @@ ctor = "0.8.0"
|
|||||||
stun_codec = "0.3.4"
|
stun_codec = "0.3.4"
|
||||||
bytecodec = "0.4.15"
|
bytecodec = "0.4.15"
|
||||||
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
|
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
|
||||||
|
smoltcp = { git = "https://github.com/smoltcp-rs/smoltcp.git", rev = "0a926767a68bc88d5512afefa7529c5ecdade4ea", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"medium-ethernet",
|
||||||
|
"proto-ipv4",
|
||||||
|
"proto-ipv6",
|
||||||
|
"socket-raw",
|
||||||
|
] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
[target.'cfg(target_os = "linux")'.dev-dependencies]
|
||||||
defguard_wireguard_rs = "0.4.2"
|
defguard_wireguard_rs = "0.4.2"
|
||||||
@@ -375,7 +389,12 @@ magic-dns = [
|
|||||||
"easytier-core/proxy-packet",
|
"easytier-core/proxy-packet",
|
||||||
"easytier-proto/magic-dns",
|
"easytier-proto/magic-dns",
|
||||||
]
|
]
|
||||||
faketcp = ["dep:flume", "easytier-proto/faketcp"]
|
faketcp = [
|
||||||
|
"dep:flume",
|
||||||
|
"dep:pnet_datalink",
|
||||||
|
"dep:smoltcp",
|
||||||
|
"easytier-proto/faketcp",
|
||||||
|
]
|
||||||
zstd = ["easytier-core/zstd", "easytier-proto/zstd"]
|
zstd = ["easytier-core/zstd", "easytier-proto/zstd"]
|
||||||
upnp = ["dep:igd-next", "dep:natpmp"]
|
upnp = ["dep:igd-next", "dep:natpmp"]
|
||||||
endpoint-discovery = [
|
endpoint-discovery = [
|
||||||
|
|||||||
@@ -11,6 +11,17 @@ use std::{
|
|||||||
os::fd::AsRawFd,
|
os::fd::AsRawFd,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Error, IfConfiguerTrait,
|
||||||
|
netlink_wire::{
|
||||||
|
AddressMessage, MessageBuilder, MessageIter, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP,
|
||||||
|
NLM_F_DUMP_INTR, NLM_F_EXCL, NLM_F_REQUEST, NLMSG_DONE, NLMSG_ERROR, NeighborMessage,
|
||||||
|
NetlinkDecode, NetlinkEncode, RTM_DELADDR, RTM_DELNEIGH, RTM_DELROUTE, RTM_GETNEIGH,
|
||||||
|
RTM_GETROUTE, RTM_NEWADDR, RTM_NEWNEIGH, RTM_NEWROUTE, RouteMessage, RouteMessageBuilder,
|
||||||
|
RouteType, netlink_error_code,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use crate::common::network::ip_mask_to_prefix;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use cidr::{IpInet, Ipv4Inet, Ipv6Inet};
|
use cidr::{IpInet, Ipv4Inet, Ipv6Inet};
|
||||||
@@ -23,18 +34,6 @@ use nix::{
|
|||||||
net::if_::InterfaceFlags,
|
net::if_::InterfaceFlags,
|
||||||
sys::socket::SockaddrLike as _,
|
sys::socket::SockaddrLike as _,
|
||||||
};
|
};
|
||||||
use pnet::ipnetwork::ip_mask_to_prefix;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
Error, IfConfiguerTrait,
|
|
||||||
netlink_wire::{
|
|
||||||
AddressMessage, MessageBuilder, MessageIter, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP,
|
|
||||||
NLM_F_DUMP_INTR, NLM_F_EXCL, NLM_F_REQUEST, NLMSG_DONE, NLMSG_ERROR, NeighborMessage,
|
|
||||||
NetlinkDecode, NetlinkEncode, RTM_DELADDR, RTM_DELNEIGH, RTM_DELROUTE, RTM_GETNEIGH,
|
|
||||||
RTM_GETROUTE, RTM_NEWADDR, RTM_NEWNEIGH, RTM_NEWROUTE, RouteMessage, RouteMessageBuilder,
|
|
||||||
RouteType, netlink_error_code,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) fn dummy_socket() -> Result<std::net::UdpSocket, Error> {
|
pub(crate) fn dummy_socket() -> Result<std::net::UdpSocket, Error> {
|
||||||
Ok(std::net::UdpSocket::bind("0:0")?)
|
Ok(std::net::UdpSocket::bind("0:0")?)
|
||||||
|
|||||||
+199
-111
@@ -1,13 +1,6 @@
|
|||||||
#[cfg(target_os = "windows")]
|
use std::{collections::HashMap, net::IpAddr};
|
||||||
use std::net::IpAddr;
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
use network_interface::{NetworkInterface, NetworkInterfaceConfig};
|
||||||
use network_interface::{
|
|
||||||
Addr as SystemAddr, NetworkInterface as SystemNetworkInterface, NetworkInterfaceConfig,
|
|
||||||
};
|
|
||||||
use pnet::datalink::NetworkInterface;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
use pnet::{ipnetwork::IpNetwork, util::MacAddr};
|
|
||||||
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
#[cfg(all(target_os = "macos", not(feature = "macos-ne")))]
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
@@ -15,8 +8,113 @@ use crate::proto::peer_rpc::GetIpListResponse;
|
|||||||
|
|
||||||
use super::netns::NetNS;
|
use super::netns::NetNS;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
struct InterfaceState {
|
||||||
|
is_point_to_point: bool,
|
||||||
|
is_loopback: bool,
|
||||||
|
is_up: bool,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
is_lower_up: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
all(target_os = "linux", not(target_env = "ohos")),
|
||||||
|
all(target_os = "macos", not(feature = "macos-ne")),
|
||||||
|
target_os = "freebsd"
|
||||||
|
))]
|
||||||
|
fn collect_interface_states() -> HashMap<String, InterfaceState> {
|
||||||
|
let mut states = HashMap::new();
|
||||||
|
if let Ok(interfaces) = nix::ifaddrs::getifaddrs() {
|
||||||
|
use nix::net::if_::InterfaceFlags;
|
||||||
|
|
||||||
|
for interface in interfaces {
|
||||||
|
let flags = interface.flags;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let is_lower_up = flags.contains(InterfaceFlags::IFF_LOWER_UP);
|
||||||
|
states.insert(
|
||||||
|
interface.interface_name,
|
||||||
|
InterfaceState {
|
||||||
|
is_point_to_point: flags.contains(InterfaceFlags::IFF_POINTOPOINT),
|
||||||
|
is_loopback: flags.contains(InterfaceFlags::IFF_LOOPBACK),
|
||||||
|
is_up: flags.contains(InterfaceFlags::IFF_UP),
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
is_lower_up,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
states
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
all(target_os = "linux", not(target_env = "ohos")),
|
||||||
|
all(target_os = "macos", not(feature = "macos-ne")),
|
||||||
|
target_os = "freebsd"
|
||||||
|
)))]
|
||||||
|
fn collect_interface_states() -> HashMap<String, InterfaceState> {
|
||||||
|
HashMap::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "freebsd", target_os = "windows"))]
|
||||||
|
fn has_nonzero_mac(iface: &NetworkInterface) -> bool {
|
||||||
|
iface.mac_addr.as_deref().is_some_and(|mac| {
|
||||||
|
let mut octets = mac.split([':', '-']);
|
||||||
|
let mut nonzero = false;
|
||||||
|
for _ in 0..6 {
|
||||||
|
let Some(value) = octets
|
||||||
|
.next()
|
||||||
|
.and_then(|octet| u8::from_str_radix(octet, 16).ok())
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
nonzero |= value != 0;
|
||||||
|
}
|
||||||
|
octets.next().is_none() && nonzero
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn ip_mask_to_prefix(mask: IpAddr) -> Result<u8, ()> {
|
||||||
|
match mask {
|
||||||
|
IpAddr::V4(mask) => {
|
||||||
|
let raw = u32::from(mask);
|
||||||
|
let prefix = raw.leading_ones() as u8;
|
||||||
|
let expected = if prefix == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
u32::MAX << (32 - prefix)
|
||||||
|
};
|
||||||
|
(raw == expected).then_some(prefix).ok_or(())
|
||||||
|
}
|
||||||
|
IpAddr::V6(mask) => {
|
||||||
|
let raw = u128::from(mask);
|
||||||
|
let prefix = raw.leading_ones() as u8;
|
||||||
|
let expected = if prefix == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
u128::MAX << (128 - prefix)
|
||||||
|
};
|
||||||
|
(raw == expected).then_some(prefix).ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct InterfaceFilter {
|
struct InterfaceFilter {
|
||||||
iface: NetworkInterface,
|
iface: NetworkInterface,
|
||||||
|
state: InterfaceState,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interface_state(
|
||||||
|
iface: &NetworkInterface,
|
||||||
|
states: &HashMap<String, InterfaceState>,
|
||||||
|
) -> InterfaceState {
|
||||||
|
states.get(&iface.name).copied().unwrap_or(InterfaceState {
|
||||||
|
is_loopback: iface.internal,
|
||||||
|
is_up: true,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
is_lower_up: true,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
@@ -40,7 +138,7 @@ impl InterfaceFilter {
|
|||||||
|
|
||||||
async fn has_valid_ip(&self) -> bool {
|
async fn has_valid_ip(&self) -> bool {
|
||||||
self.iface
|
self.iface
|
||||||
.ips
|
.addr
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ip| ip.ip())
|
.map(|ip| ip.ip())
|
||||||
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
||||||
@@ -50,18 +148,18 @@ impl InterfaceFilter {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"filter linux iface: {:?}, is_point_to_point: {}, is_loopback: {}, is_up: {}, is_lower_up: {}, is_tun: {}, has_valid_ip: {}",
|
"filter linux iface: {:?}, is_point_to_point: {}, is_loopback: {}, is_up: {}, is_lower_up: {}, is_tun: {}, has_valid_ip: {}",
|
||||||
self.iface,
|
self.iface,
|
||||||
self.iface.is_point_to_point(),
|
self.state.is_point_to_point,
|
||||||
self.iface.is_loopback(),
|
self.state.is_loopback,
|
||||||
self.iface.is_up(),
|
self.state.is_up,
|
||||||
self.iface.is_lower_up(),
|
self.state.is_lower_up,
|
||||||
self.is_tun_tap_device().await,
|
self.is_tun_tap_device().await,
|
||||||
self.has_valid_ip().await
|
self.has_valid_ip().await
|
||||||
);
|
);
|
||||||
|
|
||||||
!self.iface.is_point_to_point()
|
!self.state.is_point_to_point
|
||||||
&& !self.iface.is_loopback()
|
&& !self.state.is_loopback
|
||||||
&& self.iface.is_up()
|
&& self.state.is_up
|
||||||
&& self.iface.is_lower_up()
|
&& self.state.is_lower_up
|
||||||
&& !self.is_tun_tap_device().await
|
&& !self.is_tun_tap_device().await
|
||||||
&& self.has_valid_ip().await
|
&& self.has_valid_ip().await
|
||||||
}
|
}
|
||||||
@@ -134,13 +232,13 @@ impl InterfaceFilter {
|
|||||||
#[cfg(target_os = "freebsd")]
|
#[cfg(target_os = "freebsd")]
|
||||||
async fn is_interface_physical(&self) -> bool {
|
async fn is_interface_physical(&self) -> bool {
|
||||||
// if mac addr is not zero, then it's physical interface
|
// if mac addr is not zero, then it's physical interface
|
||||||
self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false)
|
has_nonzero_mac(&self.iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn filter_iface(&self) -> bool {
|
async fn filter_iface(&self) -> bool {
|
||||||
!self.iface.is_point_to_point()
|
!self.state.is_point_to_point
|
||||||
&& !self.iface.is_loopback()
|
&& !self.state.is_loopback
|
||||||
&& self.iface.is_up()
|
&& self.state.is_up
|
||||||
&& self.is_interface_physical().await
|
&& self.is_interface_physical().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,19 +249,19 @@ impl InterfaceFilter {
|
|||||||
tracing::debug!(
|
tracing::debug!(
|
||||||
"iface_name: {:?}, p2p: {:?}, is_up: {:?}, iface: {:?}",
|
"iface_name: {:?}, p2p: {:?}, is_up: {:?}, iface: {:?}",
|
||||||
self.iface.name,
|
self.iface.name,
|
||||||
self.iface.is_point_to_point(),
|
self.state.is_point_to_point,
|
||||||
self.iface.is_up(),
|
self.state.is_up,
|
||||||
self.iface
|
self.iface
|
||||||
);
|
);
|
||||||
!self.iface.is_point_to_point()
|
!self.state.is_point_to_point
|
||||||
&& !self.iface.is_loopback()
|
&& !self.state.is_loopback
|
||||||
&& self
|
&& self
|
||||||
.iface
|
.iface
|
||||||
.ips
|
.addr
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ip| ip.ip())
|
.map(|ip| ip.ip())
|
||||||
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
.any(|ip| !ip.is_loopback() && !ip.is_unspecified() && !ip.is_multicast())
|
||||||
&& self.iface.mac.map(|mac| !mac.is_zero()).unwrap_or(false)
|
&& has_nonzero_mac(&self.iface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,15 +309,66 @@ pub(crate) async fn collect_interfaces(net_ns: NetNS, filter: bool) -> Vec<Netwo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "faketcp")]
|
||||||
|
fn convert_pnet_interface(iface: pnet_datalink::NetworkInterface) -> NetworkInterface {
|
||||||
|
let internal = iface.is_loopback();
|
||||||
|
let addr = iface
|
||||||
|
.ips
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|network| match (network.ip(), network.mask()) {
|
||||||
|
(IpAddr::V4(ip), IpAddr::V4(netmask)) => {
|
||||||
|
Some(network_interface::Addr::V4(network_interface::V4IfAddr {
|
||||||
|
ip,
|
||||||
|
broadcast: None,
|
||||||
|
netmask: Some(netmask),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
(IpAddr::V6(ip), IpAddr::V6(netmask)) => {
|
||||||
|
Some(network_interface::Addr::V6(network_interface::V6IfAddr {
|
||||||
|
ip,
|
||||||
|
broadcast: None,
|
||||||
|
netmask: Some(netmask),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
NetworkInterface {
|
||||||
|
name: iface.name,
|
||||||
|
addr,
|
||||||
|
mac_addr: iface.mac.map(|mac| mac.to_string()),
|
||||||
|
index: iface.index,
|
||||||
|
internal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn collect_interfaces_in_current_namespace(filter: bool) -> Vec<NetworkInterface> {
|
async fn collect_interfaces_in_current_namespace(filter: bool) -> Vec<NetworkInterface> {
|
||||||
#[cfg(target_os = "windows")]
|
let ifaces = match NetworkInterface::show() {
|
||||||
let ifaces = collect_interfaces_windows();
|
Ok(ifaces) => ifaces,
|
||||||
#[cfg(not(target_os = "windows"))]
|
Err(error) => {
|
||||||
let ifaces = pnet::datalink::interfaces();
|
tracing::warn!(?error, "failed to enumerate network interfaces");
|
||||||
|
#[cfg(feature = "faketcp")]
|
||||||
|
{
|
||||||
|
match std::panic::catch_unwind(pnet_datalink::interfaces) {
|
||||||
|
Ok(ifaces) => ifaces.into_iter().map(convert_pnet_interface).collect(),
|
||||||
|
Err(_) => {
|
||||||
|
tracing::error!(
|
||||||
|
"failed to enumerate network interfaces via network-interface and pnet"
|
||||||
|
);
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "faketcp"))]
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let states = collect_interface_states();
|
||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
for iface in ifaces {
|
for iface in ifaces {
|
||||||
let f = InterfaceFilter {
|
let f = InterfaceFilter {
|
||||||
iface: iface.clone(),
|
iface: iface.clone(),
|
||||||
|
state: interface_state(&iface, &states),
|
||||||
};
|
};
|
||||||
|
|
||||||
if filter && !f.filter_iface().await {
|
if filter && !f.filter_iface().await {
|
||||||
@@ -250,83 +399,6 @@ where
|
|||||||
.expect("namespace-local network operation panicked")
|
.expect("namespace-local network operation panicked")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn collect_interfaces_windows() -> Vec<NetworkInterface> {
|
|
||||||
match SystemNetworkInterface::show() {
|
|
||||||
Ok(ifaces) => ifaces.into_iter().map(convert_windows_interface).collect(),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!(
|
|
||||||
?e,
|
|
||||||
"failed to enumerate interfaces via network-interface, falling back to pnet"
|
|
||||||
);
|
|
||||||
match std::panic::catch_unwind(pnet::datalink::interfaces) {
|
|
||||||
Ok(ifaces) => ifaces,
|
|
||||||
Err(_) => {
|
|
||||||
tracing::error!(
|
|
||||||
"failed to enumerate interfaces via both network-interface and pnet"
|
|
||||||
);
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn convert_windows_interface(iface: SystemNetworkInterface) -> NetworkInterface {
|
|
||||||
let mac = iface.mac_addr.as_deref().and_then(|mac| {
|
|
||||||
mac.parse::<MacAddr>()
|
|
||||||
.map_err(
|
|
||||||
|e| tracing::debug!(iface = %iface.name, mac, ?e, "failed to parse interface mac"),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
let ips = iface
|
|
||||||
.addr
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(convert_windows_interface_addr)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
NetworkInterface {
|
|
||||||
name: iface.name,
|
|
||||||
description: String::new(),
|
|
||||||
index: iface.index,
|
|
||||||
mac,
|
|
||||||
ips,
|
|
||||||
// pnet does not populate Windows flags either, so keep the existing semantics.
|
|
||||||
flags: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn convert_windows_interface_addr(addr: SystemAddr) -> Option<IpNetwork> {
|
|
||||||
match addr {
|
|
||||||
SystemAddr::V4(addr) => {
|
|
||||||
let netmask = addr
|
|
||||||
.netmask
|
|
||||||
.map(IpAddr::V4)
|
|
||||||
.unwrap_or(IpAddr::V4(std::net::Ipv4Addr::new(255, 255, 255, 255)));
|
|
||||||
IpNetwork::with_netmask(IpAddr::V4(addr.ip), netmask)
|
|
||||||
.map_err(
|
|
||||||
|e| tracing::debug!(ip = %addr.ip, ?addr.netmask, ?e, "failed to convert ipv4"),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
SystemAddr::V6(addr) => {
|
|
||||||
let netmask = addr
|
|
||||||
.netmask
|
|
||||||
.map(IpAddr::V6)
|
|
||||||
.unwrap_or(IpAddr::V6(std::net::Ipv6Addr::from(u128::MAX)));
|
|
||||||
IpNetwork::with_netmask(IpAddr::V6(addr.ip), netmask)
|
|
||||||
.map_err(
|
|
||||||
|e| tracing::debug!(ip = %addr.ip, ?addr.netmask, ?e, "failed to convert ipv6"),
|
|
||||||
)
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip(net_ns))]
|
#[tracing::instrument(skip(net_ns))]
|
||||||
pub(crate) async fn collect_local_ip_addrs(net_ns: NetNS) -> GetIpListResponse {
|
pub(crate) async fn collect_local_ip_addrs(net_ns: NetNS) -> GetIpListResponse {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -349,7 +421,7 @@ async fn collect_local_ip_addrs_in_current_namespace() -> GetIpListResponse {
|
|||||||
|
|
||||||
let ifaces = collect_interfaces_in_current_namespace(true).await;
|
let ifaces = collect_interfaces_in_current_namespace(true).await;
|
||||||
for iface in ifaces {
|
for iface in ifaces {
|
||||||
for ip in iface.ips {
|
for ip in iface.addr {
|
||||||
let ip: std::net::IpAddr = ip.ip();
|
let ip: std::net::IpAddr = ip.ip();
|
||||||
if let std::net::IpAddr::V4(v4) = ip {
|
if let std::net::IpAddr::V4(v4) = ip {
|
||||||
if ip.is_loopback() || ip.is_multicast() {
|
if ip.is_loopback() || ip.is_multicast() {
|
||||||
@@ -362,7 +434,7 @@ async fn collect_local_ip_addrs_in_current_namespace() -> GetIpListResponse {
|
|||||||
|
|
||||||
let ifaces = collect_interfaces_in_current_namespace(false).await;
|
let ifaces = collect_interfaces_in_current_namespace(false).await;
|
||||||
for iface in ifaces {
|
for iface in ifaces {
|
||||||
for ip in iface.ips {
|
for ip in iface.addr {
|
||||||
let ip: std::net::IpAddr = ip.ip();
|
let ip: std::net::IpAddr = ip.ip();
|
||||||
if let std::net::IpAddr::V6(v6) = ip {
|
if let std::net::IpAddr::V6(v6) = ip {
|
||||||
if v6.is_multicast() || v6.is_loopback() || v6.is_unicast_link_local() {
|
if v6.is_multicast() || v6.is_loopback() || v6.is_unicast_link_local() {
|
||||||
@@ -394,6 +466,22 @@ async fn collect_local_ip_addrs_in_current_namespace() -> GetIpListResponse {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_contiguous_ip_masks_to_prefixes() {
|
||||||
|
assert_eq!(
|
||||||
|
ip_mask_to_prefix(IpAddr::V4("255.255.254.0".parse().unwrap())),
|
||||||
|
Ok(23)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ip_mask_to_prefix(IpAddr::V6("ffff:ffff:ffff:ffff::".parse().unwrap())),
|
||||||
|
Ok(64)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ip_mask_to_prefix(IpAddr::V4("255.0.255.0".parse().unwrap())),
|
||||||
|
Err(())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn namespace_operation_does_not_migrate_between_os_threads() {
|
async fn namespace_operation_does_not_migrate_between_os_threads() {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ impl ConnectorRuntime for NativeHostRuntime {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|interface| {
|
.find(|interface| {
|
||||||
interface
|
interface
|
||||||
.ips
|
.addr
|
||||||
.iter()
|
.iter()
|
||||||
.any(|local| matches!(local.ip(), IpAddr::V6(local_ip) if local_ip == ip))
|
.any(|local| matches!(local.ip(), IpAddr::V6(local_ip) if local_ip == ip))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -260,11 +260,7 @@ mod tests {
|
|||||||
WrappedTransportConnect, WrappedTransportEngine,
|
WrappedTransportConnect, WrappedTransportEngine,
|
||||||
};
|
};
|
||||||
use easytier_core::listener::plan::ListenerRuntimeConfig;
|
use easytier_core::listener::plan::ListenerRuntimeConfig;
|
||||||
use pnet::packet::{
|
use smoltcp::wire::{IpAddress, IpProtocol, Ipv4Packet, UdpPacket};
|
||||||
ip::IpNextHeaderProtocols,
|
|
||||||
ipv4::{self, MutableIpv4Packet},
|
|
||||||
udp::{self, MutableUdpPacket},
|
|
||||||
};
|
|
||||||
#[cfg(feature = "kcp")]
|
#[cfg(feature = "kcp")]
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
@@ -500,30 +496,26 @@ mod tests {
|
|||||||
let destination_ip = "10.250.0.2".parse().unwrap();
|
let destination_ip = "10.250.0.2".parse().unwrap();
|
||||||
let mut ip_packet = vec![0u8; 28];
|
let mut ip_packet = vec![0u8; 28];
|
||||||
{
|
{
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut ip_packet).unwrap();
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut ip_packet);
|
||||||
ipv4.set_version(4);
|
ipv4.set_version(4);
|
||||||
ipv4.set_header_length(5);
|
ipv4.set_header_len(20);
|
||||||
ipv4.set_total_length(28);
|
ipv4.set_total_len(28);
|
||||||
ipv4.set_ttl(64);
|
ipv4.set_hop_limit(64);
|
||||||
ipv4.set_next_level_protocol(IpNextHeaderProtocols::Udp);
|
ipv4.set_next_header(IpProtocol::Udp);
|
||||||
ipv4.set_source(source_ip);
|
ipv4.set_src_addr(source_ip);
|
||||||
ipv4.set_destination(destination_ip);
|
ipv4.set_dst_addr(destination_ip);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut udp = MutableUdpPacket::new(&mut ip_packet[20..]).unwrap();
|
let mut udp = UdpPacket::new_unchecked(&mut ip_packet[20..]);
|
||||||
udp.set_source(10000);
|
udp.set_src_port(10000);
|
||||||
udp.set_destination(10001);
|
udp.set_dst_port(10001);
|
||||||
udp.set_length(8);
|
udp.set_len(8);
|
||||||
udp.set_checksum(udp::ipv4_checksum(
|
udp.fill_checksum(
|
||||||
&udp.to_immutable(),
|
&IpAddress::Ipv4(source_ip),
|
||||||
&source_ip,
|
&IpAddress::Ipv4(destination_ip),
|
||||||
&destination_ip,
|
);
|
||||||
));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut ipv4 = MutableIpv4Packet::new(&mut ip_packet).unwrap();
|
|
||||||
ipv4.set_checksum(ipv4::checksum(&ipv4.to_immutable()));
|
|
||||||
}
|
}
|
||||||
|
Ipv4Packet::new_unchecked(&mut ip_packet).fill_checksum();
|
||||||
let received = tokio::time::timeout(std::time::Duration::from_secs(10), async {
|
let received = tokio::time::timeout(std::time::Duration::from_secs(10), async {
|
||||||
loop {
|
loop {
|
||||||
instance_a
|
instance_a
|
||||||
|
|||||||
@@ -237,9 +237,9 @@ fn detect_default_route_ipv6_interfaces(
|
|||||||
routes: &[DetectedIpv6Route],
|
routes: &[DetectedIpv6Route],
|
||||||
max_prefix_len: u8,
|
max_prefix_len: u8,
|
||||||
) -> Vec<DetectedDefaultRouteIpv6Interface> {
|
) -> Vec<DetectedDefaultRouteIpv6Interface> {
|
||||||
|
use crate::common::network::ip_mask_to_prefix;
|
||||||
use nix::ifaddrs::getifaddrs;
|
use nix::ifaddrs::getifaddrs;
|
||||||
use nix::sys::socket::SockaddrLike;
|
use nix::sys::socket::SockaddrLike;
|
||||||
use pnet::ipnetwork::ip_mask_to_prefix;
|
|
||||||
|
|
||||||
let wan_ifindices = default_route_ifindices(routes);
|
let wan_ifindices = default_route_ifindices(routes);
|
||||||
if wan_ifindices.is_empty() {
|
if wan_ifindices.is_empty() {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ mod stack;
|
|||||||
|
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
use network_interface::NetworkInterfaceConfig;
|
use network_interface::NetworkInterfaceConfig;
|
||||||
use pnet::util::MacAddr;
|
|
||||||
use std::{
|
use std::{
|
||||||
io,
|
io,
|
||||||
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
|
||||||
@@ -25,6 +24,7 @@ use easytier_core::{
|
|||||||
use crate::{common::netns::NetNS, tunnel::FromUrl};
|
use crate::{common::netns::NetNS, tunnel::FromUrl};
|
||||||
|
|
||||||
use self::netfilter::create_tun;
|
use self::netfilter::create_tun;
|
||||||
|
use self::packet::MacAddr;
|
||||||
|
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use tokio_util::task::AbortOnDropHandle;
|
use tokio_util::task::AbortOnDropHandle;
|
||||||
@@ -35,6 +35,15 @@ struct IpToIfNameCache {
|
|||||||
ip_to_ifname: DashMap<IpAddr, (String, Option<MacAddr>)>,
|
ip_to_ifname: DashMap<IpAddr, (String, Option<MacAddr>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_mac_addr(value: &str) -> Option<MacAddr> {
|
||||||
|
let mut bytes = [0; 6];
|
||||||
|
let mut octets = value.split([':', '-']);
|
||||||
|
for byte in &mut bytes {
|
||||||
|
*byte = u8::from_str_radix(octets.next()?, 16).ok()?;
|
||||||
|
}
|
||||||
|
octets.next().is_none().then(|| MacAddr::from_bytes(&bytes))
|
||||||
|
}
|
||||||
|
|
||||||
impl IpToIfNameCache {
|
impl IpToIfNameCache {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -50,9 +59,10 @@ impl IpToIfNameCache {
|
|||||||
};
|
};
|
||||||
for iface in interfaces {
|
for iface in interfaces {
|
||||||
let mac = iface.mac_addr.as_deref().and_then(|mac| {
|
let mac = iface.mac_addr.as_deref().and_then(|mac| {
|
||||||
mac.parse::<MacAddr>().map_err(|e| {
|
parse_mac_addr(mac).or_else(|| {
|
||||||
tracing::debug!(iface = %iface.name, mac, ?e, "failed to parse interface mac")
|
tracing::debug!(iface = %iface.name, mac, "failed to parse interface mac");
|
||||||
}).ok()
|
None
|
||||||
|
})
|
||||||
});
|
});
|
||||||
for ip in iface.addr.iter() {
|
for ip in iface.addr.iter() {
|
||||||
self.ip_to_ifname.insert(ip.ip(), (iface.name.clone(), mac));
|
self.ip_to_ifname.insert(ip.ip(), (iface.name.clone(), mac));
|
||||||
|
|||||||
@@ -630,11 +630,9 @@ impl stack::Tun for LinuxBpfTun {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::socket::fake_tcp::packet::build_tcp_packet;
|
use crate::socket::fake_tcp::packet::{MacAddr, TCP_FLAG_SYN, build_tcp_packet};
|
||||||
use crate::socket::fake_tcp::stack::Tun;
|
use crate::socket::fake_tcp::stack::Tun;
|
||||||
use pnet::datalink;
|
use pnet_datalink as datalink;
|
||||||
use pnet::packet::tcp::TcpFlags;
|
|
||||||
use pnet::util::MacAddr;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::net::{IpAddr, Ipv4Addr};
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
use tokio::time::{Duration, timeout};
|
use tokio::time::{Duration, timeout};
|
||||||
@@ -656,7 +654,7 @@ mod tests {
|
|||||||
IpAddr::V4(ip) => Some(ip),
|
IpAddr::V4(ip) => Some(ip),
|
||||||
IpAddr::V6(_) => None,
|
IpAddr::V6(_) => None,
|
||||||
})?;
|
})?;
|
||||||
return Some((iface.name, ipv4, mac));
|
return Some((iface.name, ipv4, MacAddr::from_bytes(&mac.octets())));
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -741,7 +739,7 @@ mod tests {
|
|||||||
dst_addr,
|
dst_addr,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
TcpFlags::SYN,
|
TCP_FLAG_SYN,
|
||||||
Some(b"ping"),
|
Some(b"ping"),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -794,7 +792,7 @@ mod tests {
|
|||||||
non_matching_dst,
|
non_matching_dst,
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
TcpFlags::SYN,
|
TCP_FLAG_SYN,
|
||||||
Some(b"nope"),
|
Some(b"nope"),
|
||||||
);
|
);
|
||||||
send_raw_frame(&ifname, &non_matching).unwrap();
|
send_raw_frame(&ifname, &non_matching).unwrap();
|
||||||
@@ -819,7 +817,7 @@ mod tests {
|
|||||||
dst_addr,
|
dst_addr,
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
TcpFlags::SYN,
|
TCP_FLAG_SYN,
|
||||||
Some(b"ok"),
|
Some(b"ok"),
|
||||||
);
|
);
|
||||||
send_raw_frame(&ifname, &matching).unwrap();
|
send_raw_frame(&ifname, &matching).unwrap();
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ use std::{
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use pnet::{
|
use pnet_datalink::{self as datalink, DataLinkSender, NetworkInterface};
|
||||||
datalink::{self, DataLinkSender, NetworkInterface},
|
use smoltcp::wire::{
|
||||||
packet::{ethernet::EtherTypes, ip::IpNextHeaderProtocols, ipv6::Ipv6Packet},
|
EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Packet, Ipv6Packet, TcpPacket,
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
@@ -27,49 +27,38 @@ fn filter_tcp_packet(
|
|||||||
src_addr: Option<&SocketAddr>,
|
src_addr: Option<&SocketAddr>,
|
||||||
dst_addr: Option<&SocketAddr>,
|
dst_addr: Option<&SocketAddr>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
use pnet::packet::Packet;
|
let Ok(ethernet) = EthernetFrame::new_checked(packet) else {
|
||||||
use pnet::packet::ethernet::EthernetPacket;
|
|
||||||
use pnet::packet::ipv4::Ipv4Packet;
|
|
||||||
use pnet::packet::tcp::TcpPacket;
|
|
||||||
|
|
||||||
let ethernet = if let Some(ethernet) = EthernetPacket::new(packet) {
|
|
||||||
ethernet
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
match ethernet.get_ethertype() {
|
match ethernet.ethertype() {
|
||||||
EtherTypes::Ipv4 => {
|
EthernetProtocol::Ipv4 => {
|
||||||
let ipv4 = if let Some(ipv4) = Ipv4Packet::new(ethernet.payload()) {
|
let Ok(ipv4) = Ipv4Packet::new_checked(ethernet.payload()) else {
|
||||||
ipv4
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if ipv4.get_next_level_protocol() != IpNextHeaderProtocols::Tcp {
|
if ipv4.next_header() != IpProtocol::Tcp {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tcp = if let Some(tcp) = TcpPacket::new(ipv4.payload()) {
|
let Ok(tcp) = TcpPacket::new_checked(ipv4.payload()) else {
|
||||||
tcp
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(src_addr) = src_addr {
|
if let Some(src_addr) = src_addr {
|
||||||
if IpAddr::V4(ipv4.get_source()) != src_addr.ip() {
|
if IpAddr::V4(ipv4.src_addr()) != src_addr.ip() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if tcp.get_source() != src_addr.port() {
|
if tcp.src_port() != src_addr.port() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dst_addr) = dst_addr {
|
if let Some(dst_addr) = dst_addr {
|
||||||
if IpAddr::V4(ipv4.get_destination()) != dst_addr.ip() {
|
if IpAddr::V4(ipv4.dst_addr()) != dst_addr.ip() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if tcp.get_destination() != dst_addr.port() {
|
if tcp.dst_port() != dst_addr.port() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,43 +68,39 @@ fn filter_tcp_packet(
|
|||||||
"FakeTcpSocketListener packet matched filter, dispatching, src_addr: {:?}, dst_addr: {:?}, packet_src_ip: {:?}, packet_dst_ip: {:?}, packet_src_port: {:?}, packet_dst_port: {:?}",
|
"FakeTcpSocketListener packet matched filter, dispatching, src_addr: {:?}, dst_addr: {:?}, packet_src_ip: {:?}, packet_dst_ip: {:?}, packet_src_port: {:?}, packet_dst_port: {:?}",
|
||||||
src_addr,
|
src_addr,
|
||||||
dst_addr,
|
dst_addr,
|
||||||
ipv4.get_source(),
|
ipv4.src_addr(),
|
||||||
ipv4.get_destination(),
|
ipv4.dst_addr(),
|
||||||
tcp.get_source(),
|
tcp.src_port(),
|
||||||
tcp.get_destination(),
|
tcp.dst_port(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
EtherTypes::Ipv6 => {
|
EthernetProtocol::Ipv6 => {
|
||||||
let ipv6 = if let Some(ipv6) = Ipv6Packet::new(ethernet.payload()) {
|
let Ok(ipv6) = Ipv6Packet::new_checked(ethernet.payload()) else {
|
||||||
ipv6
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if ipv6.get_next_header() != IpNextHeaderProtocols::Tcp {
|
if ipv6.next_header() != IpProtocol::Tcp {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tcp = if let Some(tcp) = TcpPacket::new(ipv6.payload()) {
|
let Ok(tcp) = TcpPacket::new_checked(ipv6.payload()) else {
|
||||||
tcp
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(src_addr) = src_addr {
|
if let Some(src_addr) = src_addr {
|
||||||
if IpAddr::V6(ipv6.get_source()) != src_addr.ip() {
|
if IpAddr::V6(ipv6.src_addr()) != src_addr.ip() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if tcp.get_source() != src_addr.port() {
|
if tcp.src_port() != src_addr.port() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dst_addr) = dst_addr {
|
if let Some(dst_addr) = dst_addr {
|
||||||
if IpAddr::V6(ipv6.get_destination()) != dst_addr.ip() {
|
if IpAddr::V6(ipv6.dst_addr()) != dst_addr.ip() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if tcp.get_destination() != dst_addr.port() {
|
if tcp.dst_port() != dst_addr.port() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,7 +135,7 @@ struct InterfaceWorker {
|
|||||||
impl InterfaceWorker {
|
impl InterfaceWorker {
|
||||||
fn new(interface: NetworkInterface) -> io::Result<Arc<Self>> {
|
fn new(interface: NetworkInterface) -> io::Result<Arc<Self>> {
|
||||||
let (tx, mut rx) = match datalink::channel(&interface, Default::default()) {
|
let (tx, mut rx) = match datalink::channel(&interface, Default::default()) {
|
||||||
Ok(pnet::datalink::Channel::Ethernet(tx, rx)) => (tx, rx),
|
Ok(datalink::Channel::Ethernet(tx, rx)) => (tx, rx),
|
||||||
Ok(_) => return Err(io::Error::other("Unhandled channel type")),
|
Ok(_) => return Err(io::Error::other("Unhandled channel type")),
|
||||||
Err(e) => return Err(io::Error::other(e)),
|
Err(e) => return Err(io::Error::other(e)),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,36 +1,67 @@
|
|||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
|
use smoltcp::wire::{
|
||||||
use pnet::packet::{ip, ipv4, ipv6, tcp};
|
ETHERNET_HEADER_LEN, EthernetAddress, EthernetFrame, EthernetProtocol, IpAddress, IpProtocol,
|
||||||
use pnet::util::MacAddr;
|
Ipv4Packet, Ipv6Packet, TCP_HEADER_LEN, TcpPacket, TcpSeqNumber,
|
||||||
use std::convert::TryInto;
|
};
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
|
|
||||||
const IPV4_HEADER_LEN: usize = 20;
|
use smoltcp::wire::{IPV4_HEADER_LEN, IPV6_HEADER_LEN};
|
||||||
const IPV6_HEADER_LEN: usize = 40;
|
|
||||||
const TCP_HEADER_LEN: usize = 20;
|
pub type MacAddr = EthernetAddress;
|
||||||
|
|
||||||
|
pub const TCP_FLAG_FIN: u8 = 0x01;
|
||||||
|
pub const TCP_FLAG_SYN: u8 = 0x02;
|
||||||
|
pub const TCP_FLAG_RST: u8 = 0x04;
|
||||||
|
pub const TCP_FLAG_PSH: u8 = 0x08;
|
||||||
|
pub const TCP_FLAG_ACK: u8 = 0x10;
|
||||||
|
pub const TCP_FLAG_URG: u8 = 0x20;
|
||||||
|
pub const TCP_FLAG_ECE: u8 = 0x40;
|
||||||
|
pub const TCP_FLAG_CWR: u8 = 0x80;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IPPacket<'p> {
|
pub enum IPPacket<'p> {
|
||||||
V4(ipv4::Ipv4Packet<'p>),
|
V4(Ipv4Packet<&'p [u8]>),
|
||||||
V6(ipv6::Ipv6Packet<'p>),
|
V6(Ipv6Packet<&'p [u8]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IPPacket<'_> {
|
impl IPPacket<'_> {
|
||||||
pub fn get_source(&self) -> IpAddr {
|
pub fn get_source(&self) -> IpAddr {
|
||||||
match self {
|
match self {
|
||||||
IPPacket::V4(p) => IpAddr::V4(p.get_source()),
|
IPPacket::V4(p) => IpAddr::V4(p.src_addr()),
|
||||||
IPPacket::V6(p) => IpAddr::V6(p.get_source()),
|
IPPacket::V6(p) => IpAddr::V6(p.src_addr()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_destination(&self) -> IpAddr {
|
pub fn get_destination(&self) -> IpAddr {
|
||||||
match self {
|
match self {
|
||||||
IPPacket::V4(p) => IpAddr::V4(p.get_destination()),
|
IPPacket::V4(p) => IpAddr::V4(p.dst_addr()),
|
||||||
IPPacket::V6(p) => IpAddr::V6(p.get_destination()),
|
IPPacket::V6(p) => IpAddr::V6(p.dst_addr()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ETH_HDR_LEN: usize = 14;
|
fn set_tcp_flags<T: AsRef<[u8]> + AsMut<[u8]>>(tcp: &mut TcpPacket<T>, flags: u8) {
|
||||||
|
tcp.set_fin(flags & TCP_FLAG_FIN != 0);
|
||||||
|
tcp.set_syn(flags & TCP_FLAG_SYN != 0);
|
||||||
|
tcp.set_rst(flags & TCP_FLAG_RST != 0);
|
||||||
|
tcp.set_psh(flags & TCP_FLAG_PSH != 0);
|
||||||
|
tcp.set_ack(flags & TCP_FLAG_ACK != 0);
|
||||||
|
tcp.set_urg(flags & TCP_FLAG_URG != 0);
|
||||||
|
tcp.set_ece(flags & TCP_FLAG_ECE != 0);
|
||||||
|
tcp.set_cwr(flags & TCP_FLAG_CWR != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn tcp_flags<T: AsRef<[u8]>>(tcp: &TcpPacket<T>) -> u8 {
|
||||||
|
u8::from(tcp.fin())
|
||||||
|
| (u8::from(tcp.syn()) << 1)
|
||||||
|
| (u8::from(tcp.rst()) << 2)
|
||||||
|
| (u8::from(tcp.psh()) << 3)
|
||||||
|
| (u8::from(tcp.ack()) << 4)
|
||||||
|
| (u8::from(tcp.urg()) << 5)
|
||||||
|
| (u8::from(tcp.ece()) << 6)
|
||||||
|
| (u8::from(tcp.cwr()) << 7)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn build_tcp_packet(
|
pub fn build_tcp_packet(
|
||||||
@@ -47,76 +78,72 @@ pub fn build_tcp_packet(
|
|||||||
SocketAddr::V4(_) => IPV4_HEADER_LEN,
|
SocketAddr::V4(_) => IPV4_HEADER_LEN,
|
||||||
SocketAddr::V6(_) => IPV6_HEADER_LEN,
|
SocketAddr::V6(_) => IPV6_HEADER_LEN,
|
||||||
};
|
};
|
||||||
let wscale = (flags & tcp::TcpFlags::SYN) != 0;
|
let wscale = flags & TCP_FLAG_SYN != 0;
|
||||||
let tcp_header_len = TCP_HEADER_LEN + if wscale { 4 } else { 0 }; // nop + wscale
|
let tcp_header_len = TCP_HEADER_LEN + if wscale { 4 } else { 0 }; // nop + wscale
|
||||||
let tcp_total_len = tcp_header_len + payload.map_or(0, |payload| payload.len());
|
let tcp_total_len = tcp_header_len + payload.map_or(0, |payload| payload.len());
|
||||||
let total_len = ip_header_len + tcp_total_len;
|
let total_len = ip_header_len + tcp_total_len;
|
||||||
let mut buf = BytesMut::zeroed(ETH_HDR_LEN + total_len);
|
let mut buf = BytesMut::zeroed(ETHERNET_HEADER_LEN + total_len);
|
||||||
|
|
||||||
let mut eth_buf = buf.split_to(ETH_HDR_LEN);
|
let mut eth_buf = buf.split_to(ETHERNET_HEADER_LEN);
|
||||||
let mut ip_buf = buf.split_to(ip_header_len);
|
let mut ip_buf = buf.split_to(ip_header_len);
|
||||||
let mut tcp_buf = buf.split_to(tcp_total_len);
|
let mut tcp_buf = buf.split_to(tcp_total_len);
|
||||||
assert_eq!(0, buf.len());
|
assert_eq!(0, buf.len());
|
||||||
|
|
||||||
let mut tcp = tcp::MutableTcpPacket::new(&mut tcp_buf).unwrap();
|
let mut tcp = TcpPacket::new_unchecked(&mut tcp_buf);
|
||||||
tcp.set_window(0xffff);
|
tcp.set_window_len(0xffff);
|
||||||
tcp.set_source(local_addr.port());
|
tcp.set_src_port(local_addr.port());
|
||||||
tcp.set_destination(remote_addr.port());
|
tcp.set_dst_port(remote_addr.port());
|
||||||
tcp.set_sequence(seq);
|
tcp.set_seq_number(TcpSeqNumber(seq as i32));
|
||||||
tcp.set_acknowledgement(ack);
|
tcp.set_ack_number(TcpSeqNumber(ack as i32));
|
||||||
tcp.set_flags(flags);
|
set_tcp_flags(&mut tcp, flags);
|
||||||
tcp.set_data_offset(TCP_HEADER_LEN as u8 / 4 + if wscale { 1 } else { 0 });
|
tcp.set_header_len(tcp_header_len as u8);
|
||||||
if wscale {
|
if wscale {
|
||||||
let wscale = tcp::TcpOption::wscale(14);
|
tcp.options_mut().copy_from_slice(&[1, 3, 3, 14]);
|
||||||
tcp.set_options(&[tcp::TcpOption::nop(), wscale]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payload) = payload {
|
if let Some(payload) = payload {
|
||||||
tcp.set_payload(payload);
|
tcp.payload_mut().copy_from_slice(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ethernet = MutableEthernetPacket::new(&mut eth_buf).unwrap();
|
let mut ethernet = EthernetFrame::new_unchecked(&mut eth_buf);
|
||||||
ethernet.set_destination(dst_mac);
|
ethernet.set_dst_addr(dst_mac);
|
||||||
ethernet.set_source(src_mac);
|
ethernet.set_src_addr(src_mac);
|
||||||
ethernet.set_ethertype(match local_addr {
|
ethernet.set_ethertype(match local_addr {
|
||||||
SocketAddr::V4(_) => EtherTypes::Ipv4,
|
SocketAddr::V4(_) => EthernetProtocol::Ipv4,
|
||||||
SocketAddr::V6(_) => EtherTypes::Ipv6,
|
SocketAddr::V6(_) => EthernetProtocol::Ipv6,
|
||||||
});
|
});
|
||||||
|
|
||||||
match (local_addr, remote_addr) {
|
match (local_addr, remote_addr) {
|
||||||
(SocketAddr::V4(local), SocketAddr::V4(remote)) => {
|
(SocketAddr::V4(local), SocketAddr::V4(remote)) => {
|
||||||
let mut v4 = ipv4::MutableIpv4Packet::new(&mut ip_buf).unwrap();
|
let mut v4 = Ipv4Packet::new_unchecked(&mut ip_buf);
|
||||||
v4.set_version(4);
|
v4.set_version(4);
|
||||||
v4.set_header_length(IPV4_HEADER_LEN as u8 / 4);
|
v4.set_header_len(IPV4_HEADER_LEN as u8);
|
||||||
v4.set_next_level_protocol(ip::IpNextHeaderProtocols::Tcp);
|
v4.set_next_header(IpProtocol::Tcp);
|
||||||
v4.set_ttl(64);
|
v4.set_hop_limit(64);
|
||||||
v4.set_source(*local.ip());
|
v4.set_src_addr(*local.ip());
|
||||||
v4.set_destination(*remote.ip());
|
v4.set_dst_addr(*remote.ip());
|
||||||
v4.set_total_length(total_len.try_into().unwrap());
|
v4.set_total_len(total_len.try_into().unwrap());
|
||||||
v4.set_flags(ipv4::Ipv4Flags::DontFragment);
|
v4.set_dont_frag(true);
|
||||||
|
|
||||||
tcp.set_checksum(tcp::ipv4_checksum(
|
tcp.fill_checksum(
|
||||||
&tcp.to_immutable(),
|
&IpAddress::Ipv4(*local.ip()),
|
||||||
&v4.get_source(),
|
&IpAddress::Ipv4(*remote.ip()),
|
||||||
&v4.get_destination(),
|
);
|
||||||
));
|
v4.fill_checksum();
|
||||||
|
|
||||||
v4.set_checksum(ipv4::checksum(&v4.to_immutable()));
|
|
||||||
}
|
}
|
||||||
(SocketAddr::V6(local), SocketAddr::V6(remote)) => {
|
(SocketAddr::V6(local), SocketAddr::V6(remote)) => {
|
||||||
let mut v6 = ipv6::MutableIpv6Packet::new(&mut ip_buf).unwrap();
|
let mut v6 = Ipv6Packet::new_unchecked(&mut ip_buf);
|
||||||
v6.set_version(6);
|
v6.set_version(6);
|
||||||
v6.set_payload_length(tcp_total_len.try_into().unwrap());
|
v6.set_payload_len(tcp_total_len.try_into().unwrap());
|
||||||
v6.set_next_header(ip::IpNextHeaderProtocols::Tcp);
|
v6.set_next_header(IpProtocol::Tcp);
|
||||||
v6.set_hop_limit(64);
|
v6.set_hop_limit(64);
|
||||||
v6.set_source(*local.ip());
|
v6.set_src_addr(*local.ip());
|
||||||
v6.set_destination(*remote.ip());
|
v6.set_dst_addr(*remote.ip());
|
||||||
|
|
||||||
tcp.set_checksum(tcp::ipv6_checksum(
|
tcp.fill_checksum(
|
||||||
&tcp.to_immutable(),
|
&IpAddress::Ipv6(*local.ip()),
|
||||||
&v6.get_source(),
|
&IpAddress::Ipv6(*remote.ip()),
|
||||||
&v6.get_destination(),
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
@@ -126,40 +153,34 @@ pub fn build_tcp_packet(
|
|||||||
eth_buf.freeze()
|
eth_buf.freeze()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ip_packet(
|
pub fn parse_ip_packet(buf: &Bytes) -> Option<(MacAddr, MacAddr, IPPacket<'_>, TcpPacket<&[u8]>)> {
|
||||||
buf: &Bytes,
|
let eth = EthernetFrame::new_checked(buf.as_ref()).ok()?;
|
||||||
) -> Option<(MacAddr, MacAddr, IPPacket<'_>, tcp::TcpPacket<'_>)> {
|
let src_mac = eth.src_addr();
|
||||||
let eth = EthernetPacket::new(buf.as_ref())?;
|
let dst_mac = eth.dst_addr();
|
||||||
let src_mac = eth.get_source();
|
let ethertype = eth.ethertype();
|
||||||
let dst_mac = eth.get_destination();
|
|
||||||
let ethertype = eth.get_ethertype();
|
|
||||||
|
|
||||||
tracing::trace!("Parsing IP packet: {:?}", eth);
|
tracing::trace!("Parsing IP packet: {:?}", eth);
|
||||||
|
|
||||||
let ip_payload = &buf[ETH_HDR_LEN..];
|
let ip_payload = eth.payload();
|
||||||
|
|
||||||
match ethertype {
|
match ethertype {
|
||||||
EtherTypes::Ipv4 => {
|
EthernetProtocol::Ipv4 => {
|
||||||
let v4 = ipv4::Ipv4Packet::new(ip_payload)?;
|
let v4 = Ipv4Packet::new_checked(ip_payload).ok()?;
|
||||||
if v4.get_next_level_protocol() != ip::IpNextHeaderProtocols::Tcp {
|
if usize::from(v4.header_len()) < IPV4_HEADER_LEN {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
if v4.next_header() != IpProtocol::Tcp {
|
||||||
let tcp_offset = usize::from(v4.get_header_length()) * 4;
|
|
||||||
if tcp_offset < IPV4_HEADER_LEN || tcp_offset > ip_payload.len() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let tcp = TcpPacket::new_checked(v4.payload()).ok()?;
|
||||||
let tcp = tcp::TcpPacket::new(&ip_payload[tcp_offset..])?;
|
|
||||||
Some((src_mac, dst_mac, IPPacket::V4(v4), tcp))
|
Some((src_mac, dst_mac, IPPacket::V4(v4), tcp))
|
||||||
}
|
}
|
||||||
EtherTypes::Ipv6 => {
|
EthernetProtocol::Ipv6 => {
|
||||||
let v6 = ipv6::Ipv6Packet::new(ip_payload)?;
|
let v6 = Ipv6Packet::new_checked(ip_payload).ok()?;
|
||||||
if v6.get_next_header() != ip::IpNextHeaderProtocols::Tcp {
|
if v6.next_header() != IpProtocol::Tcp {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let tcp = TcpPacket::new_checked(v6.payload()).ok()?;
|
||||||
let tcp = tcp::TcpPacket::new(&ip_payload[IPV6_HEADER_LEN..])?;
|
|
||||||
Some((src_mac, dst_mac, IPPacket::V6(v6), tcp))
|
Some((src_mac, dst_mac, IPPacket::V6(v6), tcp))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -169,12 +190,11 @@ pub fn parse_ip_packet(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use pnet::packet::Packet as _;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_ipv4_packet_round_trip() {
|
fn parse_ipv4_packet_round_trip() {
|
||||||
let src_mac = MacAddr::new(0x02, 0, 0, 0, 0, 1);
|
let src_mac = MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 1]);
|
||||||
let dst_mac = MacAddr::new(0x02, 0, 0, 0, 0, 2);
|
let dst_mac = MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 2]);
|
||||||
let local_addr: SocketAddr = "192.0.2.1:12345".parse().unwrap();
|
let local_addr: SocketAddr = "192.0.2.1:12345".parse().unwrap();
|
||||||
let remote_addr: SocketAddr = "198.51.100.2:23456".parse().unwrap();
|
let remote_addr: SocketAddr = "198.51.100.2:23456".parse().unwrap();
|
||||||
let payload = b"hello fake tcp";
|
let payload = b"hello fake tcp";
|
||||||
@@ -186,7 +206,7 @@ mod tests {
|
|||||||
remote_addr,
|
remote_addr,
|
||||||
10,
|
10,
|
||||||
20,
|
20,
|
||||||
tcp::TcpFlags::ACK,
|
TCP_FLAG_ACK,
|
||||||
Some(payload),
|
Some(payload),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -197,15 +217,15 @@ mod tests {
|
|||||||
assert_eq!(parsed_dst_mac, dst_mac);
|
assert_eq!(parsed_dst_mac, dst_mac);
|
||||||
assert_eq!(ip_packet.get_source(), local_addr.ip());
|
assert_eq!(ip_packet.get_source(), local_addr.ip());
|
||||||
assert_eq!(ip_packet.get_destination(), remote_addr.ip());
|
assert_eq!(ip_packet.get_destination(), remote_addr.ip());
|
||||||
assert_eq!(tcp_packet.get_source(), local_addr.port());
|
assert_eq!(tcp_packet.src_port(), local_addr.port());
|
||||||
assert_eq!(tcp_packet.get_destination(), remote_addr.port());
|
assert_eq!(tcp_packet.dst_port(), remote_addr.port());
|
||||||
assert_eq!(tcp_packet.payload(), payload);
|
assert_eq!(tcp_packet.payload(), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_and_parse_ipv6_packet_round_trip() {
|
fn build_and_parse_ipv6_packet_round_trip() {
|
||||||
let src_mac = MacAddr::new(0x02, 0, 0, 0, 0, 3);
|
let src_mac = MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 3]);
|
||||||
let dst_mac = MacAddr::new(0x02, 0, 0, 0, 0, 4);
|
let dst_mac = MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 4]);
|
||||||
let local_addr: SocketAddr = "[2001:db8::1]:12345".parse().unwrap();
|
let local_addr: SocketAddr = "[2001:db8::1]:12345".parse().unwrap();
|
||||||
let remote_addr: SocketAddr = "[2001:db8::2]:23456".parse().unwrap();
|
let remote_addr: SocketAddr = "[2001:db8::2]:23456".parse().unwrap();
|
||||||
let payload = b"ipv6 payload";
|
let payload = b"ipv6 payload";
|
||||||
@@ -217,12 +237,12 @@ mod tests {
|
|||||||
remote_addr,
|
remote_addr,
|
||||||
30,
|
30,
|
||||||
40,
|
40,
|
||||||
tcp::TcpFlags::ACK,
|
TCP_FLAG_ACK,
|
||||||
Some(payload),
|
Some(payload),
|
||||||
);
|
);
|
||||||
|
|
||||||
let ethernet = EthernetPacket::new(packet.as_ref()).unwrap();
|
let ethernet = EthernetFrame::new_checked(packet.as_ref()).unwrap();
|
||||||
assert_eq!(ethernet.get_ethertype(), EtherTypes::Ipv6);
|
assert_eq!(ethernet.ethertype(), EthernetProtocol::Ipv6);
|
||||||
|
|
||||||
let (parsed_src_mac, parsed_dst_mac, ip_packet, tcp_packet) =
|
let (parsed_src_mac, parsed_dst_mac, ip_packet, tcp_packet) =
|
||||||
parse_ip_packet(&packet).unwrap();
|
parse_ip_packet(&packet).unwrap();
|
||||||
@@ -231,48 +251,159 @@ mod tests {
|
|||||||
assert_eq!(parsed_dst_mac, dst_mac);
|
assert_eq!(parsed_dst_mac, dst_mac);
|
||||||
assert_eq!(ip_packet.get_source(), local_addr.ip());
|
assert_eq!(ip_packet.get_source(), local_addr.ip());
|
||||||
assert_eq!(ip_packet.get_destination(), remote_addr.ip());
|
assert_eq!(ip_packet.get_destination(), remote_addr.ip());
|
||||||
assert_eq!(tcp_packet.get_source(), local_addr.port());
|
assert_eq!(tcp_packet.src_port(), local_addr.port());
|
||||||
assert_eq!(tcp_packet.get_destination(), remote_addr.port());
|
assert_eq!(tcp_packet.dst_port(), remote_addr.port());
|
||||||
assert_eq!(tcp_packet.payload(), payload);
|
assert_eq!(tcp_packet.payload(), payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_short_ethernet_frame() {
|
fn parse_rejects_short_ethernet_frame() {
|
||||||
let packet = Bytes::from_static(&[0u8; ETH_HDR_LEN - 1]);
|
let packet = Bytes::from_static(&[0u8; ETHERNET_HEADER_LEN - 1]);
|
||||||
assert!(parse_ip_packet(&packet).is_none());
|
assert!(parse_ip_packet(&packet).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_truncated_ipv4_tcp_packet() {
|
fn parse_rejects_truncated_ipv4_tcp_packet() {
|
||||||
let packet = build_tcp_packet(
|
let packet = build_tcp_packet(
|
||||||
MacAddr::new(0x02, 0, 0, 0, 0, 5),
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 5]),
|
||||||
MacAddr::new(0x02, 0, 0, 0, 0, 6),
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 6]),
|
||||||
"192.0.2.10:1111".parse().unwrap(),
|
"192.0.2.10:1111".parse().unwrap(),
|
||||||
"198.51.100.20:2222".parse().unwrap(),
|
"198.51.100.20:2222".parse().unwrap(),
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
tcp::TcpFlags::ACK,
|
TCP_FLAG_ACK,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let truncated = Bytes::copy_from_slice(&packet[..ETH_HDR_LEN + IPV4_HEADER_LEN + 10]);
|
let truncated =
|
||||||
|
Bytes::copy_from_slice(&packet[..ETHERNET_HEADER_LEN + IPV4_HEADER_LEN + 10]);
|
||||||
|
|
||||||
assert!(parse_ip_packet(&truncated).is_none());
|
assert!(parse_ip_packet(&truncated).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rejects_ipv4_header_shorter_than_minimum() {
|
||||||
|
let packet = build_tcp_packet(
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 5]),
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 6]),
|
||||||
|
"192.0.2.10:1111".parse().unwrap(),
|
||||||
|
"198.51.100.20:2222".parse().unwrap(),
|
||||||
|
1,
|
||||||
|
0x5000_0000,
|
||||||
|
TCP_FLAG_ACK,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut malformed = BytesMut::from(packet.as_ref());
|
||||||
|
Ipv4Packet::new_unchecked(&mut malformed[ETHERNET_HEADER_LEN..])
|
||||||
|
.set_header_len((IPV4_HEADER_LEN - 4) as u8);
|
||||||
|
let malformed = malformed.freeze();
|
||||||
|
|
||||||
|
assert!(parse_ip_packet(&malformed).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_truncated_ipv6_header() {
|
fn parse_rejects_truncated_ipv6_header() {
|
||||||
let packet = build_tcp_packet(
|
let packet = build_tcp_packet(
|
||||||
MacAddr::new(0x02, 0, 0, 0, 0, 7),
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 7]),
|
||||||
MacAddr::new(0x02, 0, 0, 0, 0, 8),
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 8]),
|
||||||
"[2001:db8::10]:1111".parse().unwrap(),
|
"[2001:db8::10]:1111".parse().unwrap(),
|
||||||
"[2001:db8::20]:2222".parse().unwrap(),
|
"[2001:db8::20]:2222".parse().unwrap(),
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
tcp::TcpFlags::ACK,
|
TCP_FLAG_ACK,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let truncated = Bytes::copy_from_slice(&packet[..ETH_HDR_LEN + IPV6_HEADER_LEN - 1]);
|
let truncated =
|
||||||
|
Bytes::copy_from_slice(&packet[..ETHERNET_HEADER_LEN + IPV6_HEADER_LEN - 1]);
|
||||||
|
|
||||||
assert!(parse_ip_packet(&truncated).is_none());
|
assert!(parse_ip_packet(&truncated).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn syn_packet_preserves_wire_format_and_unsigned_sequence() {
|
||||||
|
let packet = build_tcp_packet(
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 1]),
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 2]),
|
||||||
|
"192.0.2.1:12345".parse().unwrap(),
|
||||||
|
"198.51.100.2:23456".parse().unwrap(),
|
||||||
|
0x8000_0001,
|
||||||
|
0xffff_fffe,
|
||||||
|
TCP_FLAG_SYN | TCP_FLAG_ACK,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
&packet[..ETHERNET_HEADER_LEN],
|
||||||
|
&[0x02, 0, 0, 0, 0, 2, 0x02, 0, 0, 0, 0, 1, 0x08, 0x00]
|
||||||
|
);
|
||||||
|
let (_, _, IPPacket::V4(ipv4), tcp) = parse_ip_packet(&packet).unwrap() else {
|
||||||
|
panic!("expected IPv4 packet");
|
||||||
|
};
|
||||||
|
assert_eq!(ipv4.header_len(), IPV4_HEADER_LEN as u8);
|
||||||
|
assert!(ipv4.dont_frag());
|
||||||
|
assert_eq!(ipv4.hop_limit(), 64);
|
||||||
|
assert!(ipv4.verify_checksum());
|
||||||
|
assert_eq!(tcp.header_len(), (TCP_HEADER_LEN + 4) as u8);
|
||||||
|
assert_eq!(tcp.options(), &[1, 3, 3, 14]);
|
||||||
|
assert_eq!(tcp_flags(&tcp), TCP_FLAG_SYN | TCP_FLAG_ACK);
|
||||||
|
assert_eq!(tcp.seq_number().0 as u32, 0x8000_0001);
|
||||||
|
assert_eq!(tcp.ack_number().0 as u32, 0xffff_fffe);
|
||||||
|
assert!(tcp.verify_checksum(
|
||||||
|
&IpAddress::Ipv4(ipv4.src_addr()),
|
||||||
|
&IpAddress::Ipv4(ipv4.dst_addr()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ethernet_padding_is_not_tcp_payload() {
|
||||||
|
let packet = build_tcp_packet(
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 1]),
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 2]),
|
||||||
|
"192.0.2.1:12345".parse().unwrap(),
|
||||||
|
"198.51.100.2:23456".parse().unwrap(),
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
TCP_FLAG_ACK,
|
||||||
|
Some(b"payload"),
|
||||||
|
);
|
||||||
|
let mut padded = BytesMut::from(packet.as_ref());
|
||||||
|
padded.extend_from_slice(&[0; 16]);
|
||||||
|
let padded = padded.freeze();
|
||||||
|
|
||||||
|
let (_, _, _, tcp) = parse_ip_packet(&padded).unwrap();
|
||||||
|
assert_eq!(tcp.payload(), b"payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ipv4_options_do_not_shift_tcp_payload() {
|
||||||
|
let packet = build_tcp_packet(
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 1]),
|
||||||
|
MacAddr::from_bytes(&[0x02, 0, 0, 0, 0, 2]),
|
||||||
|
"192.0.2.1:12345".parse().unwrap(),
|
||||||
|
"198.51.100.2:23456".parse().unwrap(),
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
TCP_FLAG_ACK,
|
||||||
|
Some(b"payload"),
|
||||||
|
);
|
||||||
|
let ip_start = ETHERNET_HEADER_LEN;
|
||||||
|
let tcp_start = ip_start + IPV4_HEADER_LEN;
|
||||||
|
let mut with_options = Vec::with_capacity(packet.len() + 4);
|
||||||
|
with_options.extend_from_slice(&packet[..tcp_start]);
|
||||||
|
with_options.extend_from_slice(&[1, 1, 1, 0]);
|
||||||
|
with_options.extend_from_slice(&packet[tcp_start..]);
|
||||||
|
{
|
||||||
|
let total_len = with_options.len() - ip_start;
|
||||||
|
let mut ipv4 = Ipv4Packet::new_unchecked(&mut with_options[ip_start..]);
|
||||||
|
ipv4.set_header_len((IPV4_HEADER_LEN + 4) as u8);
|
||||||
|
ipv4.set_total_len(total_len as u16);
|
||||||
|
ipv4.fill_checksum();
|
||||||
|
}
|
||||||
|
let with_options = Bytes::from(with_options);
|
||||||
|
|
||||||
|
let (_, _, IPPacket::V4(ipv4), tcp) = parse_ip_packet(&with_options).unwrap() else {
|
||||||
|
panic!("expected IPv4 packet");
|
||||||
|
};
|
||||||
|
assert_eq!(ipv4.header_len(), (IPV4_HEADER_LEN + 4) as u8);
|
||||||
|
assert_eq!(tcp.payload(), b"payload");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,6 @@
|
|||||||
use super::packet::*;
|
use super::packet::*;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use pnet::packet::tcp::TcpOptionNumbers;
|
|
||||||
use pnet::packet::{Packet, tcp};
|
|
||||||
use pnet::util::MacAddr;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -60,6 +57,47 @@ use tracing::{error, info, trace, warn};
|
|||||||
|
|
||||||
const TIMEOUT: time::Duration = time::Duration::from_secs(1);
|
const TIMEOUT: time::Duration = time::Duration::from_secs(1);
|
||||||
const MPMC_BUFFER_LEN: usize = 512;
|
const MPMC_BUFFER_LEN: usize = 512;
|
||||||
|
const TCP_OPTION_END: u8 = 0;
|
||||||
|
const TCP_OPTION_NOP: u8 = 1;
|
||||||
|
const TCP_OPTION_SACK: u8 = 5;
|
||||||
|
|
||||||
|
struct TcpOptionIter<'a> {
|
||||||
|
remaining: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TcpOptionIter<'a> {
|
||||||
|
fn new(options: &'a [u8]) -> Self {
|
||||||
|
Self { remaining: options }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for TcpOptionIter<'a> {
|
||||||
|
type Item = (u8, &'a [u8]);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let kind = *self.remaining.first()?;
|
||||||
|
if kind == TCP_OPTION_END {
|
||||||
|
self.remaining = &[];
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if kind == TCP_OPTION_NOP {
|
||||||
|
self.remaining = &self.remaining[1..];
|
||||||
|
return Some((kind, &[]));
|
||||||
|
}
|
||||||
|
let Some(&length) = self.remaining.get(1) else {
|
||||||
|
self.remaining = &[];
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let length = usize::from(length);
|
||||||
|
if length < 2 || length > self.remaining.len() {
|
||||||
|
self.remaining = &[];
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let payload = &self.remaining[2..length];
|
||||||
|
self.remaining = &self.remaining[length..];
|
||||||
|
Some((kind, payload))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Tun: Send + Sync + 'static {
|
pub trait Tun: Send + Sync + 'static {
|
||||||
@@ -181,7 +219,7 @@ impl Socket {
|
|||||||
|
|
||||||
build_tcp_packet(
|
build_tcp_packet(
|
||||||
self.local_mac,
|
self.local_mac,
|
||||||
self.remote_mac.load().unwrap_or(MacAddr::zero()),
|
self.remote_mac.load().unwrap_or_default(),
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
self.remote_addr,
|
self.remote_addr,
|
||||||
self.seq.load(Ordering::Relaxed),
|
self.seq.load(Ordering::Relaxed),
|
||||||
@@ -201,7 +239,7 @@ impl Socket {
|
|||||||
pub fn try_send(&self, payload: &[u8]) -> Option<()> {
|
pub fn try_send(&self, payload: &[u8]) -> Option<()> {
|
||||||
match self.state.load() {
|
match self.state.load() {
|
||||||
State::Established => {
|
State::Established => {
|
||||||
let buf = self.build_tcp_packet(tcp::TcpFlags::ACK, Some(payload));
|
let buf = self.build_tcp_packet(TCP_FLAG_ACK, Some(payload));
|
||||||
self.seq.fetch_add(payload.len() as u32, Ordering::Relaxed);
|
self.seq.fetch_add(payload.len() as u32, Ordering::Relaxed);
|
||||||
self.tun.try_send(&buf).ok().and(Some(()))
|
self.tun.try_send(&buf).ok().and(Some(()))
|
||||||
}
|
}
|
||||||
@@ -211,7 +249,7 @@ impl Socket {
|
|||||||
|
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
if self.state.load() != State::Idle {
|
if self.state.load() != State::Idle {
|
||||||
let buf = self.build_tcp_packet(tcp::TcpFlags::RST, None);
|
let buf = self.build_tcp_packet(TCP_FLAG_RST, None);
|
||||||
let _ = self.tun.try_send(&buf);
|
let _ = self.tun.try_send(&buf);
|
||||||
self.state.store(State::Idle);
|
self.state.store(State::Idle);
|
||||||
}
|
}
|
||||||
@@ -256,32 +294,30 @@ impl Socket {
|
|||||||
|
|
||||||
self.remote_mac.store(Some(src_mac));
|
self.remote_mac.store(Some(src_mac));
|
||||||
|
|
||||||
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
|
if tcp_packet.rst() {
|
||||||
info!("Connection {} reset by peer", self);
|
info!("Connection {} reset by peer", self);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tcp_packet.get_flags() & tcp::TcpFlags::ACK) != 0
|
if tcp_packet.ack() && tcp_packet.payload().is_empty() {
|
||||||
&& tcp_packet.payload().is_empty()
|
|
||||||
{
|
|
||||||
self.seq
|
self.seq
|
||||||
.store(tcp_packet.get_acknowledgement(), Ordering::Relaxed);
|
.store(tcp_packet.ack_number().0 as u32, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload = tcp_packet.payload();
|
let payload = tcp_packet.payload();
|
||||||
|
|
||||||
let new_ack = tcp_packet.get_sequence().wrapping_add(payload.len() as u32);
|
let new_ack =
|
||||||
|
(tcp_packet.seq_number().0 as u32).wrapping_add(payload.len() as u32);
|
||||||
self.ack.store(new_ack, Ordering::Relaxed);
|
self.ack.store(new_ack, Ordering::Relaxed);
|
||||||
|
|
||||||
for opt in tcp_packet.get_options_iter() {
|
for (kind, option_payload) in TcpOptionIter::new(tcp_packet.options()) {
|
||||||
if opt.get_number() == TcpOptionNumbers::SACK {
|
if kind == TCP_OPTION_SACK {
|
||||||
// SACK 选项类型为 5
|
// SACK 选项类型为 5
|
||||||
let payload = opt.payload();
|
for chunk in option_payload.chunks(8) {
|
||||||
for chunk in payload.chunks(8) {
|
|
||||||
if chunk.len() != 8 {
|
if chunk.len() != 8 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let left = tcp_packet.get_acknowledgement();
|
let left = tcp_packet.ack_number().0 as u32;
|
||||||
let right = u32::from_be_bytes(chunk[0..4].try_into().unwrap());
|
let right = u32::from_be_bytes(chunk[0..4].try_into().unwrap());
|
||||||
let len = right.wrapping_sub(left);
|
let len = right.wrapping_sub(left);
|
||||||
|
|
||||||
@@ -295,12 +331,12 @@ impl Socket {
|
|||||||
|
|
||||||
let buf = build_tcp_packet(
|
let buf = build_tcp_packet(
|
||||||
self.local_mac,
|
self.local_mac,
|
||||||
self.remote_mac.load().unwrap_or(MacAddr::zero()),
|
self.remote_mac.load().unwrap_or_default(),
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
self.remote_addr,
|
self.remote_addr,
|
||||||
left,
|
left,
|
||||||
self.ack.load(Ordering::Relaxed),
|
self.ack.load(Ordering::Relaxed),
|
||||||
tcp::TcpFlags::ACK,
|
TCP_FLAG_ACK,
|
||||||
Some(&data),
|
Some(&data),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -332,18 +368,19 @@ impl Socket {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
|
if tcp_packet.rst() {
|
||||||
tracing::trace!("Connection {} reset by peer", self);
|
tracing::trace!("Connection {} reset by peer", self);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let expected_flag = tcp::TcpFlags::SYN | tcp::TcpFlags::ACK;
|
if tcp_packet.syn() && tcp_packet.ack() {
|
||||||
if (tcp_packet.get_flags() & expected_flag) == expected_flag {
|
|
||||||
// found our SYN + ACK
|
// found our SYN + ACK
|
||||||
self.seq
|
self.seq
|
||||||
.store(tcp_packet.get_acknowledgement(), Ordering::Relaxed);
|
.store(tcp_packet.ack_number().0 as u32, Ordering::Relaxed);
|
||||||
self.ack
|
self.ack.store(
|
||||||
.store(tcp_packet.get_sequence() + 1, Ordering::Relaxed);
|
(tcp_packet.seq_number().0 as u32).wrapping_add(1),
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
self.remote_mac.store(Some(src_mac));
|
self.remote_mac.store(Some(src_mac));
|
||||||
self.state.store(State::Established);
|
self.state.store(State::Established);
|
||||||
return Some(0);
|
return Some(0);
|
||||||
@@ -385,12 +422,12 @@ impl Drop for Socket {
|
|||||||
|
|
||||||
let buf = build_tcp_packet(
|
let buf = build_tcp_packet(
|
||||||
self.local_mac,
|
self.local_mac,
|
||||||
self.remote_mac.load().unwrap_or(MacAddr::zero()),
|
self.remote_mac.load().unwrap_or_default(),
|
||||||
self.local_addr,
|
self.local_addr,
|
||||||
self.remote_addr,
|
self.remote_addr,
|
||||||
self.seq.load(Ordering::Relaxed),
|
self.seq.load(Ordering::Relaxed),
|
||||||
0,
|
0,
|
||||||
tcp::TcpFlags::RST,
|
TCP_FLAG_RST,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
if let Err(e) = self.tun.try_send(&buf) {
|
if let Err(e) = self.tun.try_send(&buf) {
|
||||||
@@ -434,7 +471,7 @@ impl Stack {
|
|||||||
|
|
||||||
Stack {
|
Stack {
|
||||||
shared,
|
shared,
|
||||||
local_mac: local_mac.unwrap_or(MacAddr::zero()),
|
local_mac: local_mac.unwrap_or_default(),
|
||||||
reader_task: AbortOnDropHandle::new(t),
|
reader_task: AbortOnDropHandle::new(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,11 +551,11 @@ impl Stack {
|
|||||||
Some((_src_mac, _dst_mac, ip_packet, tcp_packet)) => {
|
Some((_src_mac, _dst_mac, ip_packet, tcp_packet)) => {
|
||||||
let local_addr = SocketAddr::new(
|
let local_addr = SocketAddr::new(
|
||||||
ip_packet.get_destination(),
|
ip_packet.get_destination(),
|
||||||
tcp_packet.get_destination(),
|
tcp_packet.dst_port(),
|
||||||
);
|
);
|
||||||
let remote_addr = SocketAddr::new(
|
let remote_addr = SocketAddr::new(
|
||||||
ip_packet.get_source(),
|
ip_packet.get_source(),
|
||||||
tcp_packet.get_source(),
|
tcp_packet.src_port(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let tuple = AddrTuple::new(local_addr, remote_addr);
|
let tuple = AddrTuple::new(local_addr, remote_addr);
|
||||||
@@ -548,7 +585,7 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tcp_packet.get_flags() & tcp::TcpFlags::RST) != 0 {
|
if tcp_packet.rst() {
|
||||||
info!("Unknown RST TCP packet from {}, ignoring", remote_addr);
|
info!("Unknown RST TCP packet from {}, ignoring", remote_addr);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
@@ -604,6 +641,43 @@ mod tests {
|
|||||||
time::{Duration, timeout},
|
time::{Duration, timeout},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tcp_option_iterator_preserves_all_sack_blocks() {
|
||||||
|
for block_count in 1..=4_u32 {
|
||||||
|
let mut options = vec![TCP_OPTION_NOP, TCP_OPTION_SACK, (2 + block_count * 8) as u8];
|
||||||
|
for block in 0..block_count {
|
||||||
|
options.extend_from_slice(&(100 + block * 10).to_be_bytes());
|
||||||
|
options.extend_from_slice(&(110 + block * 10).to_be_bytes());
|
||||||
|
}
|
||||||
|
options.push(TCP_OPTION_END);
|
||||||
|
|
||||||
|
let parsed = TcpOptionIter::new(&options).collect::<Vec<_>>();
|
||||||
|
assert_eq!(parsed[0], (TCP_OPTION_NOP, &[][..]));
|
||||||
|
assert_eq!(parsed[1].0, TCP_OPTION_SACK);
|
||||||
|
assert_eq!(parsed[1].1.len(), block_count as usize * 8);
|
||||||
|
let last = parsed[1].1.len() - 8;
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_be_bytes(parsed[1].1[last..last + 4].try_into().unwrap()),
|
||||||
|
100 + (block_count - 1) * 10
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
u32::from_be_bytes(parsed[1].1[last + 4..last + 8].try_into().unwrap()),
|
||||||
|
110 + (block_count - 1) * 10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tcp_option_iterator_stops_at_end_or_malformed_length() {
|
||||||
|
assert_eq!(
|
||||||
|
TcpOptionIter::new(&[TCP_OPTION_END, TCP_OPTION_SACK, 2]).count(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert_eq!(TcpOptionIter::new(&[TCP_OPTION_SACK]).count(), 0);
|
||||||
|
assert_eq!(TcpOptionIter::new(&[TCP_OPTION_SACK, 1]).count(), 0);
|
||||||
|
assert_eq!(TcpOptionIter::new(&[TCP_OPTION_SACK, 10, 0, 0]).count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FailingTun {
|
struct FailingTun {
|
||||||
fail: Notify,
|
fail: Notify,
|
||||||
|
|||||||
Reference in New Issue
Block a user